diff options
author | 2023-10-10 14:33:42 +0000 | |
---|---|---|
committer | 2023-10-10 14:33:42 +0000 | |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/u-boot/drivers/mmc | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/u-boot/drivers/mmc')
71 files changed, 37930 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/mmc/Kconfig b/roms/u-boot/drivers/mmc/Kconfig new file mode 100644 index 000000000..0909f502a --- /dev/null +++ b/roms/u-boot/drivers/mmc/Kconfig @@ -0,0 +1,839 @@ +menu "MMC Host controller Support" + +config MMC + bool "MMC/SD/SDIO card support" + default ARM || PPC || SANDBOX + select HAVE_BLOCK_DEVICE + select DM_MMC if DM + help + This selects MultiMediaCard, Secure Digital and Secure + Digital I/O support. + + If you want MMC/SD/SDIO support, you should say Y here and + also to your specific host controller driver. + +config MMC_WRITE + bool "support for MMC/SD write operations" + depends on MMC + default y + help + Enable write access to MMC and SD Cards + +config MMC_PWRSEQ + bool "HW reset support for eMMC" + depends on PWRSEQ + help + Ths select Hardware reset support aka pwrseq-emmc for eMMC + devices. + +config MMC_BROKEN_CD + bool "Poll for broken card detection case" + help + If card detection feature is broken, just poll to detect. + +config DM_MMC + bool "Enable MMC controllers using Driver Model" + depends on DM + select BLK + help + This enables the MultiMediaCard (MMC) uclass which supports MMC and + Secure Digital I/O (SDIO) cards. Both removable (SD, micro-SD, etc.) + and non-removable (e.g. eMMC chip) devices are supported. These + appear as block devices in U-Boot and can support filesystems such + as EXT4 and FAT. + +config SPL_DM_MMC + bool "Enable MMC controllers using Driver Model in SPL" + depends on SPL_DM && DM_MMC + default y + help + This enables the MultiMediaCard (MMC) uclass which supports MMC and + Secure Digital I/O (SDIO) cards. Both removable (SD, micro-SD, etc.) + and non-removable (e.g. eMMC chip) devices are supported. These + appear as block devices in U-Boot and can support filesystems such + as EXT4 and FAT. + +if MMC + +config MMC_SDHCI_ADMA_HELPERS + bool + +config MMC_SPI + bool "Support for SPI-based MMC controller" + depends on DM_MMC && DM_SPI + help + This selects SPI-based MMC controllers. + If you have an MMC controller on a SPI bus, say Y here. + + If unsure, say N. + +config MMC_SPI_CRC_ON + bool "Support CRC for SPI-based MMC controller" + depends on MMC_SPI + default y + help + This enables CRC for SPI-based MMC controllers. + + If unsure, say N. + +config ARM_PL180_MMCI + bool "ARM AMBA Multimedia Card Interface and compatible support" + help + This selects the ARM(R) AMBA(R) PrimeCell Multimedia Card + Interface (PL180, PL181 and compatible) support. + If you have an ARM(R) platform with a Multimedia Card slot, + say Y or M here. + +config MMC_QUIRKS + bool "Enable quirks" + default y + help + Some cards and hosts may sometimes behave unexpectedly (quirks). + This option enable workarounds to handle those quirks. Some of them + are enabled by default, other may require additional flags or are + enabled by the host driver. + +config MMC_HW_PARTITIONING + bool "Support for HW partitioning command(eMMC)" + default y + help + This adds a command and an API to do hardware partitioning on eMMC + devices. + +config SUPPORT_EMMC_RPMB + bool "Support eMMC replay protected memory block (RPMB)" + imply CMD_MMC_RPMB + help + Enable support for reading, writing and programming the + key for the Replay Protection Memory Block partition in eMMC. + +config SUPPORT_EMMC_BOOT + bool "Support some additional features of the eMMC boot partitions" + help + Enable support for eMMC boot partitions. This also enables + extensions within the mmc command. + +config MMC_IO_VOLTAGE + bool "Support IO voltage configuration" + help + IO voltage configuration allows selecting the voltage level of the IO + lines (not the level of main supply). This is required for UHS + support. For eMMC this not mandatory, but not enabling this option may + prevent the driver of using the faster modes. + +config SPL_MMC_IO_VOLTAGE + bool "Support IO voltage configuration in SPL" + default n + help + IO voltage configuration allows selecting the voltage level of the IO + lines (not the level of main supply). This is required for UHS + support. For eMMC this not mandatory, but not enabling this option may + prevent the driver of using the faster modes. + +config MMC_UHS_SUPPORT + bool "enable UHS support" + depends on MMC_IO_VOLTAGE + help + The Ultra High Speed (UHS) bus is available on some SDHC and SDXC + cards. The IO voltage must be switchable from 3.3v to 1.8v. The bus + frequency can go up to 208MHz (SDR104) + +config SPL_MMC_UHS_SUPPORT + bool "enable UHS support in SPL" + depends on SPL_MMC_IO_VOLTAGE + help + The Ultra High Speed (UHS) bus is available on some SDHC and SDXC + cards. The IO voltage must be switchable from 3.3v to 1.8v. The bus + frequency can go up to 208MHz (SDR104) + +config MMC_HS400_ES_SUPPORT + bool "enable HS400 Enhanced Strobe support" + help + The HS400 Enhanced Strobe mode is support by some eMMC. The bus + frequency is up to 200MHz. This mode does not tune the IO. + +config SPL_MMC_HS400_ES_SUPPORT + bool "enable HS400 Enhanced Strobe support in SPL" + help + The HS400 Enhanced Strobe mode is support by some eMMC. The bus + frequency is up to 200MHz. This mode does not tune the IO. + +config MMC_HS400_SUPPORT + bool "enable HS400 support" + select MMC_HS200_SUPPORT + help + The HS400 mode is support by some eMMC. The bus frequency is up to + 200MHz. This mode requires tuning the IO. + +config SPL_MMC_HS400_SUPPORT + bool "enable HS400 support in SPL" + select SPL_MMC_HS200_SUPPORT + help + The HS400 mode is support by some eMMC. The bus frequency is up to + 200MHz. This mode requires tuning the IO. + +config MMC_HS200_SUPPORT + bool "enable HS200 support" + help + The HS200 mode is support by some eMMC. The bus frequency is up to + 200MHz. This mode requires tuning the IO. + +config SPL_MMC_HS200_SUPPORT + bool "enable HS200 support in SPL" + help + The HS200 mode is support by some eMMC. The bus frequency is up to + 200MHz. This mode requires tuning the IO. + +config MMC_VERBOSE + bool "Output more information about the MMC" + default y + help + Enable the output of more information about the card such as the + operating mode. + +config MMC_TRACE + bool "MMC debugging" + default n + help + This is an option for use by developer. Enable MMC core debugging. + + If you need to see the MMC core message, say Y. + +config MMC_DAVINCI + bool "TI DAVINCI Multimedia Card Interface support" + depends on ARCH_DAVINCI + default y + help + This selects the TI DAVINCI Multimedia card Interface. + If you have an DAVINCI board with a Multimedia Card slot, + say Y here. If unsure, say N. + +config MMC_DW + bool "Synopsys DesignWare Memory Card Interface" + select BOUNCE_BUFFER + help + This selects support for the Synopsys DesignWare Mobile Storage IP + block, this provides host support for SD and MMC interfaces, in both + PIO, internal DMA mode and external DMA mode. + +config MMC_DW_CORTINA + bool "Cortina specific extensions for Synopsys DW Memory Card Interface" + depends on DM_MMC + depends on MMC_DW + depends on BLK + default n + help + This selects support for Cortina SoC specific extensions to the + Synopsys DesignWare Memory Card Interface driver. Select this option + for platforms based on Cortina CAxxxx Soc's. + +config MMC_DW_EXYNOS + bool "Exynos specific extensions for Synopsys DW Memory Card Interface" + depends on ARCH_EXYNOS + depends on MMC_DW + default y + help + This selects support for Samsung Exynos SoC specific extensions to the + Synopsys DesignWare Memory Card Interface driver. Select this option + for platforms based on Exynos4 and Exynos5 SoC's. + +config MMC_DW_K3 + bool "K3 specific extensions for Synopsys DW Memory Card Interface" + depends on MMC_DW + help + This selects support for Hisilicon K3 SoC specific extensions to the + Synopsys DesignWare Memory Card Interface driver. Select this option + for platforms based on Hisilicon K3 SoC's. + +config MMC_DW_ROCKCHIP + bool "Rockchip SD/MMC controller support" + depends on DM_MMC && OF_CONTROL + depends on MMC_DW + help + This enables support for the Rockchip SD/MMM controller, which is + based on Designware IP. The device is compatible with at least + SD 3.0, SDIO 3.0 and MMC 4.5 and supports common eMMC chips as well + as removeable SD and micro-SD cards. + +config MMC_DW_SOCFPGA + bool "SOCFPGA specific extensions for Synopsys DW Memory Card Interface" + depends on ARCH_SOCFPGA + depends on MMC_DW + default y + help + This selects support for Altera SOCFPGA specific extensions to the + Synopsys DesignWare Memory Card Interface driver. Select this option + for platforms based on Altera SOCFPGA. + +config MMC_DW_SNPS + bool "Extensions for DW Memory Card Interface used in Synopsys ARC devboards" + depends on MMC_DW + depends on DM_MMC + depends on OF_CONTROL + depends on CLK + help + This selects support for Synopsys DesignWare Memory Card Interface driver + extensions used in various Synopsys ARC devboards. + +config NEXELL_DWMMC + bool "Nexell SD/MMC controller support" + depends on ARCH_NEXELL + depends on MMC_DW + depends on DM_MMC + depends on PINCTRL_NEXELL + default y + +config MMC_MESON_GX + bool "Meson GX EMMC controller support" + depends on DM_MMC && BLK && ARCH_MESON + help + Support for EMMC host controller on Meson GX ARM SoCs platform (S905) + +config MMC_MXC + bool "Freescale i.MX21/27/31 or MPC512x Multimedia Card support" + help + This selects the Freescale i.MX21, i.MX27, i.MX31 or MPC512x + Multimedia Card Interface. If you have an i.MX or MPC512x platform + with a Multimedia Card slot, say Y here. + + If unsure, say N. + +config MMC_MXS + bool "Freescale MXS Multimedia Card Interface support" + depends on MX23 || MX28 || MX6 || MX7 + select BOUNCE_BUFFER + select APBH_DMA + select APBH_DMA_BURST if ARCH_MX6 || ARCH_MX7 + select APBH_DMA_BURST8 if ARCH_MX6 || ARCH_MX7 + help + This selects the Freescale SSP MMC controller found on MXS based + platforms like mx23/28. + + If unsure, say N. + +config MMC_PCI + bool "Support for MMC controllers on PCI" + depends on MMC_SDHCI + help + This selects PCI-based MMC controllers. + If you have an MMC controller on a PCI bus, say Y here. + +config MMC_OCTEONTX + bool "Marvell Octeon Multimedia Card Interface support" + depends on (ARCH_OCTEON || ARCH_OCTEONTX || ARCH_OCTEONTX2) + depends on DM_MMC + help + This selects the Octeon Multimedia card Interface. + If you have an OcteonTX/TX2 or MIPS Octeon board with a + Multimedia Card slot, say Y here. + + If unsure, say N. + +config MVEBU_MMC + bool "Kirkwood MMC controller support" + depends on DM_MMC && BLK && ARCH_KIRKWOOD + help + Support for MMC host controller on Kirkwood SoCs. + If you are on a Kirkwood architecture, say Y here. + + If unsure, say N. + +config PXA_MMC_GENERIC + bool "Support for MMC controllers on PXA" + help + This selects MMC controllers on PXA. + If you are on a PXA architecture, say Y here. + + If unsure, say N. + +config MMC_OMAP_HS + bool "TI OMAP High Speed Multimedia Card Interface support" + select DM_REGULATOR_PBIAS if DM_MMC && DM_REGULATOR + select DM_REGULATOR_PBIAS if DM_MMC && DM_REGULATOR + help + This selects the TI OMAP High Speed Multimedia card Interface. + If you have an omap2plus board with a Multimedia Card slot, + say Y here. + + If unsure, say N. + +config MMC_OMAP_HS_ADMA + bool "ADMA support for OMAP HS MMC" + depends on MMC_OMAP_HS && !OMAP34XX + default y if !AM33XX + help + This enables support for the ADMA2 controller (SDA3.00 Part A2 DMA + controller). If supported by the hardware, selecting this option will + increase performances. + +config MMC_OMAP36XX_PINS + bool "Enable MMC1 on OMAP36xx/37xx" + depends on OMAP34XX && MMC_OMAP_HS + help + This enables extended-drain in the MMC/SD/SDIO1I/O and + GPIO-associated I/O cells (gpio_126, gpio_127, and gpio_129) + specific to the OMAP36xx/37xx using MMC1 + + If you have a controller with this interface, say Y here. + + If unsure, say N. + +config SH_SDHI + bool "SuperH/Renesas ARM SoCs on-chip SDHI host controller support" + depends on ARCH_RMOBILE + help + Support for the on-chip SDHI host controller on SuperH/Renesas ARM SoCs platform + +config SH_MMCIF + bool "SuperH/Renesas ARM SoCs on-chip MMCIF host controller support" + depends on ARCH_RMOBILE || SH + help + Support for the on-chip MMCIF host controller on SuperH/Renesas ARM SoCs platform + +config MMC_UNIPHIER + bool "UniPhier SD/MMC Host Controller support" + depends on ARCH_UNIPHIER + depends on BLK && DM_MMC + depends on OF_CONTROL + help + This selects support for the Matsushita SD/MMC Host Controller on + SocioNext UniPhier SoCs. + +config RENESAS_SDHI + bool "Renesas R-Car SD/MMC Host Controller support" + depends on ARCH_RMOBILE + depends on BLK && DM_MMC + depends on OF_CONTROL + select BOUNCE_BUFFER + help + This selects support for the Matsushita SD/MMC Host Controller on + Renesas R-Car SoCs. + +config MMC_BCM2835 + bool "BCM2835 family custom SD/MMC Host Controller support" + depends on ARCH_BCM283X + depends on BLK && DM_MMC + depends on OF_CONTROL + default y + help + This selects support for the custom SD host controller in the BCM2835 + family of devices. + + If you have a BCM2835 platform with SD or MMC devices, say Y here. + + If unsure, say N. + +config JZ47XX_MMC + bool "Ingenic JZ47xx SD/MMC Host Controller support" + depends on ARCH_JZ47XX + help + This selects support for the SD Card Controller on Ingenic JZ47xx SoCs. + +config MMC_SANDBOX + bool "Sandbox MMC support" + depends on SANDBOX + depends on BLK && DM_MMC && OF_CONTROL + help + This select a dummy sandbox MMC driver. At present this does nothing + other than allow sandbox to be build with MMC support. This + improves build coverage for sandbox and makes it easier to detect + MMC build errors with sandbox. + +config MMC_SDHCI + bool "Secure Digital Host Controller Interface support" + help + This selects the generic Secure Digital Host Controller Interface. + It is used by manufacturers such as Texas Instruments(R), Ricoh(R) + and Toshiba(R). Most controllers found in laptops are of this type. + + If you have a controller with this interface, say Y here. + + If unsure, say N. + +config MMC_SDHCI_IO_ACCESSORS + bool + depends on MMC_SDHCI + help + This is silent Kconfig symbol that is selected by the drivers that + need to overwrite SDHCI IO memory accessors. + +config MMC_SDHCI_SDMA + bool "Support SDHCI SDMA" + depends on MMC_SDHCI + help + This enables support for the SDMA (Single Operation DMA) defined + in the SD Host Controller Standard Specification Version 1.00 . + +config MMC_SDHCI_ADMA + bool "Support SDHCI ADMA2" + depends on MMC_SDHCI + select MMC_SDHCI_ADMA_HELPERS + help + This enables support for the ADMA (Advanced DMA) defined + in the SD Host Controller Standard Specification Version 3.00 + +config SPL_MMC_SDHCI_ADMA + bool "Support SDHCI ADMA2 in SPL" + depends on MMC_SDHCI + select MMC_SDHCI_ADMA_HELPERS + help + This enables support for the ADMA (Advanced DMA) defined + in the SD Host Controller Standard Specification Version 3.00 in SPL. + +config MMC_SDHCI_ASPEED + bool "Aspeed SDHCI controller" + depends on ARCH_ASPEED + depends on DM_MMC + depends on MMC_SDHCI + help + Enables support for the Aspeed SDHCI 2.0 controller present on Aspeed + SoCs. This device is compatible with SD 3.0 and/or MMC 4.3 + specifications. On the AST2600, the device is also compatible with + MMC 5.1 and eMMC 3.0. + +config MMC_SDHCI_ATMEL + bool "Atmel SDHCI controller support" + depends on ARCH_AT91 + depends on DM_MMC && BLK && ARCH_AT91 + depends on MMC_SDHCI + help + This enables support for the Atmel SDHCI controller, which supports + the embedded MultiMedia Card (e.MMC) Specification V4.51, the SD + Memory Card Specification V3.0, and the SDIO V3.0 specification. + It is compliant with the SD Host Controller Standard V3.0 + specification. + +config MMC_SDHCI_BCM2835 + tristate "SDHCI support for the BCM2835 SD/MMC Controller" + depends on ARCH_BCM283X + depends on MMC_SDHCI + select MMC_SDHCI_IO_ACCESSORS + help + This selects the BCM2835 SD/MMC controller. + + If you have a BCM2835 platform with SD or MMC devices, + say Y here. + + If unsure, say N. + +config MMC_SDHCI_BCMSTB + tristate "SDHCI support for the BCMSTB SD/MMC Controller" + depends on MMC_SDHCI + help + This selects the Broadcom set-top box SD/MMC controller. + + If you have a BCMSTB platform with SD or MMC devices, + say Y here. + + If unsure, say N. + +config MMC_SDHCI_CADENCE + bool "SDHCI support for the Cadence SD/SDIO/eMMC controller" + depends on BLK && DM_MMC + depends on MMC_SDHCI + depends on OF_CONTROL + help + This selects the Cadence SD/SDIO/eMMC driver. + + If you have a controller with this interface, say Y here. + + If unsure, say N. + +config MMC_SDHCI_AM654 + bool "SDHCI Controller on TI's Am654 devices" + depends on ARCH_K3 + depends on MMC_SDHCI + depends on DM_MMC && OF_CONTROL && BLK + depends on REGMAP + select MMC_SDHCI_IO_ACCESSORS + help + Support for Secure Digital Host Controller Interface (SDHCI) + controllers present on TI's AM654 SOCs. + +config MMC_SDHCI_IPROC + bool "SDHCI support for the iProc SD/MMC Controller" + depends on MMC_SDHCI + help + This selects the iProc SD/MMC controller. + + If you have a Broadcom IPROC platform with SD or MMC devices, + say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_KONA + bool "SDHCI support on Broadcom KONA platform" + depends on MMC_SDHCI + help + This selects the Broadcom Kona Secure Digital Host Controller + Interface(SDHCI) support. + This is used in Broadcom mobile SoCs. + + If you have a controller with this interface, say Y here. + +config MMC_SDHCI_MSM + bool "Qualcomm SDHCI controller" + depends on BLK && DM_MMC + depends on MMC_SDHCI + help + Enables support for SDHCI 2.0 controller present on some Qualcomm + Snapdragon devices. This device is compatible with eMMC v4.5 and + SD 3.0 specifications. Both SD and eMMC devices are supported. + Card-detect gpios are not supported. + +config MMC_SDHCI_MV + bool "SDHCI support on Marvell platform" + depends on ARCH_MVEBU + depends on MMC_SDHCI + help + This selects the Secure Digital Host Controller Interface on + Marvell platform. + + If you have a controller with this interface, say Y here. + + If unsure, say N. + +config MMC_SDHCI_PIC32 + bool "Microchip PIC32 on-chip SDHCI support" + depends on DM_MMC && MACH_PIC32 + depends on MMC_SDHCI + help + Support for Microchip PIC32 SDHCI controller. + +config MMC_SDHCI_ROCKCHIP + bool "Arasan SDHCI controller for Rockchip support" + depends on ARCH_ROCKCHIP + depends on DM_MMC && BLK + depends on MMC_SDHCI + help + Support for Arasan SDHCI host controller on Rockchip ARM SoCs platform + +config MMC_SDHCI_S5P + bool "SDHCI support on Samsung S5P SoC" + depends on MMC_SDHCI + help + This selects the Secure Digital Host Controller Interface (SDHCI) + on Samsung S5P SoCs. + + If you have a controller with this interface, say Y here. + + If unsure, say N. + +config MMC_SDHCI_SPEAR + bool "SDHCI support on ST SPEAr platform" + depends on MMC_SDHCI + help + This selects the Secure Digital Host Controller Interface (SDHCI) + often referrered to as the HSMMC block in some of the ST SPEAR range + of SoC + + If you have a controller with this interface, say Y here. + + If unsure, say N. + +config MMC_SDHCI_STI + bool "SDHCI support for STMicroelectronics SoC" + depends on MMC_SDHCI && OF_CONTROL + help + This selects the Secure Digital Host Controller Interface (SDHCI) + on STMicroelectronics STiH410 SoC. + +config MMC_SDHCI_XENON + bool "SDHCI support for the Xenon SDHCI controller" + depends on MMC_SDHCI && DM_MMC && OF_CONTROL + help + Support for Xenon SDHCI host controller on Marvell Armada 3700 + 7k/8k ARM SoCs platforms + + If you have a controller with this interface, say Y here. + + If unsure, say N. + +config MMC_SDHCI_TANGIER + bool "Tangier SDHCI controller support" + depends on DM_MMC && BLK + depends on MMC_SDHCI + help + This selects support for SDHCI controller on Tanginer + SoC. Note that this controller does not sit on PCI bus and, + hence, cannot be enumerated by standard PCI means. + + If you're using an Intel Tangier SoC (available on Intel + Edison board), say Y here. + + If unsure, say N. + +config MMC_SDHCI_TEGRA + bool "SDHCI platform support for the Tegra SD/MMC Controller" + depends on ARCH_TEGRA + select BOUNCE_BUFFER + default y + help + This selects the Tegra SD/MMC controller. If you have a Tegra + platform with SD or MMC devices, say Y here. + + If unsure, say N. + +config TEGRA124_MMC_DISABLE_EXT_LOOPBACK + bool "Disable external clock loopback" + depends on MMC_SDHCI_TEGRA && TEGRA124 + help + Disable the external clock loopback and use the internal one on SDMMC3 + as per the SDMMC_VENDOR_MISC_CNTRL_0 register's SDMMC_SPARE1 bits + being set to 0xfffd according to the TRM. + + TODO(marcel.ziswiler@toradex.com): Move to device tree controlled + approach once proper kernel integration made it mainline. + +config MMC_SDHCI_ZYNQ + bool "Arasan SDHCI controller support" + depends on DM_MMC && OF_CONTROL && BLK + depends on MMC_SDHCI + help + Support for Arasan SDHCI host controller on Zynq/ZynqMP ARM SoCs platform + +config ZYNQ_SDHCI_MAX_FREQ + int "Set the maximum frequency of the controller" + depends on MMC_SDHCI_ZYNQ + help + Set the maximum frequency of the controller. + +config ZYNQ_SDHCI_MIN_FREQ + int "Set the minimum frequency of the controller" + depends on MMC_SDHCI_ZYNQ + default 0 + help + Set the minimum frequency of the controller. + +config ZYNQ_HISPD_BROKEN + bool "High speed broken for Zynq SDHCI controller" + depends on MMC_SDHCI_ZYNQ + help + Set if high speed mode is broken. + +config MMC_SUNXI + bool "Allwinner sunxi SD/MMC Host Controller support" + depends on ARCH_SUNXI && !UART0_PORT_F + default y + help + This selects support for the SD/MMC Host Controller on + Allwinner sunxi SoCs. + +config MMC_SUNXI_HAS_NEW_MODE + bool + depends on MMC_SUNXI + +config MMC_SUNXI_HAS_MODE_SWITCH + bool + depends on MMC_SUNXI + +config GENERIC_ATMEL_MCI + bool "Atmel Multimedia Card Interface support" + depends on DM_MMC && BLK && ARCH_AT91 + help + This enables support for Atmel High Speed Multimedia Card Interface + (HSMCI), which supports the MultiMedia Card (MMC) Specification V4.3, + the SD Memory Card Specification V2.0, the SDIO V2.0 specification + and CE-ATA V1.1. + +config STM32_SDMMC2 + bool "STMicroelectronics STM32H7 SD/MMC Host Controller support" + depends on DM_MMC && BLK && OF_CONTROL + help + This selects support for the SD/MMC controller on STM32H7 SoCs. + If you have a board based on such a SoC and with a SD/MMC slot, + say Y or M here. + +config FTSDC010 + bool "Ftsdc010 SD/MMC controller Support" + help + This SD/MMC controller is present in Andestech SoCs which is based on Faraday IP. + +config FTSDC010_SDIO + bool "Support ftsdc010 sdio" + default n + depends on FTSDC010 + help + This can enable ftsdc010 sdio function. + +config MMC_MTK + bool "MediaTek SD/MMC Card Interface support" + depends on ARCH_MEDIATEK || ARCH_MTMIPS + depends on BLK && DM_MMC + depends on OF_CONTROL + help + This selects the MediaTek(R) Secure digital and Multimedia card Interface. + If you have a machine with a integrated SD/MMC card reader, say Y or M here. + This is needed if support for any SD/SDIO/MMC devices is required. + If unsure, say N. + +endif + +config FSL_ESDHC + bool "Freescale/NXP eSDHC controller support" + help + This selects support for the eSDHC (Enhanced Secure Digital Host + Controller) found on numerous Freescale/NXP SoCs. + +config FSL_ESDHC_SUPPORT_ADMA2 + bool "enable ADMA2 support" + depends on FSL_ESDHC + select MMC_SDHCI_ADMA_HELPERS + help + This enables support for the ADMA2 transfer mode. If supported by the + eSDHC it will allow 64bit DMA addresses. + +config FSL_ESDHC_33V_IO_RELIABILITY_WORKAROUND + bool "enable eSDHC workaround for 3.3v IO reliability issue" + depends on FSL_ESDHC && DM_MMC + default n + help + When eSDHC operates at 3.3v, damage can accumulate in an internal + level shifter at a higher than expected rate. The faster the interface + runs, the more damage accumulates. This issue now is found on LX2160A + eSDHC1 for only SD card. The hardware workaround is recommended to use + an on-board level shifter that is 1.8v on SoC side and 3.3v on SD card + side. For boards without hardware workaround, this option could be + enabled, ensuring 1.8v IO voltage and disabling eSDHC if no card. + This option assumes no hotplug, and u-boot has to make all the way to + to linux to use 1.8v UHS-I speed mode if has card. + +config FSL_ESDHC_VS33_NOT_SUPPORT + bool "3.3V power supply not supported" + depends on FSL_ESDHC + help + For eSDHC, power supply is through peripheral circuit. 3.3V support is + common. Select this if 3.3V power supply not supported. + +config FSL_ESDHC_IMX + bool "Freescale/NXP i.MX eSDHC controller support" + help + This selects support for the i.MX eSDHC (Enhanced Secure Digital Host + Controller) found on numerous Freescale/NXP SoCs. + +config FSL_USDHC + bool "Freescale/NXP i.MX uSDHC controller support" + depends on MX6 || MX7 ||ARCH_MX7ULP || IMX8 || IMX8M || IMXRT + select FSL_ESDHC_IMX + help + This enables the Ultra Secured Digital Host Controller enhancements + +endmenu + +config SYS_FSL_ERRATUM_ESDHC111 + bool + +config SYS_FSL_ERRATUM_ESDHC13 + bool + +config SYS_FSL_ERRATUM_ESDHC135 + bool + +config SYS_FSL_ERRATUM_ESDHC_A001 + bool + +config SYS_FSL_ERRATUM_A011334 + bool + +config SYS_FSL_ESDHC_UNRELIABLE_PULSE_DETECTION_WORKAROUND + bool diff --git a/roms/u-boot/drivers/mmc/Makefile b/roms/u-boot/drivers/mmc/Makefile new file mode 100644 index 000000000..89d6af3db --- /dev/null +++ b/roms/u-boot/drivers/mmc/Makefile @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2006 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. + +obj-y += mmc.o +obj-$(CONFIG_$(SPL_)DM_MMC) += mmc-uclass.o +obj-$(CONFIG_$(SPL_)MMC_WRITE) += mmc_write.o +obj-$(CONFIG_MMC_PWRSEQ) += mmc-pwrseq.o +obj-$(CONFIG_MMC_SDHCI_ADMA_HELPERS) += sdhci-adma.o + +ifndef CONFIG_$(SPL_)BLK +obj-y += mmc_legacy.o +endif + +obj-$(CONFIG_SUPPORT_EMMC_BOOT) += mmc_boot.o + +ifdef CONFIG_SPL_BUILD +obj-$(CONFIG_SPL_MMC_BOOT) += fsl_esdhc_spl.o +endif + +obj-$(CONFIG_ARM_PL180_MMCI) += arm_pl180_mmci.o +obj-$(CONFIG_MMC_DAVINCI) += davinci_mmc.o +obj-$(CONFIG_MMC_DW) += dw_mmc.o +obj-$(CONFIG_MMC_DW_CORTINA) += ca_dw_mmc.o +obj-$(CONFIG_MMC_DW_EXYNOS) += exynos_dw_mmc.o +obj-$(CONFIG_MMC_DW_K3) += hi6220_dw_mmc.o +obj-$(CONFIG_MMC_DW_ROCKCHIP) += rockchip_dw_mmc.o +obj-$(CONFIG_MMC_DW_SOCFPGA) += socfpga_dw_mmc.o +obj-$(CONFIG_MMC_DW_SNPS) += snps_dw_mmc.o +obj-$(CONFIG_FSL_ESDHC) += fsl_esdhc.o +obj-$(CONFIG_FSL_ESDHC_IMX) += fsl_esdhc_imx.o +obj-$(CONFIG_FTSDC010) += ftsdc010_mci.o +obj-$(CONFIG_GENERIC_ATMEL_MCI) += gen_atmel_mci.o +obj-$(CONFIG_MMC_MESON_GX) += meson_gx_mmc.o +obj-$(CONFIG_MMC_SPI) += mmc_spi.o +obj-$(CONFIG_MVEBU_MMC) += mvebu_mmc.o +obj-$(CONFIG_MMC_OMAP_HS) += omap_hsmmc.o +obj-$(CONFIG_MMC_MXC) += mxcmmc.o +obj-$(CONFIG_MMC_MXS) += mxsmmc.o +obj-$(CONFIG_MMC_OCTEONTX) += octeontx_hsmmc.o +obj-$(CONFIG_MMC_PCI) += pci_mmc.o +obj-$(CONFIG_PXA_MMC_GENERIC) += pxa_mmc_gen.o +obj-$(CONFIG_$(SPL_TPL_)SUPPORT_EMMC_RPMB) += rpmb.o +obj-$(CONFIG_MMC_SANDBOX) += sandbox_mmc.o +obj-$(CONFIG_SH_MMCIF) += sh_mmcif.o +obj-$(CONFIG_SH_SDHI) += sh_sdhi.o +obj-$(CONFIG_STM32_SDMMC2) += stm32_sdmmc2.o +obj-$(CONFIG_JZ47XX_MMC) += jz_mmc.o +obj-$(CONFIG_NEXELL_DWMMC) += nexell_dw_mmc.o + +# SDHCI +obj-$(CONFIG_MMC_SDHCI) += sdhci.o +obj-$(CONFIG_MMC_SDHCI_ASPEED) += aspeed_sdhci.o +obj-$(CONFIG_MMC_SDHCI_ATMEL) += atmel_sdhci.o +obj-$(CONFIG_MMC_SDHCI_BCM2835) += bcm2835_sdhci.o +obj-$(CONFIG_MMC_SDHCI_BCMSTB) += bcmstb_sdhci.o +obj-$(CONFIG_MMC_SDHCI_CADENCE) += sdhci-cadence.o +obj-$(CONFIG_MMC_SDHCI_AM654) += am654_sdhci.o +obj-$(CONFIG_MMC_SDHCI_IPROC) += iproc_sdhci.o +obj-$(CONFIG_MMC_SDHCI_KONA) += kona_sdhci.o +obj-$(CONFIG_MMC_SDHCI_MSM) += msm_sdhci.o +obj-$(CONFIG_MMC_SDHCI_MV) += mv_sdhci.o +obj-$(CONFIG_MMC_SDHCI_PIC32) += pic32_sdhci.o +obj-$(CONFIG_MMC_SDHCI_ROCKCHIP) += rockchip_sdhci.o +obj-$(CONFIG_MMC_SDHCI_S5P) += s5p_sdhci.o +obj-$(CONFIG_MMC_SDHCI_SPEAR) += spear_sdhci.o +obj-$(CONFIG_MMC_SDHCI_STI) += sti_sdhci.o +obj-$(CONFIG_MMC_SDHCI_TANGIER) += tangier_sdhci.o +obj-$(CONFIG_MMC_SDHCI_TEGRA) += tegra_mmc.o +obj-$(CONFIG_MMC_SDHCI_XENON) += xenon_sdhci.o +obj-$(CONFIG_MMC_SDHCI_ZYNQ) += zynq_sdhci.o + +obj-$(CONFIG_MMC_SUNXI) += sunxi_mmc.o +obj-$(CONFIG_MMC_UNIPHIER) += tmio-common.o uniphier-sd.o +obj-$(CONFIG_RENESAS_SDHI) += tmio-common.o renesas-sdhi.o +obj-$(CONFIG_MMC_BCM2835) += bcm2835_sdhost.o +obj-$(CONFIG_MMC_MTK) += mtk-sd.o diff --git a/roms/u-boot/drivers/mmc/am654_sdhci.c b/roms/u-boot/drivers/mmc/am654_sdhci.c new file mode 100644 index 000000000..a86d96aac --- /dev/null +++ b/roms/u-boot/drivers/mmc/am654_sdhci.c @@ -0,0 +1,685 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ + * + * Texas Instruments' K3 SD Host Controller Interface + */ + +#include <clk.h> +#include <common.h> +#include <dm.h> +#include <malloc.h> +#include <mmc.h> +#include <power-domain.h> +#include <regmap.h> +#include <sdhci.h> +#include <soc.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/err.h> + +/* CTL_CFG Registers */ +#define CTL_CFG_2 0x14 + +#define SLOTTYPE_MASK GENMASK(31, 30) +#define SLOTTYPE_EMBEDDED BIT(30) + +/* PHY Registers */ +#define PHY_CTRL1 0x100 +#define PHY_CTRL2 0x104 +#define PHY_CTRL3 0x108 +#define PHY_CTRL4 0x10C +#define PHY_CTRL5 0x110 +#define PHY_CTRL6 0x114 +#define PHY_STAT1 0x130 +#define PHY_STAT2 0x134 + +#define IOMUX_ENABLE_SHIFT 31 +#define IOMUX_ENABLE_MASK BIT(IOMUX_ENABLE_SHIFT) +#define OTAPDLYENA_SHIFT 20 +#define OTAPDLYENA_MASK BIT(OTAPDLYENA_SHIFT) +#define OTAPDLYSEL_SHIFT 12 +#define OTAPDLYSEL_MASK GENMASK(15, 12) +#define STRBSEL_SHIFT 24 +#define STRBSEL_4BIT_MASK GENMASK(27, 24) +#define STRBSEL_8BIT_MASK GENMASK(31, 24) +#define SEL50_SHIFT 8 +#define SEL50_MASK BIT(SEL50_SHIFT) +#define SEL100_SHIFT 9 +#define SEL100_MASK BIT(SEL100_SHIFT) +#define FREQSEL_SHIFT 8 +#define FREQSEL_MASK GENMASK(10, 8) +#define CLKBUFSEL_SHIFT 0 +#define CLKBUFSEL_MASK GENMASK(2, 0) +#define DLL_TRIM_ICP_SHIFT 4 +#define DLL_TRIM_ICP_MASK GENMASK(7, 4) +#define DR_TY_SHIFT 20 +#define DR_TY_MASK GENMASK(22, 20) +#define ENDLL_SHIFT 1 +#define ENDLL_MASK BIT(ENDLL_SHIFT) +#define DLLRDY_SHIFT 0 +#define DLLRDY_MASK BIT(DLLRDY_SHIFT) +#define PDB_SHIFT 0 +#define PDB_MASK BIT(PDB_SHIFT) +#define CALDONE_SHIFT 1 +#define CALDONE_MASK BIT(CALDONE_SHIFT) +#define RETRIM_SHIFT 17 +#define RETRIM_MASK BIT(RETRIM_SHIFT) +#define SELDLYTXCLK_SHIFT 17 +#define SELDLYTXCLK_MASK BIT(SELDLYTXCLK_SHIFT) +#define SELDLYRXCLK_SHIFT 16 +#define SELDLYRXCLK_MASK BIT(SELDLYRXCLK_SHIFT) +#define ITAPDLYSEL_SHIFT 0 +#define ITAPDLYSEL_MASK GENMASK(4, 0) +#define ITAPDLYENA_SHIFT 8 +#define ITAPDLYENA_MASK BIT(ITAPDLYENA_SHIFT) +#define ITAPCHGWIN_SHIFT 9 +#define ITAPCHGWIN_MASK BIT(ITAPCHGWIN_SHIFT) + +#define DRIVER_STRENGTH_50_OHM 0x0 +#define DRIVER_STRENGTH_33_OHM 0x1 +#define DRIVER_STRENGTH_66_OHM 0x2 +#define DRIVER_STRENGTH_100_OHM 0x3 +#define DRIVER_STRENGTH_40_OHM 0x4 + +#define AM654_SDHCI_MIN_FREQ 400000 +#define CLOCK_TOO_SLOW_HZ 50000000 + +struct am654_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; + struct regmap *base; + bool non_removable; + u32 otap_del_sel[MMC_MODES_END]; + u32 itap_del_sel[MMC_MODES_END]; + u32 trm_icp; + u32 drv_strength; + u32 strb_sel; + u32 clkbuf_sel; + u32 flags; +#define DLL_PRESENT BIT(0) +#define IOMUX_PRESENT BIT(1) +#define FREQSEL_2_BIT BIT(2) +#define STRBSEL_4_BIT BIT(3) +#define DLL_CALIB BIT(4) +}; + +struct timing_data { + const char *otap_binding; + const char *itap_binding; + u32 capability; +}; + +static const struct timing_data td[] = { + [MMC_LEGACY] = {"ti,otap-del-sel-legacy", + "ti,itap-del-sel-legacy", + 0}, + [MMC_HS] = {"ti,otap-del-sel-mmc-hs", + "ti,itap-del-sel-mms-hs", + MMC_CAP(MMC_HS)}, + [SD_HS] = {"ti,otap-del-sel-sd-hs", + "ti,itap-del-sel-sd-hs", + MMC_CAP(SD_HS)}, + [UHS_SDR12] = {"ti,otap-del-sel-sdr12", + "ti,itap-del-sel-sdr12", + MMC_CAP(UHS_SDR12)}, + [UHS_SDR25] = {"ti,otap-del-sel-sdr25", + "ti,itap-del-sel-sdr25", + MMC_CAP(UHS_SDR25)}, + [UHS_SDR50] = {"ti,otap-del-sel-sdr50", + NULL, + MMC_CAP(UHS_SDR50)}, + [UHS_SDR104] = {"ti,otap-del-sel-sdr104", + NULL, + MMC_CAP(UHS_SDR104)}, + [UHS_DDR50] = {"ti,otap-del-sel-ddr50", + NULL, + MMC_CAP(UHS_DDR50)}, + [MMC_DDR_52] = {"ti,otap-del-sel-ddr52", + "ti,itap-del-sel-ddr52", + MMC_CAP(MMC_DDR_52)}, + [MMC_HS_200] = {"ti,otap-del-sel-hs200", + NULL, + MMC_CAP(MMC_HS_200)}, + [MMC_HS_400] = {"ti,otap-del-sel-hs400", + NULL, + MMC_CAP(MMC_HS_400)}, +}; + +struct am654_driver_data { + const struct sdhci_ops *ops; + u32 flags; +}; + +static int am654_sdhci_setup_dll(struct am654_sdhci_plat *plat, + unsigned int speed) +{ + int sel50, sel100, freqsel; + u32 mask, val; + int ret; + + /* Disable delay chain mode */ + regmap_update_bits(plat->base, PHY_CTRL5, + SELDLYTXCLK_MASK | SELDLYRXCLK_MASK, 0); + + if (plat->flags & FREQSEL_2_BIT) { + switch (speed) { + case 200000000: + sel50 = 0; + sel100 = 0; + break; + case 100000000: + sel50 = 0; + sel100 = 1; + break; + default: + sel50 = 1; + sel100 = 0; + } + + /* Configure PHY DLL frequency */ + mask = SEL50_MASK | SEL100_MASK; + val = (sel50 << SEL50_SHIFT) | (sel100 << SEL100_SHIFT); + regmap_update_bits(plat->base, PHY_CTRL5, mask, val); + } else { + switch (speed) { + case 200000000: + freqsel = 0x0; + break; + default: + freqsel = 0x4; + } + regmap_update_bits(plat->base, PHY_CTRL5, FREQSEL_MASK, + freqsel << FREQSEL_SHIFT); + } + + /* Configure DLL TRIM */ + mask = DLL_TRIM_ICP_MASK; + val = plat->trm_icp << DLL_TRIM_ICP_SHIFT; + + /* Configure DLL driver strength */ + mask |= DR_TY_MASK; + val |= plat->drv_strength << DR_TY_SHIFT; + regmap_update_bits(plat->base, PHY_CTRL1, mask, val); + + /* Enable DLL */ + regmap_update_bits(plat->base, PHY_CTRL1, ENDLL_MASK, + 0x1 << ENDLL_SHIFT); + /* + * Poll for DLL ready. Use a one second timeout. + * Works in all experiments done so far + */ + ret = regmap_read_poll_timeout(plat->base, PHY_STAT1, val, + val & DLLRDY_MASK, 1000, 1000000); + + return ret; +} + +static void am654_sdhci_write_itapdly(struct am654_sdhci_plat *plat, + u32 itapdly) +{ + /* Set ITAPCHGWIN before writing to ITAPDLY */ + regmap_update_bits(plat->base, PHY_CTRL4, ITAPCHGWIN_MASK, + 1 << ITAPCHGWIN_SHIFT); + regmap_update_bits(plat->base, PHY_CTRL4, ITAPDLYSEL_MASK, + itapdly << ITAPDLYSEL_SHIFT); + regmap_update_bits(plat->base, PHY_CTRL4, ITAPCHGWIN_MASK, 0); +} + +static void am654_sdhci_setup_delay_chain(struct am654_sdhci_plat *plat, + int mode) +{ + u32 mask, val; + + val = 1 << SELDLYTXCLK_SHIFT | 1 << SELDLYRXCLK_SHIFT; + mask = SELDLYTXCLK_MASK | SELDLYRXCLK_MASK; + regmap_update_bits(plat->base, PHY_CTRL5, mask, val); + + am654_sdhci_write_itapdly(plat, plat->itap_del_sel[mode]); +} + +static int am654_sdhci_set_ios_post(struct sdhci_host *host) +{ + struct udevice *dev = host->mmc->dev; + struct am654_sdhci_plat *plat = dev_get_plat(dev); + unsigned int speed = host->mmc->clock; + int mode = host->mmc->selected_mode; + u32 otap_del_sel; + u32 mask, val; + int ret; + + /* Reset SD Clock Enable */ + val = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + val &= ~SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, val, SDHCI_CLOCK_CONTROL); + + regmap_update_bits(plat->base, PHY_CTRL1, ENDLL_MASK, 0); + + /* restart clock */ + sdhci_set_clock(host->mmc, speed); + + /* switch phy back on */ + otap_del_sel = plat->otap_del_sel[mode]; + mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK; + val = (1 << OTAPDLYENA_SHIFT) | + (otap_del_sel << OTAPDLYSEL_SHIFT); + + /* Write to STRBSEL for HS400 speed mode */ + if (host->mmc->selected_mode == MMC_HS_400) { + if (plat->flags & STRBSEL_4_BIT) + mask |= STRBSEL_4BIT_MASK; + else + mask |= STRBSEL_8BIT_MASK; + + val |= plat->strb_sel << STRBSEL_SHIFT; + } + + regmap_update_bits(plat->base, PHY_CTRL4, mask, val); + + if (mode > UHS_SDR25 && speed >= CLOCK_TOO_SLOW_HZ) { + ret = am654_sdhci_setup_dll(plat, speed); + if (ret) + return ret; + } else { + am654_sdhci_setup_delay_chain(plat, mode); + } + + regmap_update_bits(plat->base, PHY_CTRL5, CLKBUFSEL_MASK, + plat->clkbuf_sel); + + return 0; +} + +int am654_sdhci_init(struct am654_sdhci_plat *plat) +{ + u32 ctl_cfg_2 = 0; + u32 mask, val; + int ret; + + /* Reset OTAP to default value */ + mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK; + regmap_update_bits(plat->base, PHY_CTRL4, mask, 0x0); + + if (plat->flags & DLL_CALIB) { + regmap_read(plat->base, PHY_STAT1, &val); + if (~val & CALDONE_MASK) { + /* Calibrate IO lines */ + regmap_update_bits(plat->base, PHY_CTRL1, PDB_MASK, + PDB_MASK); + ret = regmap_read_poll_timeout(plat->base, PHY_STAT1, + val, val & CALDONE_MASK, + 1, 20); + if (ret) + return ret; + } + } + + /* Enable pins by setting IO mux to 0 */ + if (plat->flags & IOMUX_PRESENT) + regmap_update_bits(plat->base, PHY_CTRL1, IOMUX_ENABLE_MASK, 0); + + /* Set slot type based on SD or eMMC */ + if (plat->non_removable) + ctl_cfg_2 = SLOTTYPE_EMBEDDED; + + regmap_update_bits(plat->base, CTL_CFG_2, SLOTTYPE_MASK, ctl_cfg_2); + + return 0; +} + +#define MAX_SDCD_DEBOUNCE_TIME 2000 +static int am654_sdhci_deferred_probe(struct sdhci_host *host) +{ + struct udevice *dev = host->mmc->dev; + struct am654_sdhci_plat *plat = dev_get_plat(dev); + unsigned long start; + int val; + + /* + * The controller takes about 1 second to debounce the card detect line + * and doesn't let us power on until that time is up. Instead of waiting + * for 1 second at every stage, poll on the CARD_PRESENT bit upto a + * maximum of 2 seconds to be safe.. + */ + start = get_timer(0); + do { + if (get_timer(start) > MAX_SDCD_DEBOUNCE_TIME) + return -ENOMEDIUM; + + val = mmc_getcd(host->mmc); + } while (!val); + + am654_sdhci_init(plat); + + return sdhci_probe(dev); +} + +static void am654_sdhci_write_b(struct sdhci_host *host, u8 val, int reg) +{ + if (reg == SDHCI_HOST_CONTROL) { + switch (host->mmc->selected_mode) { + /* + * According to the data manual, HISPD bit + * should not be set in these speed modes. + */ + case SD_HS: + case MMC_HS: + case UHS_SDR12: + case UHS_SDR25: + val &= ~SDHCI_CTRL_HISPD; + default: + break; + } + } + + writeb(val, host->ioaddr + reg); +} +#ifdef MMC_SUPPORTS_TUNING +#define ITAP_MAX 32 +static int am654_sdhci_execute_tuning(struct mmc *mmc, u8 opcode) +{ + struct udevice *dev = mmc->dev; + struct am654_sdhci_plat *plat = dev_get_plat(dev); + int cur_val, prev_val = 1, fail_len = 0, pass_window = 0, pass_len; + u32 itap; + + /* Enable ITAPDLY */ + regmap_update_bits(plat->base, PHY_CTRL4, ITAPDLYENA_MASK, + 1 << ITAPDLYENA_SHIFT); + + for (itap = 0; itap < ITAP_MAX; itap++) { + am654_sdhci_write_itapdly(plat, itap); + + cur_val = !mmc_send_tuning(mmc, opcode, NULL); + if (cur_val && !prev_val) + pass_window = itap; + + if (!cur_val) + fail_len++; + + prev_val = cur_val; + } + /* + * Having determined the length of the failing window and start of + * the passing window calculate the length of the passing window and + * set the final value halfway through it considering the range as a + * circular buffer + */ + pass_len = ITAP_MAX - fail_len; + itap = (pass_window + (pass_len >> 1)) % ITAP_MAX; + am654_sdhci_write_itapdly(plat, itap); + + return 0; +} +#endif +const struct sdhci_ops am654_sdhci_ops = { +#ifdef MMC_SUPPORTS_TUNING + .platform_execute_tuning = am654_sdhci_execute_tuning, +#endif + .deferred_probe = am654_sdhci_deferred_probe, + .set_ios_post = &am654_sdhci_set_ios_post, + .set_control_reg = sdhci_set_control_reg, + .write_b = am654_sdhci_write_b, +}; + +const struct am654_driver_data am654_drv_data = { + .ops = &am654_sdhci_ops, + .flags = DLL_PRESENT | IOMUX_PRESENT | FREQSEL_2_BIT | STRBSEL_4_BIT, +}; + +const struct am654_driver_data am654_sr1_drv_data = { + .ops = &am654_sdhci_ops, + .flags = IOMUX_PRESENT | FREQSEL_2_BIT | DLL_PRESENT | DLL_CALIB | + STRBSEL_4_BIT, +}; + +const struct am654_driver_data j721e_8bit_drv_data = { + .ops = &am654_sdhci_ops, + .flags = DLL_PRESENT | DLL_CALIB, +}; + +static int j721e_4bit_sdhci_set_ios_post(struct sdhci_host *host) +{ + struct udevice *dev = host->mmc->dev; + struct am654_sdhci_plat *plat = dev_get_plat(dev); + u32 otap_del_sel, mask, val; + + otap_del_sel = plat->otap_del_sel[host->mmc->selected_mode]; + mask = OTAPDLYENA_MASK | OTAPDLYSEL_MASK; + val = (1 << OTAPDLYENA_SHIFT) | (otap_del_sel << OTAPDLYSEL_SHIFT); + regmap_update_bits(plat->base, PHY_CTRL4, mask, val); + + regmap_update_bits(plat->base, PHY_CTRL5, CLKBUFSEL_MASK, + plat->clkbuf_sel); + + return 0; +} + +const struct sdhci_ops j721e_4bit_sdhci_ops = { +#ifdef MMC_SUPPORTS_TUNING + .platform_execute_tuning = am654_sdhci_execute_tuning, +#endif + .deferred_probe = am654_sdhci_deferred_probe, + .set_ios_post = &j721e_4bit_sdhci_set_ios_post, + .set_control_reg = sdhci_set_control_reg, + .write_b = am654_sdhci_write_b, +}; + +const struct am654_driver_data j721e_4bit_drv_data = { + .ops = &j721e_4bit_sdhci_ops, + .flags = IOMUX_PRESENT, +}; + +static const struct am654_driver_data sdhci_am64_8bit_drvdata = { + .ops = &am654_sdhci_ops, + .flags = DLL_PRESENT | DLL_CALIB, +}; + +static const struct am654_driver_data sdhci_am64_4bit_drvdata = { + .ops = &j721e_4bit_sdhci_ops, + .flags = IOMUX_PRESENT, +}; + +const struct soc_attr am654_sdhci_soc_attr[] = { + { .family = "AM65X", .revision = "SR1.0", .data = &am654_sr1_drv_data}, + {/* sentinel */} +}; + +static int sdhci_am654_get_otap_delay(struct udevice *dev, + struct mmc_config *cfg) +{ + struct am654_sdhci_plat *plat = dev_get_plat(dev); + int ret; + int i; + + /* ti,otap-del-sel-legacy is mandatory */ + ret = dev_read_u32(dev, "ti,otap-del-sel-legacy", + &plat->otap_del_sel[0]); + if (ret) + return ret; + /* + * Remove the corresponding capability if an otap-del-sel + * value is not found + */ + for (i = MMC_HS; i <= MMC_HS_400; i++) { + ret = dev_read_u32(dev, td[i].otap_binding, + &plat->otap_del_sel[i]); + if (ret) { + dev_dbg(dev, "Couldn't find %s\n", td[i].otap_binding); + /* + * Remove the corresponding capability + * if an otap-del-sel value is not found + */ + cfg->host_caps &= ~td[i].capability; + } + + if (td[i].itap_binding) + dev_read_u32(dev, td[i].itap_binding, + &plat->itap_del_sel[i]); + } + + return 0; +} + +static int am654_sdhci_probe(struct udevice *dev) +{ + struct am654_driver_data *drv_data = + (struct am654_driver_data *)dev_get_driver_data(dev); + struct am654_sdhci_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct sdhci_host *host = dev_get_priv(dev); + struct mmc_config *cfg = &plat->cfg; + const struct soc_attr *soc; + const struct am654_driver_data *soc_drv_data; + struct clk clk; + unsigned long clock; + int ret; + + ret = clk_get_by_name(dev, "clk_xin", &clk); + if (ret) { + dev_err(dev, "failed to get clock\n"); + return ret; + } + + clock = clk_get_rate(&clk); + if (IS_ERR_VALUE(clock)) { + dev_err(dev, "failed to get rate\n"); + return clock; + } + + host->max_clk = clock; + host->mmc = &plat->mmc; + host->mmc->dev = dev; + host->ops = drv_data->ops; + ret = sdhci_setup_cfg(cfg, host, cfg->f_max, + AM654_SDHCI_MIN_FREQ); + if (ret) + return ret; + + ret = sdhci_am654_get_otap_delay(dev, cfg); + if (ret) + return ret; + + /* Update ops based on SoC revision */ + soc = soc_device_match(am654_sdhci_soc_attr); + if (soc && soc->data) { + soc_drv_data = soc->data; + host->ops = soc_drv_data->ops; + } + + host->mmc->priv = host; + upriv->mmc = host->mmc; + + regmap_init_mem_index(dev_ofnode(dev), &plat->base, 1); + + return 0; +} + +static int am654_sdhci_of_to_plat(struct udevice *dev) +{ + struct am654_sdhci_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + struct mmc_config *cfg = &plat->cfg; + u32 drv_strength; + int ret; + + host->name = dev->name; + host->ioaddr = (void *)dev_read_addr(dev); + plat->non_removable = dev_read_bool(dev, "non-removable"); + + if (plat->flags & DLL_PRESENT) { + ret = dev_read_u32(dev, "ti,trm-icp", &plat->trm_icp); + if (ret) + return ret; + + ret = dev_read_u32(dev, "ti,driver-strength-ohm", + &drv_strength); + if (ret) + return ret; + + switch (drv_strength) { + case 50: + plat->drv_strength = DRIVER_STRENGTH_50_OHM; + break; + case 33: + plat->drv_strength = DRIVER_STRENGTH_33_OHM; + break; + case 66: + plat->drv_strength = DRIVER_STRENGTH_66_OHM; + break; + case 100: + plat->drv_strength = DRIVER_STRENGTH_100_OHM; + break; + case 40: + plat->drv_strength = DRIVER_STRENGTH_40_OHM; + break; + default: + dev_err(dev, "Invalid driver strength\n"); + return -EINVAL; + } + } + + dev_read_u32(dev, "ti,clkbuf-sel", &plat->clkbuf_sel); + + ret = mmc_of_parse(dev, cfg); + if (ret) + return ret; + + return 0; +} + +static int am654_sdhci_bind(struct udevice *dev) +{ + struct am654_driver_data *drv_data = + (struct am654_driver_data *)dev_get_driver_data(dev); + struct am654_sdhci_plat *plat = dev_get_plat(dev); + const struct soc_attr *soc; + const struct am654_driver_data *soc_drv_data; + + plat->flags = drv_data->flags; + + /* Update flags based on SoC revision */ + soc = soc_device_match(am654_sdhci_soc_attr); + if (soc && soc->data) { + soc_drv_data = soc->data; + plat->flags = soc_drv_data->flags; + } + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id am654_sdhci_ids[] = { + { + .compatible = "ti,am654-sdhci-5.1", + .data = (ulong)&am654_drv_data, + }, + { + .compatible = "ti,j721e-sdhci-8bit", + .data = (ulong)&j721e_8bit_drv_data, + }, + { + .compatible = "ti,j721e-sdhci-4bit", + .data = (ulong)&j721e_4bit_drv_data, + }, + { + .compatible = "ti,am64-sdhci-8bit", + .data = (ulong)&sdhci_am64_8bit_drvdata, + }, + { + .compatible = "ti,am64-sdhci-4bit", + .data = (ulong)&sdhci_am64_4bit_drvdata, + }, + { } +}; + +U_BOOT_DRIVER(am654_sdhci_drv) = { + .name = "am654_sdhci", + .id = UCLASS_MMC, + .of_match = am654_sdhci_ids, + .of_to_plat = am654_sdhci_of_to_plat, + .ops = &sdhci_ops, + .bind = am654_sdhci_bind, + .probe = am654_sdhci_probe, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct am654_sdhci_plat), +}; diff --git a/roms/u-boot/drivers/mmc/arm_pl180_mmci.c b/roms/u-boot/drivers/mmc/arm_pl180_mmci.c new file mode 100644 index 000000000..b2d1b4f9a --- /dev/null +++ b/roms/u-boot/drivers/mmc/arm_pl180_mmci.c @@ -0,0 +1,557 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ARM PrimeCell MultiMedia Card Interface - PL180 + * + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ulf Hansson <ulf.hansson@stericsson.com> + * Author: Martin Lundholm <martin.xa.lundholm@stericsson.com> + * Ported to drivers/mmc/ by: Matt Waddel <matt.waddel@linaro.org> + */ + +/* #define DEBUG */ + +#include "common.h" +#include <clk.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <mmc.h> +#include <dm/device_compat.h> + +#include <asm/io.h> +#include <asm-generic/gpio.h> + +#include "arm_pl180_mmci.h" +#include <linux/delay.h> + +#ifdef CONFIG_DM_MMC +#include <dm.h> +#define MMC_CLOCK_MAX 48000000 +#define MMC_CLOCK_MIN 400000 + +struct arm_pl180_mmc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; +#endif + +static int wait_for_command_end(struct mmc *dev, struct mmc_cmd *cmd) +{ + u32 hoststatus, statusmask; + struct pl180_mmc_host *host = dev->priv; + + statusmask = SDI_STA_CTIMEOUT | SDI_STA_CCRCFAIL; + if ((cmd->resp_type & MMC_RSP_PRESENT)) + statusmask |= SDI_STA_CMDREND; + else + statusmask |= SDI_STA_CMDSENT; + + do + hoststatus = readl(&host->base->status) & statusmask; + while (!hoststatus); + + writel(statusmask, &host->base->status_clear); + if (hoststatus & SDI_STA_CTIMEOUT) { + debug("CMD%d time out\n", cmd->cmdidx); + return -ETIMEDOUT; + } else if ((hoststatus & SDI_STA_CCRCFAIL) && + (cmd->resp_type & MMC_RSP_CRC)) { + printf("CMD%d CRC error\n", cmd->cmdidx); + return -EILSEQ; + } + + if (cmd->resp_type & MMC_RSP_PRESENT) { + cmd->response[0] = readl(&host->base->response0); + cmd->response[1] = readl(&host->base->response1); + cmd->response[2] = readl(&host->base->response2); + cmd->response[3] = readl(&host->base->response3); + debug("CMD%d response[0]:0x%08X, response[1]:0x%08X, " + "response[2]:0x%08X, response[3]:0x%08X\n", + cmd->cmdidx, cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + } + + return 0; +} + +/* send command to the mmc card and wait for results */ +static int do_command(struct mmc *dev, struct mmc_cmd *cmd) +{ + int result; + u32 sdi_cmd = 0; + struct pl180_mmc_host *host = dev->priv; + + sdi_cmd = ((cmd->cmdidx & SDI_CMD_CMDINDEX_MASK) | SDI_CMD_CPSMEN); + + if (cmd->resp_type) { + sdi_cmd |= SDI_CMD_WAITRESP; + if (cmd->resp_type & MMC_RSP_136) + sdi_cmd |= SDI_CMD_LONGRESP; + } + + writel((u32)cmd->cmdarg, &host->base->argument); + udelay(COMMAND_REG_DELAY); + writel(sdi_cmd, &host->base->command); + result = wait_for_command_end(dev, cmd); + + /* After CMD2 set RCA to a none zero value. */ + if ((result == 0) && (cmd->cmdidx == MMC_CMD_ALL_SEND_CID)) + dev->rca = 10; + + /* After CMD3 open drain is switched off and push pull is used. */ + if ((result == 0) && (cmd->cmdidx == MMC_CMD_SET_RELATIVE_ADDR)) { + u32 sdi_pwr = readl(&host->base->power) & ~SDI_PWR_OPD; + writel(sdi_pwr, &host->base->power); + } + + return result; +} + +static int read_bytes(struct mmc *dev, u32 *dest, u32 blkcount, u32 blksize) +{ + u32 *tempbuff = dest; + u64 xfercount = blkcount * blksize; + struct pl180_mmc_host *host = dev->priv; + u32 status, status_err; + + debug("read_bytes: blkcount=%u blksize=%u\n", blkcount, blksize); + + status = readl(&host->base->status); + status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | + SDI_STA_RXOVERR); + while ((!status_err) && (xfercount >= sizeof(u32))) { + if (status & SDI_STA_RXDAVL) { + *(tempbuff) = readl(&host->base->fifo); + tempbuff++; + xfercount -= sizeof(u32); + } + status = readl(&host->base->status); + status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | + SDI_STA_RXOVERR); + } + + status_err = status & + (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND | + SDI_STA_RXOVERR); + while (!status_err) { + status = readl(&host->base->status); + status_err = status & + (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND | + SDI_STA_RXOVERR); + } + + if (status & SDI_STA_DTIMEOUT) { + printf("Read data timed out, xfercount: %llu, status: 0x%08X\n", + xfercount, status); + return -ETIMEDOUT; + } else if (status & SDI_STA_DCRCFAIL) { + printf("Read data bytes CRC error: 0x%x\n", status); + return -EILSEQ; + } else if (status & SDI_STA_RXOVERR) { + printf("Read data RX overflow error\n"); + return -EIO; + } + + writel(SDI_ICR_MASK, &host->base->status_clear); + + if (xfercount) { + printf("Read data error, xfercount: %llu\n", xfercount); + return -ENOBUFS; + } + + return 0; +} + +static int write_bytes(struct mmc *dev, u32 *src, u32 blkcount, u32 blksize) +{ + u32 *tempbuff = src; + int i; + u64 xfercount = blkcount * blksize; + struct pl180_mmc_host *host = dev->priv; + u32 status, status_err; + + debug("write_bytes: blkcount=%u blksize=%u\n", blkcount, blksize); + + status = readl(&host->base->status); + status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT); + while (!status_err && xfercount) { + if (status & SDI_STA_TXFIFOBW) { + if (xfercount >= SDI_FIFO_BURST_SIZE * sizeof(u32)) { + for (i = 0; i < SDI_FIFO_BURST_SIZE; i++) + writel(*(tempbuff + i), + &host->base->fifo); + tempbuff += SDI_FIFO_BURST_SIZE; + xfercount -= SDI_FIFO_BURST_SIZE * sizeof(u32); + } else { + while (xfercount >= sizeof(u32)) { + writel(*(tempbuff), &host->base->fifo); + tempbuff++; + xfercount -= sizeof(u32); + } + } + } + status = readl(&host->base->status); + status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT); + } + + status_err = status & + (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND); + while (!status_err) { + status = readl(&host->base->status); + status_err = status & + (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND); + } + + if (status & SDI_STA_DTIMEOUT) { + printf("Write data timed out, xfercount:%llu,status:0x%08X\n", + xfercount, status); + return -ETIMEDOUT; + } else if (status & SDI_STA_DCRCFAIL) { + printf("Write data CRC error\n"); + return -EILSEQ; + } + + writel(SDI_ICR_MASK, &host->base->status_clear); + + if (xfercount) { + printf("Write data error, xfercount:%llu", xfercount); + return -ENOBUFS; + } + + return 0; +} + +static int do_data_transfer(struct mmc *dev, + struct mmc_cmd *cmd, + struct mmc_data *data) +{ + int error = -ETIMEDOUT; + struct pl180_mmc_host *host = dev->priv; + u32 blksz = 0; + u32 data_ctrl = 0; + u32 data_len = (u32) (data->blocks * data->blocksize); + + if (!host->version2) { + blksz = (ffs(data->blocksize) - 1); + data_ctrl |= ((blksz << 4) & SDI_DCTRL_DBLKSIZE_MASK); + } else { + blksz = data->blocksize; + data_ctrl |= (blksz << SDI_DCTRL_DBLOCKSIZE_V2_SHIFT); + } + data_ctrl |= SDI_DCTRL_DTEN | SDI_DCTRL_BUSYMODE; + + writel(SDI_DTIMER_DEFAULT, &host->base->datatimer); + writel(data_len, &host->base->datalength); + udelay(DATA_REG_DELAY); + + if (data->flags & MMC_DATA_READ) { + data_ctrl |= SDI_DCTRL_DTDIR_IN; + writel(data_ctrl, &host->base->datactrl); + + error = do_command(dev, cmd); + if (error) + return error; + + error = read_bytes(dev, (u32 *)data->dest, (u32)data->blocks, + (u32)data->blocksize); + } else if (data->flags & MMC_DATA_WRITE) { + error = do_command(dev, cmd); + if (error) + return error; + + writel(data_ctrl, &host->base->datactrl); + error = write_bytes(dev, (u32 *)data->src, (u32)data->blocks, + (u32)data->blocksize); + } + + return error; +} + +static int host_request(struct mmc *dev, + struct mmc_cmd *cmd, + struct mmc_data *data) +{ + int result; + + if (data) + result = do_data_transfer(dev, cmd, data); + else + result = do_command(dev, cmd); + + return result; +} + +static int host_set_ios(struct mmc *dev) +{ + struct pl180_mmc_host *host = dev->priv; + u32 sdi_clkcr; + + sdi_clkcr = readl(&host->base->clock); + + /* Ramp up the clock rate */ + if (dev->clock) { + u32 clkdiv = 0; + u32 tmp_clock; + + if (dev->clock >= dev->cfg->f_max) { + clkdiv = 0; + dev->clock = dev->cfg->f_max; + } else { + clkdiv = (host->clock_in / dev->clock) - 2; + } + + tmp_clock = host->clock_in / (clkdiv + 2); + while (tmp_clock > dev->clock) { + clkdiv++; + tmp_clock = host->clock_in / (clkdiv + 2); + } + + if (clkdiv > SDI_CLKCR_CLKDIV_MASK) + clkdiv = SDI_CLKCR_CLKDIV_MASK; + + tmp_clock = host->clock_in / (clkdiv + 2); + dev->clock = tmp_clock; + sdi_clkcr &= ~(SDI_CLKCR_CLKDIV_MASK); + sdi_clkcr |= clkdiv; + } + + /* Set the bus width */ + if (dev->bus_width) { + u32 buswidth = 0; + + switch (dev->bus_width) { + case 1: + buswidth |= SDI_CLKCR_WIDBUS_1; + break; + case 4: + buswidth |= SDI_CLKCR_WIDBUS_4; + break; + case 8: + buswidth |= SDI_CLKCR_WIDBUS_8; + break; + default: + printf("Invalid bus width: %d\n", dev->bus_width); + break; + } + sdi_clkcr &= ~(SDI_CLKCR_WIDBUS_MASK); + sdi_clkcr |= buswidth; + } + + writel(sdi_clkcr, &host->base->clock); + udelay(CLK_CHANGE_DELAY); + + return 0; +} + +#ifndef CONFIG_DM_MMC +/* MMC uses open drain drivers in the enumeration phase */ +static int mmc_host_reset(struct mmc *dev) +{ + struct pl180_mmc_host *host = dev->priv; + + writel(host->pwr_init, &host->base->power); + + return 0; +} + +static const struct mmc_ops arm_pl180_mmci_ops = { + .send_cmd = host_request, + .set_ios = host_set_ios, + .init = mmc_host_reset, +}; + +/* + * mmc_host_init - initialize the mmc controller. + * Set initial clock and power for mmc slot. + * Initialize mmc struct and register with mmc framework. + */ + +int arm_pl180_mmci_init(struct pl180_mmc_host *host, struct mmc **mmc) +{ + u32 sdi_u32; + + writel(host->pwr_init, &host->base->power); + writel(host->clkdiv_init, &host->base->clock); + udelay(CLK_CHANGE_DELAY); + + /* Disable mmc interrupts */ + sdi_u32 = readl(&host->base->mask0) & ~SDI_MASK0_MASK; + writel(sdi_u32, &host->base->mask0); + + host->cfg.name = host->name; + host->cfg.ops = &arm_pl180_mmci_ops; + + /* TODO remove the duplicates */ + host->cfg.host_caps = host->caps; + host->cfg.voltages = host->voltages; + host->cfg.f_min = host->clock_min; + host->cfg.f_max = host->clock_max; + if (host->b_max != 0) + host->cfg.b_max = host->b_max; + else + host->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + *mmc = mmc_create(&host->cfg, host); + if (!*mmc) + return -1; + debug("registered mmc interface number is:%d\n", + (*mmc)->block_dev.devnum); + + return 0; +} +#endif + +#ifdef CONFIG_DM_MMC +static void arm_pl180_mmc_init(struct pl180_mmc_host *host) +{ + u32 sdi_u32; + + writel(host->pwr_init, &host->base->power); + writel(host->clkdiv_init, &host->base->clock); + udelay(CLK_CHANGE_DELAY); + + /* Disable mmc interrupts */ + sdi_u32 = readl(&host->base->mask0) & ~SDI_MASK0_MASK; + writel(sdi_u32, &host->base->mask0); +} + +static int arm_pl180_mmc_probe(struct udevice *dev) +{ + struct arm_pl180_mmc_plat *pdata = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct mmc *mmc = &pdata->mmc; + struct pl180_mmc_host *host = dev_get_priv(dev); + struct mmc_config *cfg = &pdata->cfg; + struct clk clk; + u32 bus_width; + u32 periphid; + int ret; + + ret = clk_get_by_index(dev, 0, &clk); + if (ret < 0) + return ret; + + ret = clk_enable(&clk); + if (ret) { + clk_free(&clk); + dev_err(dev, "failed to enable clock\n"); + return ret; + } + + host->pwr_init = INIT_PWR; + host->clkdiv_init = SDI_CLKCR_CLKDIV_INIT_V1 | SDI_CLKCR_CLKEN | + SDI_CLKCR_HWFC_EN; + host->clock_in = clk_get_rate(&clk); + + periphid = dev_read_u32_default(dev, "arm,primecell-periphid", 0); + switch (periphid) { + case STM32_MMCI_ID: /* stm32 variant */ + host->version2 = false; + break; + default: + host->version2 = true; + } + + cfg->name = dev->name; + cfg->voltages = VOLTAGE_WINDOW_SD; + cfg->host_caps = 0; + cfg->f_min = host->clock_in / (2 * (SDI_CLKCR_CLKDIV_INIT_V1 + 1)); + cfg->f_max = dev_read_u32_default(dev, "max-frequency", MMC_CLOCK_MAX); + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + gpio_request_by_name(dev, "cd-gpios", 0, &host->cd_gpio, GPIOD_IS_IN); + + bus_width = dev_read_u32_default(dev, "bus-width", 1); + switch (bus_width) { + case 8: + cfg->host_caps |= MMC_MODE_8BIT; + /* Hosts capable of 8-bit transfers can also do 4 bits */ + case 4: + cfg->host_caps |= MMC_MODE_4BIT; + break; + case 1: + break; + default: + dev_err(dev, "Invalid bus-width value %u\n", bus_width); + } + + arm_pl180_mmc_init(host); + mmc->priv = host; + mmc->dev = dev; + upriv->mmc = mmc; + + return 0; +} + +int arm_pl180_mmc_bind(struct udevice *dev) +{ + struct arm_pl180_mmc_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static int dm_host_request(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + + return host_request(mmc, cmd, data); +} + +static int dm_host_set_ios(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + + return host_set_ios(mmc); +} + +static int dm_mmc_getcd(struct udevice *dev) +{ + struct pl180_mmc_host *host = dev_get_priv(dev); + int value = 1; + + if (dm_gpio_is_valid(&host->cd_gpio)) + value = dm_gpio_get_value(&host->cd_gpio); + + return value; +} + +static const struct dm_mmc_ops arm_pl180_dm_mmc_ops = { + .send_cmd = dm_host_request, + .set_ios = dm_host_set_ios, + .get_cd = dm_mmc_getcd, +}; + +static int arm_pl180_mmc_of_to_plat(struct udevice *dev) +{ + struct pl180_mmc_host *host = dev_get_priv(dev); + fdt_addr_t addr; + + addr = dev_read_addr(dev); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + + host->base = (void *)addr; + + return 0; +} + +static const struct udevice_id arm_pl180_mmc_match[] = { + { .compatible = "arm,pl180" }, + { .compatible = "arm,primecell" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(arm_pl180_mmc) = { + .name = "arm_pl180_mmc", + .id = UCLASS_MMC, + .of_match = arm_pl180_mmc_match, + .ops = &arm_pl180_dm_mmc_ops, + .probe = arm_pl180_mmc_probe, + .of_to_plat = arm_pl180_mmc_of_to_plat, + .bind = arm_pl180_mmc_bind, + .priv_auto = sizeof(struct pl180_mmc_host), + .plat_auto = sizeof(struct arm_pl180_mmc_plat), +}; +#endif diff --git a/roms/u-boot/drivers/mmc/arm_pl180_mmci.h b/roms/u-boot/drivers/mmc/arm_pl180_mmci.h new file mode 100644 index 000000000..61ee96a11 --- /dev/null +++ b/roms/u-boot/drivers/mmc/arm_pl180_mmci.h @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * ARM PrimeCell MultiMedia Card Interface - PL180 + * + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Ulf Hansson <ulf.hansson@stericsson.com> + * Author: Martin Lundholm <martin.xa.lundholm@stericsson.com> + * Ported to drivers/mmc/ by: Matt Waddel <matt.waddel@linaro.org> + */ + +#ifndef __ARM_PL180_MMCI_H__ +#define __ARM_PL180_MMCI_H__ + +/* need definition of struct mmc_config */ +#include <mmc.h> + +#define COMMAND_REG_DELAY 300 +#define DATA_REG_DELAY 1000 +#define CLK_CHANGE_DELAY 2000 + +#define INIT_PWR 0xBF /* Power on, full power, not open drain */ +#define ARM_MCLK (100*1000*1000) + +/* SDI Power Control register bits */ +#define SDI_PWR_PWRCTRL_MASK 0x00000003 +#define SDI_PWR_PWRCTRL_ON 0x00000003 +#define SDI_PWR_PWRCTRL_OFF 0x00000000 +#define SDI_PWR_DAT2DIREN 0x00000004 +#define SDI_PWR_CMDDIREN 0x00000008 +#define SDI_PWR_DAT0DIREN 0x00000010 +#define SDI_PWR_DAT31DIREN 0x00000020 +#define SDI_PWR_OPD 0x00000040 +#define SDI_PWR_FBCLKEN 0x00000080 +#define SDI_PWR_DAT74DIREN 0x00000100 +#define SDI_PWR_RSTEN 0x00000200 + +#define VOLTAGE_WINDOW_MMC 0x00FF8080 +#define VOLTAGE_WINDOW_SD 0x80010000 + +/* SDI clock control register bits */ +#define SDI_CLKCR_CLKDIV_MASK 0x000000FF +#define SDI_CLKCR_CLKEN 0x00000100 +#define SDI_CLKCR_PWRSAV 0x00000200 +#define SDI_CLKCR_BYPASS 0x00000400 +#define SDI_CLKCR_WIDBUS_MASK 0x00001800 +#define SDI_CLKCR_WIDBUS_1 0x00000000 +#define SDI_CLKCR_WIDBUS_4 0x00000800 +/* V2 only */ +#define SDI_CLKCR_WIDBUS_8 0x00001000 +#define SDI_CLKCR_NEDGE 0x00002000 +#define SDI_CLKCR_HWFC_EN 0x00004000 + +#define SDI_CLKCR_CLKDIV_INIT_V1 0x000000C6 /* MCLK/(2*(0xC6+1)) => 505KHz */ +#define SDI_CLKCR_CLKDIV_INIT_V2 0x000000FD + +/* SDI command register bits */ +#define SDI_CMD_CMDINDEX_MASK 0x000000FF +#define SDI_CMD_WAITRESP 0x00000040 +#define SDI_CMD_LONGRESP 0x00000080 +#define SDI_CMD_WAITINT 0x00000100 +#define SDI_CMD_WAITPEND 0x00000200 +#define SDI_CMD_CPSMEN 0x00000400 +#define SDI_CMD_SDIOSUSPEND 0x00000800 +#define SDI_CMD_ENDCMDCOMPL 0x00001000 +#define SDI_CMD_NIEN 0x00002000 +#define SDI_CMD_CE_ATACMD 0x00004000 +#define SDI_CMD_CBOOTMODEEN 0x00008000 + +#define SDI_DTIMER_DEFAULT 0xFFFF0000 + +/* SDI Status register bits */ +#define SDI_STA_CCRCFAIL 0x00000001 +#define SDI_STA_DCRCFAIL 0x00000002 +#define SDI_STA_CTIMEOUT 0x00000004 +#define SDI_STA_DTIMEOUT 0x00000008 +#define SDI_STA_TXUNDERR 0x00000010 +#define SDI_STA_RXOVERR 0x00000020 +#define SDI_STA_CMDREND 0x00000040 +#define SDI_STA_CMDSENT 0x00000080 +#define SDI_STA_DATAEND 0x00000100 +#define SDI_STA_STBITERR 0x00000200 +#define SDI_STA_DBCKEND 0x00000400 +#define SDI_STA_CMDACT 0x00000800 +#define SDI_STA_TXACT 0x00001000 +#define SDI_STA_RXACT 0x00002000 +#define SDI_STA_TXFIFOBW 0x00004000 +#define SDI_STA_RXFIFOBR 0x00008000 +#define SDI_STA_TXFIFOF 0x00010000 +#define SDI_STA_RXFIFOF 0x00020000 +#define SDI_STA_TXFIFOE 0x00040000 +#define SDI_STA_RXFIFOE 0x00080000 +#define SDI_STA_TXDAVL 0x00100000 +#define SDI_STA_RXDAVL 0x00200000 +#define SDI_STA_SDIOIT 0x00400000 +#define SDI_STA_CEATAEND 0x00800000 +#define SDI_STA_CARDBUSY 0x01000000 +#define SDI_STA_BOOTMODE 0x02000000 +#define SDI_STA_BOOTACKERR 0x04000000 +#define SDI_STA_BOOTACKTIMEOUT 0x08000000 +#define SDI_STA_RSTNEND 0x10000000 + +/* SDI Interrupt Clear register bits */ +#define SDI_ICR_MASK 0x1DC007FF +#define SDI_ICR_CCRCFAILC 0x00000001 +#define SDI_ICR_DCRCFAILC 0x00000002 +#define SDI_ICR_CTIMEOUTC 0x00000004 +#define SDI_ICR_DTIMEOUTC 0x00000008 +#define SDI_ICR_TXUNDERRC 0x00000010 +#define SDI_ICR_RXOVERRC 0x00000020 +#define SDI_ICR_CMDRENDC 0x00000040 +#define SDI_ICR_CMDSENTC 0x00000080 +#define SDI_ICR_DATAENDC 0x00000100 +#define SDI_ICR_STBITERRC 0x00000200 +#define SDI_ICR_DBCKENDC 0x00000400 +#define SDI_ICR_SDIOITC 0x00400000 +#define SDI_ICR_CEATAENDC 0x00800000 +#define SDI_ICR_BUSYENDC 0x01000000 +#define SDI_ICR_BOOTACKERRC 0x04000000 +#define SDI_ICR_BOOTACKTIMEOUTC 0x08000000 +#define SDI_ICR_RSTNENDC 0x10000000 + +#define SDI_MASK0_MASK 0x1FFFFFFF + +/* SDI Data control register bits */ +#define SDI_DCTRL_DTEN 0x00000001 +#define SDI_DCTRL_DTDIR_IN 0x00000002 +#define SDI_DCTRL_DTMODE_STREAM 0x00000004 +#define SDI_DCTRL_DMAEN 0x00000008 +#define SDI_DCTRL_DBLKSIZE_MASK 0x000000F0 +#define SDI_DCTRL_RWSTART 0x00000100 +#define SDI_DCTRL_RWSTOP 0x00000200 +#define SDI_DCTRL_RWMOD 0x00000200 +#define SDI_DCTRL_SDIOEN 0x00000800 +#define SDI_DCTRL_DMAREQCTL 0x00001000 +#define SDI_DCTRL_DBOOTMODEEN 0x00002000 +#define SDI_DCTRL_BUSYMODE 0x00004000 +#define SDI_DCTRL_DDR_MODE 0x00008000 +#define SDI_DCTRL_DBLOCKSIZE_V2_MASK 0x7fff0000 +#define SDI_DCTRL_DBLOCKSIZE_V2_SHIFT 16 + +#define SDI_FIFO_BURST_SIZE 8 + +#define STM32_MMCI_ID 0x00880180 + +struct sdi_registers { + u32 power; /* 0x00*/ + u32 clock; /* 0x04*/ + u32 argument; /* 0x08*/ + u32 command; /* 0x0c*/ + u32 respcommand; /* 0x10*/ + u32 response0; /* 0x14*/ + u32 response1; /* 0x18*/ + u32 response2; /* 0x1c*/ + u32 response3; /* 0x20*/ + u32 datatimer; /* 0x24*/ + u32 datalength; /* 0x28*/ + u32 datactrl; /* 0x2c*/ + u32 datacount; /* 0x30*/ + u32 status; /* 0x34*/ + u32 status_clear; /* 0x38*/ + u32 mask0; /* 0x3c*/ + u32 mask1; /* 0x40*/ + u32 card_select; /* 0x44*/ + u32 fifo_count; /* 0x48*/ + u32 padding1[(0x80-0x4C)>>2]; + u32 fifo; /* 0x80*/ + u32 padding2[(0xFE0-0x84)>>2]; + u32 periph_id0; /* 0xFE0 mmc Peripheral Identifcation Register*/ + u32 periph_id1; /* 0xFE4*/ + u32 periph_id2; /* 0xFE8*/ + u32 periph_id3; /* 0xFEC*/ + u32 pcell_id0; /* 0xFF0*/ + u32 pcell_id1; /* 0xFF4*/ + u32 pcell_id2; /* 0xFF8*/ + u32 pcell_id3; /* 0xFFC*/ +}; + +struct pl180_mmc_host { + struct sdi_registers *base; + char name[32]; + unsigned int b_max; + unsigned int voltages; + unsigned int caps; + unsigned int clock_in; + unsigned int clock_min; + unsigned int clock_max; + unsigned int clkdiv_init; + unsigned int pwr_init; + int version2; + struct mmc_config cfg; +#ifdef CONFIG_DM_MMC + struct gpio_desc cd_gpio; +#endif +}; + +int arm_pl180_mmci_init(struct pl180_mmc_host *host, struct mmc **mmc); + +#endif diff --git a/roms/u-boot/drivers/mmc/aspeed_sdhci.c b/roms/u-boot/drivers/mmc/aspeed_sdhci.c new file mode 100644 index 000000000..453731571 --- /dev/null +++ b/roms/u-boot/drivers/mmc/aspeed_sdhci.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019 IBM Corp. + * Eddie James <eajames@linux.ibm.com> + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <malloc.h> +#include <sdhci.h> +#include <linux/err.h> + +struct aspeed_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +static int aspeed_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct aspeed_sdhci_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + u32 max_clk; + struct clk clk; + int ret; + + ret = clk_get_by_index(dev, 0, &clk); + if (ret) + return ret; + + ret = clk_enable(&clk); + if (ret) + goto free; + + host->name = dev->name; + host->ioaddr = dev_read_addr_ptr(dev); + + max_clk = clk_get_rate(&clk); + if (IS_ERR_VALUE(max_clk)) { + ret = max_clk; + goto err; + } + + host->max_clk = max_clk; + host->mmc = &plat->mmc; + host->mmc->dev = dev; + host->mmc->priv = host; + upriv->mmc = host->mmc; + + ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0); + if (ret) + goto err; + + ret = sdhci_probe(dev); + if (ret) + goto err; + + return 0; + +err: + clk_disable(&clk); +free: + clk_free(&clk); + return ret; +} + +static int aspeed_sdhci_bind(struct udevice *dev) +{ + struct aspeed_sdhci_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id aspeed_sdhci_ids[] = { + { .compatible = "aspeed,ast2400-sdhci" }, + { .compatible = "aspeed,ast2500-sdhci" }, + { .compatible = "aspeed,ast2600-sdhci" }, + { } +}; + +U_BOOT_DRIVER(aspeed_sdhci_drv) = { + .name = "aspeed_sdhci", + .id = UCLASS_MMC, + .of_match = aspeed_sdhci_ids, + .ops = &sdhci_ops, + .bind = aspeed_sdhci_bind, + .probe = aspeed_sdhci_probe, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct aspeed_sdhci_plat), +}; diff --git a/roms/u-boot/drivers/mmc/atmel_sdhci.c b/roms/u-boot/drivers/mmc/atmel_sdhci.c new file mode 100644 index 000000000..2b5ceeab9 --- /dev/null +++ b/roms/u-boot/drivers/mmc/atmel_sdhci.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Atmel Corporation + * Wenyou.Yang <wenyou.yang@atmel.com> + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <malloc.h> +#include <sdhci.h> +#include <asm/arch/clk.h> +#include <asm/global_data.h> + +#define ATMEL_SDHC_MIN_FREQ 400000 +#define ATMEL_SDHC_GCK_RATE 240000000 + +#ifndef CONFIG_DM_MMC +int atmel_sdhci_init(void *regbase, u32 id) +{ + struct sdhci_host *host; + u32 max_clk, min_clk = ATMEL_SDHC_MIN_FREQ; + + host = (struct sdhci_host *)calloc(1, sizeof(struct sdhci_host)); + if (!host) { + printf("%s: sdhci_host calloc failed\n", __func__); + return -ENOMEM; + } + + host->name = "atmel_sdhci"; + host->ioaddr = regbase; + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD; + max_clk = at91_get_periph_generated_clk(id); + if (!max_clk) { + printf("%s: Failed to get the proper clock\n", __func__); + free(host); + return -ENODEV; + } + host->max_clk = max_clk; + + add_sdhci(host, 0, min_clk); + + return 0; +} + +#else + +DECLARE_GLOBAL_DATA_PTR; + +struct atmel_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +static int atmel_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct atmel_sdhci_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + u32 max_clk; + struct clk clk; + int ret; + + ret = clk_get_by_index(dev, 0, &clk); + if (ret) + return ret; + + ret = clk_enable(&clk); + if (ret) + return ret; + + host->name = dev->name; + host->ioaddr = dev_read_addr_ptr(dev); + + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD; + host->bus_width = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), + "bus-width", 4); + + ret = clk_get_by_index(dev, 1, &clk); + if (ret) + return ret; + + clk_set_rate(&clk, ATMEL_SDHC_GCK_RATE); + + max_clk = clk_get_rate(&clk); + if (!max_clk) + return -EINVAL; + + ret = clk_enable(&clk); + /* return error only if the clock really has a clock enable func */ + if (ret && ret != -ENOSYS) + return ret; + + ret = mmc_of_parse(dev, &plat->cfg); + if (ret) + return ret; + + host->max_clk = max_clk; + host->mmc = &plat->mmc; + host->mmc->dev = dev; + + ret = sdhci_setup_cfg(&plat->cfg, host, 0, ATMEL_SDHC_MIN_FREQ); + if (ret) + return ret; + + host->mmc->priv = host; + upriv->mmc = host->mmc; + + clk_free(&clk); + + return sdhci_probe(dev); +} + +static int atmel_sdhci_bind(struct udevice *dev) +{ + struct atmel_sdhci_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id atmel_sdhci_ids[] = { + { .compatible = "atmel,sama5d2-sdhci" }, + { .compatible = "microchip,sam9x60-sdhci" }, + { .compatible = "microchip,sama7g5-sdhci" }, + { } +}; + +U_BOOT_DRIVER(atmel_sdhci_drv) = { + .name = "atmel_sdhci", + .id = UCLASS_MMC, + .of_match = atmel_sdhci_ids, + .ops = &sdhci_ops, + .bind = atmel_sdhci_bind, + .probe = atmel_sdhci_probe, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct atmel_sdhci_plat), +}; +#endif diff --git a/roms/u-boot/drivers/mmc/bcm2835_sdhci.c b/roms/u-boot/drivers/mmc/bcm2835_sdhci.c new file mode 100644 index 000000000..5e48394fd --- /dev/null +++ b/roms/u-boot/drivers/mmc/bcm2835_sdhci.c @@ -0,0 +1,256 @@ +/* + * This code was extracted from: + * git://github.com/gonzoua/u-boot-pi.git master + * and hence presumably (C) 2012 Oleksandr Tymoshenko + * + * Tweaks for U-Boot upstreaming + * (C) 2012 Stephen Warren + * + * Portions (e.g. read/write macros, concepts for back-to-back register write + * timing workarounds) obviously extracted from the Linux kernel at: + * https://github.com/raspberrypi/linux.git rpi-3.6.y + * + * The Linux kernel code has the following (c) and license, which is hence + * propagated to Oleksandr's tree and here: + * + * Support for SDHCI device on 2835 + * Based on sdhci-bcm2708.c (c) 2010 Broadcom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* Supports: + * SDHCI platform device - Arasan SD controller in BCM2708 + * + * Inspired by sdhci-pci.c, by Pierre Ossman + */ + +#include <common.h> +#include <dm.h> +#include <log.h> +#include <malloc.h> +#include <memalign.h> +#include <sdhci.h> +#include <time.h> +#include <asm/arch/msg.h> +#include <asm/arch/mbox.h> +#include <mach/sdhci.h> +#include <mach/timer.h> + +/* 400KHz is max freq for card ID etc. Use that as min */ +#define MIN_FREQ 400000 +#define SDHCI_BUFFER 0x20 + +struct bcm2835_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct bcm2835_sdhci_host { + struct sdhci_host host; + uint twoticks_delay; + ulong last_write; +}; + +static inline struct bcm2835_sdhci_host *to_bcm(struct sdhci_host *host) +{ + return (struct bcm2835_sdhci_host *)host; +} + +static inline void bcm2835_sdhci_raw_writel(struct sdhci_host *host, u32 val, + int reg) +{ + struct bcm2835_sdhci_host *bcm_host = to_bcm(host); + + /* + * The Arasan has a bugette whereby it may lose the content of + * successive writes to registers that are within two SD-card clock + * cycles of each other (a clock domain crossing problem). + * It seems, however, that the data register does not have this problem. + * (Which is just as well - otherwise we'd have to nobble the DMA engine + * too) + */ + if (reg != SDHCI_BUFFER) { + while (timer_get_us() - bcm_host->last_write < + bcm_host->twoticks_delay) + ; + } + + writel(val, host->ioaddr + reg); + bcm_host->last_write = timer_get_us(); +} + +static inline u32 bcm2835_sdhci_raw_readl(struct sdhci_host *host, int reg) +{ + return readl(host->ioaddr + reg); +} + +static void bcm2835_sdhci_writel(struct sdhci_host *host, u32 val, int reg) +{ + bcm2835_sdhci_raw_writel(host, val, reg); +} + +static void bcm2835_sdhci_writew(struct sdhci_host *host, u16 val, int reg) +{ + static u32 shadow; + u32 oldval = (reg == SDHCI_COMMAND) ? shadow : + bcm2835_sdhci_raw_readl(host, reg & ~3); + u32 word_num = (reg >> 1) & 1; + u32 word_shift = word_num * 16; + u32 mask = 0xffff << word_shift; + u32 newval = (oldval & ~mask) | (val << word_shift); + + if (reg == SDHCI_TRANSFER_MODE) + shadow = newval; + else + bcm2835_sdhci_raw_writel(host, newval, reg & ~3); +} + +static void bcm2835_sdhci_writeb(struct sdhci_host *host, u8 val, int reg) +{ + u32 oldval = bcm2835_sdhci_raw_readl(host, reg & ~3); + u32 byte_num = reg & 3; + u32 byte_shift = byte_num * 8; + u32 mask = 0xff << byte_shift; + u32 newval = (oldval & ~mask) | (val << byte_shift); + + bcm2835_sdhci_raw_writel(host, newval, reg & ~3); +} + +static u32 bcm2835_sdhci_readl(struct sdhci_host *host, int reg) +{ + u32 val = bcm2835_sdhci_raw_readl(host, reg); + + return val; +} + +static u16 bcm2835_sdhci_readw(struct sdhci_host *host, int reg) +{ + u32 val = bcm2835_sdhci_raw_readl(host, (reg & ~3)); + u32 word_num = (reg >> 1) & 1; + u32 word_shift = word_num * 16; + u32 word = (val >> word_shift) & 0xffff; + + return word; +} + +static u8 bcm2835_sdhci_readb(struct sdhci_host *host, int reg) +{ + u32 val = bcm2835_sdhci_raw_readl(host, (reg & ~3)); + u32 byte_num = reg & 3; + u32 byte_shift = byte_num * 8; + u32 byte = (val >> byte_shift) & 0xff; + + return byte; +} + +static const struct sdhci_ops bcm2835_ops = { + .write_l = bcm2835_sdhci_writel, + .write_w = bcm2835_sdhci_writew, + .write_b = bcm2835_sdhci_writeb, + .read_l = bcm2835_sdhci_readl, + .read_w = bcm2835_sdhci_readw, + .read_b = bcm2835_sdhci_readb, +}; + +static int bcm2835_sdhci_bind(struct udevice *dev) +{ + struct bcm2835_sdhci_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static int bcm2835_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct bcm2835_sdhci_plat *plat = dev_get_plat(dev); + struct bcm2835_sdhci_host *priv = dev_get_priv(dev); + struct sdhci_host *host = &priv->host; + fdt_addr_t base; + int emmc_freq; + int ret; + int clock_id = (int)dev_get_driver_data(dev); + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) + return -EINVAL; + + ret = bcm2835_get_mmc_clock(clock_id); + if (ret < 0) { + debug("%s: Failed to set MMC clock (err=%d)\n", __func__, ret); + return ret; + } + emmc_freq = ret; + + /* + * See the comments in bcm2835_sdhci_raw_writel(). + * + * This should probably be dynamically calculated based on the actual + * frequency. However, this is the longest we'll have to wait, and + * doesn't seem to slow access down too much, so the added complexity + * doesn't seem worth it for now. + * + * 1/MIN_FREQ is (max) time per tick of eMMC clock. + * 2/MIN_FREQ is time for two ticks. + * Multiply by 1000000 to get uS per two ticks. + * +1 for hack rounding. + */ + priv->twoticks_delay = ((2 * 1000000) / MIN_FREQ) + 1; + priv->last_write = 0; + + host->name = dev->name; + host->ioaddr = (void *)(uintptr_t)base; + host->quirks = SDHCI_QUIRK_BROKEN_VOLTAGE | SDHCI_QUIRK_BROKEN_R1B | + SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_NO_HISPD_BIT; + host->max_clk = emmc_freq; + host->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + host->ops = &bcm2835_ops; + + host->mmc = &plat->mmc; + host->mmc->dev = dev; + + ret = sdhci_setup_cfg(&plat->cfg, host, emmc_freq, MIN_FREQ); + if (ret) { + debug("%s: Failed to setup SDHCI (err=%d)\n", __func__, ret); + return ret; + } + + upriv->mmc = &plat->mmc; + host->mmc->priv = host; + + return sdhci_probe(dev); +} + +static const struct udevice_id bcm2835_sdhci_match[] = { + { + .compatible = "brcm,bcm2835-sdhci", + .data = BCM2835_MBOX_CLOCK_ID_EMMC + }, + { + .compatible = "brcm,bcm2711-emmc2", + .data = BCM2835_MBOX_CLOCK_ID_EMMC2 + }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(sdhci_cdns) = { + .name = "sdhci-bcm2835", + .id = UCLASS_MMC, + .of_match = bcm2835_sdhci_match, + .bind = bcm2835_sdhci_bind, + .probe = bcm2835_sdhci_probe, + .priv_auto = sizeof(struct bcm2835_sdhci_host), + .plat_auto = sizeof(struct bcm2835_sdhci_plat), + .ops = &sdhci_ops, +}; diff --git a/roms/u-boot/drivers/mmc/bcm2835_sdhost.c b/roms/u-boot/drivers/mmc/bcm2835_sdhost.c new file mode 100644 index 000000000..894dbdd68 --- /dev/null +++ b/roms/u-boot/drivers/mmc/bcm2835_sdhost.c @@ -0,0 +1,812 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * bcm2835 sdhost driver. + * + * The 2835 has two SD controllers: The Arasan sdhci controller + * (supported by the iproc driver) and a custom sdhost controller + * (supported by this driver). + * + * The sdhci controller supports both sdcard and sdio. The sdhost + * controller supports the sdcard only, but has better performance. + * Also note that the rpi3 has sdio wifi, so driving the sdcard with + * the sdhost controller allows to use the sdhci controller for wifi + * support. + * + * The configuration is done by devicetree via pin muxing. Both + * SD controller are available on the same pins (2 pin groups = pin 22 + * to 27 + pin 48 to 53). So it's possible to use both SD controllers + * at the same time with different pin groups. + * + * This code was ported to U-Boot by + * Alexander Graf <agraf@suse.de> + * and is based on drivers/mmc/host/bcm2835.c in Linux which is written by + * Phil Elwell <phil@raspberrypi.org> + * Copyright (C) 2015-2016 Raspberry Pi (Trading) Ltd. + * which is based on + * mmc-bcm2835.c by Gellert Weisz + * which is, in turn, based on + * sdhci-bcm2708.c by Broadcom + * sdhci-bcm2835.c by Stephen Warren and Oleksandr Tymoshenko + * sdhci.c and sdhci-pci.c by Pierre Ossman + */ +#include <clk.h> +#include <common.h> +#include <dm.h> +#include <mmc.h> +#include <asm/arch/msg.h> +#include <asm/arch/mbox.h> +#include <asm/unaligned.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/bug.h> +#include <linux/compat.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/sizes.h> +#include <mach/gpio.h> +#include <power/regulator.h> + +#define msleep(a) udelay(a * 1000) + +#define SDCMD 0x00 /* Command to SD card - 16 R/W */ +#define SDARG 0x04 /* Argument to SD card - 32 R/W */ +#define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */ +#define SDCDIV 0x0c /* Start value for clock divider - 11 R/W */ +#define SDRSP0 0x10 /* SD card response (31:0) - 32 R */ +#define SDRSP1 0x14 /* SD card response (63:32) - 32 R */ +#define SDRSP2 0x18 /* SD card response (95:64) - 32 R */ +#define SDRSP3 0x1c /* SD card response (127:96) - 32 R */ +#define SDHSTS 0x20 /* SD host status - 11 R/W */ +#define SDVDD 0x30 /* SD card power control - 1 R/W */ +#define SDEDM 0x34 /* Emergency Debug Mode - 13 R/W */ +#define SDHCFG 0x38 /* Host configuration - 2 R/W */ +#define SDHBCT 0x3c /* Host byte count (debug) - 32 R/W */ +#define SDDATA 0x40 /* Data to/from SD card - 32 R/W */ +#define SDHBLC 0x50 /* Host block count (SDIO/SDHC) - 9 R/W */ + +#define SDCMD_NEW_FLAG 0x8000 +#define SDCMD_FAIL_FLAG 0x4000 +#define SDCMD_BUSYWAIT 0x800 +#define SDCMD_NO_RESPONSE 0x400 +#define SDCMD_LONG_RESPONSE 0x200 +#define SDCMD_WRITE_CMD 0x80 +#define SDCMD_READ_CMD 0x40 +#define SDCMD_CMD_MASK 0x3f + +#define SDCDIV_MAX_CDIV 0x7ff + +#define SDHSTS_BUSY_IRPT 0x400 +#define SDHSTS_BLOCK_IRPT 0x200 +#define SDHSTS_SDIO_IRPT 0x100 +#define SDHSTS_REW_TIME_OUT 0x80 +#define SDHSTS_CMD_TIME_OUT 0x40 +#define SDHSTS_CRC16_ERROR 0x20 +#define SDHSTS_CRC7_ERROR 0x10 +#define SDHSTS_FIFO_ERROR 0x08 +#define SDHSTS_DATA_FLAG 0x01 + +#define SDHSTS_CLEAR_MASK (SDHSTS_BUSY_IRPT | \ + SDHSTS_BLOCK_IRPT | \ + SDHSTS_SDIO_IRPT | \ + SDHSTS_REW_TIME_OUT | \ + SDHSTS_CMD_TIME_OUT | \ + SDHSTS_CRC16_ERROR | \ + SDHSTS_CRC7_ERROR | \ + SDHSTS_FIFO_ERROR) + +#define SDHSTS_TRANSFER_ERROR_MASK (SDHSTS_CRC7_ERROR | \ + SDHSTS_CRC16_ERROR | \ + SDHSTS_REW_TIME_OUT | \ + SDHSTS_FIFO_ERROR) + +#define SDHSTS_ERROR_MASK (SDHSTS_CMD_TIME_OUT | \ + SDHSTS_TRANSFER_ERROR_MASK) + +#define SDHCFG_BUSY_IRPT_EN BIT(10) +#define SDHCFG_BLOCK_IRPT_EN BIT(8) +#define SDHCFG_SDIO_IRPT_EN BIT(5) +#define SDHCFG_DATA_IRPT_EN BIT(4) +#define SDHCFG_SLOW_CARD BIT(3) +#define SDHCFG_WIDE_EXT_BUS BIT(2) +#define SDHCFG_WIDE_INT_BUS BIT(1) +#define SDHCFG_REL_CMD_LINE BIT(0) + +#define SDVDD_POWER_OFF 0 +#define SDVDD_POWER_ON 1 + +#define SDEDM_FORCE_DATA_MODE BIT(19) +#define SDEDM_CLOCK_PULSE BIT(20) +#define SDEDM_BYPASS BIT(21) + +#define SDEDM_FIFO_FILL_SHIFT 4 +#define SDEDM_FIFO_FILL_MASK 0x1f +static u32 edm_fifo_fill(u32 edm) +{ + return (edm >> SDEDM_FIFO_FILL_SHIFT) & SDEDM_FIFO_FILL_MASK; +} + +#define SDEDM_WRITE_THRESHOLD_SHIFT 9 +#define SDEDM_READ_THRESHOLD_SHIFT 14 +#define SDEDM_THRESHOLD_MASK 0x1f + +#define SDEDM_FSM_MASK 0xf +#define SDEDM_FSM_IDENTMODE 0x0 +#define SDEDM_FSM_DATAMODE 0x1 +#define SDEDM_FSM_READDATA 0x2 +#define SDEDM_FSM_WRITEDATA 0x3 +#define SDEDM_FSM_READWAIT 0x4 +#define SDEDM_FSM_READCRC 0x5 +#define SDEDM_FSM_WRITECRC 0x6 +#define SDEDM_FSM_WRITEWAIT1 0x7 +#define SDEDM_FSM_POWERDOWN 0x8 +#define SDEDM_FSM_POWERUP 0x9 +#define SDEDM_FSM_WRITESTART1 0xa +#define SDEDM_FSM_WRITESTART2 0xb +#define SDEDM_FSM_GENPULSES 0xc +#define SDEDM_FSM_WRITEWAIT2 0xd +#define SDEDM_FSM_STARTPOWDOWN 0xf + +#define SDDATA_FIFO_WORDS 16 + +#define FIFO_READ_THRESHOLD 4 +#define FIFO_WRITE_THRESHOLD 4 +#define SDDATA_FIFO_PIO_BURST 8 + +#define SDHST_TIMEOUT_MAX_USEC 100000 + +struct bcm2835_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct bcm2835_host { + void __iomem *ioaddr; + u32 phys_addr; + + int clock; /* Current clock speed */ + unsigned int max_clk; /* Max possible freq */ + unsigned int blocks; /* remaining PIO blocks */ + + u32 ns_per_fifo_word; + + /* cached registers */ + u32 hcfg; + u32 cdiv; + + struct mmc_cmd *cmd; /* Current command */ + struct mmc_data *data; /* Current data request */ + bool use_busy:1; /* Wait for busy interrupt */ + + struct udevice *dev; + struct mmc *mmc; + struct bcm2835_plat *plat; +}; + +static void bcm2835_dumpregs(struct bcm2835_host *host) +{ + dev_dbg(host->dev, "=========== REGISTER DUMP ===========\n"); + dev_dbg(host->dev, "SDCMD 0x%08x\n", readl(host->ioaddr + SDCMD)); + dev_dbg(host->dev, "SDARG 0x%08x\n", readl(host->ioaddr + SDARG)); + dev_dbg(host->dev, "SDTOUT 0x%08x\n", readl(host->ioaddr + SDTOUT)); + dev_dbg(host->dev, "SDCDIV 0x%08x\n", readl(host->ioaddr + SDCDIV)); + dev_dbg(host->dev, "SDRSP0 0x%08x\n", readl(host->ioaddr + SDRSP0)); + dev_dbg(host->dev, "SDRSP1 0x%08x\n", readl(host->ioaddr + SDRSP1)); + dev_dbg(host->dev, "SDRSP2 0x%08x\n", readl(host->ioaddr + SDRSP2)); + dev_dbg(host->dev, "SDRSP3 0x%08x\n", readl(host->ioaddr + SDRSP3)); + dev_dbg(host->dev, "SDHSTS 0x%08x\n", readl(host->ioaddr + SDHSTS)); + dev_dbg(host->dev, "SDVDD 0x%08x\n", readl(host->ioaddr + SDVDD)); + dev_dbg(host->dev, "SDEDM 0x%08x\n", readl(host->ioaddr + SDEDM)); + dev_dbg(host->dev, "SDHCFG 0x%08x\n", readl(host->ioaddr + SDHCFG)); + dev_dbg(host->dev, "SDHBCT 0x%08x\n", readl(host->ioaddr + SDHBCT)); + dev_dbg(host->dev, "SDHBLC 0x%08x\n", readl(host->ioaddr + SDHBLC)); + dev_dbg(host->dev, "===========================================\n"); +} + +static void bcm2835_reset_internal(struct bcm2835_host *host) +{ + u32 temp; + + writel(SDVDD_POWER_OFF, host->ioaddr + SDVDD); + writel(0, host->ioaddr + SDCMD); + writel(0, host->ioaddr + SDARG); + /* Set timeout to a big enough value so we don't hit it */ + writel(0xf00000, host->ioaddr + SDTOUT); + writel(0, host->ioaddr + SDCDIV); + /* Clear status register */ + writel(SDHSTS_CLEAR_MASK, host->ioaddr + SDHSTS); + writel(0, host->ioaddr + SDHCFG); + writel(0, host->ioaddr + SDHBCT); + writel(0, host->ioaddr + SDHBLC); + + /* Limit fifo usage due to silicon bug */ + temp = readl(host->ioaddr + SDEDM); + temp &= ~((SDEDM_THRESHOLD_MASK << SDEDM_READ_THRESHOLD_SHIFT) | + (SDEDM_THRESHOLD_MASK << SDEDM_WRITE_THRESHOLD_SHIFT)); + temp |= (FIFO_READ_THRESHOLD << SDEDM_READ_THRESHOLD_SHIFT) | + (FIFO_WRITE_THRESHOLD << SDEDM_WRITE_THRESHOLD_SHIFT); + writel(temp, host->ioaddr + SDEDM); + /* Wait for FIFO threshold to populate */ + msleep(20); + writel(SDVDD_POWER_ON, host->ioaddr + SDVDD); + /* Wait for all components to go through power on cycle */ + msleep(20); + host->clock = 0; + writel(host->hcfg, host->ioaddr + SDHCFG); + writel(host->cdiv, host->ioaddr + SDCDIV); +} + +static int bcm2835_wait_transfer_complete(struct bcm2835_host *host) +{ + ulong tstart_ms = get_timer(0); + + while (1) { + u32 edm, fsm; + + edm = readl(host->ioaddr + SDEDM); + fsm = edm & SDEDM_FSM_MASK; + + if ((fsm == SDEDM_FSM_IDENTMODE) || + (fsm == SDEDM_FSM_DATAMODE)) + break; + + if ((fsm == SDEDM_FSM_READWAIT) || + (fsm == SDEDM_FSM_WRITESTART1) || + (fsm == SDEDM_FSM_READDATA)) { + writel(edm | SDEDM_FORCE_DATA_MODE, + host->ioaddr + SDEDM); + break; + } + + /* Error out after ~1s */ + ulong tlapse_ms = get_timer(tstart_ms); + if ( tlapse_ms > 1000 /* ms */ ) { + + dev_err(host->dev, + "wait_transfer_complete - still waiting after %lu ms\n", + tlapse_ms); + bcm2835_dumpregs(host); + return -ETIMEDOUT; + } + } + + return 0; +} + +static int bcm2835_transfer_block_pio(struct bcm2835_host *host, bool is_read) +{ + struct mmc_data *data = host->data; + size_t blksize = data->blocksize; + int copy_words; + u32 hsts = 0; + u32 *buf; + + if (blksize % sizeof(u32)) + return -EINVAL; + + buf = is_read ? (u32 *)data->dest : (u32 *)data->src; + + if (is_read) + data->dest += blksize; + else + data->src += blksize; + + copy_words = blksize / sizeof(u32); + + /* + * Copy all contents from/to the FIFO as far as it reaches, + * then wait for it to fill/empty again and rewind. + */ + while (copy_words) { + int burst_words, words; + u32 edm; + + burst_words = min(SDDATA_FIFO_PIO_BURST, copy_words); + edm = readl(host->ioaddr + SDEDM); + if (is_read) + words = edm_fifo_fill(edm); + else + words = SDDATA_FIFO_WORDS - edm_fifo_fill(edm); + + if (words < burst_words) { + int fsm_state = (edm & SDEDM_FSM_MASK); + + if ((is_read && + (fsm_state != SDEDM_FSM_READDATA && + fsm_state != SDEDM_FSM_READWAIT && + fsm_state != SDEDM_FSM_READCRC)) || + (!is_read && + (fsm_state != SDEDM_FSM_WRITEDATA && + fsm_state != SDEDM_FSM_WRITEWAIT1 && + fsm_state != SDEDM_FSM_WRITEWAIT2 && + fsm_state != SDEDM_FSM_WRITECRC && + fsm_state != SDEDM_FSM_WRITESTART1 && + fsm_state != SDEDM_FSM_WRITESTART2))) { + hsts = readl(host->ioaddr + SDHSTS); + printf("fsm %x, hsts %08x\n", fsm_state, hsts); + if (hsts & SDHSTS_ERROR_MASK) + break; + } + + continue; + } else if (words > copy_words) { + words = copy_words; + } + + copy_words -= words; + + /* Copy current chunk to/from the FIFO */ + while (words) { + if (is_read) + *(buf++) = readl(host->ioaddr + SDDATA); + else + writel(*(buf++), host->ioaddr + SDDATA); + words--; + } + } + + return 0; +} + +static int bcm2835_transfer_pio(struct bcm2835_host *host) +{ + u32 sdhsts; + bool is_read; + int ret = 0; + + is_read = (host->data->flags & MMC_DATA_READ) != 0; + ret = bcm2835_transfer_block_pio(host, is_read); + if (ret) + return ret; + + sdhsts = readl(host->ioaddr + SDHSTS); + if (sdhsts & (SDHSTS_CRC16_ERROR | + SDHSTS_CRC7_ERROR | + SDHSTS_FIFO_ERROR)) { + printf("%s transfer error - HSTS %08x\n", + is_read ? "read" : "write", sdhsts); + ret = -EILSEQ; + } else if ((sdhsts & (SDHSTS_CMD_TIME_OUT | + SDHSTS_REW_TIME_OUT))) { + printf("%s timeout error - HSTS %08x\n", + is_read ? "read" : "write", sdhsts); + ret = -ETIMEDOUT; + } + + return ret; +} + +static void bcm2835_prepare_data(struct bcm2835_host *host, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + WARN_ON(host->data); + + host->data = data; + if (!data) + return; + + /* Use PIO */ + host->blocks = data->blocks; + + writel(data->blocksize, host->ioaddr + SDHBCT); + writel(data->blocks, host->ioaddr + SDHBLC); +} + +static u32 bcm2835_read_wait_sdcmd(struct bcm2835_host *host) +{ + u32 value; + int ret; + int timeout_us = SDHST_TIMEOUT_MAX_USEC; + + ret = readl_poll_timeout(host->ioaddr + SDCMD, value, + !(value & SDCMD_NEW_FLAG), timeout_us); + if (ret == -ETIMEDOUT) + printf("%s: timeout (%d us)\n", __func__, timeout_us); + + return value; +} + +static int bcm2835_send_command(struct bcm2835_host *host, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + u32 sdcmd, sdhsts; + + WARN_ON(host->cmd); + + if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) { + printf("unsupported response type!\n"); + return -EINVAL; + } + + sdcmd = bcm2835_read_wait_sdcmd(host); + if (sdcmd & SDCMD_NEW_FLAG) { + printf("previous command never completed.\n"); + bcm2835_dumpregs(host); + return -EBUSY; + } + + host->cmd = cmd; + + /* Clear any error flags */ + sdhsts = readl(host->ioaddr + SDHSTS); + if (sdhsts & SDHSTS_ERROR_MASK) + writel(sdhsts, host->ioaddr + SDHSTS); + + bcm2835_prepare_data(host, cmd, data); + + writel(cmd->cmdarg, host->ioaddr + SDARG); + + sdcmd = cmd->cmdidx & SDCMD_CMD_MASK; + + host->use_busy = false; + if (!(cmd->resp_type & MMC_RSP_PRESENT)) { + sdcmd |= SDCMD_NO_RESPONSE; + } else { + if (cmd->resp_type & MMC_RSP_136) + sdcmd |= SDCMD_LONG_RESPONSE; + if (cmd->resp_type & MMC_RSP_BUSY) { + sdcmd |= SDCMD_BUSYWAIT; + host->use_busy = true; + } + } + + if (data) { + if (data->flags & MMC_DATA_WRITE) + sdcmd |= SDCMD_WRITE_CMD; + if (data->flags & MMC_DATA_READ) + sdcmd |= SDCMD_READ_CMD; + } + + writel(sdcmd | SDCMD_NEW_FLAG, host->ioaddr + SDCMD); + + return 0; +} + +static int bcm2835_finish_command(struct bcm2835_host *host) +{ + struct mmc_cmd *cmd = host->cmd; + u32 sdcmd; + int ret = 0; + + sdcmd = bcm2835_read_wait_sdcmd(host); + + /* Check for errors */ + if (sdcmd & SDCMD_NEW_FLAG) { + printf("command never completed.\n"); + bcm2835_dumpregs(host); + return -EIO; + } else if (sdcmd & SDCMD_FAIL_FLAG) { + u32 sdhsts = readl(host->ioaddr + SDHSTS); + + /* Clear the errors */ + writel(SDHSTS_ERROR_MASK, host->ioaddr + SDHSTS); + + if (!(sdhsts & SDHSTS_CRC7_ERROR) || + (host->cmd->cmdidx != MMC_CMD_SEND_OP_COND)) { + if (sdhsts & SDHSTS_CMD_TIME_OUT) { + ret = -ETIMEDOUT; + } else { + printf("unexpected command %d error\n", + host->cmd->cmdidx); + bcm2835_dumpregs(host); + ret = -EILSEQ; + } + + return ret; + } + } + + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) { + int i; + + for (i = 0; i < 4; i++) { + cmd->response[3 - i] = + readl(host->ioaddr + SDRSP0 + i * 4); + } + } else { + cmd->response[0] = readl(host->ioaddr + SDRSP0); + } + } + + /* Processed actual command. */ + host->cmd = NULL; + + return ret; +} + +static int bcm2835_check_cmd_error(struct bcm2835_host *host, u32 intmask) +{ + int ret = -EINVAL; + + if (!(intmask & SDHSTS_ERROR_MASK)) + return 0; + + if (!host->cmd) + return -EINVAL; + + printf("sdhost_busy_irq: intmask %08x\n", intmask); + if (intmask & SDHSTS_CRC7_ERROR) { + ret = -EILSEQ; + } else if (intmask & (SDHSTS_CRC16_ERROR | + SDHSTS_FIFO_ERROR)) { + ret = -EILSEQ; + } else if (intmask & (SDHSTS_REW_TIME_OUT | SDHSTS_CMD_TIME_OUT)) { + ret = -ETIMEDOUT; + } + bcm2835_dumpregs(host); + return ret; +} + +static int bcm2835_check_data_error(struct bcm2835_host *host, u32 intmask) +{ + int ret = 0; + + if (!host->data) + return 0; + if (intmask & (SDHSTS_CRC16_ERROR | SDHSTS_FIFO_ERROR)) + ret = -EILSEQ; + if (intmask & SDHSTS_REW_TIME_OUT) + ret = -ETIMEDOUT; + + if (ret) + printf("%s:%d %d\n", __func__, __LINE__, ret); + + return ret; +} + +static int bcm2835_transmit(struct bcm2835_host *host) +{ + u32 intmask = readl(host->ioaddr + SDHSTS); + int ret; + + /* Check for errors */ + ret = bcm2835_check_data_error(host, intmask); + if (ret) + return ret; + + ret = bcm2835_check_cmd_error(host, intmask); + if (ret) + return ret; + + /* Handle wait for busy end */ + if (host->use_busy && (intmask & SDHSTS_BUSY_IRPT)) { + writel(SDHSTS_BUSY_IRPT, host->ioaddr + SDHSTS); + host->use_busy = false; + bcm2835_finish_command(host); + } + + /* Handle PIO data transfer */ + if (host->data) { + ret = bcm2835_transfer_pio(host); + if (ret) + return ret; + host->blocks--; + if (host->blocks == 0) { + /* Wait for command to complete for real */ + ret = bcm2835_wait_transfer_complete(host); + if (ret) + return ret; + /* Transfer complete */ + host->data = NULL; + } + } + + return 0; +} + +static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock) +{ + int div; + + /* The SDCDIV register has 11 bits, and holds (div - 2). But + * in data mode the max is 50MHz wihout a minimum, and only + * the bottom 3 bits are used. Since the switch over is + * automatic (unless we have marked the card as slow...), + * chosen values have to make sense in both modes. Ident mode + * must be 100-400KHz, so can range check the requested + * clock. CMD15 must be used to return to data mode, so this + * can be monitored. + * + * clock 250MHz -> 0->125MHz, 1->83.3MHz, 2->62.5MHz, 3->50.0MHz + * 4->41.7MHz, 5->35.7MHz, 6->31.3MHz, 7->27.8MHz + * + * 623->400KHz/27.8MHz + * reset value (507)->491159/50MHz + * + * BUT, the 3-bit clock divisor in data mode is too small if + * the core clock is higher than 250MHz, so instead use the + * SLOW_CARD configuration bit to force the use of the ident + * clock divisor at all times. + */ + + if (clock < 100000) { + /* Can't stop the clock, but make it as slow as possible + * to show willing + */ + host->cdiv = SDCDIV_MAX_CDIV; + writel(host->cdiv, host->ioaddr + SDCDIV); + return; + } + + div = host->max_clk / clock; + if (div < 2) + div = 2; + if ((host->max_clk / div) > clock) + div++; + div -= 2; + + if (div > SDCDIV_MAX_CDIV) + div = SDCDIV_MAX_CDIV; + + clock = host->max_clk / (div + 2); + host->mmc->clock = clock; + + /* Calibrate some delays */ + + host->ns_per_fifo_word = (1000000000 / clock) * + ((host->mmc->card_caps & MMC_MODE_4BIT) ? 8 : 32); + + host->cdiv = div; + writel(host->cdiv, host->ioaddr + SDCDIV); + + /* Set the timeout to 500ms */ + writel(host->mmc->clock / 2, host->ioaddr + SDTOUT); +} + +static inline int is_power_of_2(u64 x) +{ + return !(x & (x - 1)); +} + +static int bcm2835_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct bcm2835_host *host = dev_get_priv(dev); + u32 edm, fsm; + int ret = 0; + + if (data && !is_power_of_2(data->blocksize)) { + printf("unsupported block size (%d bytes)\n", data->blocksize); + + if (cmd) + return -EINVAL; + } + + edm = readl(host->ioaddr + SDEDM); + fsm = edm & SDEDM_FSM_MASK; + + if ((fsm != SDEDM_FSM_IDENTMODE) && + (fsm != SDEDM_FSM_DATAMODE) && + (cmd && cmd->cmdidx != MMC_CMD_STOP_TRANSMISSION)) { + printf("previous command (%d) not complete (EDM %08x)\n", + readl(host->ioaddr + SDCMD) & SDCMD_CMD_MASK, edm); + bcm2835_dumpregs(host); + + if (cmd) + return -EILSEQ; + + return 0; + } + + if (cmd) { + ret = bcm2835_send_command(host, cmd, data); + if (!ret && !host->use_busy) + ret = bcm2835_finish_command(host); + } + + /* Wait for completion of busy signal or data transfer */ + while (host->use_busy || host->data) { + ret = bcm2835_transmit(host); + if (ret) + break; + } + + return ret; +} + +static int bcm2835_set_ios(struct udevice *dev) +{ + struct bcm2835_host *host = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + + if (!mmc->clock || mmc->clock != host->clock) { + bcm2835_set_clock(host, mmc->clock); + host->clock = mmc->clock; + } + + /* set bus width */ + host->hcfg &= ~SDHCFG_WIDE_EXT_BUS; + if (mmc->bus_width == 4) + host->hcfg |= SDHCFG_WIDE_EXT_BUS; + + host->hcfg |= SDHCFG_WIDE_INT_BUS; + + /* Disable clever clock switching, to cope with fast core clocks */ + host->hcfg |= SDHCFG_SLOW_CARD; + + writel(host->hcfg, host->ioaddr + SDHCFG); + + return 0; +} + +static void bcm2835_add_host(struct bcm2835_host *host) +{ + struct mmc_config *cfg = &host->plat->cfg; + + cfg->f_max = host->max_clk; + cfg->f_min = host->max_clk / SDCDIV_MAX_CDIV; + cfg->b_max = 65535; + + dev_dbg(host->dev, "f_max %d, f_min %d\n", + cfg->f_max, cfg->f_min); + + /* host controller capabilities */ + cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS | MMC_MODE_HS_52MHz; + + /* report supported voltage ranges */ + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + + /* Set interrupt enables */ + host->hcfg = SDHCFG_BUSY_IRPT_EN; + + bcm2835_reset_internal(host); +} + +static int bcm2835_probe(struct udevice *dev) +{ + struct bcm2835_plat *plat = dev_get_plat(dev); + struct bcm2835_host *host = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + + host->dev = dev; + host->mmc = mmc; + host->plat = plat; + upriv->mmc = &plat->mmc; + plat->cfg.name = dev->name; + + host->phys_addr = dev_read_addr(dev); + if (host->phys_addr == FDT_ADDR_T_NONE) + return -EINVAL; + + host->ioaddr = devm_ioremap(dev, host->phys_addr, SZ_256); + if (!host->ioaddr) + return -ENOMEM; + + host->max_clk = bcm2835_get_mmc_clock(BCM2835_MBOX_CLOCK_ID_CORE); + + bcm2835_add_host(host); + + dev_dbg(dev, "%s -> OK\n", __func__); + + return 0; +} + +static const struct udevice_id bcm2835_match[] = { + { .compatible = "brcm,bcm2835-sdhost" }, + { } +}; + +static const struct dm_mmc_ops bcm2835_ops = { + .send_cmd = bcm2835_send_cmd, + .set_ios = bcm2835_set_ios, +}; + +static int bcm2835_bind(struct udevice *dev) +{ + struct bcm2835_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +U_BOOT_DRIVER(bcm2835_sdhost) = { + .name = "bcm2835-sdhost", + .id = UCLASS_MMC, + .of_match = bcm2835_match, + .bind = bcm2835_bind, + .probe = bcm2835_probe, + .priv_auto = sizeof(struct bcm2835_host), + .plat_auto = sizeof(struct bcm2835_plat), + .ops = &bcm2835_ops, +}; diff --git a/roms/u-boot/drivers/mmc/bcmstb_sdhci.c b/roms/u-boot/drivers/mmc/bcmstb_sdhci.c new file mode 100644 index 000000000..dc96818cf --- /dev/null +++ b/roms/u-boot/drivers/mmc/bcmstb_sdhci.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2018 Cisco Systems, Inc. + * (C) Copyright 2019 Synamedia + * + * Author: Thomas Fitzsimmons <fitzsim@fitzsim.org> + */ + +#include <common.h> +#include <dm.h> +#include <mach/sdhci.h> +#include <malloc.h> +#include <sdhci.h> + +/* + * The BCMSTB SDHCI has a quirk in that its actual maximum frequency + * capability is 100 MHz. The divisor that is eventually written to + * SDHCI_CLOCK_CONTROL is calculated based on what the MMC device + * reports, and relative to this maximum frequency. + * + * This define used to be set to 52000000 (52 MHz), the desired + * maximum frequency, but that would result in the communication + * actually running at 100 MHz (seemingly without issue), which is + * out-of-spec. + * + * Now, by setting this to 0 (auto-detect), 100 MHz will be read from + * the capabilities register, and the resulting divisor will be + * doubled, meaning that the clock control register will be set to the + * in-spec 52 MHz value. + */ +#define BCMSTB_SDHCI_MAXIMUM_CLOCK_FREQUENCY 0 +/* + * When the minimum clock frequency is set to 0 (auto-detect), U-Boot + * sets it to 100 MHz divided by SDHCI_MAX_DIV_SPEC_300, or 48,875 Hz, + * which results in the controller timing out when trying to + * communicate with the MMC device. Hard-code this value to 400000 + * (400 kHz) to prevent this. + */ +#define BCMSTB_SDHCI_MINIMUM_CLOCK_FREQUENCY 400000 + +/* + * This driver has only been tested with eMMC devices; SD devices may + * not work. + */ +struct sdhci_bcmstb_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +static int sdhci_bcmstb_bind(struct udevice *dev) +{ + struct sdhci_bcmstb_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static int sdhci_bcmstb_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct sdhci_bcmstb_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + fdt_addr_t base; + int ret; + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) + return -EINVAL; + + host->name = dev->name; + host->ioaddr = (void *)base; + + ret = mmc_of_parse(dev, &plat->cfg); + if (ret) + return ret; + + host->mmc = &plat->mmc; + host->mmc->dev = dev; + ret = sdhci_setup_cfg(&plat->cfg, host, + BCMSTB_SDHCI_MAXIMUM_CLOCK_FREQUENCY, + BCMSTB_SDHCI_MINIMUM_CLOCK_FREQUENCY); + if (ret) + return ret; + + upriv->mmc = &plat->mmc; + host->mmc->priv = host; + + return sdhci_probe(dev); +} + +static const struct udevice_id sdhci_bcmstb_match[] = { + { .compatible = "brcm,bcm7425-sdhci" }, + { .compatible = "brcm,sdhci-brcmstb" }, + { } +}; + +U_BOOT_DRIVER(sdhci_bcmstb) = { + .name = "sdhci-bcmstb", + .id = UCLASS_MMC, + .of_match = sdhci_bcmstb_match, + .ops = &sdhci_ops, + .bind = sdhci_bcmstb_bind, + .probe = sdhci_bcmstb_probe, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct sdhci_bcmstb_plat), +}; diff --git a/roms/u-boot/drivers/mmc/ca_dw_mmc.c b/roms/u-boot/drivers/mmc/ca_dw_mmc.c new file mode 100644 index 000000000..a17ed8c11 --- /dev/null +++ b/roms/u-boot/drivers/mmc/ca_dw_mmc.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2019 Cortina Access + * Arthur Li <arthur.li@cortina-access.com> + */ + +#include <common.h> +#include <dwmmc.h> +#include <fdtdec.h> +#include <asm/global_data.h> +#include <linux/libfdt.h> +#include <malloc.h> +#include <errno.h> +#include <dm.h> +#include <mapmem.h> + +#define SD_CLK_SEL_MASK (0x3) +#define SD_DLL_DEFAULT (0x143000) +#define SD_SCLK_MAX (200000000) + +#define SD_CLK_SEL_200MHZ (0x2) +#define SD_CLK_SEL_100MHZ (0x1) +#define SD_CLK_SEL_50MHZ (0x0) + +#define IO_DRV_SD_DS_OFFSET (16) +#define IO_DRV_SD_DS_MASK (0xff << IO_DRV_SD_DS_OFFSET) + +#define MIN_FREQ (400000) + +DECLARE_GLOBAL_DATA_PTR; + +struct ca_mmc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct ca_dwmmc_priv_data { + struct dwmci_host host; + void __iomem *sd_dll_reg; + void __iomem *io_drv_reg; + u8 ds; +}; + +static int ca_dwmci_clksel(struct dwmci_host *host) +{ + struct ca_dwmmc_priv_data *priv = host->priv; + u32 val = readl(priv->sd_dll_reg); + + val &= ~SD_CLK_SEL_MASK; + if (host->bus_hz >= 200000000) + val |= SD_CLK_SEL_200MHZ; + else if (host->bus_hz >= 100000000) + val |= SD_CLK_SEL_100MHZ; + + writel(val, priv->sd_dll_reg); + + return 0; +} + +static void ca_dwmci_board_init(struct dwmci_host *host) +{ + struct ca_dwmmc_priv_data *priv = host->priv; + u32 val = readl(priv->io_drv_reg); + + writel(SD_DLL_DEFAULT, priv->sd_dll_reg); + + val &= ~IO_DRV_SD_DS_MASK; + if (priv && priv->ds) + val |= priv->ds << IO_DRV_SD_DS_OFFSET; + writel(val, priv->io_drv_reg); +} + +unsigned int ca_dwmci_get_mmc_clock(struct dwmci_host *host, uint freq) +{ + struct ca_dwmmc_priv_data *priv = host->priv; + u8 sd_clk_sel = readl(priv->sd_dll_reg) & SD_CLK_SEL_MASK; + u8 clk_div; + + switch (sd_clk_sel) { + case SD_CLK_SEL_50MHZ: + clk_div = 4; + break; + case SD_CLK_SEL_100MHZ: + clk_div = 2; + break; + default: + clk_div = 1; + } + + return SD_SCLK_MAX / clk_div / (host->div + 1); +} + +static int ca_dwmmc_of_to_plat(struct udevice *dev) +{ + struct ca_dwmmc_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + u32 tmp; + + host->name = dev->name; + host->dev_index = 0; + + host->buswidth = dev_read_u32_default(dev, "bus-width", 1); + host->bus_hz = dev_read_u32_default(dev, "max-frequency", 50000000); + priv->ds = dev_read_u32_default(dev, "io_ds", 0x33); + host->fifo_mode = dev_read_bool(dev, "fifo-mode"); + + dev_read_u32(dev, "sd_dll_ctrl", &tmp); + priv->sd_dll_reg = map_sysmem((uintptr_t)tmp, sizeof(uintptr_t)); + if (!priv->sd_dll_reg) + return -EINVAL; + + dev_read_u32(dev, "io_drv_ctrl", &tmp); + priv->io_drv_reg = map_sysmem((uintptr_t)tmp, sizeof(uintptr_t)); + if (!priv->io_drv_reg) + return -EINVAL; + + host->ioaddr = dev_read_addr_ptr(dev); + if (!host->ioaddr) + return -EINVAL; + + host->priv = priv; + + return 0; +} + +struct dm_mmc_ops ca_dwmci_dm_ops; + +static int ca_dwmmc_probe(struct udevice *dev) +{ + struct ca_mmc_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct ca_dwmmc_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + + memcpy(&ca_dwmci_dm_ops, &dm_dwmci_ops, sizeof(struct dm_mmc_ops)); + + dwmci_setup_cfg(&plat->cfg, host, host->bus_hz, MIN_FREQ); + if (host->buswidth == 1) + (&plat->cfg)->host_caps &= ~(MMC_MODE_8BIT | MMC_MODE_4BIT); + + host->mmc = &plat->mmc; + host->mmc->priv = &priv->host; + upriv->mmc = host->mmc; + host->mmc->dev = dev; + host->clksel = ca_dwmci_clksel; + host->board_init = ca_dwmci_board_init; + host->get_mmc_clk = ca_dwmci_get_mmc_clock; + + return dwmci_probe(dev); +} + +static int ca_dwmmc_bind(struct udevice *dev) +{ + struct ca_mmc_plat *plat = dev_get_plat(dev); + + return dwmci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id ca_dwmmc_ids[] = { + { .compatible = "cortina,ca-mmc" }, + { } +}; + +U_BOOT_DRIVER(ca_dwmmc_drv) = { + .name = "cortina_dwmmc", + .id = UCLASS_MMC, + .of_match = ca_dwmmc_ids, + .of_to_plat = ca_dwmmc_of_to_plat, + .bind = ca_dwmmc_bind, + .ops = &ca_dwmci_dm_ops, + .probe = ca_dwmmc_probe, + .priv_auto = sizeof(struct ca_dwmmc_priv_data), + .plat_auto = sizeof(struct ca_mmc_plat), +}; diff --git a/roms/u-boot/drivers/mmc/davinci_mmc.c b/roms/u-boot/drivers/mmc/davinci_mmc.c new file mode 100644 index 000000000..05ca36128 --- /dev/null +++ b/roms/u-boot/drivers/mmc/davinci_mmc.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Davinci MMC Controller Driver + * + * Copyright (C) 2010 Texas Instruments Incorporated + */ + +#include <config.h> +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <mmc.h> +#include <command.h> +#include <part.h> +#include <malloc.h> +#include <asm/io.h> +#include <asm/arch/sdmmc_defs.h> +#include <asm-generic/gpio.h> +#include <linux/delay.h> + +#define WATCHDOG_COUNT (100000) + +#define get_val(addr) REG(addr) +#define set_val(addr, val) REG(addr) = (val) +#define set_bit(addr, val) set_val((addr), (get_val(addr) | (val))) +#define clear_bit(addr, val) set_val((addr), (get_val(addr) & ~(val))) + +#ifdef CONFIG_DM_MMC +/* Davinci MMC board definitions */ +struct davinci_mmc_priv { + struct davinci_mmc_regs *reg_base; /* Register base address */ + uint input_clk; /* Input clock to MMC controller */ + struct gpio_desc cd_gpio; /* Card Detect GPIO */ + struct gpio_desc wp_gpio; /* Write Protect GPIO */ +}; +#endif + +/* Set davinci clock prescalar value based on the required clock in HZ */ +#if !CONFIG_IS_ENABLED(DM_MMC) +static void dmmc_set_clock(struct mmc *mmc, uint clock) +{ + struct davinci_mmc *host = mmc->priv; +#else + +static void davinci_mmc_set_clock(struct udevice *dev, uint clock) +{ + struct davinci_mmc_priv *host = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); +#endif + struct davinci_mmc_regs *regs = host->reg_base; + uint clkrt, sysclk2, act_clock; + + if (clock < mmc->cfg->f_min) + clock = mmc->cfg->f_min; + if (clock > mmc->cfg->f_max) + clock = mmc->cfg->f_max; + + set_val(®s->mmcclk, 0); + sysclk2 = host->input_clk; + clkrt = (sysclk2 / (2 * clock)) - 1; + + /* Calculate the actual clock for the divider used */ + act_clock = (sysclk2 / (2 * (clkrt + 1))); + + /* Adjust divider if actual clock exceeds the required clock */ + if (act_clock > clock) + clkrt++; + + /* check clock divider boundary and correct it */ + if (clkrt > 0xFF) + clkrt = 0xFF; + + set_val(®s->mmcclk, (clkrt | MMCCLK_CLKEN)); +} + +/* Status bit wait loop for MMCST1 */ +static int +dmmc_wait_fifo_status(volatile struct davinci_mmc_regs *regs, uint status) +{ + uint wdog = WATCHDOG_COUNT; + + while (--wdog && ((get_val(®s->mmcst1) & status) != status)) + udelay(10); + + if (!(get_val(®s->mmcctl) & MMCCTL_WIDTH_4_BIT)) + udelay(100); + + if (wdog == 0) + return -ECOMM; + + return 0; +} + +/* Busy bit wait loop for MMCST1 */ +static int dmmc_busy_wait(volatile struct davinci_mmc_regs *regs) +{ + uint wdog = WATCHDOG_COUNT; + + while (--wdog && (get_val(®s->mmcst1) & MMCST1_BUSY)) + udelay(10); + + if (wdog == 0) + return -ECOMM; + + return 0; +} + +/* Status bit wait loop for MMCST0 - Checks for error bits as well */ +static int dmmc_check_status(volatile struct davinci_mmc_regs *regs, + uint *cur_st, uint st_ready, uint st_error) +{ + uint wdog = WATCHDOG_COUNT; + uint mmcstatus = *cur_st; + + while (wdog--) { + if (mmcstatus & st_ready) { + *cur_st = mmcstatus; + mmcstatus = get_val(®s->mmcst1); + return 0; + } else if (mmcstatus & st_error) { + if (mmcstatus & MMCST0_TOUTRS) + return -ETIMEDOUT; + printf("[ ST0 ERROR %x]\n", mmcstatus); + /* + * Ignore CRC errors as some MMC cards fail to + * initialize on DM365-EVM on the SD1 slot + */ + if (mmcstatus & MMCST0_CRCRS) + return 0; + return -ECOMM; + } + udelay(10); + + mmcstatus = get_val(®s->mmcst0); + } + + printf("Status %x Timeout ST0:%x ST1:%x\n", st_ready, mmcstatus, + get_val(®s->mmcst1)); + return -ECOMM; +} + +/* + * Sends a command out on the bus. Takes the device pointer, + * a command pointer, and an optional data pointer. + */ +#if !CONFIG_IS_ENABLED(DM_MMC) +static int dmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) +{ + struct davinci_mmc *host = mmc->priv; +#else +static int +davinci_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, struct mmc_data *data) +{ + struct davinci_mmc_priv *host = dev_get_priv(dev); +#endif + volatile struct davinci_mmc_regs *regs = host->reg_base; + uint mmcstatus, status_rdy, status_err; + uint i, cmddata, bytes_left = 0; + int fifo_words, fifo_bytes, err; + char *data_buf = NULL; + + /* Clear status registers */ + mmcstatus = get_val(®s->mmcst0); + fifo_words = 16; + fifo_bytes = fifo_words << 2; + + /* Wait for any previous busy signal to be cleared */ + dmmc_busy_wait(regs); + + cmddata = cmd->cmdidx; + cmddata |= MMCCMD_PPLEN; + + /* Send init clock for CMD0 */ + if (cmd->cmdidx == MMC_CMD_GO_IDLE_STATE) + cmddata |= MMCCMD_INITCK; + + switch (cmd->resp_type) { + case MMC_RSP_R1b: + cmddata |= MMCCMD_BSYEXP; + /* Fall-through */ + case MMC_RSP_R1: /* R1, R1b, R5, R6, R7 */ + cmddata |= MMCCMD_RSPFMT_R1567; + break; + case MMC_RSP_R2: + cmddata |= MMCCMD_RSPFMT_R2; + break; + case MMC_RSP_R3: /* R3, R4 */ + cmddata |= MMCCMD_RSPFMT_R3; + break; + } + + set_val(®s->mmcim, 0); + + if (data) { + /* clear previous data transfer if any and set new one */ + bytes_left = (data->blocksize * data->blocks); + + /* Reset FIFO - Always use 32 byte fifo threshold */ + set_val(®s->mmcfifoctl, + (MMCFIFOCTL_FIFOLEV | MMCFIFOCTL_FIFORST)); + + cmddata |= MMCCMD_DMATRIG; + + cmddata |= MMCCMD_WDATX; + if (data->flags == MMC_DATA_READ) { + set_val(®s->mmcfifoctl, MMCFIFOCTL_FIFOLEV); + } else if (data->flags == MMC_DATA_WRITE) { + set_val(®s->mmcfifoctl, + (MMCFIFOCTL_FIFOLEV | + MMCFIFOCTL_FIFODIR)); + cmddata |= MMCCMD_DTRW; + } + + set_val(®s->mmctod, 0xFFFF); + set_val(®s->mmcnblk, (data->blocks & MMCNBLK_NBLK_MASK)); + set_val(®s->mmcblen, (data->blocksize & MMCBLEN_BLEN_MASK)); + + if (data->flags == MMC_DATA_WRITE) { + uint val; + data_buf = (char *)data->src; + /* For write, fill FIFO with data before issue of CMD */ + for (i = 0; (i < fifo_words) && bytes_left; i++) { + memcpy((char *)&val, data_buf, 4); + set_val(®s->mmcdxr, val); + data_buf += 4; + bytes_left -= 4; + } + } + } else { + set_val(®s->mmcblen, 0); + set_val(®s->mmcnblk, 0); + } + + set_val(®s->mmctor, 0x1FFF); + + /* Send the command */ + set_val(®s->mmcarghl, cmd->cmdarg); + set_val(®s->mmccmd, cmddata); + + status_rdy = MMCST0_RSPDNE; + status_err = (MMCST0_TOUTRS | MMCST0_TOUTRD | + MMCST0_CRCWR | MMCST0_CRCRD); + if (cmd->resp_type & MMC_RSP_CRC) + status_err |= MMCST0_CRCRS; + + mmcstatus = get_val(®s->mmcst0); + err = dmmc_check_status(regs, &mmcstatus, status_rdy, status_err); + if (err) + return err; + + /* For R1b wait for busy done */ + if (cmd->resp_type == MMC_RSP_R1b) + dmmc_busy_wait(regs); + + /* Collect response from controller for specific commands */ + if (mmcstatus & MMCST0_RSPDNE) { + /* Copy the response to the response buffer */ + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[0] = get_val(®s->mmcrsp67); + cmd->response[1] = get_val(®s->mmcrsp45); + cmd->response[2] = get_val(®s->mmcrsp23); + cmd->response[3] = get_val(®s->mmcrsp01); + } else if (cmd->resp_type & MMC_RSP_PRESENT) { + cmd->response[0] = get_val(®s->mmcrsp67); + } + } + + if (data == NULL) + return 0; + + if (data->flags == MMC_DATA_READ) { + /* check for DATDNE along with DRRDY as the controller might + * set the DATDNE without DRRDY for smaller transfers with + * less than FIFO threshold bytes + */ + status_rdy = MMCST0_DRRDY | MMCST0_DATDNE; + status_err = MMCST0_TOUTRD | MMCST0_CRCRD; + data_buf = data->dest; + } else { + status_rdy = MMCST0_DXRDY | MMCST0_DATDNE; + status_err = MMCST0_CRCWR; + } + + /* Wait until all of the blocks are transferred */ + while (bytes_left) { + err = dmmc_check_status(regs, &mmcstatus, status_rdy, + status_err); + if (err) + return err; + + if (data->flags == MMC_DATA_READ) { + /* + * MMC controller sets the Data receive ready bit + * (DRRDY) in MMCST0 even before the entire FIFO is + * full. This results in erratic behavior if we start + * reading the FIFO soon after DRRDY. Wait for the + * FIFO full bit in MMCST1 for proper FIFO clearing. + */ + if (bytes_left > fifo_bytes) + dmmc_wait_fifo_status(regs, 0x4a); + else if (bytes_left == fifo_bytes) { + dmmc_wait_fifo_status(regs, 0x40); + if (cmd->cmdidx == MMC_CMD_SEND_EXT_CSD) + udelay(600); + } + + for (i = 0; bytes_left && (i < fifo_words); i++) { + cmddata = get_val(®s->mmcdrr); + memcpy(data_buf, (char *)&cmddata, 4); + data_buf += 4; + bytes_left -= 4; + } + } else { + /* + * MMC controller sets the Data transmit ready bit + * (DXRDY) in MMCST0 even before the entire FIFO is + * empty. This results in erratic behavior if we start + * writing the FIFO soon after DXRDY. Wait for the + * FIFO empty bit in MMCST1 for proper FIFO clearing. + */ + dmmc_wait_fifo_status(regs, MMCST1_FIFOEMP); + for (i = 0; bytes_left && (i < fifo_words); i++) { + memcpy((char *)&cmddata, data_buf, 4); + set_val(®s->mmcdxr, cmddata); + data_buf += 4; + bytes_left -= 4; + } + dmmc_busy_wait(regs); + } + } + + err = dmmc_check_status(regs, &mmcstatus, MMCST0_DATDNE, status_err); + if (err) + return err; + + return 0; +} + +/* Initialize Davinci MMC controller */ +#if !CONFIG_IS_ENABLED(DM_MMC) +static int dmmc_init(struct mmc *mmc) +{ + struct davinci_mmc *host = mmc->priv; +#else +static int davinci_dm_mmc_init(struct udevice *dev) +{ + struct davinci_mmc_priv *host = dev_get_priv(dev); +#endif + struct davinci_mmc_regs *regs = host->reg_base; + + /* Clear status registers explicitly - soft reset doesn't clear it + * If Uboot is invoked from UBL with SDMMC Support, the status + * registers can have uncleared bits + */ + get_val(®s->mmcst0); + get_val(®s->mmcst1); + + /* Hold software reset */ + set_bit(®s->mmcctl, MMCCTL_DATRST); + set_bit(®s->mmcctl, MMCCTL_CMDRST); + udelay(10); + + set_val(®s->mmcclk, 0x0); + set_val(®s->mmctor, 0x1FFF); + set_val(®s->mmctod, 0xFFFF); + + /* Clear software reset */ + clear_bit(®s->mmcctl, MMCCTL_DATRST); + clear_bit(®s->mmcctl, MMCCTL_CMDRST); + + udelay(10); + + /* Reset FIFO - Always use the maximum fifo threshold */ + set_val(®s->mmcfifoctl, (MMCFIFOCTL_FIFOLEV | MMCFIFOCTL_FIFORST)); + set_val(®s->mmcfifoctl, MMCFIFOCTL_FIFOLEV); + + return 0; +} + +/* Set buswidth or clock as indicated by the MMC framework */ +#if !CONFIG_IS_ENABLED(DM_MMC) +static int dmmc_set_ios(struct mmc *mmc) +{ + struct davinci_mmc *host = mmc->priv; + struct davinci_mmc_regs *regs = host->reg_base; +#else +static int davinci_mmc_set_ios(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + + struct davinci_mmc_priv *host = dev_get_priv(dev); + struct davinci_mmc_regs *regs = host->reg_base; +#endif + /* Set the bus width */ + if (mmc->bus_width == 4) + set_bit(®s->mmcctl, MMCCTL_WIDTH_4_BIT); + else + clear_bit(®s->mmcctl, MMCCTL_WIDTH_4_BIT); + + /* Set clock speed */ + if (mmc->clock) { +#if !CONFIG_IS_ENABLED(DM_MMC) + dmmc_set_clock(mmc, mmc->clock); +#else + davinci_mmc_set_clock(dev, mmc->clock); +#endif + } + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +static const struct mmc_ops dmmc_ops = { + .send_cmd = dmmc_send_cmd, + .set_ios = dmmc_set_ios, + .init = dmmc_init, +}; +#else + +static int davinci_mmc_getcd(struct udevice *dev) +{ + int value = -1; +#if CONFIG_IS_ENABLED(DM_GPIO) + struct davinci_mmc_priv *priv = dev_get_priv(dev); + value = dm_gpio_get_value(&priv->cd_gpio); +#endif + /* if no CD return as 1 */ + if (value < 0) + return 1; + + return value; +} + +static int davinci_mmc_getwp(struct udevice *dev) +{ + int value = -1; +#if CONFIG_IS_ENABLED(DM_GPIO) + struct davinci_mmc_priv *priv = dev_get_priv(dev); + + value = dm_gpio_get_value(&priv->wp_gpio); +#endif + /* if no WP return as 0 */ + if (value < 0) + return 0; + + return value; +} + +static const struct dm_mmc_ops davinci_mmc_ops = { + .send_cmd = davinci_mmc_send_cmd, + .set_ios = davinci_mmc_set_ios, + .get_cd = davinci_mmc_getcd, + .get_wp = davinci_mmc_getwp, +}; +#endif + +#if !CONFIG_IS_ENABLED(DM_MMC) +/* Called from board_mmc_init during startup. Can be called multiple times +* depending on the number of slots available on board and controller +*/ +int davinci_mmc_init(struct bd_info *bis, struct davinci_mmc *host) +{ + host->cfg.name = "davinci"; + host->cfg.ops = &dmmc_ops; + host->cfg.f_min = 200000; + host->cfg.f_max = 25000000; + host->cfg.voltages = host->voltages; + host->cfg.host_caps = host->host_caps; + + host->cfg.b_max = DAVINCI_MAX_BLOCKS; + + mmc_create(&host->cfg, host); + + return 0; +} +#else + + +static int davinci_mmc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct davinci_mmc_plat *plat = dev_get_plat(dev); + struct davinci_mmc_priv *priv = dev_get_priv(dev); + + priv->reg_base = plat->reg_base; + priv->input_clk = clk_get(DAVINCI_MMCSD_CLKID); +#if CONFIG_IS_ENABLED(DM_GPIO) + /* These GPIOs are optional */ + gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio, GPIOD_IS_IN); + gpio_request_by_name(dev, "wp-gpios", 0, &priv->wp_gpio, GPIOD_IS_IN); +#endif + upriv->mmc = &plat->mmc; + + return davinci_dm_mmc_init(dev); +} + +static int davinci_mmc_bind(struct udevice *dev) +{ + struct davinci_mmc_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +#if CONFIG_IS_ENABLED(OF_CONTROL) +static int davinci_mmc_of_to_plat(struct udevice *dev) +{ + struct davinci_mmc_plat *plat = dev_get_plat(dev); + struct mmc_config *cfg = &plat->cfg; + + plat->reg_base = (struct davinci_mmc_regs *)dev_read_addr(dev); + cfg->f_min = 200000; + cfg->f_max = 25000000; + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34, + cfg->host_caps = MMC_MODE_4BIT, /* DA850 supports only 4-bit SD/MMC */ + cfg->b_max = DAVINCI_MAX_BLOCKS; + cfg->name = "da830-mmc"; + + return 0; +} + +static const struct udevice_id davinci_mmc_ids[] = { + { .compatible = "ti,da830-mmc" }, + {}, +}; +#endif +U_BOOT_DRIVER(ti_da830_mmc) = { + .name = "davinci_mmc", + .id = UCLASS_MMC, +#if CONFIG_IS_ENABLED(OF_CONTROL) + .of_match = davinci_mmc_ids, + .plat_auto = sizeof(struct davinci_mmc_plat), + .of_to_plat = davinci_mmc_of_to_plat, +#endif +#if CONFIG_BLK + .bind = davinci_mmc_bind, +#endif + .probe = davinci_mmc_probe, + .ops = &davinci_mmc_ops, + .priv_auto = sizeof(struct davinci_mmc_priv), +#if !CONFIG_IS_ENABLED(OF_CONTROL) + .flags = DM_FLAG_PRE_RELOC, +#endif +}; +#endif diff --git a/roms/u-boot/drivers/mmc/dw_mmc.c b/roms/u-boot/drivers/mmc/dw_mmc.c new file mode 100644 index 000000000..a949dad57 --- /dev/null +++ b/roms/u-boot/drivers/mmc/dw_mmc.c @@ -0,0 +1,632 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2012 SAMSUNG Electronics + * Jaehoon Chung <jh80.chung@samsung.com> + * Rajeshawari Shinde <rajeshwari.s@samsung.com> + */ + +#include <bouncebuf.h> +#include <common.h> +#include <cpu_func.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <memalign.h> +#include <mmc.h> +#include <dwmmc.h> +#include <wait_bit.h> +#include <asm/cache.h> +#include <linux/delay.h> +#include <power/regulator.h> + +#define PAGE_SIZE 4096 + +static int dwmci_wait_reset(struct dwmci_host *host, u32 value) +{ + unsigned long timeout = 1000; + u32 ctrl; + + dwmci_writel(host, DWMCI_CTRL, value); + + while (timeout--) { + ctrl = dwmci_readl(host, DWMCI_CTRL); + if (!(ctrl & DWMCI_RESET_ALL)) + return 1; + } + return 0; +} + +static void dwmci_set_idma_desc(struct dwmci_idmac *idmac, + u32 desc0, u32 desc1, u32 desc2) +{ + struct dwmci_idmac *desc = idmac; + + desc->flags = desc0; + desc->cnt = desc1; + desc->addr = desc2; + desc->next_addr = (ulong)desc + sizeof(struct dwmci_idmac); +} + +static void dwmci_prepare_data(struct dwmci_host *host, + struct mmc_data *data, + struct dwmci_idmac *cur_idmac, + void *bounce_buffer) +{ + unsigned long ctrl; + unsigned int i = 0, flags, cnt, blk_cnt; + ulong data_start, data_end; + + + blk_cnt = data->blocks; + + dwmci_wait_reset(host, DWMCI_CTRL_FIFO_RESET); + + /* Clear IDMAC interrupt */ + dwmci_writel(host, DWMCI_IDSTS, 0xFFFFFFFF); + + data_start = (ulong)cur_idmac; + dwmci_writel(host, DWMCI_DBADDR, (ulong)cur_idmac); + + do { + flags = DWMCI_IDMAC_OWN | DWMCI_IDMAC_CH ; + flags |= (i == 0) ? DWMCI_IDMAC_FS : 0; + if (blk_cnt <= 8) { + flags |= DWMCI_IDMAC_LD; + cnt = data->blocksize * blk_cnt; + } else + cnt = data->blocksize * 8; + + dwmci_set_idma_desc(cur_idmac, flags, cnt, + (ulong)bounce_buffer + (i * PAGE_SIZE)); + + cur_idmac++; + if (blk_cnt <= 8) + break; + blk_cnt -= 8; + i++; + } while(1); + + data_end = (ulong)cur_idmac; + flush_dcache_range(data_start, roundup(data_end, ARCH_DMA_MINALIGN)); + + ctrl = dwmci_readl(host, DWMCI_CTRL); + ctrl |= DWMCI_IDMAC_EN | DWMCI_DMA_EN; + dwmci_writel(host, DWMCI_CTRL, ctrl); + + ctrl = dwmci_readl(host, DWMCI_BMOD); + ctrl |= DWMCI_BMOD_IDMAC_FB | DWMCI_BMOD_IDMAC_EN; + dwmci_writel(host, DWMCI_BMOD, ctrl); + + dwmci_writel(host, DWMCI_BLKSIZ, data->blocksize); + dwmci_writel(host, DWMCI_BYTCNT, data->blocksize * data->blocks); +} + +static int dwmci_fifo_ready(struct dwmci_host *host, u32 bit, u32 *len) +{ + u32 timeout = 20000; + + *len = dwmci_readl(host, DWMCI_STATUS); + while (--timeout && (*len & bit)) { + udelay(200); + *len = dwmci_readl(host, DWMCI_STATUS); + } + + if (!timeout) { + debug("%s: FIFO underflow timeout\n", __func__); + return -ETIMEDOUT; + } + + return 0; +} + +static unsigned int dwmci_get_timeout(struct mmc *mmc, const unsigned int size) +{ + unsigned int timeout; + + timeout = size * 8; /* counting in bits */ + timeout *= 10; /* wait 10 times as long */ + timeout /= mmc->clock; + timeout /= mmc->bus_width; + timeout /= mmc->ddr_mode ? 2 : 1; + timeout *= 1000; /* counting in msec */ + timeout = (timeout < 1000) ? 1000 : timeout; + + return timeout; +} + +static int dwmci_data_transfer(struct dwmci_host *host, struct mmc_data *data) +{ + struct mmc *mmc = host->mmc; + int ret = 0; + u32 timeout, mask, size, i, len = 0; + u32 *buf = NULL; + ulong start = get_timer(0); + u32 fifo_depth = (((host->fifoth_val & RX_WMARK_MASK) >> + RX_WMARK_SHIFT) + 1) * 2; + + size = data->blocksize * data->blocks; + if (data->flags == MMC_DATA_READ) + buf = (unsigned int *)data->dest; + else + buf = (unsigned int *)data->src; + + timeout = dwmci_get_timeout(mmc, size); + + size /= 4; + + for (;;) { + mask = dwmci_readl(host, DWMCI_RINTSTS); + /* Error during data transfer. */ + if (mask & (DWMCI_DATA_ERR | DWMCI_DATA_TOUT)) { + debug("%s: DATA ERROR!\n", __func__); + ret = -EINVAL; + break; + } + + if (host->fifo_mode && size) { + len = 0; + if (data->flags == MMC_DATA_READ && + (mask & (DWMCI_INTMSK_RXDR | DWMCI_INTMSK_DTO))) { + dwmci_writel(host, DWMCI_RINTSTS, + DWMCI_INTMSK_RXDR | DWMCI_INTMSK_DTO); + while (size) { + ret = dwmci_fifo_ready(host, + DWMCI_FIFO_EMPTY, + &len); + if (ret < 0) + break; + + len = (len >> DWMCI_FIFO_SHIFT) & + DWMCI_FIFO_MASK; + len = min(size, len); + for (i = 0; i < len; i++) + *buf++ = + dwmci_readl(host, DWMCI_DATA); + size = size > len ? (size - len) : 0; + } + } else if (data->flags == MMC_DATA_WRITE && + (mask & DWMCI_INTMSK_TXDR)) { + while (size) { + ret = dwmci_fifo_ready(host, + DWMCI_FIFO_FULL, + &len); + if (ret < 0) + break; + + len = fifo_depth - ((len >> + DWMCI_FIFO_SHIFT) & + DWMCI_FIFO_MASK); + len = min(size, len); + for (i = 0; i < len; i++) + dwmci_writel(host, DWMCI_DATA, + *buf++); + size = size > len ? (size - len) : 0; + } + dwmci_writel(host, DWMCI_RINTSTS, + DWMCI_INTMSK_TXDR); + } + } + + /* Data arrived correctly. */ + if (mask & DWMCI_INTMSK_DTO) { + ret = 0; + break; + } + + /* Check for timeout. */ + if (get_timer(start) > timeout) { + debug("%s: Timeout waiting for data!\n", + __func__); + ret = -ETIMEDOUT; + break; + } + } + + dwmci_writel(host, DWMCI_RINTSTS, mask); + + return ret; +} + +static int dwmci_set_transfer_mode(struct dwmci_host *host, + struct mmc_data *data) +{ + unsigned long mode; + + mode = DWMCI_CMD_DATA_EXP; + if (data->flags & MMC_DATA_WRITE) + mode |= DWMCI_CMD_RW; + + return mode; +} + +#ifdef CONFIG_DM_MMC +static int dwmci_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); +#else +static int dwmci_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ +#endif + struct dwmci_host *host = mmc->priv; + ALLOC_CACHE_ALIGN_BUFFER(struct dwmci_idmac, cur_idmac, + data ? DIV_ROUND_UP(data->blocks, 8) : 0); + int ret = 0, flags = 0, i; + unsigned int timeout = 500; + u32 retry = 100000; + u32 mask, ctrl; + ulong start = get_timer(0); + struct bounce_buffer bbstate; + + while (dwmci_readl(host, DWMCI_STATUS) & DWMCI_BUSY) { + if (get_timer(start) > timeout) { + debug("%s: Timeout on data busy\n", __func__); + return -ETIMEDOUT; + } + } + + dwmci_writel(host, DWMCI_RINTSTS, DWMCI_INTMSK_ALL); + + if (data) { + if (host->fifo_mode) { + dwmci_writel(host, DWMCI_BLKSIZ, data->blocksize); + dwmci_writel(host, DWMCI_BYTCNT, + data->blocksize * data->blocks); + dwmci_wait_reset(host, DWMCI_CTRL_FIFO_RESET); + } else { + if (data->flags == MMC_DATA_READ) { + ret = bounce_buffer_start(&bbstate, + (void*)data->dest, + data->blocksize * + data->blocks, GEN_BB_WRITE); + } else { + ret = bounce_buffer_start(&bbstate, + (void*)data->src, + data->blocksize * + data->blocks, GEN_BB_READ); + } + + if (ret) + return ret; + + dwmci_prepare_data(host, data, cur_idmac, + bbstate.bounce_buffer); + } + } + + dwmci_writel(host, DWMCI_CMDARG, cmd->cmdarg); + + if (data) + flags = dwmci_set_transfer_mode(host, data); + + if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) + return -1; + + if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) + flags |= DWMCI_CMD_ABORT_STOP; + else + flags |= DWMCI_CMD_PRV_DAT_WAIT; + + if (cmd->resp_type & MMC_RSP_PRESENT) { + flags |= DWMCI_CMD_RESP_EXP; + if (cmd->resp_type & MMC_RSP_136) + flags |= DWMCI_CMD_RESP_LENGTH; + } + + if (cmd->resp_type & MMC_RSP_CRC) + flags |= DWMCI_CMD_CHECK_CRC; + + flags |= (cmd->cmdidx | DWMCI_CMD_START | DWMCI_CMD_USE_HOLD_REG); + + debug("Sending CMD%d\n",cmd->cmdidx); + + dwmci_writel(host, DWMCI_CMD, flags); + + for (i = 0; i < retry; i++) { + mask = dwmci_readl(host, DWMCI_RINTSTS); + if (mask & DWMCI_INTMSK_CDONE) { + if (!data) + dwmci_writel(host, DWMCI_RINTSTS, mask); + break; + } + } + + if (i == retry) { + debug("%s: Timeout.\n", __func__); + return -ETIMEDOUT; + } + + if (mask & DWMCI_INTMSK_RTO) { + /* + * Timeout here is not necessarily fatal. (e)MMC cards + * will splat here when they receive CMD55 as they do + * not support this command and that is exactly the way + * to tell them apart from SD cards. Thus, this output + * below shall be debug(). eMMC cards also do not favor + * CMD8, please keep that in mind. + */ + debug("%s: Response Timeout.\n", __func__); + return -ETIMEDOUT; + } else if (mask & DWMCI_INTMSK_RE) { + debug("%s: Response Error.\n", __func__); + return -EIO; + } else if ((cmd->resp_type & MMC_RSP_CRC) && + (mask & DWMCI_INTMSK_RCRC)) { + debug("%s: Response CRC Error.\n", __func__); + return -EIO; + } + + + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[0] = dwmci_readl(host, DWMCI_RESP3); + cmd->response[1] = dwmci_readl(host, DWMCI_RESP2); + cmd->response[2] = dwmci_readl(host, DWMCI_RESP1); + cmd->response[3] = dwmci_readl(host, DWMCI_RESP0); + } else { + cmd->response[0] = dwmci_readl(host, DWMCI_RESP0); + } + } + + if (data) { + ret = dwmci_data_transfer(host, data); + + /* only dma mode need it */ + if (!host->fifo_mode) { + if (data->flags == MMC_DATA_READ) + mask = DWMCI_IDINTEN_RI; + else + mask = DWMCI_IDINTEN_TI; + ret = wait_for_bit_le32(host->ioaddr + DWMCI_IDSTS, + mask, true, 1000, false); + if (ret) + debug("%s: DWMCI_IDINTEN mask 0x%x timeout.\n", + __func__, mask); + /* clear interrupts */ + dwmci_writel(host, DWMCI_IDSTS, DWMCI_IDINTEN_MASK); + + ctrl = dwmci_readl(host, DWMCI_CTRL); + ctrl &= ~(DWMCI_DMA_EN); + dwmci_writel(host, DWMCI_CTRL, ctrl); + bounce_buffer_stop(&bbstate); + } + } + + udelay(100); + + return ret; +} + +static int dwmci_setup_bus(struct dwmci_host *host, u32 freq) +{ + u32 div, status; + int timeout = 10000; + unsigned long sclk; + + if ((freq == host->clock) || (freq == 0)) + return 0; + /* + * If host->get_mmc_clk isn't defined, + * then assume that host->bus_hz is source clock value. + * host->bus_hz should be set by user. + */ + if (host->get_mmc_clk) + sclk = host->get_mmc_clk(host, freq); + else if (host->bus_hz) + sclk = host->bus_hz; + else { + debug("%s: Didn't get source clock value.\n", __func__); + return -EINVAL; + } + + if (sclk == freq) + div = 0; /* bypass mode */ + else + div = DIV_ROUND_UP(sclk, 2 * freq); + + dwmci_writel(host, DWMCI_CLKENA, 0); + dwmci_writel(host, DWMCI_CLKSRC, 0); + + dwmci_writel(host, DWMCI_CLKDIV, div); + dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_PRV_DAT_WAIT | + DWMCI_CMD_UPD_CLK | DWMCI_CMD_START); + + do { + status = dwmci_readl(host, DWMCI_CMD); + if (timeout-- < 0) { + debug("%s: Timeout!\n", __func__); + return -ETIMEDOUT; + } + } while (status & DWMCI_CMD_START); + + dwmci_writel(host, DWMCI_CLKENA, DWMCI_CLKEN_ENABLE | + DWMCI_CLKEN_LOW_PWR); + + dwmci_writel(host, DWMCI_CMD, DWMCI_CMD_PRV_DAT_WAIT | + DWMCI_CMD_UPD_CLK | DWMCI_CMD_START); + + timeout = 10000; + do { + status = dwmci_readl(host, DWMCI_CMD); + if (timeout-- < 0) { + debug("%s: Timeout!\n", __func__); + return -ETIMEDOUT; + } + } while (status & DWMCI_CMD_START); + + host->clock = freq; + + return 0; +} + +#ifdef CONFIG_DM_MMC +static int dwmci_set_ios(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); +#else +static int dwmci_set_ios(struct mmc *mmc) +{ +#endif + struct dwmci_host *host = (struct dwmci_host *)mmc->priv; + u32 ctype, regs; + + debug("Buswidth = %d, clock: %d\n", mmc->bus_width, mmc->clock); + + dwmci_setup_bus(host, mmc->clock); + switch (mmc->bus_width) { + case 8: + ctype = DWMCI_CTYPE_8BIT; + break; + case 4: + ctype = DWMCI_CTYPE_4BIT; + break; + default: + ctype = DWMCI_CTYPE_1BIT; + break; + } + + dwmci_writel(host, DWMCI_CTYPE, ctype); + + regs = dwmci_readl(host, DWMCI_UHS_REG); + if (mmc->ddr_mode) + regs |= DWMCI_DDR_MODE; + else + regs &= ~DWMCI_DDR_MODE; + + dwmci_writel(host, DWMCI_UHS_REG, regs); + + if (host->clksel) { + int ret; + + ret = host->clksel(host); + if (ret) + return ret; + } + +#if CONFIG_IS_ENABLED(DM_REGULATOR) + if (mmc->vqmmc_supply) { + int ret; + + if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180) + regulator_set_value(mmc->vqmmc_supply, 1800000); + else + regulator_set_value(mmc->vqmmc_supply, 3300000); + + ret = regulator_set_enable_if_allowed(mmc->vqmmc_supply, true); + if (ret) + return ret; + } +#endif + + return 0; +} + +static int dwmci_init(struct mmc *mmc) +{ + struct dwmci_host *host = mmc->priv; + + if (host->board_init) + host->board_init(host); + + dwmci_writel(host, DWMCI_PWREN, 1); + + if (!dwmci_wait_reset(host, DWMCI_RESET_ALL)) { + debug("%s[%d] Fail-reset!!\n", __func__, __LINE__); + return -EIO; + } + + /* Enumerate at 400KHz */ + dwmci_setup_bus(host, mmc->cfg->f_min); + + dwmci_writel(host, DWMCI_RINTSTS, 0xFFFFFFFF); + dwmci_writel(host, DWMCI_INTMASK, 0); + + dwmci_writel(host, DWMCI_TMOUT, 0xFFFFFFFF); + + dwmci_writel(host, DWMCI_IDINTEN, 0); + dwmci_writel(host, DWMCI_BMOD, 1); + + if (!host->fifoth_val) { + uint32_t fifo_size; + + fifo_size = dwmci_readl(host, DWMCI_FIFOTH); + fifo_size = ((fifo_size & RX_WMARK_MASK) >> RX_WMARK_SHIFT) + 1; + host->fifoth_val = MSIZE(0x2) | RX_WMARK(fifo_size / 2 - 1) | + TX_WMARK(fifo_size / 2); + } + dwmci_writel(host, DWMCI_FIFOTH, host->fifoth_val); + + dwmci_writel(host, DWMCI_CLKENA, 0); + dwmci_writel(host, DWMCI_CLKSRC, 0); + + if (!host->fifo_mode) + dwmci_writel(host, DWMCI_IDINTEN, DWMCI_IDINTEN_MASK); + + return 0; +} + +#ifdef CONFIG_DM_MMC +int dwmci_probe(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + + return dwmci_init(mmc); +} + +const struct dm_mmc_ops dm_dwmci_ops = { + .send_cmd = dwmci_send_cmd, + .set_ios = dwmci_set_ios, +}; + +#else +static const struct mmc_ops dwmci_ops = { + .send_cmd = dwmci_send_cmd, + .set_ios = dwmci_set_ios, + .init = dwmci_init, +}; +#endif + +void dwmci_setup_cfg(struct mmc_config *cfg, struct dwmci_host *host, + u32 max_clk, u32 min_clk) +{ + cfg->name = host->name; +#ifndef CONFIG_DM_MMC + cfg->ops = &dwmci_ops; +#endif + cfg->f_min = min_clk; + cfg->f_max = max_clk; + + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + + cfg->host_caps = host->caps; + + if (host->buswidth == 8) { + cfg->host_caps |= MMC_MODE_8BIT; + cfg->host_caps &= ~MMC_MODE_4BIT; + } else { + cfg->host_caps |= MMC_MODE_4BIT; + cfg->host_caps &= ~MMC_MODE_8BIT; + } + cfg->host_caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz; + + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; +} + +#ifdef CONFIG_BLK +int dwmci_bind(struct udevice *dev, struct mmc *mmc, struct mmc_config *cfg) +{ + return mmc_bind(dev, mmc, cfg); +} +#else +int add_dwmci(struct dwmci_host *host, u32 max_clk, u32 min_clk) +{ + dwmci_setup_cfg(&host->cfg, host, max_clk, min_clk); + + host->mmc = mmc_create(&host->cfg, host); + if (host->mmc == NULL) + return -1; + + return 0; +} +#endif diff --git a/roms/u-boot/drivers/mmc/exynos_dw_mmc.c b/roms/u-boot/drivers/mmc/exynos_dw_mmc.c new file mode 100644 index 000000000..544798bb7 --- /dev/null +++ b/roms/u-boot/drivers/mmc/exynos_dw_mmc.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2012 SAMSUNG Electronics + * Jaehoon Chung <jh80.chung@samsung.com> + */ + +#include <common.h> +#include <dwmmc.h> +#include <fdtdec.h> +#include <asm/global_data.h> +#include <linux/libfdt.h> +#include <malloc.h> +#include <errno.h> +#include <asm/arch/dwmmc.h> +#include <asm/arch/clk.h> +#include <asm/arch/pinmux.h> +#include <asm/arch/power.h> +#include <asm/gpio.h> + +#define DWMMC_MAX_CH_NUM 4 +#define DWMMC_MAX_FREQ 52000000 +#define DWMMC_MIN_FREQ 400000 +#define DWMMC_MMC0_SDR_TIMING_VAL 0x03030001 +#define DWMMC_MMC2_SDR_TIMING_VAL 0x03020001 + +#ifdef CONFIG_DM_MMC +#include <dm.h> +DECLARE_GLOBAL_DATA_PTR; + +struct exynos_mmc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; +#endif + +/* Exynos implmentation specific drver private data */ +struct dwmci_exynos_priv_data { +#ifdef CONFIG_DM_MMC + struct dwmci_host host; +#endif + u32 sdr_timing; +}; + +/* + * Function used as callback function to initialise the + * CLKSEL register for every mmc channel. + */ +static int exynos_dwmci_clksel(struct dwmci_host *host) +{ +#ifdef CONFIG_DM_MMC + struct dwmci_exynos_priv_data *priv = + container_of(host, struct dwmci_exynos_priv_data, host); +#else + struct dwmci_exynos_priv_data *priv = host->priv; +#endif + dwmci_writel(host, DWMCI_CLKSEL, priv->sdr_timing); + + return 0; +} + +unsigned int exynos_dwmci_get_clk(struct dwmci_host *host, uint freq) +{ + unsigned long sclk; + int8_t clk_div; + + /* + * Since SDCLKIN is divided inside controller by the DIVRATIO + * value set in the CLKSEL register, we need to use the same output + * clock value to calculate the CLKDIV value. + * as per user manual:cclk_in = SDCLKIN / (DIVRATIO + 1) + */ + clk_div = ((dwmci_readl(host, DWMCI_CLKSEL) >> DWMCI_DIVRATIO_BIT) + & DWMCI_DIVRATIO_MASK) + 1; + sclk = get_mmc_clk(host->dev_index); + + /* + * Assume to know divider value. + * When clock unit is broken, need to set "host->div" + */ + return sclk / clk_div / (host->div + 1); +} + +static void exynos_dwmci_board_init(struct dwmci_host *host) +{ + struct dwmci_exynos_priv_data *priv = host->priv; + + if (host->quirks & DWMCI_QUIRK_DISABLE_SMU) { + dwmci_writel(host, EMMCP_MPSBEGIN0, 0); + dwmci_writel(host, EMMCP_SEND0, 0); + dwmci_writel(host, EMMCP_CTRL0, + MPSCTRL_SECURE_READ_BIT | + MPSCTRL_SECURE_WRITE_BIT | + MPSCTRL_NON_SECURE_READ_BIT | + MPSCTRL_NON_SECURE_WRITE_BIT | MPSCTRL_VALID); + } + + /* Set to timing value at initial time */ + if (priv->sdr_timing) + exynos_dwmci_clksel(host); +} + +static int exynos_dwmci_core_init(struct dwmci_host *host) +{ + unsigned int div; + unsigned long freq, sclk; + + if (host->bus_hz) + freq = host->bus_hz; + else + freq = DWMMC_MAX_FREQ; + + /* request mmc clock vlaue of 52MHz. */ + sclk = get_mmc_clk(host->dev_index); + div = DIV_ROUND_UP(sclk, freq); + /* set the clock divisor for mmc */ + set_mmc_clk(host->dev_index, div); + + host->name = "EXYNOS DWMMC"; +#ifdef CONFIG_EXYNOS5420 + host->quirks = DWMCI_QUIRK_DISABLE_SMU; +#endif + host->board_init = exynos_dwmci_board_init; + + host->caps = MMC_MODE_DDR_52MHz; + host->clksel = exynos_dwmci_clksel; + host->get_mmc_clk = exynos_dwmci_get_clk; + +#ifndef CONFIG_DM_MMC + /* Add the mmc channel to be registered with mmc core */ + if (add_dwmci(host, DWMMC_MAX_FREQ, DWMMC_MIN_FREQ)) { + printf("DWMMC%d registration failed\n", host->dev_index); + return -1; + } +#endif + + return 0; +} + +static int do_dwmci_init(struct dwmci_host *host) +{ + int flag, err; + + flag = host->buswidth == 8 ? PINMUX_FLAG_8BIT_MODE : PINMUX_FLAG_NONE; + err = exynos_pinmux_config(host->dev_id, flag); + if (err) { + printf("DWMMC%d not configure\n", host->dev_index); + return err; + } + + return exynos_dwmci_core_init(host); +} + +static int exynos_dwmci_get_config(const void *blob, int node, + struct dwmci_host *host, + struct dwmci_exynos_priv_data *priv) +{ + int err = 0; + u32 base, timing[3]; + + /* Extract device id for each mmc channel */ + host->dev_id = pinmux_decode_periph_id(blob, node); + + host->dev_index = fdtdec_get_int(blob, node, "index", host->dev_id); + if (host->dev_index == host->dev_id) + host->dev_index = host->dev_id - PERIPH_ID_SDMMC0; + + if (host->dev_index > 4) { + printf("DWMMC%d: Can't get the dev index\n", host->dev_index); + return -EINVAL; + } + + /* Get the bus width from the device node (Default is 4bit buswidth) */ + host->buswidth = fdtdec_get_int(blob, node, "samsung,bus-width", 4); + + /* Set the base address from the device node */ + base = fdtdec_get_addr(blob, node, "reg"); + if (!base) { + printf("DWMMC%d: Can't get base address\n", host->dev_index); + return -EINVAL; + } + host->ioaddr = (void *)base; + + /* Extract the timing info from the node */ + err = fdtdec_get_int_array(blob, node, "samsung,timing", timing, 3); + if (err) { + printf("DWMMC%d: Can't get sdr-timings for devider\n", + host->dev_index); + return -EINVAL; + } + + priv->sdr_timing = (DWMCI_SET_SAMPLE_CLK(timing[0]) | + DWMCI_SET_DRV_CLK(timing[1]) | + DWMCI_SET_DIV_RATIO(timing[2])); + + /* sdr_timing didn't assigned anything, use the default value */ + if (!priv->sdr_timing) { + if (host->dev_index == 0) + priv->sdr_timing = DWMMC_MMC0_SDR_TIMING_VAL; + else if (host->dev_index == 2) + priv->sdr_timing = DWMMC_MMC2_SDR_TIMING_VAL; + } + + host->fifoth_val = fdtdec_get_int(blob, node, "fifoth_val", 0); + host->bus_hz = fdtdec_get_int(blob, node, "bus_hz", 0); + host->div = fdtdec_get_int(blob, node, "div", 0); + + return 0; +} + +#ifdef CONFIG_DM_MMC +static int exynos_dwmmc_probe(struct udevice *dev) +{ + struct exynos_mmc_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct dwmci_exynos_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + int err; + + err = exynos_dwmci_get_config(gd->fdt_blob, dev_of_offset(dev), host, + priv); + if (err) + return err; + err = do_dwmci_init(host); + if (err) + return err; + + dwmci_setup_cfg(&plat->cfg, host, DWMMC_MAX_FREQ, DWMMC_MIN_FREQ); + host->mmc = &plat->mmc; + host->mmc->priv = &priv->host; + host->priv = dev; + upriv->mmc = host->mmc; + + return dwmci_probe(dev); +} + +static int exynos_dwmmc_bind(struct udevice *dev) +{ + struct exynos_mmc_plat *plat = dev_get_plat(dev); + + return dwmci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id exynos_dwmmc_ids[] = { + { .compatible = "samsung,exynos4412-dw-mshc" }, + { .compatible = "samsung,exynos-dwmmc" }, + { } +}; + +U_BOOT_DRIVER(exynos_dwmmc_drv) = { + .name = "exynos_dwmmc", + .id = UCLASS_MMC, + .of_match = exynos_dwmmc_ids, + .bind = exynos_dwmmc_bind, + .ops = &dm_dwmci_ops, + .probe = exynos_dwmmc_probe, + .priv_auto = sizeof(struct dwmci_exynos_priv_data), + .plat_auto = sizeof(struct exynos_mmc_plat), +}; +#endif diff --git a/roms/u-boot/drivers/mmc/fsl_esdhc.c b/roms/u-boot/drivers/mmc/fsl_esdhc.c new file mode 100644 index 000000000..1d98fa65c --- /dev/null +++ b/roms/u-boot/drivers/mmc/fsl_esdhc.c @@ -0,0 +1,1181 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2007, 2010-2011 Freescale Semiconductor, Inc + * Copyright 2019-2021 NXP + * Andy Fleming + * + * Based vaguely on the pxa mmc code: + * (C) Copyright 2003 + * Kyle Harris, Nexus Technologies, Inc. kharris@nexus-tech.net + */ + +#include <config.h> +#include <common.h> +#include <command.h> +#include <cpu_func.h> +#include <errno.h> +#include <hwconfig.h> +#include <mmc.h> +#include <part.h> +#include <malloc.h> +#include <fsl_esdhc.h> +#include <fdt_support.h> +#include <asm/cache.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <sdhci.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct fsl_esdhc { + uint dsaddr; /* SDMA system address register */ + uint blkattr; /* Block attributes register */ + uint cmdarg; /* Command argument register */ + uint xfertyp; /* Transfer type register */ + uint cmdrsp0; /* Command response 0 register */ + uint cmdrsp1; /* Command response 1 register */ + uint cmdrsp2; /* Command response 2 register */ + uint cmdrsp3; /* Command response 3 register */ + uint datport; /* Buffer data port register */ + uint prsstat; /* Present state register */ + uint proctl; /* Protocol control register */ + uint sysctl; /* System Control Register */ + uint irqstat; /* Interrupt status register */ + uint irqstaten; /* Interrupt status enable register */ + uint irqsigen; /* Interrupt signal enable register */ + uint autoc12err; /* Auto CMD error status register */ + uint hostcapblt; /* Host controller capabilities register */ + uint wml; /* Watermark level register */ + char reserved1[8]; /* reserved */ + uint fevt; /* Force event register */ + uint admaes; /* ADMA error status register */ + uint adsaddrl; /* ADMA system address low register */ + uint adsaddrh; /* ADMA system address high register */ + char reserved2[156]; + uint hostver; /* Host controller version register */ + char reserved3[4]; /* reserved */ + uint dmaerraddr; /* DMA error address register */ + char reserved4[4]; /* reserved */ + uint dmaerrattr; /* DMA error attribute register */ + char reserved5[4]; /* reserved */ + uint hostcapblt2; /* Host controller capabilities register 2 */ + char reserved6[8]; /* reserved */ + uint tbctl; /* Tuning block control register */ + char reserved7[32]; /* reserved */ + uint sdclkctl; /* SD clock control register */ + uint sdtimingctl; /* SD timing control register */ + char reserved8[20]; /* reserved */ + uint dllcfg0; /* DLL config 0 register */ + uint dllcfg1; /* DLL config 1 register */ + char reserved9[8]; /* reserved */ + uint dllstat0; /* DLL status 0 register */ + char reserved10[664];/* reserved */ + uint esdhcctl; /* eSDHC control register */ +}; + +struct fsl_esdhc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +/** + * struct fsl_esdhc_priv + * + * @esdhc_regs: registers of the sdhc controller + * @sdhc_clk: Current clk of the sdhc controller + * @bus_width: bus width, 1bit, 4bit or 8bit + * @cfg: mmc config + * @mmc: mmc + * Following is used when Driver Model is enabled for MMC + * @dev: pointer for the device + * @cd_gpio: gpio for card detection + * @wp_gpio: gpio for write protection + */ +struct fsl_esdhc_priv { + struct fsl_esdhc *esdhc_regs; + unsigned int sdhc_clk; + bool is_sdhc_per_clk; + unsigned int clock; +#if !CONFIG_IS_ENABLED(DM_MMC) + struct mmc *mmc; +#endif + struct udevice *dev; + struct sdhci_adma_desc *adma_desc_table; + dma_addr_t dma_addr; +}; + +/* Return the XFERTYP flags for a given command and data packet */ +static uint esdhc_xfertyp(struct mmc_cmd *cmd, struct mmc_data *data) +{ + uint xfertyp = 0; + + if (data) { + xfertyp |= XFERTYP_DPSEL; + if (!IS_ENABLED(CONFIG_SYS_FSL_ESDHC_USE_PIO) && + cmd->cmdidx != MMC_CMD_SEND_TUNING_BLOCK && + cmd->cmdidx != MMC_CMD_SEND_TUNING_BLOCK_HS200) + xfertyp |= XFERTYP_DMAEN; + if (data->blocks > 1) { + xfertyp |= XFERTYP_MSBSEL; + xfertyp |= XFERTYP_BCEN; + if (IS_ENABLED(CONFIG_SYS_FSL_ERRATUM_ESDHC111)) + xfertyp |= XFERTYP_AC12EN; + } + + if (data->flags & MMC_DATA_READ) + xfertyp |= XFERTYP_DTDSEL; + } + + if (cmd->resp_type & MMC_RSP_CRC) + xfertyp |= XFERTYP_CCCEN; + if (cmd->resp_type & MMC_RSP_OPCODE) + xfertyp |= XFERTYP_CICEN; + if (cmd->resp_type & MMC_RSP_136) + xfertyp |= XFERTYP_RSPTYP_136; + else if (cmd->resp_type & MMC_RSP_BUSY) + xfertyp |= XFERTYP_RSPTYP_48_BUSY; + else if (cmd->resp_type & MMC_RSP_PRESENT) + xfertyp |= XFERTYP_RSPTYP_48; + + if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) + xfertyp |= XFERTYP_CMDTYP_ABORT; + + return XFERTYP_CMD(cmd->cmdidx) | xfertyp; +} + +/* + * PIO Read/Write Mode reduce the performace as DMA is not used in this mode. + */ +static void esdhc_pio_read_write(struct fsl_esdhc_priv *priv, + struct mmc_data *data) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + uint blocks; + char *buffer; + uint databuf; + uint size; + uint irqstat; + ulong start; + + if (data->flags & MMC_DATA_READ) { + blocks = data->blocks; + buffer = data->dest; + while (blocks) { + start = get_timer(0); + size = data->blocksize; + irqstat = esdhc_read32(®s->irqstat); + while (!(esdhc_read32(®s->prsstat) & PRSSTAT_BREN)) { + if (get_timer(start) > PIO_TIMEOUT) { + printf("\nData Read Failed in PIO Mode."); + return; + } + } + while (size && (!(irqstat & IRQSTAT_TC))) { + udelay(100); /* Wait before last byte transfer complete */ + irqstat = esdhc_read32(®s->irqstat); + databuf = in_le32(®s->datport); + *((uint *)buffer) = databuf; + buffer += 4; + size -= 4; + } + blocks--; + } + } else { + blocks = data->blocks; + buffer = (char *)data->src; + while (blocks) { + start = get_timer(0); + size = data->blocksize; + irqstat = esdhc_read32(®s->irqstat); + while (!(esdhc_read32(®s->prsstat) & PRSSTAT_BWEN)) { + if (get_timer(start) > PIO_TIMEOUT) { + printf("\nData Write Failed in PIO Mode."); + return; + } + } + while (size && (!(irqstat & IRQSTAT_TC))) { + udelay(100); /* Wait before last byte transfer complete */ + databuf = *((uint *)buffer); + buffer += 4; + size -= 4; + irqstat = esdhc_read32(®s->irqstat); + out_le32(®s->datport, databuf); + } + blocks--; + } + } +} + +static void esdhc_setup_watermark_level(struct fsl_esdhc_priv *priv, + struct mmc_data *data) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + uint wml_value = data->blocksize / 4; + + if (data->flags & MMC_DATA_READ) { + if (wml_value > WML_RD_WML_MAX) + wml_value = WML_RD_WML_MAX_VAL; + + esdhc_clrsetbits32(®s->wml, WML_RD_WML_MASK, wml_value); + } else { + if (wml_value > WML_WR_WML_MAX) + wml_value = WML_WR_WML_MAX_VAL; + + esdhc_clrsetbits32(®s->wml, WML_WR_WML_MASK, + wml_value << 16); + } +} + +static void esdhc_setup_dma(struct fsl_esdhc_priv *priv, struct mmc_data *data) +{ + uint trans_bytes = data->blocksize * data->blocks; + struct fsl_esdhc *regs = priv->esdhc_regs; + phys_addr_t adma_addr; + void *buf; + + if (data->flags & MMC_DATA_WRITE) + buf = (void *)data->src; + else + buf = data->dest; + + priv->dma_addr = dma_map_single(buf, trans_bytes, + mmc_get_dma_dir(data)); + + if (IS_ENABLED(CONFIG_FSL_ESDHC_SUPPORT_ADMA2) && + priv->adma_desc_table) { + debug("Using ADMA2\n"); + /* prefer ADMA2 if it is available */ + sdhci_prepare_adma_table(priv->adma_desc_table, data, + priv->dma_addr); + + adma_addr = virt_to_phys(priv->adma_desc_table); + esdhc_write32(®s->adsaddrl, lower_32_bits(adma_addr)); + if (IS_ENABLED(CONFIG_DMA_ADDR_T_64BIT)) + esdhc_write32(®s->adsaddrh, upper_32_bits(adma_addr)); + esdhc_clrsetbits32(®s->proctl, PROCTL_DMAS_MASK, + PROCTL_DMAS_ADMA2); + } else { + debug("Using SDMA\n"); + if (upper_32_bits(priv->dma_addr)) + printf("Cannot use 64 bit addresses with SDMA\n"); + esdhc_write32(®s->dsaddr, lower_32_bits(priv->dma_addr)); + esdhc_clrsetbits32(®s->proctl, PROCTL_DMAS_MASK, + PROCTL_DMAS_SDMA); + } + + esdhc_write32(®s->blkattr, data->blocks << 16 | data->blocksize); +} + +static int esdhc_setup_data(struct fsl_esdhc_priv *priv, struct mmc *mmc, + struct mmc_data *data) +{ + int timeout; + bool is_write = data->flags & MMC_DATA_WRITE; + struct fsl_esdhc *regs = priv->esdhc_regs; + + if (is_write && !(esdhc_read32(®s->prsstat) & PRSSTAT_WPSPL)) { + printf("Can not write to locked SD card.\n"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_SYS_FSL_ESDHC_USE_PIO)) + esdhc_setup_watermark_level(priv, data); + else + esdhc_setup_dma(priv, data); + + /* Calculate the timeout period for data transactions */ + /* + * 1)Timeout period = (2^(timeout+13)) SD Clock cycles + * 2)Timeout period should be minimum 0.250sec as per SD Card spec + * So, Number of SD Clock cycles for 0.25sec should be minimum + * (SD Clock/sec * 0.25 sec) SD Clock cycles + * = (mmc->clock * 1/4) SD Clock cycles + * As 1) >= 2) + * => (2^(timeout+13)) >= mmc->clock * 1/4 + * Taking log2 both the sides + * => timeout + 13 >= log2(mmc->clock/4) + * Rounding up to next power of 2 + * => timeout + 13 = log2(mmc->clock/4) + 1 + * => timeout + 13 = fls(mmc->clock/4) + * + * However, the MMC spec "It is strongly recommended for hosts to + * implement more than 500ms timeout value even if the card + * indicates the 250ms maximum busy length." Even the previous + * value of 300ms is known to be insufficient for some cards. + * So, we use + * => timeout + 13 = fls(mmc->clock/2) + */ + timeout = fls(mmc->clock/2); + timeout -= 13; + + if (timeout > 14) + timeout = 14; + + if (timeout < 0) + timeout = 0; + + if (IS_ENABLED(CONFIG_SYS_FSL_ERRATUM_ESDHC_A001) && + (timeout == 4 || timeout == 8 || timeout == 12)) + timeout++; + + if (IS_ENABLED(ESDHCI_QUIRK_BROKEN_TIMEOUT_VALUE)) + timeout = 0xE; + + esdhc_clrsetbits32(®s->sysctl, SYSCTL_TIMEOUT_MASK, timeout << 16); + + return 0; +} + +/* + * Sends a command out on the bus. Takes the mmc pointer, + * a command pointer, and an optional data pointer. + */ +static int esdhc_send_cmd_common(struct fsl_esdhc_priv *priv, struct mmc *mmc, + struct mmc_cmd *cmd, struct mmc_data *data) +{ + int err = 0; + uint xfertyp; + uint irqstat; + u32 flags = IRQSTAT_CC | IRQSTAT_CTOE; + struct fsl_esdhc *regs = priv->esdhc_regs; + unsigned long start; + + if (IS_ENABLED(CONFIG_SYS_FSL_ERRATUM_ESDHC111) && + cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) + return 0; + + esdhc_write32(®s->irqstat, -1); + + sync(); + + /* Wait for the bus to be idle */ + while ((esdhc_read32(®s->prsstat) & PRSSTAT_CICHB) || + (esdhc_read32(®s->prsstat) & PRSSTAT_CIDHB)) + ; + + while (esdhc_read32(®s->prsstat) & PRSSTAT_DLA) + ; + + /* Wait at least 8 SD clock cycles before the next command */ + /* + * Note: This is way more than 8 cycles, but 1ms seems to + * resolve timing issues with some cards + */ + udelay(1000); + + /* Set up for a data transfer if we have one */ + if (data) { + err = esdhc_setup_data(priv, mmc, data); + if(err) + return err; + } + + /* Figure out the transfer arguments */ + xfertyp = esdhc_xfertyp(cmd, data); + + /* Mask all irqs */ + esdhc_write32(®s->irqsigen, 0); + + /* Send the command */ + esdhc_write32(®s->cmdarg, cmd->cmdarg); + esdhc_write32(®s->xfertyp, xfertyp); + + if (cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK || + cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200) + flags = IRQSTAT_BRR; + + /* Wait for the command to complete */ + start = get_timer(0); + while (!(esdhc_read32(®s->irqstat) & flags)) { + if (get_timer(start) > 1000) { + err = -ETIMEDOUT; + goto out; + } + } + + irqstat = esdhc_read32(®s->irqstat); + + if (irqstat & CMD_ERR) { + err = -ECOMM; + goto out; + } + + if (irqstat & IRQSTAT_CTOE) { + err = -ETIMEDOUT; + goto out; + } + + /* Workaround for ESDHC errata ENGcm03648 */ + if (!data && (cmd->resp_type & MMC_RSP_BUSY)) { + int timeout = 6000; + + /* Poll on DATA0 line for cmd with busy signal for 600 ms */ + while (timeout > 0 && !(esdhc_read32(®s->prsstat) & + PRSSTAT_DAT0)) { + udelay(100); + timeout--; + } + + if (timeout <= 0) { + printf("Timeout waiting for DAT0 to go high!\n"); + err = -ETIMEDOUT; + goto out; + } + } + + /* Copy the response to the response buffer */ + if (cmd->resp_type & MMC_RSP_136) { + u32 cmdrsp3, cmdrsp2, cmdrsp1, cmdrsp0; + + cmdrsp3 = esdhc_read32(®s->cmdrsp3); + cmdrsp2 = esdhc_read32(®s->cmdrsp2); + cmdrsp1 = esdhc_read32(®s->cmdrsp1); + cmdrsp0 = esdhc_read32(®s->cmdrsp0); + cmd->response[0] = (cmdrsp3 << 8) | (cmdrsp2 >> 24); + cmd->response[1] = (cmdrsp2 << 8) | (cmdrsp1 >> 24); + cmd->response[2] = (cmdrsp1 << 8) | (cmdrsp0 >> 24); + cmd->response[3] = (cmdrsp0 << 8); + } else + cmd->response[0] = esdhc_read32(®s->cmdrsp0); + + /* Wait until all of the blocks are transferred */ + if (data) { + if (IS_ENABLED(CONFIG_SYS_FSL_ESDHC_USE_PIO)) { + esdhc_pio_read_write(priv, data); + } else { + flags = DATA_COMPLETE; + if (cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK || + cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200) + flags = IRQSTAT_BRR; + + do { + irqstat = esdhc_read32(®s->irqstat); + + if (irqstat & IRQSTAT_DTOE) { + err = -ETIMEDOUT; + goto out; + } + + if (irqstat & DATA_ERR) { + err = -ECOMM; + goto out; + } + } while ((irqstat & flags) != flags); + + /* + * Need invalidate the dcache here again to avoid any + * cache-fill during the DMA operations such as the + * speculative pre-fetching etc. + */ + dma_unmap_single(priv->dma_addr, + data->blocks * data->blocksize, + mmc_get_dma_dir(data)); + } + } + +out: + /* Reset CMD and DATA portions on error */ + if (err) { + esdhc_write32(®s->sysctl, esdhc_read32(®s->sysctl) | + SYSCTL_RSTC); + while (esdhc_read32(®s->sysctl) & SYSCTL_RSTC) + ; + + if (data) { + esdhc_write32(®s->sysctl, + esdhc_read32(®s->sysctl) | + SYSCTL_RSTD); + while ((esdhc_read32(®s->sysctl) & SYSCTL_RSTD)) + ; + } + } + + esdhc_write32(®s->irqstat, -1); + + return err; +} + +static void set_sysctl(struct fsl_esdhc_priv *priv, struct mmc *mmc, uint clock) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + int div = 1; + int pre_div = 2; + unsigned int sdhc_clk = priv->sdhc_clk; + u32 time_out; + u32 value; + uint clk; + + if (clock < mmc->cfg->f_min) + clock = mmc->cfg->f_min; + + while (sdhc_clk / (16 * pre_div) > clock && pre_div < 256) + pre_div *= 2; + + while (sdhc_clk / (div * pre_div) > clock && div < 16) + div++; + + if (IS_ENABLED(CONFIG_SYS_FSL_ERRATUM_A011334) && + clock == 200000000 && mmc->selected_mode == MMC_HS_400) { + u32 div_ratio = pre_div * div; + + if (div_ratio <= 4) { + pre_div = 4; + div = 1; + } else if (div_ratio <= 8) { + pre_div = 4; + div = 2; + } else if (div_ratio <= 12) { + pre_div = 4; + div = 3; + } else { + printf("unsupported clock division.\n"); + } + } + + mmc->clock = sdhc_clk / pre_div / div; + priv->clock = mmc->clock; + + pre_div >>= 1; + div -= 1; + + clk = (pre_div << 8) | (div << 4); + + esdhc_clrbits32(®s->sysctl, SYSCTL_CKEN); + + esdhc_clrsetbits32(®s->sysctl, SYSCTL_CLOCK_MASK, clk); + + time_out = 20; + value = PRSSTAT_SDSTB; + while (!(esdhc_read32(®s->prsstat) & value)) { + if (time_out == 0) { + printf("fsl_esdhc: Internal clock never stabilised.\n"); + break; + } + time_out--; + mdelay(1); + } + + esdhc_setbits32(®s->sysctl, SYSCTL_PEREN | SYSCTL_CKEN); +} + +static void esdhc_clock_control(struct fsl_esdhc_priv *priv, bool enable) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + u32 value; + u32 time_out; + + value = esdhc_read32(®s->sysctl); + + if (enable) + value |= SYSCTL_CKEN; + else + value &= ~SYSCTL_CKEN; + + esdhc_write32(®s->sysctl, value); + + time_out = 20; + value = PRSSTAT_SDSTB; + while (!(esdhc_read32(®s->prsstat) & value)) { + if (time_out == 0) { + printf("fsl_esdhc: Internal clock never stabilised.\n"); + break; + } + time_out--; + mdelay(1); + } +} + +static void esdhc_flush_async_fifo(struct fsl_esdhc_priv *priv) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + u32 time_out; + + esdhc_setbits32(®s->esdhcctl, ESDHCCTL_FAF); + + time_out = 20; + while (esdhc_read32(®s->esdhcctl) & ESDHCCTL_FAF) { + if (time_out == 0) { + printf("fsl_esdhc: Flush asynchronous FIFO timeout.\n"); + break; + } + time_out--; + mdelay(1); + } +} + +static void esdhc_tuning_block_enable(struct fsl_esdhc_priv *priv, + bool en) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + + esdhc_clock_control(priv, false); + esdhc_flush_async_fifo(priv); + if (en) + esdhc_setbits32(®s->tbctl, TBCTL_TB_EN); + else + esdhc_clrbits32(®s->tbctl, TBCTL_TB_EN); + esdhc_clock_control(priv, true); +} + +static void esdhc_exit_hs400(struct fsl_esdhc_priv *priv) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + + esdhc_clrbits32(®s->sdtimingctl, FLW_CTL_BG); + esdhc_clrbits32(®s->sdclkctl, CMD_CLK_CTL); + + esdhc_clock_control(priv, false); + esdhc_clrbits32(®s->tbctl, HS400_MODE); + esdhc_clock_control(priv, true); + + esdhc_clrbits32(®s->dllcfg0, DLL_FREQ_SEL | DLL_ENABLE); + esdhc_clrbits32(®s->tbctl, HS400_WNDW_ADJUST); + + esdhc_tuning_block_enable(priv, false); +} + +static int esdhc_set_timing(struct fsl_esdhc_priv *priv, enum bus_mode mode) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + ulong start; + u32 val; + + /* Exit HS400 mode before setting any other mode */ + if (esdhc_read32(®s->tbctl) & HS400_MODE && + mode != MMC_HS_400) + esdhc_exit_hs400(priv); + + esdhc_clock_control(priv, false); + + if (mode == MMC_HS_200) + esdhc_clrsetbits32(®s->autoc12err, UHSM_MASK, + UHSM_SDR104_HS200); + if (mode == MMC_HS_400) { + esdhc_setbits32(®s->tbctl, HS400_MODE); + esdhc_setbits32(®s->sdclkctl, CMD_CLK_CTL); + esdhc_clock_control(priv, true); + + if (priv->clock == 200000000) + esdhc_setbits32(®s->dllcfg0, DLL_FREQ_SEL); + + esdhc_setbits32(®s->dllcfg0, DLL_ENABLE); + + esdhc_setbits32(®s->dllcfg0, DLL_RESET); + udelay(1); + esdhc_clrbits32(®s->dllcfg0, DLL_RESET); + + start = get_timer(0); + val = DLL_STS_SLV_LOCK; + while (!(esdhc_read32(®s->dllstat0) & val)) { + if (get_timer(start) > 1000) { + printf("fsl_esdhc: delay chain lock timeout\n"); + return -ETIMEDOUT; + } + } + + esdhc_setbits32(®s->tbctl, HS400_WNDW_ADJUST); + + esdhc_clock_control(priv, false); + esdhc_flush_async_fifo(priv); + } + esdhc_clock_control(priv, true); + return 0; +} + +static int esdhc_set_ios_common(struct fsl_esdhc_priv *priv, struct mmc *mmc) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + int ret; + + if (priv->is_sdhc_per_clk) { + /* Select to use peripheral clock */ + esdhc_clock_control(priv, false); + esdhc_setbits32(®s->esdhcctl, ESDHCCTL_PCS); + esdhc_clock_control(priv, true); + } + + if (mmc->selected_mode == MMC_HS_400) + esdhc_tuning_block_enable(priv, true); + + /* Set the clock speed */ + if (priv->clock != mmc->clock) + set_sysctl(priv, mmc, mmc->clock); + + /* Set timing */ + ret = esdhc_set_timing(priv, mmc->selected_mode); + if (ret) + return ret; + + /* Set the bus width */ + esdhc_clrbits32(®s->proctl, PROCTL_DTW_4 | PROCTL_DTW_8); + + if (mmc->bus_width == 4) + esdhc_setbits32(®s->proctl, PROCTL_DTW_4); + else if (mmc->bus_width == 8) + esdhc_setbits32(®s->proctl, PROCTL_DTW_8); + + return 0; +} + +static void esdhc_enable_cache_snooping(struct fsl_esdhc *regs) +{ +#ifdef CONFIG_ARCH_MPC830X + immap_t *immr = (immap_t *)CONFIG_SYS_IMMR; + sysconf83xx_t *sysconf = &immr->sysconf; + + setbits_be32(&sysconf->sdhccr, 0x02000000); +#else + esdhc_write32(®s->esdhcctl, 0x00000040); +#endif +} + +static int esdhc_init_common(struct fsl_esdhc_priv *priv, struct mmc *mmc) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + ulong start; + + /* Reset the entire host controller */ + esdhc_setbits32(®s->sysctl, SYSCTL_RSTA); + + /* Wait until the controller is available */ + start = get_timer(0); + while ((esdhc_read32(®s->sysctl) & SYSCTL_RSTA)) { + if (get_timer(start) > 1000) + return -ETIMEDOUT; + } + + /* Clean TBCTL[TB_EN] which is not able to be reset by reset all */ + esdhc_clrbits32(®s->tbctl, TBCTL_TB_EN); + + esdhc_enable_cache_snooping(regs); + + esdhc_setbits32(®s->sysctl, SYSCTL_HCKEN | SYSCTL_IPGEN); + + /* Set the initial clock speed */ + set_sysctl(priv, mmc, 400000); + + /* Disable the BRR and BWR bits in IRQSTAT */ + esdhc_clrbits32(®s->irqstaten, IRQSTATEN_BRR | IRQSTATEN_BWR); + + /* Put the PROCTL reg back to the default */ + esdhc_write32(®s->proctl, PROCTL_INIT); + + /* Set timout to the maximum value */ + esdhc_clrsetbits32(®s->sysctl, SYSCTL_TIMEOUT_MASK, 14 << 16); + + if (IS_ENABLED(CONFIG_SYS_FSL_ESDHC_UNRELIABLE_PULSE_DETECTION_WORKAROUND)) + esdhc_clrbits32(®s->dllcfg1, DLL_PD_PULSE_STRETCH_SEL); + + return 0; +} + +static int esdhc_getcd_common(struct fsl_esdhc_priv *priv) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + +#ifdef CONFIG_ESDHC_DETECT_QUIRK + if (CONFIG_ESDHC_DETECT_QUIRK) + return 1; +#endif + if (esdhc_read32(®s->prsstat) & PRSSTAT_CINS) + return 1; + + return 0; +} + +static void fsl_esdhc_get_cfg_common(struct fsl_esdhc_priv *priv, + struct mmc_config *cfg) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + u32 caps; + + caps = esdhc_read32(®s->hostcapblt); + + /* + * For eSDHC, power supply is through peripheral circuit. Some eSDHC + * versions have value 0 of the bit but that does not reflect the + * truth. 3.3V is common for SD/MMC, and is supported for all boards + * with eSDHC in current u-boot. So, make 3.3V is supported in + * default in code. CONFIG_FSL_ESDHC_VS33_NOT_SUPPORT can be enabled + * if future board does not support 3.3V. + */ + caps |= HOSTCAPBLT_VS33; + if (IS_ENABLED(CONFIG_FSL_ESDHC_VS33_NOT_SUPPORT)) + caps &= ~HOSTCAPBLT_VS33; + + if (IS_ENABLED(CONFIG_SYS_FSL_ERRATUM_ESDHC135)) + caps &= ~(HOSTCAPBLT_SRS | HOSTCAPBLT_VS18 | HOSTCAPBLT_VS30); + if (caps & HOSTCAPBLT_VS18) + cfg->voltages |= MMC_VDD_165_195; + if (caps & HOSTCAPBLT_VS30) + cfg->voltages |= MMC_VDD_29_30 | MMC_VDD_30_31; + if (caps & HOSTCAPBLT_VS33) + cfg->voltages |= MMC_VDD_32_33 | MMC_VDD_33_34; + + cfg->name = "FSL_SDHC"; + + if (caps & HOSTCAPBLT_HSS) + cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + + cfg->f_min = 400000; + cfg->f_max = min(priv->sdhc_clk, (u32)200000000); + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; +} + +#ifdef CONFIG_OF_LIBFDT +__weak int esdhc_status_fixup(void *blob, const char *compat) +{ + if (IS_ENABLED(CONFIG_FSL_ESDHC_PIN_MUX) && !hwconfig("esdhc")) { + do_fixup_by_compat(blob, compat, "status", "disabled", + sizeof("disabled"), 1); + return 1; + } + + return 0; +} + + +#if CONFIG_IS_ENABLED(DM_MMC) +static int fsl_esdhc_get_cd(struct udevice *dev); +static void esdhc_disable_for_no_card(void *blob) +{ + struct udevice *dev; + + for (uclass_first_device(UCLASS_MMC, &dev); + dev; + uclass_next_device(&dev)) { + char esdhc_path[50]; + + if (fsl_esdhc_get_cd(dev)) + continue; + + snprintf(esdhc_path, sizeof(esdhc_path), "/soc/esdhc@%lx", + (unsigned long)dev_read_addr(dev)); + do_fixup_by_path(blob, esdhc_path, "status", "disabled", + sizeof("disabled"), 1); + } +} +#else +static void esdhc_disable_for_no_card(void *blob) +{ +} +#endif + +void fdt_fixup_esdhc(void *blob, struct bd_info *bd) +{ + const char *compat = "fsl,esdhc"; + + if (esdhc_status_fixup(blob, compat)) + return; + + if (IS_ENABLED(CONFIG_FSL_ESDHC_33V_IO_RELIABILITY_WORKAROUND)) + esdhc_disable_for_no_card(blob); + + do_fixup_by_compat_u32(blob, compat, "clock-frequency", + gd->arch.sdhc_clk, 1); +} +#endif + +#if !CONFIG_IS_ENABLED(DM_MMC) +static int esdhc_getcd(struct mmc *mmc) +{ + struct fsl_esdhc_priv *priv = mmc->priv; + + return esdhc_getcd_common(priv); +} + +static int esdhc_init(struct mmc *mmc) +{ + struct fsl_esdhc_priv *priv = mmc->priv; + + return esdhc_init_common(priv, mmc); +} + +static int esdhc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct fsl_esdhc_priv *priv = mmc->priv; + + return esdhc_send_cmd_common(priv, mmc, cmd, data); +} + +static int esdhc_set_ios(struct mmc *mmc) +{ + struct fsl_esdhc_priv *priv = mmc->priv; + + return esdhc_set_ios_common(priv, mmc); +} + +static const struct mmc_ops esdhc_ops = { + .getcd = esdhc_getcd, + .init = esdhc_init, + .send_cmd = esdhc_send_cmd, + .set_ios = esdhc_set_ios, +}; + +int fsl_esdhc_initialize(struct bd_info *bis, struct fsl_esdhc_cfg *cfg) +{ + struct fsl_esdhc_plat *plat; + struct fsl_esdhc_priv *priv; + struct mmc_config *mmc_cfg; + struct mmc *mmc; + + if (!cfg) + return -EINVAL; + + priv = calloc(sizeof(struct fsl_esdhc_priv), 1); + if (!priv) + return -ENOMEM; + plat = calloc(sizeof(struct fsl_esdhc_plat), 1); + if (!plat) { + free(priv); + return -ENOMEM; + } + + priv->esdhc_regs = (struct fsl_esdhc *)(unsigned long)(cfg->esdhc_base); + priv->sdhc_clk = cfg->sdhc_clk; + if (gd->arch.sdhc_per_clk) + priv->is_sdhc_per_clk = true; + + mmc_cfg = &plat->cfg; + + if (cfg->max_bus_width == 8) { + mmc_cfg->host_caps |= MMC_MODE_1BIT | MMC_MODE_4BIT | + MMC_MODE_8BIT; + } else if (cfg->max_bus_width == 4) { + mmc_cfg->host_caps |= MMC_MODE_1BIT | MMC_MODE_4BIT; + } else if (cfg->max_bus_width == 1) { + mmc_cfg->host_caps |= MMC_MODE_1BIT; + } else { + mmc_cfg->host_caps |= MMC_MODE_1BIT | MMC_MODE_4BIT | + MMC_MODE_8BIT; + printf("No max bus width provided. Assume 8-bit supported.\n"); + } + + if (IS_ENABLED(CONFIG_ESDHC_DETECT_8_BIT_QUIRK)) + mmc_cfg->host_caps &= ~MMC_MODE_8BIT; + + mmc_cfg->ops = &esdhc_ops; + + fsl_esdhc_get_cfg_common(priv, mmc_cfg); + + mmc = mmc_create(mmc_cfg, priv); + if (!mmc) + return -EIO; + + priv->mmc = mmc; + return 0; +} + +int fsl_esdhc_mmc_init(struct bd_info *bis) +{ + struct fsl_esdhc_cfg *cfg; + + cfg = calloc(sizeof(struct fsl_esdhc_cfg), 1); + cfg->esdhc_base = CONFIG_SYS_FSL_ESDHC_ADDR; + /* Prefer peripheral clock which provides higher frequency. */ + if (gd->arch.sdhc_per_clk) + cfg->sdhc_clk = gd->arch.sdhc_per_clk; + else + cfg->sdhc_clk = gd->arch.sdhc_clk; + return fsl_esdhc_initialize(bis, cfg); +} +#else /* DM_MMC */ +static int fsl_esdhc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + u32 caps, hostver; + fdt_addr_t addr; + struct mmc *mmc; + int ret; + + addr = dev_read_addr(dev); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; +#ifdef CONFIG_PPC + priv->esdhc_regs = (struct fsl_esdhc *)lower_32_bits(addr); +#else + priv->esdhc_regs = (struct fsl_esdhc *)addr; +#endif + priv->dev = dev; + + if (IS_ENABLED(CONFIG_FSL_ESDHC_SUPPORT_ADMA2)) { + /* + * Only newer eSDHC controllers can do ADMA2 if the ADMA flag + * is set in the host capabilities register. + */ + caps = esdhc_read32(&priv->esdhc_regs->hostcapblt); + hostver = esdhc_read32(&priv->esdhc_regs->hostver); + if (caps & HOSTCAPBLT_DMAS && + HOSTVER_VENDOR(hostver) > VENDOR_V_22) { + priv->adma_desc_table = sdhci_adma_init(); + if (!priv->adma_desc_table) + debug("Could not allocate ADMA tables, falling back to SDMA\n"); + } + } + + if (gd->arch.sdhc_per_clk) { + priv->sdhc_clk = gd->arch.sdhc_per_clk; + priv->is_sdhc_per_clk = true; + } else { + priv->sdhc_clk = gd->arch.sdhc_clk; + } + + if (priv->sdhc_clk <= 0) { + dev_err(dev, "Unable to get clk for %s\n", dev->name); + return -EINVAL; + } + + fsl_esdhc_get_cfg_common(priv, &plat->cfg); + + mmc_of_parse(dev, &plat->cfg); + + mmc = &plat->mmc; + mmc->cfg = &plat->cfg; + mmc->dev = dev; + + upriv->mmc = mmc; + + ret = esdhc_init_common(priv, mmc); + if (ret) + return ret; + + if (IS_ENABLED(CONFIG_FSL_ESDHC_33V_IO_RELIABILITY_WORKAROUND) && + !fsl_esdhc_get_cd(dev)) + esdhc_setbits32(&priv->esdhc_regs->proctl, PROCTL_VOLT_SEL); + + return 0; +} + +static int fsl_esdhc_get_cd(struct udevice *dev) +{ + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + + if (plat->cfg.host_caps & MMC_CAP_NONREMOVABLE) + return 1; + + return esdhc_getcd_common(priv); +} + +static int fsl_esdhc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + + return esdhc_send_cmd_common(priv, &plat->mmc, cmd, data); +} + +static int fsl_esdhc_set_ios(struct udevice *dev) +{ + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + + return esdhc_set_ios_common(priv, &plat->mmc); +} + +static int fsl_esdhc_reinit(struct udevice *dev) +{ + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + + return esdhc_init_common(priv, &plat->mmc); +} + +#ifdef MMC_SUPPORTS_TUNING +static int fsl_esdhc_execute_tuning(struct udevice *dev, uint32_t opcode) +{ + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + struct fsl_esdhc *regs = priv->esdhc_regs; + struct mmc *mmc = &plat->mmc; + u32 val, irqstaten; + int i; + + if (IS_ENABLED(CONFIG_SYS_FSL_ERRATUM_A011334) && + plat->mmc.hs400_tuning) + set_sysctl(priv, mmc, mmc->clock); + + esdhc_tuning_block_enable(priv, true); + esdhc_setbits32(®s->autoc12err, EXECUTE_TUNING); + + irqstaten = esdhc_read32(®s->irqstaten); + esdhc_write32(®s->irqstaten, IRQSTATEN_BRR); + + for (i = 0; i < MAX_TUNING_LOOP; i++) { + mmc_send_tuning(mmc, opcode, NULL); + mdelay(1); + + val = esdhc_read32(®s->autoc12err); + if (!(val & EXECUTE_TUNING)) { + if (val & SMPCLKSEL) + break; + } + } + + esdhc_write32(®s->irqstaten, irqstaten); + + if (i != MAX_TUNING_LOOP) { + if (plat->mmc.hs400_tuning) + esdhc_setbits32(®s->sdtimingctl, FLW_CTL_BG); + return 0; + } + + printf("fsl_esdhc: tuning failed!\n"); + esdhc_clrbits32(®s->autoc12err, SMPCLKSEL); + esdhc_clrbits32(®s->autoc12err, EXECUTE_TUNING); + esdhc_tuning_block_enable(priv, false); + return -ETIMEDOUT; +} +#endif + +int fsl_esdhc_hs400_prepare_ddr(struct udevice *dev) +{ + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + + esdhc_tuning_block_enable(priv, false); + return 0; +} + +static const struct dm_mmc_ops fsl_esdhc_ops = { + .get_cd = fsl_esdhc_get_cd, + .send_cmd = fsl_esdhc_send_cmd, + .set_ios = fsl_esdhc_set_ios, +#ifdef MMC_SUPPORTS_TUNING + .execute_tuning = fsl_esdhc_execute_tuning, +#endif + .reinit = fsl_esdhc_reinit, + .hs400_prepare_ddr = fsl_esdhc_hs400_prepare_ddr, +}; + +static const struct udevice_id fsl_esdhc_ids[] = { + { .compatible = "fsl,esdhc", }, + { /* sentinel */ } +}; + +static int fsl_esdhc_bind(struct udevice *dev) +{ + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +U_BOOT_DRIVER(fsl_esdhc) = { + .name = "fsl-esdhc-mmc", + .id = UCLASS_MMC, + .of_match = fsl_esdhc_ids, + .ops = &fsl_esdhc_ops, + .bind = fsl_esdhc_bind, + .probe = fsl_esdhc_probe, + .plat_auto = sizeof(struct fsl_esdhc_plat), + .priv_auto = sizeof(struct fsl_esdhc_priv), +}; +#endif diff --git a/roms/u-boot/drivers/mmc/fsl_esdhc_imx.c b/roms/u-boot/drivers/mmc/fsl_esdhc_imx.c new file mode 100644 index 000000000..465d935da --- /dev/null +++ b/roms/u-boot/drivers/mmc/fsl_esdhc_imx.c @@ -0,0 +1,1753 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2007, 2010-2011 Freescale Semiconductor, Inc + * Copyright 2019, 2021 NXP + * Andy Fleming + * Yangbo Lu <yangbo.lu@nxp.com> + * + * Based vaguely on the pxa mmc code: + * (C) Copyright 2003 + * Kyle Harris, Nexus Technologies, Inc. kharris@nexus-tech.net + */ + +#include <config.h> +#include <common.h> +#include <command.h> +#include <clk.h> +#include <cpu_func.h> +#include <errno.h> +#include <hwconfig.h> +#include <log.h> +#include <mmc.h> +#include <part.h> +#include <asm/cache.h> +#include <asm/global_data.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <power/regulator.h> +#include <malloc.h> +#include <fsl_esdhc_imx.h> +#include <fdt_support.h> +#include <asm/io.h> +#include <dm.h> +#include <asm-generic/gpio.h> +#include <dm/pinctrl.h> +#include <dt-structs.h> +#include <mapmem.h> +#include <dm/ofnode.h> +#include <linux/iopoll.h> + +#if !CONFIG_IS_ENABLED(BLK) +#include "mmc_private.h" +#endif + +#ifndef ESDHCI_QUIRK_BROKEN_TIMEOUT_VALUE +#ifdef CONFIG_FSL_USDHC +#define ESDHCI_QUIRK_BROKEN_TIMEOUT_VALUE 1 +#endif +#endif + +DECLARE_GLOBAL_DATA_PTR; + +#define SDHCI_IRQ_EN_BITS (IRQSTATEN_CC | IRQSTATEN_TC | \ + IRQSTATEN_CINT | \ + IRQSTATEN_CTOE | IRQSTATEN_CCE | IRQSTATEN_CEBE | \ + IRQSTATEN_CIE | IRQSTATEN_DTOE | IRQSTATEN_DCE | \ + IRQSTATEN_DEBE | IRQSTATEN_BRR | IRQSTATEN_BWR | \ + IRQSTATEN_DINT) +#define MAX_TUNING_LOOP 40 +#define ESDHC_DRIVER_STAGE_VALUE 0xffffffff + +struct fsl_esdhc { + uint dsaddr; /* SDMA system address register */ + uint blkattr; /* Block attributes register */ + uint cmdarg; /* Command argument register */ + uint xfertyp; /* Transfer type register */ + uint cmdrsp0; /* Command response 0 register */ + uint cmdrsp1; /* Command response 1 register */ + uint cmdrsp2; /* Command response 2 register */ + uint cmdrsp3; /* Command response 3 register */ + uint datport; /* Buffer data port register */ + uint prsstat; /* Present state register */ + uint proctl; /* Protocol control register */ + uint sysctl; /* System Control Register */ + uint irqstat; /* Interrupt status register */ + uint irqstaten; /* Interrupt status enable register */ + uint irqsigen; /* Interrupt signal enable register */ + uint autoc12err; /* Auto CMD error status register */ + uint hostcapblt; /* Host controller capabilities register */ + uint wml; /* Watermark level register */ + uint mixctrl; /* For USDHC */ + char reserved1[4]; /* reserved */ + uint fevt; /* Force event register */ + uint admaes; /* ADMA error status register */ + uint adsaddr; /* ADMA system address register */ + char reserved2[4]; + uint dllctrl; + uint dllstat; + uint clktunectrlstatus; + char reserved3[4]; + uint strobe_dllctrl; + uint strobe_dllstat; + char reserved4[72]; + uint vendorspec; + uint mmcboot; + uint vendorspec2; + uint tuning_ctrl; /* on i.MX6/7/8/RT */ + char reserved5[44]; + uint hostver; /* Host controller version register */ + char reserved6[4]; /* reserved */ + uint dmaerraddr; /* DMA error address register */ + char reserved7[4]; /* reserved */ + uint dmaerrattr; /* DMA error attribute register */ + char reserved8[4]; /* reserved */ + uint hostcapblt2; /* Host controller capabilities register 2 */ + char reserved9[8]; /* reserved */ + uint tcr; /* Tuning control register */ + char reserved10[28]; /* reserved */ + uint sddirctl; /* SD direction control register */ + char reserved11[712];/* reserved */ + uint scr; /* eSDHC control register */ +}; + +struct fsl_esdhc_plat { +#if CONFIG_IS_ENABLED(OF_PLATDATA) + /* Put this first since driver model will copy the data here */ + struct dtd_fsl_esdhc dtplat; +#endif + + struct mmc_config cfg; + struct mmc mmc; +}; + +struct esdhc_soc_data { + u32 flags; +}; + +/** + * struct fsl_esdhc_priv + * + * @esdhc_regs: registers of the sdhc controller + * @sdhc_clk: Current clk of the sdhc controller + * @bus_width: bus width, 1bit, 4bit or 8bit + * @cfg: mmc config + * @mmc: mmc + * Following is used when Driver Model is enabled for MMC + * @dev: pointer for the device + * @non_removable: 0: removable; 1: non-removable + * @broken_cd: 0: use GPIO for card detect; 1: Do not use GPIO for card detect + * @wp_enable: 1: enable checking wp; 0: no check + * @vs18_enable: 1: use 1.8V voltage; 0: use 3.3V + * @flags: ESDHC_FLAG_xx in include/fsl_esdhc_imx.h + * @caps: controller capabilities + * @tuning_step: tuning step setting in tuning_ctrl register + * @start_tuning_tap: the start point for tuning in tuning_ctrl register + * @strobe_dll_delay_target: settings in strobe_dllctrl + * @signal_voltage: indicating the current voltage + * @signal_voltage_switch_extra_delay_ms: extra delay for IO voltage switch + * @cd_gpio: gpio for card detection + * @wp_gpio: gpio for write protection + */ +struct fsl_esdhc_priv { + struct fsl_esdhc *esdhc_regs; + unsigned int sdhc_clk; + struct clk per_clk; + unsigned int clock; + unsigned int mode; + unsigned int bus_width; +#if !CONFIG_IS_ENABLED(BLK) + struct mmc *mmc; +#endif + struct udevice *dev; + int non_removable; + int broken_cd; + int wp_enable; + int vs18_enable; + u32 flags; + u32 caps; + u32 tuning_step; + u32 tuning_start_tap; + u32 strobe_dll_delay_target; + u32 signal_voltage; + u32 signal_voltage_switch_extra_delay_ms; +#if CONFIG_IS_ENABLED(DM_REGULATOR) + struct udevice *vqmmc_dev; + struct udevice *vmmc_dev; +#endif +#if CONFIG_IS_ENABLED(DM_GPIO) + struct gpio_desc cd_gpio; + struct gpio_desc wp_gpio; +#endif +}; + +/* Return the XFERTYP flags for a given command and data packet */ +static uint esdhc_xfertyp(struct mmc_cmd *cmd, struct mmc_data *data) +{ + uint xfertyp = 0; + + if (data) { + xfertyp |= XFERTYP_DPSEL; +#ifndef CONFIG_SYS_FSL_ESDHC_USE_PIO + xfertyp |= XFERTYP_DMAEN; +#endif + if (data->blocks > 1) { + xfertyp |= XFERTYP_MSBSEL; + xfertyp |= XFERTYP_BCEN; +#ifdef CONFIG_SYS_FSL_ERRATUM_ESDHC111 + xfertyp |= XFERTYP_AC12EN; +#endif + } + + if (data->flags & MMC_DATA_READ) + xfertyp |= XFERTYP_DTDSEL; + } + + if (cmd->resp_type & MMC_RSP_CRC) + xfertyp |= XFERTYP_CCCEN; + if (cmd->resp_type & MMC_RSP_OPCODE) + xfertyp |= XFERTYP_CICEN; + if (cmd->resp_type & MMC_RSP_136) + xfertyp |= XFERTYP_RSPTYP_136; + else if (cmd->resp_type & MMC_RSP_BUSY) + xfertyp |= XFERTYP_RSPTYP_48_BUSY; + else if (cmd->resp_type & MMC_RSP_PRESENT) + xfertyp |= XFERTYP_RSPTYP_48; + + if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) + xfertyp |= XFERTYP_CMDTYP_ABORT; + + return XFERTYP_CMD(cmd->cmdidx) | xfertyp; +} + +#ifdef CONFIG_SYS_FSL_ESDHC_USE_PIO +/* + * PIO Read/Write Mode reduce the performace as DMA is not used in this mode. + */ +static void esdhc_pio_read_write(struct fsl_esdhc_priv *priv, + struct mmc_data *data) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + uint blocks; + char *buffer; + uint databuf; + uint size; + uint irqstat; + ulong start; + + if (data->flags & MMC_DATA_READ) { + blocks = data->blocks; + buffer = data->dest; + while (blocks) { + start = get_timer(0); + size = data->blocksize; + irqstat = esdhc_read32(®s->irqstat); + while (!(esdhc_read32(®s->prsstat) & PRSSTAT_BREN)) { + if (get_timer(start) > PIO_TIMEOUT) { + printf("\nData Read Failed in PIO Mode."); + return; + } + } + while (size && (!(irqstat & IRQSTAT_TC))) { + udelay(100); /* Wait before last byte transfer complete */ + irqstat = esdhc_read32(®s->irqstat); + databuf = in_le32(®s->datport); + *((uint *)buffer) = databuf; + buffer += 4; + size -= 4; + } + blocks--; + } + } else { + blocks = data->blocks; + buffer = (char *)data->src; + while (blocks) { + start = get_timer(0); + size = data->blocksize; + irqstat = esdhc_read32(®s->irqstat); + while (!(esdhc_read32(®s->prsstat) & PRSSTAT_BWEN)) { + if (get_timer(start) > PIO_TIMEOUT) { + printf("\nData Write Failed in PIO Mode."); + return; + } + } + while (size && (!(irqstat & IRQSTAT_TC))) { + udelay(100); /* Wait before last byte transfer complete */ + databuf = *((uint *)buffer); + buffer += 4; + size -= 4; + irqstat = esdhc_read32(®s->irqstat); + out_le32(®s->datport, databuf); + } + blocks--; + } + } +} +#endif + +static int esdhc_setup_data(struct fsl_esdhc_priv *priv, struct mmc *mmc, + struct mmc_data *data) +{ + int timeout; + struct fsl_esdhc *regs = priv->esdhc_regs; +#if defined(CONFIG_S32V234) || defined(CONFIG_IMX8) || defined(CONFIG_IMX8M) + dma_addr_t addr; +#endif + uint wml_value; + + wml_value = data->blocksize/4; + + if (data->flags & MMC_DATA_READ) { + if (wml_value > WML_RD_WML_MAX) + wml_value = WML_RD_WML_MAX_VAL; + + esdhc_clrsetbits32(®s->wml, WML_RD_WML_MASK, wml_value); +#ifndef CONFIG_SYS_FSL_ESDHC_USE_PIO +#if defined(CONFIG_S32V234) || defined(CONFIG_IMX8) || defined(CONFIG_IMX8M) + addr = virt_to_phys((void *)(data->dest)); + if (upper_32_bits(addr)) + printf("Error found for upper 32 bits\n"); + else + esdhc_write32(®s->dsaddr, lower_32_bits(addr)); +#else + esdhc_write32(®s->dsaddr, (u32)data->dest); +#endif +#endif + } else { +#ifndef CONFIG_SYS_FSL_ESDHC_USE_PIO + flush_dcache_range((ulong)data->src, + (ulong)data->src+data->blocks + *data->blocksize); +#endif + if (wml_value > WML_WR_WML_MAX) + wml_value = WML_WR_WML_MAX_VAL; + if (priv->wp_enable) { + if ((esdhc_read32(®s->prsstat) & + PRSSTAT_WPSPL) == 0) { + printf("\nThe SD card is locked. Can not write to a locked card.\n\n"); + return -ETIMEDOUT; + } + } else { +#if CONFIG_IS_ENABLED(DM_GPIO) + if (dm_gpio_is_valid(&priv->wp_gpio) && + dm_gpio_get_value(&priv->wp_gpio)) { + printf("\nThe SD card is locked. Can not write to a locked card.\n\n"); + return -ETIMEDOUT; + } +#endif + } + + esdhc_clrsetbits32(®s->wml, WML_WR_WML_MASK, + wml_value << 16); +#ifndef CONFIG_SYS_FSL_ESDHC_USE_PIO +#if defined(CONFIG_S32V234) || defined(CONFIG_IMX8) || defined(CONFIG_IMX8M) + addr = virt_to_phys((void *)(data->src)); + if (upper_32_bits(addr)) + printf("Error found for upper 32 bits\n"); + else + esdhc_write32(®s->dsaddr, lower_32_bits(addr)); +#else + esdhc_write32(®s->dsaddr, (u32)data->src); +#endif +#endif + } + + esdhc_write32(®s->blkattr, data->blocks << 16 | data->blocksize); + + /* Calculate the timeout period for data transactions */ + /* + * 1)Timeout period = (2^(timeout+13)) SD Clock cycles + * 2)Timeout period should be minimum 0.250sec as per SD Card spec + * So, Number of SD Clock cycles for 0.25sec should be minimum + * (SD Clock/sec * 0.25 sec) SD Clock cycles + * = (mmc->clock * 1/4) SD Clock cycles + * As 1) >= 2) + * => (2^(timeout+13)) >= mmc->clock * 1/4 + * Taking log2 both the sides + * => timeout + 13 >= log2(mmc->clock/4) + * Rounding up to next power of 2 + * => timeout + 13 = log2(mmc->clock/4) + 1 + * => timeout + 13 = fls(mmc->clock/4) + * + * However, the MMC spec "It is strongly recommended for hosts to + * implement more than 500ms timeout value even if the card + * indicates the 250ms maximum busy length." Even the previous + * value of 300ms is known to be insufficient for some cards. + * So, we use + * => timeout + 13 = fls(mmc->clock/2) + */ + timeout = fls(mmc->clock/2); + timeout -= 13; + + if (timeout > 14) + timeout = 14; + + if (timeout < 0) + timeout = 0; + +#ifdef CONFIG_SYS_FSL_ERRATUM_ESDHC_A001 + if ((timeout == 4) || (timeout == 8) || (timeout == 12)) + timeout++; +#endif + +#ifdef ESDHCI_QUIRK_BROKEN_TIMEOUT_VALUE + timeout = 0xE; +#endif + esdhc_clrsetbits32(®s->sysctl, SYSCTL_TIMEOUT_MASK, timeout << 16); + + return 0; +} + +static void check_and_invalidate_dcache_range + (struct mmc_cmd *cmd, + struct mmc_data *data) { + unsigned start = 0; + unsigned end = 0; + unsigned size = roundup(ARCH_DMA_MINALIGN, + data->blocks*data->blocksize); +#if defined(CONFIG_S32V234) || defined(CONFIG_IMX8) || defined(CONFIG_IMX8M) + dma_addr_t addr; + + addr = virt_to_phys((void *)(data->dest)); + if (upper_32_bits(addr)) + printf("Error found for upper 32 bits\n"); + else + start = lower_32_bits(addr); +#else + start = (unsigned)data->dest; +#endif + end = start + size; + invalidate_dcache_range(start, end); +} + +#ifdef CONFIG_MCF5441x +/* + * Swaps 32-bit words to little-endian byte order. + */ +static inline void sd_swap_dma_buff(struct mmc_data *data) +{ + int i, size = data->blocksize >> 2; + u32 *buffer = (u32 *)data->dest; + u32 sw; + + while (data->blocks--) { + for (i = 0; i < size; i++) { + sw = __sw32(*buffer); + *buffer++ = sw; + } + } +} +#endif + +/* + * Sends a command out on the bus. Takes the mmc pointer, + * a command pointer, and an optional data pointer. + */ +static int esdhc_send_cmd_common(struct fsl_esdhc_priv *priv, struct mmc *mmc, + struct mmc_cmd *cmd, struct mmc_data *data) +{ + int err = 0; + uint xfertyp; + uint irqstat; + u32 flags = IRQSTAT_CC | IRQSTAT_CTOE; + struct fsl_esdhc *regs = priv->esdhc_regs; + unsigned long start; + +#ifdef CONFIG_SYS_FSL_ERRATUM_ESDHC111 + if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) + return 0; +#endif + + esdhc_write32(®s->irqstat, -1); + + sync(); + + /* Wait for the bus to be idle */ + while ((esdhc_read32(®s->prsstat) & PRSSTAT_CICHB) || + (esdhc_read32(®s->prsstat) & PRSSTAT_CIDHB)) + ; + + while (esdhc_read32(®s->prsstat) & PRSSTAT_DLA) + ; + + /* Set up for a data transfer if we have one */ + if (data) { + err = esdhc_setup_data(priv, mmc, data); + if(err) + return err; + + if (data->flags & MMC_DATA_READ) + check_and_invalidate_dcache_range(cmd, data); + } + + /* Figure out the transfer arguments */ + xfertyp = esdhc_xfertyp(cmd, data); + + /* Mask all irqs */ + esdhc_write32(®s->irqsigen, 0); + + /* Send the command */ + esdhc_write32(®s->cmdarg, cmd->cmdarg); +#if defined(CONFIG_FSL_USDHC) + esdhc_write32(®s->mixctrl, + (esdhc_read32(®s->mixctrl) & 0xFFFFFF80) | (xfertyp & 0x7F) + | (mmc->ddr_mode ? XFERTYP_DDREN : 0)); + esdhc_write32(®s->xfertyp, xfertyp & 0xFFFF0000); +#else + esdhc_write32(®s->xfertyp, xfertyp); +#endif + + if ((cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK) || + (cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200)) + flags = IRQSTAT_BRR; + + /* Wait for the command to complete */ + start = get_timer(0); + while (!(esdhc_read32(®s->irqstat) & flags)) { + if (get_timer(start) > 1000) { + err = -ETIMEDOUT; + goto out; + } + } + + irqstat = esdhc_read32(®s->irqstat); + + if (irqstat & CMD_ERR) { + err = -ECOMM; + goto out; + } + + if (irqstat & IRQSTAT_CTOE) { + err = -ETIMEDOUT; + goto out; + } + + /* Workaround for ESDHC errata ENGcm03648 */ + if (!data && (cmd->resp_type & MMC_RSP_BUSY)) { + int timeout = 50000; + + /* Poll on DATA0 line for cmd with busy signal for 5000 ms */ + while (timeout > 0 && !(esdhc_read32(®s->prsstat) & + PRSSTAT_DAT0)) { + udelay(100); + timeout--; + } + + if (timeout <= 0) { + printf("Timeout waiting for DAT0 to go high!\n"); + err = -ETIMEDOUT; + goto out; + } + } + + /* Copy the response to the response buffer */ + if (cmd->resp_type & MMC_RSP_136) { + u32 cmdrsp3, cmdrsp2, cmdrsp1, cmdrsp0; + + cmdrsp3 = esdhc_read32(®s->cmdrsp3); + cmdrsp2 = esdhc_read32(®s->cmdrsp2); + cmdrsp1 = esdhc_read32(®s->cmdrsp1); + cmdrsp0 = esdhc_read32(®s->cmdrsp0); + cmd->response[0] = (cmdrsp3 << 8) | (cmdrsp2 >> 24); + cmd->response[1] = (cmdrsp2 << 8) | (cmdrsp1 >> 24); + cmd->response[2] = (cmdrsp1 << 8) | (cmdrsp0 >> 24); + cmd->response[3] = (cmdrsp0 << 8); + } else + cmd->response[0] = esdhc_read32(®s->cmdrsp0); + + /* Wait until all of the blocks are transferred */ + if (data) { +#ifdef CONFIG_SYS_FSL_ESDHC_USE_PIO + esdhc_pio_read_write(priv, data); +#else + flags = DATA_COMPLETE; + if ((cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK) || + (cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200)) { + flags = IRQSTAT_BRR; + } + + do { + irqstat = esdhc_read32(®s->irqstat); + + if (irqstat & IRQSTAT_DTOE) { + err = -ETIMEDOUT; + goto out; + } + + if (irqstat & DATA_ERR) { + err = -ECOMM; + goto out; + } + } while ((irqstat & flags) != flags); + + /* + * Need invalidate the dcache here again to avoid any + * cache-fill during the DMA operations such as the + * speculative pre-fetching etc. + */ + if (data->flags & MMC_DATA_READ) { + check_and_invalidate_dcache_range(cmd, data); +#ifdef CONFIG_MCF5441x + sd_swap_dma_buff(data); +#endif + } +#endif + } + +out: + /* Reset CMD and DATA portions on error */ + if (err) { + esdhc_write32(®s->sysctl, esdhc_read32(®s->sysctl) | + SYSCTL_RSTC); + while (esdhc_read32(®s->sysctl) & SYSCTL_RSTC) + ; + + if (data) { + esdhc_write32(®s->sysctl, + esdhc_read32(®s->sysctl) | + SYSCTL_RSTD); + while ((esdhc_read32(®s->sysctl) & SYSCTL_RSTD)) + ; + } + + /* If this was CMD11, then notify that power cycle is needed */ + if (cmd->cmdidx == SD_CMD_SWITCH_UHS18V) + printf("CMD11 to switch to 1.8V mode failed, card requires power cycle.\n"); + } + + esdhc_write32(®s->irqstat, -1); + + return err; +} + +static void set_sysctl(struct fsl_esdhc_priv *priv, struct mmc *mmc, uint clock) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + int div = 1; + u32 tmp; + int ret; +#ifdef ARCH_MXC +#ifdef CONFIG_MX53 + /* For i.MX53 eSDHCv3, SYSCTL.SDCLKFS may not be set to 0. */ + int pre_div = (regs == (struct fsl_esdhc *)MMC_SDHC3_BASE_ADDR) ? 2 : 1; +#else + int pre_div = 1; +#endif +#else + int pre_div = 2; +#endif + int ddr_pre_div = mmc->ddr_mode ? 2 : 1; + int sdhc_clk = priv->sdhc_clk; + uint clk; + + while (sdhc_clk / (16 * pre_div * ddr_pre_div) > clock && pre_div < 256) + pre_div *= 2; + + while (sdhc_clk / (div * pre_div * ddr_pre_div) > clock && div < 16) + div++; + + pre_div >>= 1; + div -= 1; + + clk = (pre_div << 8) | (div << 4); + +#ifdef CONFIG_FSL_USDHC + esdhc_clrbits32(®s->vendorspec, VENDORSPEC_CKEN); +#else + esdhc_clrbits32(®s->sysctl, SYSCTL_CKEN); +#endif + + esdhc_clrsetbits32(®s->sysctl, SYSCTL_CLOCK_MASK, clk); + + ret = readx_poll_timeout(esdhc_read32, ®s->prsstat, tmp, tmp & PRSSTAT_SDSTB, 100); + if (ret) + pr_warn("fsl_esdhc_imx: Internal clock never stabilised.\n"); + +#ifdef CONFIG_FSL_USDHC + esdhc_setbits32(®s->vendorspec, VENDORSPEC_PEREN | VENDORSPEC_CKEN); +#else + esdhc_setbits32(®s->sysctl, SYSCTL_PEREN | SYSCTL_CKEN); +#endif + + priv->clock = clock; +} + +#ifdef MMC_SUPPORTS_TUNING +static int esdhc_change_pinstate(struct udevice *dev) +{ + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + int ret; + + switch (priv->mode) { + case UHS_SDR50: + case UHS_DDR50: + ret = pinctrl_select_state(dev, "state_100mhz"); + break; + case UHS_SDR104: + case MMC_HS_200: + case MMC_HS_400: + case MMC_HS_400_ES: + ret = pinctrl_select_state(dev, "state_200mhz"); + break; + default: + ret = pinctrl_select_state(dev, "default"); + break; + } + + if (ret) + printf("%s %d error\n", __func__, priv->mode); + + return ret; +} + +static void esdhc_reset_tuning(struct mmc *mmc) +{ + struct fsl_esdhc_priv *priv = dev_get_priv(mmc->dev); + struct fsl_esdhc *regs = priv->esdhc_regs; + + if (priv->flags & ESDHC_FLAG_USDHC) { + if (priv->flags & ESDHC_FLAG_STD_TUNING) { + esdhc_clrbits32(®s->autoc12err, + MIX_CTRL_SMPCLK_SEL | + MIX_CTRL_EXE_TUNE); + } + } +} + +static void esdhc_set_strobe_dll(struct mmc *mmc) +{ + struct fsl_esdhc_priv *priv = dev_get_priv(mmc->dev); + struct fsl_esdhc *regs = priv->esdhc_regs; + u32 val; + + if (priv->clock > ESDHC_STROBE_DLL_CLK_FREQ) { + esdhc_write32(®s->strobe_dllctrl, ESDHC_STROBE_DLL_CTRL_RESET); + + /* + * enable strobe dll ctrl and adjust the delay target + * for the uSDHC loopback read clock + */ + val = ESDHC_STROBE_DLL_CTRL_ENABLE | + (priv->strobe_dll_delay_target << + ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_SHIFT); + esdhc_write32(®s->strobe_dllctrl, val); + /* wait 1us to make sure strobe dll status register stable */ + mdelay(1); + val = esdhc_read32(®s->strobe_dllstat); + if (!(val & ESDHC_STROBE_DLL_STS_REF_LOCK)) + pr_warn("HS400 strobe DLL status REF not lock!\n"); + if (!(val & ESDHC_STROBE_DLL_STS_SLV_LOCK)) + pr_warn("HS400 strobe DLL status SLV not lock!\n"); + } +} + +static int esdhc_set_timing(struct mmc *mmc) +{ + struct fsl_esdhc_priv *priv = dev_get_priv(mmc->dev); + struct fsl_esdhc *regs = priv->esdhc_regs; + u32 mixctrl; + + mixctrl = esdhc_read32(®s->mixctrl); + mixctrl &= ~(MIX_CTRL_DDREN | MIX_CTRL_HS400_EN); + + switch (mmc->selected_mode) { + case MMC_LEGACY: + esdhc_reset_tuning(mmc); + esdhc_write32(®s->mixctrl, mixctrl); + break; + case MMC_HS_400: + case MMC_HS_400_ES: + mixctrl |= MIX_CTRL_DDREN | MIX_CTRL_HS400_EN; + esdhc_write32(®s->mixctrl, mixctrl); + break; + case MMC_HS: + case MMC_HS_52: + case MMC_HS_200: + case SD_HS: + case UHS_SDR12: + case UHS_SDR25: + case UHS_SDR50: + case UHS_SDR104: + esdhc_write32(®s->mixctrl, mixctrl); + break; + case UHS_DDR50: + case MMC_DDR_52: + mixctrl |= MIX_CTRL_DDREN; + esdhc_write32(®s->mixctrl, mixctrl); + break; + default: + printf("Not supported %d\n", mmc->selected_mode); + return -EINVAL; + } + + priv->mode = mmc->selected_mode; + + return esdhc_change_pinstate(mmc->dev); +} + +static int esdhc_set_voltage(struct mmc *mmc) +{ + struct fsl_esdhc_priv *priv = dev_get_priv(mmc->dev); + struct fsl_esdhc *regs = priv->esdhc_regs; +#if CONFIG_IS_ENABLED(DM_REGULATOR) + int ret; +#endif + + priv->signal_voltage = mmc->signal_voltage; + switch (mmc->signal_voltage) { + case MMC_SIGNAL_VOLTAGE_330: + if (priv->vs18_enable) + return -ENOTSUPP; +#if CONFIG_IS_ENABLED(DM_REGULATOR) + if (!IS_ERR_OR_NULL(priv->vqmmc_dev)) { + ret = regulator_set_value(priv->vqmmc_dev, 3300000); + if (ret) { + printf("Setting to 3.3V error"); + return -EIO; + } + /* Wait for 5ms */ + mdelay(5); + } +#endif + + esdhc_clrbits32(®s->vendorspec, ESDHC_VENDORSPEC_VSELECT); + if (!(esdhc_read32(®s->vendorspec) & + ESDHC_VENDORSPEC_VSELECT)) + return 0; + + return -EAGAIN; + case MMC_SIGNAL_VOLTAGE_180: +#if CONFIG_IS_ENABLED(DM_REGULATOR) + if (!IS_ERR_OR_NULL(priv->vqmmc_dev)) { + ret = regulator_set_value(priv->vqmmc_dev, 1800000); + if (ret) { + printf("Setting to 1.8V error"); + return -EIO; + } + } +#endif + esdhc_setbits32(®s->vendorspec, ESDHC_VENDORSPEC_VSELECT); + /* + * some board like imx8mm-evk need about 18ms to switch + * the IO voltage from 3.3v to 1.8v, common code only + * delay 10ms, so need to delay extra time to make sure + * the IO voltage change to 1.8v. + */ + if (priv->signal_voltage_switch_extra_delay_ms) + mdelay(priv->signal_voltage_switch_extra_delay_ms); + if (esdhc_read32(®s->vendorspec) & ESDHC_VENDORSPEC_VSELECT) + return 0; + + return -EAGAIN; + case MMC_SIGNAL_VOLTAGE_120: + return -ENOTSUPP; + default: + return 0; + } +} + +static void esdhc_stop_tuning(struct mmc *mmc) +{ + struct mmc_cmd cmd; + + cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1b; + + mmc_send_cmd(mmc, &cmd, NULL); +} + +static int fsl_esdhc_execute_tuning(struct udevice *dev, uint32_t opcode) +{ + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + struct fsl_esdhc *regs = priv->esdhc_regs; + struct mmc *mmc = &plat->mmc; + u32 irqstaten = esdhc_read32(®s->irqstaten); + u32 irqsigen = esdhc_read32(®s->irqsigen); + int i, ret = -ETIMEDOUT; + u32 val, mixctrl; + + /* clock tuning is not needed for upto 52MHz */ + if (mmc->clock <= 52000000) + return 0; + + /* This is readw/writew SDHCI_HOST_CONTROL2 when tuning */ + if (priv->flags & ESDHC_FLAG_STD_TUNING) { + val = esdhc_read32(®s->autoc12err); + mixctrl = esdhc_read32(®s->mixctrl); + val &= ~MIX_CTRL_SMPCLK_SEL; + mixctrl &= ~(MIX_CTRL_FBCLK_SEL | MIX_CTRL_AUTO_TUNE_EN); + + val |= MIX_CTRL_EXE_TUNE; + mixctrl |= MIX_CTRL_FBCLK_SEL | MIX_CTRL_AUTO_TUNE_EN; + + esdhc_write32(®s->autoc12err, val); + esdhc_write32(®s->mixctrl, mixctrl); + } + + /* sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE); */ + mixctrl = esdhc_read32(®s->mixctrl); + mixctrl = MIX_CTRL_DTDSEL_READ | (mixctrl & ~MIX_CTRL_SDHCI_MASK); + esdhc_write32(®s->mixctrl, mixctrl); + + esdhc_write32(®s->irqstaten, IRQSTATEN_BRR); + esdhc_write32(®s->irqsigen, IRQSTATEN_BRR); + + /* + * Issue opcode repeatedly till Execute Tuning is set to 0 or the number + * of loops reaches 40 times. + */ + for (i = 0; i < MAX_TUNING_LOOP; i++) { + u32 ctrl; + + if (opcode == MMC_CMD_SEND_TUNING_BLOCK_HS200) { + if (mmc->bus_width == 8) + esdhc_write32(®s->blkattr, 0x7080); + else if (mmc->bus_width == 4) + esdhc_write32(®s->blkattr, 0x7040); + } else { + esdhc_write32(®s->blkattr, 0x7040); + } + + /* sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE) */ + val = esdhc_read32(®s->mixctrl); + val = MIX_CTRL_DTDSEL_READ | (val & ~MIX_CTRL_SDHCI_MASK); + esdhc_write32(®s->mixctrl, val); + + /* We are using STD tuning, no need to check return value */ + mmc_send_tuning(mmc, opcode, NULL); + + ctrl = esdhc_read32(®s->autoc12err); + if ((!(ctrl & MIX_CTRL_EXE_TUNE)) && + (ctrl & MIX_CTRL_SMPCLK_SEL)) { + ret = 0; + break; + } + } + + esdhc_write32(®s->irqstaten, irqstaten); + esdhc_write32(®s->irqsigen, irqsigen); + + esdhc_stop_tuning(mmc); + + return ret; +} +#endif + +static int esdhc_set_ios_common(struct fsl_esdhc_priv *priv, struct mmc *mmc) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + int ret __maybe_unused; + u32 clock; + +#ifdef MMC_SUPPORTS_TUNING + /* + * call esdhc_set_timing() before update the clock rate, + * This is because current we support DDR and SDR mode, + * Once the DDR_EN bit is set, the card clock will be + * divide by 2 automatically. So need to do this before + * setting clock rate. + */ + if (priv->mode != mmc->selected_mode) { + ret = esdhc_set_timing(mmc); + if (ret) { + printf("esdhc_set_timing error %d\n", ret); + return ret; + } + } +#endif + + /* Set the clock speed */ + clock = mmc->clock; + if (clock < mmc->cfg->f_min) + clock = mmc->cfg->f_min; + + if (priv->clock != clock) + set_sysctl(priv, mmc, clock); + +#ifdef MMC_SUPPORTS_TUNING + if (mmc->clk_disable) { +#ifdef CONFIG_FSL_USDHC + esdhc_clrbits32(®s->vendorspec, VENDORSPEC_CKEN); +#else + esdhc_clrbits32(®s->sysctl, SYSCTL_CKEN); +#endif + } else { +#ifdef CONFIG_FSL_USDHC + esdhc_setbits32(®s->vendorspec, VENDORSPEC_PEREN | + VENDORSPEC_CKEN); +#else + esdhc_setbits32(®s->sysctl, SYSCTL_PEREN | SYSCTL_CKEN); +#endif + } + + /* + * For HS400/HS400ES mode, make sure set the strobe dll in the + * target clock rate. So call esdhc_set_strobe_dll() after the + * clock updated. + */ + if (mmc->selected_mode == MMC_HS_400 || mmc->selected_mode == MMC_HS_400_ES) + esdhc_set_strobe_dll(mmc); + + if (priv->signal_voltage != mmc->signal_voltage) { + ret = esdhc_set_voltage(mmc); + if (ret) { + if (ret != -ENOTSUPP) + printf("esdhc_set_voltage error %d\n", ret); + return ret; + } + } +#endif + + /* Set the bus width */ + esdhc_clrbits32(®s->proctl, PROCTL_DTW_4 | PROCTL_DTW_8); + + if (mmc->bus_width == 4) + esdhc_setbits32(®s->proctl, PROCTL_DTW_4); + else if (mmc->bus_width == 8) + esdhc_setbits32(®s->proctl, PROCTL_DTW_8); + + return 0; +} + +static int esdhc_init_common(struct fsl_esdhc_priv *priv, struct mmc *mmc) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + ulong start; + + /* Reset the entire host controller */ + esdhc_setbits32(®s->sysctl, SYSCTL_RSTA); + + /* Wait until the controller is available */ + start = get_timer(0); + while ((esdhc_read32(®s->sysctl) & SYSCTL_RSTA)) { + if (get_timer(start) > 1000) + return -ETIMEDOUT; + } + +#if defined(CONFIG_FSL_USDHC) + /* RSTA doesn't reset MMC_BOOT register, so manually reset it */ + esdhc_write32(®s->mmcboot, 0x0); + /* Reset MIX_CTRL and CLK_TUNE_CTRL_STATUS regs to 0 */ + esdhc_write32(®s->mixctrl, 0x0); + esdhc_write32(®s->clktunectrlstatus, 0x0); + + /* Put VEND_SPEC to default value */ + if (priv->vs18_enable) + esdhc_write32(®s->vendorspec, (VENDORSPEC_INIT | + ESDHC_VENDORSPEC_VSELECT)); + else + esdhc_write32(®s->vendorspec, VENDORSPEC_INIT); + + /* Disable DLL_CTRL delay line */ + esdhc_write32(®s->dllctrl, 0x0); +#endif + +#ifndef ARCH_MXC + /* Enable cache snooping */ + esdhc_write32(®s->scr, 0x00000040); +#endif + +#ifndef CONFIG_FSL_USDHC + esdhc_setbits32(®s->sysctl, SYSCTL_HCKEN | SYSCTL_IPGEN); +#else + esdhc_setbits32(®s->vendorspec, VENDORSPEC_HCKEN | VENDORSPEC_IPGEN); +#endif + + /* Set the initial clock speed */ + mmc_set_clock(mmc, 400000, MMC_CLK_ENABLE); + + /* Disable the BRR and BWR bits in IRQSTAT */ + esdhc_clrbits32(®s->irqstaten, IRQSTATEN_BRR | IRQSTATEN_BWR); + +#ifdef CONFIG_MCF5441x + esdhc_write32(®s->proctl, PROCTL_INIT | PROCTL_D3CD); +#else + /* Put the PROCTL reg back to the default */ + esdhc_write32(®s->proctl, PROCTL_INIT); +#endif + + /* Set timout to the maximum value */ + esdhc_clrsetbits32(®s->sysctl, SYSCTL_TIMEOUT_MASK, 14 << 16); + + return 0; +} + +static int esdhc_getcd_common(struct fsl_esdhc_priv *priv) +{ + struct fsl_esdhc *regs = priv->esdhc_regs; + int timeout = 1000; + +#ifdef CONFIG_ESDHC_DETECT_QUIRK + if (CONFIG_ESDHC_DETECT_QUIRK) + return 1; +#endif + +#if CONFIG_IS_ENABLED(DM_MMC) + if (priv->non_removable) + return 1; + + if (priv->broken_cd) + return 1; +#if CONFIG_IS_ENABLED(DM_GPIO) + if (dm_gpio_is_valid(&priv->cd_gpio)) + return dm_gpio_get_value(&priv->cd_gpio); +#endif +#endif + + while (!(esdhc_read32(®s->prsstat) & PRSSTAT_CINS) && --timeout) + udelay(1000); + + return timeout > 0; +} + +static int esdhc_reset(struct fsl_esdhc *regs) +{ + ulong start; + + /* reset the controller */ + esdhc_setbits32(®s->sysctl, SYSCTL_RSTA); + + /* hardware clears the bit when it is done */ + start = get_timer(0); + while ((esdhc_read32(®s->sysctl) & SYSCTL_RSTA)) { + if (get_timer(start) > 100) { + printf("MMC/SD: Reset never completed.\n"); + return -ETIMEDOUT; + } + } + + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +static int esdhc_getcd(struct mmc *mmc) +{ + struct fsl_esdhc_priv *priv = mmc->priv; + + return esdhc_getcd_common(priv); +} + +static int esdhc_init(struct mmc *mmc) +{ + struct fsl_esdhc_priv *priv = mmc->priv; + + return esdhc_init_common(priv, mmc); +} + +static int esdhc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct fsl_esdhc_priv *priv = mmc->priv; + + return esdhc_send_cmd_common(priv, mmc, cmd, data); +} + +static int esdhc_set_ios(struct mmc *mmc) +{ + struct fsl_esdhc_priv *priv = mmc->priv; + + return esdhc_set_ios_common(priv, mmc); +} + +static const struct mmc_ops esdhc_ops = { + .getcd = esdhc_getcd, + .init = esdhc_init, + .send_cmd = esdhc_send_cmd, + .set_ios = esdhc_set_ios, +}; +#endif + +static int fsl_esdhc_init(struct fsl_esdhc_priv *priv, + struct fsl_esdhc_plat *plat) +{ + struct mmc_config *cfg; + struct fsl_esdhc *regs; + u32 caps, voltage_caps; + int ret; + + if (!priv) + return -EINVAL; + + regs = priv->esdhc_regs; + + /* First reset the eSDHC controller */ + ret = esdhc_reset(regs); + if (ret) + return ret; + +#ifdef CONFIG_MCF5441x + /* ColdFire, using SDHC_DATA[3] for card detection */ + esdhc_write32(®s->proctl, PROCTL_INIT | PROCTL_D3CD); +#endif + +#ifndef CONFIG_FSL_USDHC + esdhc_setbits32(®s->sysctl, SYSCTL_PEREN | SYSCTL_HCKEN + | SYSCTL_IPGEN | SYSCTL_CKEN); + /* Clearing tuning bits in case ROM has set it already */ + esdhc_write32(®s->mixctrl, 0); + esdhc_write32(®s->autoc12err, 0); + esdhc_write32(®s->clktunectrlstatus, 0); +#else + esdhc_setbits32(®s->vendorspec, VENDORSPEC_PEREN | + VENDORSPEC_HCKEN | VENDORSPEC_IPGEN | VENDORSPEC_CKEN); +#endif + + if (priv->vs18_enable) + esdhc_setbits32(®s->vendorspec, ESDHC_VENDORSPEC_VSELECT); + + esdhc_write32(®s->irqstaten, SDHCI_IRQ_EN_BITS); + cfg = &plat->cfg; +#ifndef CONFIG_DM_MMC + memset(cfg, '\0', sizeof(*cfg)); +#endif + + voltage_caps = 0; + caps = esdhc_read32(®s->hostcapblt); + +#ifdef CONFIG_MCF5441x + /* + * MCF5441x RM declares in more points that sdhc clock speed must + * never exceed 25 Mhz. From this, the HS bit needs to be disabled + * from host capabilities. + */ + caps &= ~ESDHC_HOSTCAPBLT_HSS; +#endif + +#ifdef CONFIG_SYS_FSL_ERRATUM_ESDHC135 + caps = caps & ~(ESDHC_HOSTCAPBLT_SRS | + ESDHC_HOSTCAPBLT_VS18 | ESDHC_HOSTCAPBLT_VS30); +#endif + + if (caps & ESDHC_HOSTCAPBLT_VS18) + voltage_caps |= MMC_VDD_165_195; + if (caps & ESDHC_HOSTCAPBLT_VS30) + voltage_caps |= MMC_VDD_29_30 | MMC_VDD_30_31; + if (caps & ESDHC_HOSTCAPBLT_VS33) + voltage_caps |= MMC_VDD_32_33 | MMC_VDD_33_34; + + cfg->name = "FSL_SDHC"; +#if !CONFIG_IS_ENABLED(DM_MMC) + cfg->ops = &esdhc_ops; +#endif +#ifdef CONFIG_SYS_SD_VOLTAGE + cfg->voltages = CONFIG_SYS_SD_VOLTAGE; +#else + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; +#endif + if ((cfg->voltages & voltage_caps) == 0) { + printf("voltage not supported by controller\n"); + return -1; + } + + if (priv->bus_width == 8) + cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT; + else if (priv->bus_width == 4) + cfg->host_caps = MMC_MODE_4BIT; + + cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT; +#ifdef CONFIG_SYS_FSL_ESDHC_HAS_DDR_MODE + cfg->host_caps |= MMC_MODE_DDR_52MHz; +#endif + + if (priv->bus_width > 0) { + if (priv->bus_width < 8) + cfg->host_caps &= ~MMC_MODE_8BIT; + if (priv->bus_width < 4) + cfg->host_caps &= ~MMC_MODE_4BIT; + } + + if (caps & ESDHC_HOSTCAPBLT_HSS) + cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + +#ifdef CONFIG_ESDHC_DETECT_8_BIT_QUIRK + if (CONFIG_ESDHC_DETECT_8_BIT_QUIRK) + cfg->host_caps &= ~MMC_MODE_8BIT; +#endif + + cfg->host_caps |= priv->caps; + + cfg->f_min = 400000; + cfg->f_max = min(priv->sdhc_clk, (u32)200000000); + + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + esdhc_write32(®s->dllctrl, 0); + if (priv->flags & ESDHC_FLAG_USDHC) { + if (priv->flags & ESDHC_FLAG_STD_TUNING) { + u32 val = esdhc_read32(®s->tuning_ctrl); + + val |= ESDHC_STD_TUNING_EN; + val &= ~ESDHC_TUNING_START_TAP_MASK; + val |= priv->tuning_start_tap; + val &= ~ESDHC_TUNING_STEP_MASK; + val |= (priv->tuning_step) << ESDHC_TUNING_STEP_SHIFT; + + /* Disable the CMD CRC check for tuning, if not, need to + * add some delay after every tuning command, because + * hardware standard tuning logic will directly go to next + * step once it detect the CMD CRC error, will not wait for + * the card side to finally send out the tuning data, trigger + * the buffer read ready interrupt immediately. If usdhc send + * the next tuning command some eMMC card will stuck, can't + * response, block the tuning procedure or the first command + * after the whole tuning procedure always can't get any response. + */ + val |= ESDHC_TUNING_CMD_CRC_CHECK_DISABLE; + esdhc_write32(®s->tuning_ctrl, val); + } + } + + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +static int fsl_esdhc_cfg_to_priv(struct fsl_esdhc_cfg *cfg, + struct fsl_esdhc_priv *priv) +{ + if (!cfg || !priv) + return -EINVAL; + + priv->esdhc_regs = (struct fsl_esdhc *)(unsigned long)(cfg->esdhc_base); + priv->bus_width = cfg->max_bus_width; + priv->sdhc_clk = cfg->sdhc_clk; + priv->wp_enable = cfg->wp_enable; + priv->vs18_enable = cfg->vs18_enable; + + return 0; +}; + +int fsl_esdhc_initialize(struct bd_info *bis, struct fsl_esdhc_cfg *cfg) +{ + struct fsl_esdhc_plat *plat; + struct fsl_esdhc_priv *priv; + struct mmc *mmc; + int ret; + + if (!cfg) + return -EINVAL; + + priv = calloc(sizeof(struct fsl_esdhc_priv), 1); + if (!priv) + return -ENOMEM; + plat = calloc(sizeof(struct fsl_esdhc_plat), 1); + if (!plat) { + free(priv); + return -ENOMEM; + } + + ret = fsl_esdhc_cfg_to_priv(cfg, priv); + if (ret) { + debug("%s xlate failure\n", __func__); + free(plat); + free(priv); + return ret; + } + + ret = fsl_esdhc_init(priv, plat); + if (ret) { + debug("%s init failure\n", __func__); + free(plat); + free(priv); + return ret; + } + + mmc = mmc_create(&plat->cfg, priv); + if (!mmc) + return -EIO; + + priv->mmc = mmc; + + return 0; +} + +int fsl_esdhc_mmc_init(struct bd_info *bis) +{ + struct fsl_esdhc_cfg *cfg; + + cfg = calloc(sizeof(struct fsl_esdhc_cfg), 1); + cfg->esdhc_base = CONFIG_SYS_FSL_ESDHC_ADDR; + cfg->sdhc_clk = gd->arch.sdhc_clk; + return fsl_esdhc_initialize(bis, cfg); +} +#endif + +#ifdef CONFIG_OF_LIBFDT +__weak int esdhc_status_fixup(void *blob, const char *compat) +{ +#ifdef CONFIG_FSL_ESDHC_PIN_MUX + if (!hwconfig("esdhc")) { + do_fixup_by_compat(blob, compat, "status", "disabled", + sizeof("disabled"), 1); + return 1; + } +#endif + return 0; +} + +void fdt_fixup_esdhc(void *blob, struct bd_info *bd) +{ + const char *compat = "fsl,esdhc"; + + if (esdhc_status_fixup(blob, compat)) + return; + + do_fixup_by_compat_u32(blob, compat, "clock-frequency", + gd->arch.sdhc_clk, 1); +} +#endif + +#if CONFIG_IS_ENABLED(DM_MMC) +#include <asm/arch/clock.h> +__weak void init_clk_usdhc(u32 index) +{ +} + +static int fsl_esdhc_of_to_plat(struct udevice *dev) +{ +#if !CONFIG_IS_ENABLED(OF_PLATDATA) + struct fsl_esdhc_priv *priv = dev_get_priv(dev); +#if CONFIG_IS_ENABLED(DM_REGULATOR) + struct udevice *vqmmc_dev; + int ret; +#endif + const void *fdt = gd->fdt_blob; + int node = dev_of_offset(dev); + + fdt_addr_t addr; + unsigned int val; + + addr = dev_read_addr(dev); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + priv->esdhc_regs = (struct fsl_esdhc *)addr; + priv->dev = dev; + priv->mode = -1; + + val = dev_read_u32_default(dev, "bus-width", -1); + if (val == 8) + priv->bus_width = 8; + else if (val == 4) + priv->bus_width = 4; + else + priv->bus_width = 1; + + val = fdtdec_get_int(fdt, node, "fsl,tuning-step", 1); + priv->tuning_step = val; + val = fdtdec_get_int(fdt, node, "fsl,tuning-start-tap", + ESDHC_TUNING_START_TAP_DEFAULT); + priv->tuning_start_tap = val; + val = fdtdec_get_int(fdt, node, "fsl,strobe-dll-delay-target", + ESDHC_STROBE_DLL_CTRL_SLV_DLY_TARGET_DEFAULT); + priv->strobe_dll_delay_target = val; + val = fdtdec_get_int(fdt, node, "fsl,signal-voltage-switch-extra-delay-ms", 0); + priv->signal_voltage_switch_extra_delay_ms = val; + + if (dev_read_bool(dev, "broken-cd")) + priv->broken_cd = 1; + + if (dev_read_bool(dev, "non-removable")) { + priv->non_removable = 1; + } else { + priv->non_removable = 0; +#if CONFIG_IS_ENABLED(DM_GPIO) + gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio, + GPIOD_IS_IN); +#endif + } + + if (dev_read_prop(dev, "fsl,wp-controller", NULL)) { + priv->wp_enable = 1; + } else { + priv->wp_enable = 0; +#if CONFIG_IS_ENABLED(DM_GPIO) + gpio_request_by_name(dev, "wp-gpios", 0, &priv->wp_gpio, + GPIOD_IS_IN); +#endif + } + + priv->vs18_enable = 0; + +#if CONFIG_IS_ENABLED(DM_REGULATOR) + /* + * If emmc I/O has a fixed voltage at 1.8V, this must be provided, + * otherwise, emmc will work abnormally. + */ + ret = device_get_supply_regulator(dev, "vqmmc-supply", &vqmmc_dev); + if (ret) { + dev_dbg(dev, "no vqmmc-supply\n"); + } else { + priv->vqmmc_dev = vqmmc_dev; + ret = regulator_set_enable(vqmmc_dev, true); + if (ret) { + dev_err(dev, "fail to enable vqmmc-supply\n"); + return ret; + } + + if (regulator_get_value(vqmmc_dev) == 1800000) + priv->vs18_enable = 1; + } +#endif +#endif + return 0; +} + +static int fsl_esdhc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + struct esdhc_soc_data *data = + (struct esdhc_soc_data *)dev_get_driver_data(dev); + struct mmc *mmc; +#if !CONFIG_IS_ENABLED(BLK) + struct blk_desc *bdesc; +#endif + int ret; + +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct dtd_fsl_esdhc *dtplat = &plat->dtplat; + unsigned int val; + + priv->esdhc_regs = map_sysmem(dtplat->reg[0], dtplat->reg[1]); + val = plat->dtplat.bus_width; + if (val == 8) + priv->bus_width = 8; + else if (val == 4) + priv->bus_width = 4; + else + priv->bus_width = 1; + + if (dtplat->non_removable) + priv->non_removable = 1; + else + priv->non_removable = 0; + + if (CONFIG_IS_ENABLED(DM_GPIO) && !priv->non_removable) { + struct udevice *gpiodev; + + ret = device_get_by_ofplat_idx(dtplat->cd_gpios->idx, &gpiodev); + if (ret) + return ret; + + ret = gpio_dev_request_index(gpiodev, gpiodev->name, "cd-gpios", + dtplat->cd_gpios->arg[0], GPIOD_IS_IN, + dtplat->cd_gpios->arg[1], &priv->cd_gpio); + + if (ret) + return ret; + } +#endif + + if (data) + priv->flags = data->flags; + + /* + * TODO: + * Because lack of clk driver, if SDHC clk is not enabled, + * need to enable it first before this driver is invoked. + * + * we use MXC_ESDHC_CLK to get clk freq. + * If one would like to make this function work, + * the aliases should be provided in dts as this: + * + * aliases { + * mmc0 = &usdhc1; + * mmc1 = &usdhc2; + * mmc2 = &usdhc3; + * mmc3 = &usdhc4; + * }; + * Then if your board only supports mmc2 and mmc3, but we can + * correctly get the seq as 2 and 3, then let mxc_get_clock + * work as expected. + */ + + init_clk_usdhc(dev_seq(dev)); + +#if CONFIG_IS_ENABLED(CLK) + /* Assigned clock already set clock */ + ret = clk_get_by_name(dev, "per", &priv->per_clk); + if (ret) { + printf("Failed to get per_clk\n"); + return ret; + } + ret = clk_enable(&priv->per_clk); + if (ret) { + printf("Failed to enable per_clk\n"); + return ret; + } + + priv->sdhc_clk = clk_get_rate(&priv->per_clk); +#else + priv->sdhc_clk = mxc_get_clock(MXC_ESDHC_CLK + dev_seq(dev)); + if (priv->sdhc_clk <= 0) { + dev_err(dev, "Unable to get clk for %s\n", dev->name); + return -EINVAL; + } +#endif + + ret = fsl_esdhc_init(priv, plat); + if (ret) { + dev_err(dev, "fsl_esdhc_init failure\n"); + return ret; + } + +#if !CONFIG_IS_ENABLED(OF_PLATDATA) + ret = mmc_of_parse(dev, &plat->cfg); + if (ret) + return ret; +#endif + + mmc = &plat->mmc; + mmc->cfg = &plat->cfg; + mmc->dev = dev; +#if !CONFIG_IS_ENABLED(BLK) + mmc->priv = priv; + + /* Setup dsr related values */ + mmc->dsr_imp = 0; + mmc->dsr = ESDHC_DRIVER_STAGE_VALUE; + /* Setup the universal parts of the block interface just once */ + bdesc = mmc_get_blk_desc(mmc); + bdesc->if_type = IF_TYPE_MMC; + bdesc->removable = 1; + bdesc->devnum = mmc_get_next_devnum(); + bdesc->block_read = mmc_bread; + bdesc->block_write = mmc_bwrite; + bdesc->block_erase = mmc_berase; + + /* setup initial part type */ + bdesc->part_type = mmc->cfg->part_type; + mmc_list_add(mmc); +#endif + + upriv->mmc = mmc; + + return esdhc_init_common(priv, mmc); +} + +#if CONFIG_IS_ENABLED(DM_MMC) +static int fsl_esdhc_get_cd(struct udevice *dev) +{ + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + + return esdhc_getcd_common(priv); +} + +static int fsl_esdhc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + + return esdhc_send_cmd_common(priv, &plat->mmc, cmd, data); +} + +static int fsl_esdhc_set_ios(struct udevice *dev) +{ + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + + return esdhc_set_ios_common(priv, &plat->mmc); +} + +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT) +static int fsl_esdhc_set_enhanced_strobe(struct udevice *dev) +{ + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + struct fsl_esdhc *regs = priv->esdhc_regs; + u32 m; + + m = esdhc_read32(®s->mixctrl); + m |= MIX_CTRL_HS400_ES; + esdhc_write32(®s->mixctrl, m); + + return 0; +} +#endif + +static int fsl_esdhc_wait_dat0(struct udevice *dev, int state, + int timeout_us) +{ + int ret; + u32 tmp; + struct fsl_esdhc_priv *priv = dev_get_priv(dev); + struct fsl_esdhc *regs = priv->esdhc_regs; + + ret = readx_poll_timeout(esdhc_read32, ®s->prsstat, tmp, + !!(tmp & PRSSTAT_DAT0) == !!state, + timeout_us); + return ret; +} + +static const struct dm_mmc_ops fsl_esdhc_ops = { + .get_cd = fsl_esdhc_get_cd, + .send_cmd = fsl_esdhc_send_cmd, + .set_ios = fsl_esdhc_set_ios, +#ifdef MMC_SUPPORTS_TUNING + .execute_tuning = fsl_esdhc_execute_tuning, +#endif +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT) + .set_enhanced_strobe = fsl_esdhc_set_enhanced_strobe, +#endif + .wait_dat0 = fsl_esdhc_wait_dat0, +}; +#endif + +static struct esdhc_soc_data usdhc_imx7d_data = { + .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING + | ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200 + | ESDHC_FLAG_HS400, +}; + +static struct esdhc_soc_data usdhc_imx8qm_data = { + .flags = ESDHC_FLAG_USDHC | ESDHC_FLAG_STD_TUNING | + ESDHC_FLAG_HAVE_CAP1 | ESDHC_FLAG_HS200 | + ESDHC_FLAG_HS400 | ESDHC_FLAG_HS400_ES, +}; + +static const struct udevice_id fsl_esdhc_ids[] = { + { .compatible = "fsl,imx51-esdhc", }, + { .compatible = "fsl,imx53-esdhc", }, + { .compatible = "fsl,imx6ul-usdhc", }, + { .compatible = "fsl,imx6sx-usdhc", }, + { .compatible = "fsl,imx6sl-usdhc", }, + { .compatible = "fsl,imx6q-usdhc", }, + { .compatible = "fsl,imx7d-usdhc", .data = (ulong)&usdhc_imx7d_data,}, + { .compatible = "fsl,imx7ulp-usdhc", }, + { .compatible = "fsl,imx8qm-usdhc", .data = (ulong)&usdhc_imx8qm_data,}, + { .compatible = "fsl,imx8mm-usdhc", .data = (ulong)&usdhc_imx8qm_data,}, + { .compatible = "fsl,imx8mn-usdhc", .data = (ulong)&usdhc_imx8qm_data,}, + { .compatible = "fsl,imx8mq-usdhc", .data = (ulong)&usdhc_imx8qm_data,}, + { .compatible = "fsl,imxrt-usdhc", }, + { .compatible = "fsl,esdhc", }, + { /* sentinel */ } +}; + +#if CONFIG_IS_ENABLED(BLK) +static int fsl_esdhc_bind(struct udevice *dev) +{ + struct fsl_esdhc_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} +#endif + +U_BOOT_DRIVER(fsl_esdhc) = { + .name = "fsl_esdhc", + .id = UCLASS_MMC, + .of_match = fsl_esdhc_ids, + .of_to_plat = fsl_esdhc_of_to_plat, + .ops = &fsl_esdhc_ops, +#if CONFIG_IS_ENABLED(BLK) + .bind = fsl_esdhc_bind, +#endif + .probe = fsl_esdhc_probe, + .plat_auto = sizeof(struct fsl_esdhc_plat), + .priv_auto = sizeof(struct fsl_esdhc_priv), +}; + +DM_DRIVER_ALIAS(fsl_esdhc, fsl_imx6q_usdhc) +#endif diff --git a/roms/u-boot/drivers/mmc/fsl_esdhc_spl.c b/roms/u-boot/drivers/mmc/fsl_esdhc_spl.c new file mode 100644 index 000000000..bee76572a --- /dev/null +++ b/roms/u-boot/drivers/mmc/fsl_esdhc_spl.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2013 Freescale Semiconductor, Inc. + */ + +#include <common.h> +#include <cpu_func.h> +#include <hang.h> +#include <mmc.h> +#include <malloc.h> + +/* + * The environment variables are written to just after the u-boot image + * on SDCard, so we must read the MBR to get the start address and code + * length of the u-boot image, then calculate the address of the env. + */ +#define ESDHC_BOOT_IMAGE_SIZE 0x48 +#define ESDHC_BOOT_IMAGE_ADDR 0x50 +#define MBRDBR_BOOT_SIG_55 0x1fe +#define MBRDBR_BOOT_SIG_AA 0x1ff +#define CONFIG_CFG_DATA_SECTOR 0 + + +void mmc_spl_load_image(uint32_t offs, unsigned int size, void *vdst) +{ + uint blk_start, blk_cnt, err; + + struct mmc *mmc = find_mmc_device(0); + if (!mmc) { + puts("spl: mmc device not found!!\n"); + hang(); + } + + if (mmc_init(mmc)) { + puts("MMC init failed\n"); + return; + } + + blk_start = ALIGN(offs, mmc->read_bl_len) / mmc->read_bl_len; + blk_cnt = ALIGN(size, mmc->read_bl_len) / mmc->read_bl_len; + + err = mmc->block_dev.block_read(&mmc->block_dev, blk_start, blk_cnt, + vdst); + if (err != blk_cnt) { + puts("spl: mmc read failed!!\n"); + hang(); + } +} + +/* + * The main entry for mmc booting. It's necessary that SDRAM is already + * configured and available since this code loads the main U-Boot image + * from mmc into SDRAM and starts it from there. + */ + +void __noreturn mmc_boot(void) +{ + __attribute__((noreturn)) void (*uboot)(void); + uint blk_start, blk_cnt, err; +#ifndef CONFIG_FSL_CORENET + uchar *tmp_buf; + u32 blklen; + uchar val; + uint i, byte_num; +#endif + u32 offset, code_len; + struct mmc *mmc; + + mmc = find_mmc_device(0); + if (!mmc) { + puts("spl: mmc device not found!!\n"); + hang(); + } + +#ifdef CONFIG_FSL_CORENET + offset = CONFIG_SYS_MMC_U_BOOT_OFFS; + code_len = CONFIG_SYS_MMC_U_BOOT_SIZE; +#else + blklen = mmc->read_bl_len; + tmp_buf = malloc(blklen); + if (!tmp_buf) { + puts("spl: malloc memory failed!!\n"); + hang(); + } + memset(tmp_buf, 0, blklen); + + /* + * Read source addr from sd card + */ + err = mmc->block_dev.block_read(&mmc->block_dev, + CONFIG_CFG_DATA_SECTOR, 1, tmp_buf); + if (err != 1) { + puts("spl: mmc read failed!!\n"); + hang(); + } + + val = *(tmp_buf + MBRDBR_BOOT_SIG_55); + if (0x55 != val) { + puts("spl: mmc signature is not valid!!\n"); + hang(); + } + val = *(tmp_buf + MBRDBR_BOOT_SIG_AA); + if (0xAA != val) { + puts("spl: mmc signature is not valid!!\n"); + hang(); + } + + byte_num = 4; + offset = 0; + for (i = 0; i < byte_num; i++) { + val = *(tmp_buf + ESDHC_BOOT_IMAGE_ADDR + i); + offset = (offset << 8) + val; + } + offset += CONFIG_SYS_MMC_U_BOOT_OFFS; + /* Get the code size from offset 0x48 */ + byte_num = 4; + code_len = 0; + for (i = 0; i < byte_num; i++) { + val = *(tmp_buf + ESDHC_BOOT_IMAGE_SIZE + i); + code_len = (code_len << 8) + val; + } + code_len -= CONFIG_SYS_MMC_U_BOOT_OFFS; + /* + * Load U-Boot image from mmc into RAM + */ +#endif + blk_start = ALIGN(offset, mmc->read_bl_len) / mmc->read_bl_len; + blk_cnt = ALIGN(code_len, mmc->read_bl_len) / mmc->read_bl_len; + err = mmc->block_dev.block_read(&mmc->block_dev, blk_start, blk_cnt, + (uchar *)CONFIG_SYS_MMC_U_BOOT_DST); + if (err != blk_cnt) { + puts("spl: mmc read failed!!\n"); +#ifndef CONFIG_FSL_CORENET + free(tmp_buf); +#endif + hang(); + } + + /* + * Clean d-cache and invalidate i-cache, to + * make sure that no stale data is executed. + */ + flush_cache(CONFIG_SYS_MMC_U_BOOT_DST, CONFIG_SYS_MMC_U_BOOT_SIZE); + + /* + * Jump to U-Boot image + */ + uboot = (void *)CONFIG_SYS_MMC_U_BOOT_START; + (*uboot)(); +} diff --git a/roms/u-boot/drivers/mmc/ftsdc010_mci.c b/roms/u-boot/drivers/mmc/ftsdc010_mci.c new file mode 100644 index 000000000..0fa037224 --- /dev/null +++ b/roms/u-boot/drivers/mmc/ftsdc010_mci.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Faraday MMC/SD Host Controller + * + * (C) Copyright 2010 Faraday Technology + * Dante Su <dantesu@faraday-tech.com> + * + * Copyright 2018 Andes Technology, Inc. + * Author: Rick Chen (rick@andestech.com) + */ + +#include <common.h> +#include <clk.h> +#include <log.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <asm/global_data.h> +#include <linux/bitops.h> +#include <linux/io.h> +#include <linux/errno.h> +#include <asm/byteorder.h> +#include <faraday/ftsdc010.h> +#include "ftsdc010_mci.h" +#include <dm.h> +#include <dt-structs.h> +#include <errno.h> +#include <mapmem.h> +#include <pwrseq.h> +#include <syscon.h> +#include <linux/err.h> + +DECLARE_GLOBAL_DATA_PTR; + +#define CFG_CMD_TIMEOUT (CONFIG_SYS_HZ >> 4) /* 250 ms */ +#define CFG_RST_TIMEOUT CONFIG_SYS_HZ /* 1 sec reset timeout */ + +#if CONFIG_IS_ENABLED(OF_PLATDATA) +struct ftsdc010 { + fdt32_t bus_width; + bool cap_mmc_highspeed; + bool cap_sd_highspeed; + fdt32_t clock_freq_min_max[2]; + struct phandle_2_cell clocks[4]; + fdt32_t fifo_depth; + fdt32_t reg[2]; +}; +#endif + +struct ftsdc010_plat { +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct ftsdc010 dtplat; +#endif + struct mmc_config cfg; + struct mmc mmc; +}; + +struct ftsdc_priv { + struct clk clk; + struct ftsdc010_chip chip; + int fifo_depth; + bool fifo_mode; + u32 minmax[2]; +}; + +static inline int ftsdc010_send_cmd(struct mmc *mmc, struct mmc_cmd *mmc_cmd) +{ + struct ftsdc010_chip *chip = mmc->priv; + struct ftsdc010_mmc __iomem *regs = chip->regs; + int ret = -ETIMEDOUT; + uint32_t ts, st; + uint32_t cmd = FTSDC010_CMD_IDX(mmc_cmd->cmdidx); + uint32_t arg = mmc_cmd->cmdarg; + uint32_t flags = mmc_cmd->resp_type; + + cmd |= FTSDC010_CMD_CMD_EN; + + if (chip->acmd) { + cmd |= FTSDC010_CMD_APP_CMD; + chip->acmd = 0; + } + + if (flags & MMC_RSP_PRESENT) + cmd |= FTSDC010_CMD_NEED_RSP; + + if (flags & MMC_RSP_136) + cmd |= FTSDC010_CMD_LONG_RSP; + + writel(FTSDC010_STATUS_RSP_MASK | FTSDC010_STATUS_CMD_SEND, + ®s->clr); + writel(arg, ®s->argu); + writel(cmd, ®s->cmd); + + if (!(flags & (MMC_RSP_PRESENT | MMC_RSP_136))) { + for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { + if (readl(®s->status) & FTSDC010_STATUS_CMD_SEND) { + writel(FTSDC010_STATUS_CMD_SEND, ®s->clr); + ret = 0; + break; + } + } + } else { + st = 0; + for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) { + st = readl(®s->status); + writel(st & FTSDC010_STATUS_RSP_MASK, ®s->clr); + if (st & FTSDC010_STATUS_RSP_MASK) + break; + } + if (st & FTSDC010_STATUS_RSP_CRC_OK) { + if (flags & MMC_RSP_136) { + mmc_cmd->response[0] = readl(®s->rsp3); + mmc_cmd->response[1] = readl(®s->rsp2); + mmc_cmd->response[2] = readl(®s->rsp1); + mmc_cmd->response[3] = readl(®s->rsp0); + } else { + mmc_cmd->response[0] = readl(®s->rsp0); + } + ret = 0; + } else { + debug("ftsdc010: rsp err (cmd=%d, st=0x%x)\n", + mmc_cmd->cmdidx, st); + } + } + + if (ret) { + debug("ftsdc010: cmd timeout (op code=%d)\n", + mmc_cmd->cmdidx); + } else if (mmc_cmd->cmdidx == MMC_CMD_APP_CMD) { + chip->acmd = 1; + } + + return ret; +} + +static void ftsdc010_clkset(struct mmc *mmc, uint32_t rate) +{ + struct ftsdc010_chip *chip = mmc->priv; + struct ftsdc010_mmc __iomem *regs = chip->regs; + uint32_t div; + + for (div = 0; div < 0x7f; ++div) { + if (rate >= chip->sclk / (2 * (div + 1))) + break; + } + chip->rate = chip->sclk / (2 * (div + 1)); + + writel(FTSDC010_CCR_CLK_DIV(div), ®s->ccr); + + if (IS_SD(mmc)) { + setbits_le32(®s->ccr, FTSDC010_CCR_CLK_SD); + + if (chip->rate > 25000000) + setbits_le32(®s->ccr, FTSDC010_CCR_CLK_HISPD); + else + clrbits_le32(®s->ccr, FTSDC010_CCR_CLK_HISPD); + } +} + +static int ftsdc010_wait(struct ftsdc010_mmc __iomem *regs, uint32_t mask) +{ + int ret = -ETIMEDOUT; + uint32_t st, timeout = 10000000; + while (timeout--) { + st = readl(®s->status); + if (!(st & mask)) + continue; + writel(st & mask, ®s->clr); + ret = 0; + break; + } + + if (ret){ + debug("ftsdc010: wait st(0x%x) timeout\n", mask); + } + + return ret; +} + +/* + * u-boot mmc api + */ +static int ftsdc010_request(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + int ret = -EOPNOTSUPP; + uint32_t len = 0; + struct ftsdc010_chip *chip = mmc->priv; + struct ftsdc010_mmc __iomem *regs = chip->regs; + + if (data && (data->flags & MMC_DATA_WRITE) && chip->wprot) { + printf("ftsdc010: the card is write protected!\n"); + return ret; + } + + if (data) { + uint32_t dcr; + + len = data->blocksize * data->blocks; + + /* 1. data disable + fifo reset */ + dcr = 0; +#ifdef CONFIG_FTSDC010_SDIO + dcr |= FTSDC010_DCR_FIFO_RST; +#endif + writel(dcr, ®s->dcr); + + /* 2. clear status register */ + writel(FTSDC010_STATUS_DATA_MASK | FTSDC010_STATUS_FIFO_URUN + | FTSDC010_STATUS_FIFO_ORUN, ®s->clr); + + /* 3. data timeout (1 sec) */ + writel(chip->rate, ®s->dtr); + + /* 4. data length (bytes) */ + writel(len, ®s->dlr); + + /* 5. data enable */ + dcr = (ffs(data->blocksize) - 1) | FTSDC010_DCR_DATA_EN; + if (data->flags & MMC_DATA_WRITE) + dcr |= FTSDC010_DCR_DATA_WRITE; + writel(dcr, ®s->dcr); + } + + ret = ftsdc010_send_cmd(mmc, cmd); + if (ret) { + printf("ftsdc010: CMD%d failed\n", cmd->cmdidx); + return ret; + } + + if (!data) + return ret; + + if (data->flags & MMC_DATA_WRITE) { + const uint8_t *buf = (const uint8_t *)data->src; + + while (len > 0) { + int wlen; + + /* wait for tx ready */ + ret = ftsdc010_wait(regs, FTSDC010_STATUS_FIFO_URUN); + if (ret) + break; + + /* write bytes to ftsdc010 */ + for (wlen = 0; wlen < len && wlen < chip->fifo; ) { + writel(*(uint32_t *)buf, ®s->dwr); + buf += 4; + wlen += 4; + } + + len -= wlen; + } + + } else { + uint8_t *buf = (uint8_t *)data->dest; + + while (len > 0) { + int rlen; + + /* wait for rx ready */ + ret = ftsdc010_wait(regs, FTSDC010_STATUS_FIFO_ORUN); + if (ret) + break; + + /* fetch bytes from ftsdc010 */ + for (rlen = 0; rlen < len && rlen < chip->fifo; ) { + *(uint32_t *)buf = readl(®s->dwr); + buf += 4; + rlen += 4; + } + + len -= rlen; + } + + } + + if (!ret) { + ret = ftsdc010_wait(regs, + FTSDC010_STATUS_DATA_END | FTSDC010_STATUS_DATA_CRC_OK); + } + + return ret; +} + +static int ftsdc010_set_ios(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct ftsdc010_chip *chip = mmc->priv; + struct ftsdc010_mmc __iomem *regs = chip->regs; + + ftsdc010_clkset(mmc, mmc->clock); + + clrbits_le32(®s->bwr, FTSDC010_BWR_MODE_MASK); + switch (mmc->bus_width) { + case 4: + setbits_le32(®s->bwr, FTSDC010_BWR_MODE_4BIT); + break; + case 8: + setbits_le32(®s->bwr, FTSDC010_BWR_MODE_8BIT); + break; + default: + setbits_le32(®s->bwr, FTSDC010_BWR_MODE_1BIT); + break; + } + + return 0; +} + +static int ftsdc010_get_cd(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct ftsdc010_chip *chip = mmc->priv; + struct ftsdc010_mmc __iomem *regs = chip->regs; + return !(readl(®s->status) & FTSDC010_STATUS_CARD_DETECT); +} + +static int ftsdc010_get_wp(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct ftsdc010_chip *chip = mmc->priv; + struct ftsdc010_mmc __iomem *regs = chip->regs; + if (readl(®s->status) & FTSDC010_STATUS_WRITE_PROT) { + printf("ftsdc010: write protected\n"); + chip->wprot = 1; + } + + return 0; +} + +static int ftsdc010_init(struct mmc *mmc) +{ + struct ftsdc010_chip *chip = mmc->priv; + struct ftsdc010_mmc __iomem *regs = chip->regs; + uint32_t ts; + + chip->fifo = (readl(®s->feature) & 0xff) << 2; + + /* 1. chip reset */ + writel(FTSDC010_CMD_SDC_RST, ®s->cmd); + for (ts = get_timer(0); get_timer(ts) < CFG_RST_TIMEOUT; ) { + if (readl(®s->cmd) & FTSDC010_CMD_SDC_RST) + continue; + break; + } + if (readl(®s->cmd) & FTSDC010_CMD_SDC_RST) { + printf("ftsdc010: reset failed\n"); + return -EOPNOTSUPP; + } + + /* 2. enter low speed mode (400k card detection) */ + ftsdc010_clkset(mmc, 400000); + + /* 3. interrupt disabled */ + writel(0, ®s->int_mask); + + return 0; +} + +static int ftsdc010_probe(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + return ftsdc010_init(mmc); +} + +const struct dm_mmc_ops dm_ftsdc010_mmc_ops = { + .send_cmd = ftsdc010_request, + .set_ios = ftsdc010_set_ios, + .get_cd = ftsdc010_get_cd, + .get_wp = ftsdc010_get_wp, +}; + +static void ftsdc_setup_cfg(struct mmc_config *cfg, const char *name, int buswidth, + uint caps, u32 max_clk, u32 min_clk) +{ + cfg->name = name; + cfg->f_min = min_clk; + cfg->f_max = max_clk; + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + cfg->host_caps = caps; + if (buswidth == 8) { + cfg->host_caps |= MMC_MODE_8BIT; + cfg->host_caps &= ~MMC_MODE_4BIT; + } else { + cfg->host_caps |= MMC_MODE_4BIT; + cfg->host_caps &= ~MMC_MODE_8BIT; + } + cfg->part_type = PART_TYPE_DOS; + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; +} + +static int ftsdc010_mmc_of_to_plat(struct udevice *dev) +{ +#if !CONFIG_IS_ENABLED(OF_PLATDATA) + struct ftsdc_priv *priv = dev_get_priv(dev); + struct ftsdc010_chip *chip = &priv->chip; + chip->name = dev->name; + chip->ioaddr = dev_read_addr_ptr(dev); + chip->buswidth = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), + "bus-width", 4); + chip->priv = dev; + priv->fifo_depth = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), + "fifo-depth", 0); + priv->fifo_mode = fdtdec_get_bool(gd->fdt_blob, dev_of_offset(dev), + "fifo-mode"); + if (fdtdec_get_int_array(gd->fdt_blob, dev_of_offset(dev), + "clock-freq-min-max", priv->minmax, 2)) { + int val = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), + "max-frequency", -EINVAL); + if (val < 0) + return val; + + priv->minmax[0] = 400000; /* 400 kHz */ + priv->minmax[1] = val; + } else { + debug("%s: 'clock-freq-min-max' property was deprecated.\n", + __func__); + } +#endif + chip->sclk = priv->minmax[1]; + chip->regs = chip->ioaddr; + return 0; +} + +static int ftsdc010_mmc_probe(struct udevice *dev) +{ + struct ftsdc010_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct ftsdc_priv *priv = dev_get_priv(dev); + struct ftsdc010_chip *chip = &priv->chip; + struct udevice *pwr_dev __maybe_unused; + +#if CONFIG_IS_ENABLED(OF_PLATDATA) + int ret; + struct ftsdc010 *dtplat = &plat->dtplat; + chip->name = dev->name; + chip->ioaddr = map_sysmem(dtplat->reg[0], dtplat->reg[1]); + chip->buswidth = dtplat->bus_width; + chip->priv = dev; + chip->dev_index = 1; + memcpy(priv->minmax, dtplat->clock_freq_min_max, sizeof(priv->minmax)); + ret = clk_get_by_driver_info(dev, dtplat->clocks, &priv->clk); + if (ret < 0) + return ret; +#endif + + if (dev_read_bool(dev, "cap-mmc-highspeed") || \ + dev_read_bool(dev, "cap-sd-highspeed")) + chip->caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz; + + ftsdc_setup_cfg(&plat->cfg, dev->name, chip->buswidth, chip->caps, + priv->minmax[1] , priv->minmax[0]); + chip->mmc = &plat->mmc; + chip->mmc->priv = &priv->chip; + chip->mmc->dev = dev; + upriv->mmc = chip->mmc; + return ftsdc010_probe(dev); +} + +int ftsdc010_mmc_bind(struct udevice *dev) +{ + struct ftsdc010_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id ftsdc010_mmc_ids[] = { + { .compatible = "andestech,atfsdc010" }, + { } +}; + +U_BOOT_DRIVER(ftsdc010_mmc) = { + .name = "ftsdc010_mmc", + .id = UCLASS_MMC, + .of_match = ftsdc010_mmc_ids, + .of_to_plat = ftsdc010_mmc_of_to_plat, + .ops = &dm_ftsdc010_mmc_ops, + .bind = ftsdc010_mmc_bind, + .probe = ftsdc010_mmc_probe, + .priv_auto = sizeof(struct ftsdc_priv), + .plat_auto = sizeof(struct ftsdc010_plat), +}; diff --git a/roms/u-boot/drivers/mmc/ftsdc010_mci.h b/roms/u-boot/drivers/mmc/ftsdc010_mci.h new file mode 100644 index 000000000..782d92be2 --- /dev/null +++ b/roms/u-boot/drivers/mmc/ftsdc010_mci.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Faraday FTSDC010 Secure Digital Memory Card Host Controller + * + * Copyright (C) 2011 Andes Technology Corporation + * Macpaul Lin, Andes Technology Corporation <macpaul@andestech.com> + */ +#include <mmc.h> + +#ifndef __FTSDC010_MCI_H +#define __FTSDC010_MCI_H + +struct ftsdc010_chip { + void __iomem *regs; + uint32_t wprot; /* write protected (locked) */ + uint32_t rate; /* actual SD clock in Hz */ + uint32_t sclk; /* FTSDC010 source clock in Hz */ + uint32_t fifo; /* fifo depth in bytes */ + uint32_t acmd; + struct mmc_config cfg; /* mmc configuration */ + const char *name; + void *ioaddr; + unsigned int caps; + unsigned int version; + unsigned int clock; + unsigned int bus_hz; + unsigned int div; + int dev_index; + int dev_id; + int buswidth; + u32 fifoth_val; + struct mmc *mmc; + void *priv; + bool fifo_mode; +}; + +#endif /* __FTSDC010_MCI_H */ diff --git a/roms/u-boot/drivers/mmc/gen_atmel_mci.c b/roms/u-boot/drivers/mmc/gen_atmel_mci.c new file mode 100644 index 000000000..da8142503 --- /dev/null +++ b/roms/u-boot/drivers/mmc/gen_atmel_mci.c @@ -0,0 +1,631 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2010 + * Rob Emanuele <rob@emanuele.us> + * Reinhard Meyer, EMK Elektronik <reinhard.meyer@emk-elektronik.de> + * + * Original Driver: + * Copyright (C) 2004-2006 Atmel Corporation + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <log.h> +#include <mmc.h> +#include <part.h> +#include <malloc.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <asm/byteorder.h> +#include <asm/arch/clk.h> +#include <asm/arch/hardware.h> +#include "atmel_mci.h" + +#ifndef CONFIG_SYS_MMC_CLK_OD +# define CONFIG_SYS_MMC_CLK_OD 150000 +#endif + +#define MMC_DEFAULT_BLKLEN 512 + +#if defined(CONFIG_ATMEL_MCI_PORTB) +# define MCI_BUS 1 +#else +# define MCI_BUS 0 +#endif + +#ifdef CONFIG_DM_MMC +struct atmel_mci_plat { + struct mmc mmc; + struct mmc_config cfg; + struct atmel_mci *mci; +}; +#endif + +struct atmel_mci_priv { +#ifndef CONFIG_DM_MMC + struct mmc_config cfg; + struct atmel_mci *mci; +#endif + unsigned int initialized:1; + unsigned int curr_clk; +#ifdef CONFIG_DM_MMC + ulong bus_clk_rate; +#endif +}; + +/* Read Atmel MCI IP version */ +static unsigned int atmel_mci_get_version(struct atmel_mci *mci) +{ + return readl(&mci->version) & 0x00000fff; +} + +/* + * Print command and status: + * + * - always when DEBUG is defined + * - on command errors + */ +static void dump_cmd(u32 cmdr, u32 arg, u32 status, const char* msg) +{ + debug("gen_atmel_mci: CMDR %08x (%2u) ARGR %08x (SR: %08x) %s\n", + cmdr, cmdr & 0x3F, arg, status, msg); +} + +static inline void mci_set_blklen(atmel_mci_t *mci, int blklen) +{ + unsigned int version = atmel_mci_get_version(mci); + + blklen &= 0xfffc; + + /* MCI IP version >= 0x200 has blkr */ + if (version >= 0x200) + writel(MMCI_BFINS(BLKLEN, blklen, readl(&mci->blkr)), + &mci->blkr); + else + writel(MMCI_BFINS(BLKLEN, blklen, readl(&mci->mr)), &mci->mr); +} + +/* Setup for MCI Clock and Block Size */ +#ifdef CONFIG_DM_MMC +static void mci_set_mode(struct udevice *dev, u32 hz, u32 blklen) +{ + struct atmel_mci_plat *plat = dev_get_plat(dev); + struct atmel_mci_priv *priv = dev_get_priv(dev); + struct mmc *mmc = &plat->mmc; + u32 bus_hz = priv->bus_clk_rate; + atmel_mci_t *mci = plat->mci; +#else +static void mci_set_mode(struct mmc *mmc, u32 hz, u32 blklen) +{ + struct atmel_mci_priv *priv = mmc->priv; + u32 bus_hz = get_mci_clk_rate(); + atmel_mci_t *mci = priv->mci; +#endif + + u32 clkdiv = 255; + unsigned int version = atmel_mci_get_version(mci); + u32 clkodd = 0; + u32 mr; + + debug("mci: bus_hz is %u, setting clock %u Hz, block size %u\n", + bus_hz, hz, blklen); + if (hz > 0) { + if (version >= 0x500) { + clkdiv = DIV_ROUND_UP(bus_hz, hz) - 2; + if (clkdiv > 511) + clkdiv = 511; + + clkodd = clkdiv & 1; + clkdiv >>= 1; + + debug("mci: setting clock %u Hz, block size %u\n", + bus_hz / (clkdiv * 2 + clkodd + 2), blklen); + } else { + /* find clkdiv yielding a rate <= than requested */ + for (clkdiv = 0; clkdiv < 255; clkdiv++) { + if ((bus_hz / (clkdiv + 1) / 2) <= hz) + break; + } + debug("mci: setting clock %u Hz, block size %u\n", + (bus_hz / (clkdiv + 1)) / 2, blklen); + + } + } + if (version >= 0x500) + priv->curr_clk = bus_hz / (clkdiv * 2 + clkodd + 2); + else + priv->curr_clk = (bus_hz / (clkdiv + 1)) / 2; + + mr = MMCI_BF(CLKDIV, clkdiv); + + /* MCI IP version >= 0x200 has R/WPROOF */ + if (version >= 0x200) + mr |= MMCI_BIT(RDPROOF) | MMCI_BIT(WRPROOF); + + /* + * MCI IP version >= 0x500 use bit 16 as clkodd. + * MCI IP version < 0x500 use upper 16 bits for blklen. + */ + if (version >= 0x500) + mr |= MMCI_BF(CLKODD, clkodd); + + writel(mr, &mci->mr); + + mci_set_blklen(mci, blklen); + + if (mmc->card_caps & mmc->cfg->host_caps & MMC_MODE_HS) + writel(MMCI_BIT(HSMODE), &mci->cfg); + + priv->initialized = 1; +} + +/* Return the CMDR with flags for a given command and data packet */ +static u32 mci_encode_cmd( + struct mmc_cmd *cmd, struct mmc_data *data, u32* error_flags) +{ + u32 cmdr = 0; + + /* Default Flags for Errors */ + *error_flags |= (MMCI_BIT(DTOE) | MMCI_BIT(RDIRE) | MMCI_BIT(RENDE) | + MMCI_BIT(RINDE) | MMCI_BIT(RTOE)); + + /* Default Flags for the Command */ + cmdr |= MMCI_BIT(MAXLAT); + + if (data) { + cmdr |= MMCI_BF(TRCMD, 1); + if (data->blocks > 1) + cmdr |= MMCI_BF(TRTYP, 1); + if (data->flags & MMC_DATA_READ) + cmdr |= MMCI_BIT(TRDIR); + } + + if (cmd->resp_type & MMC_RSP_CRC) + *error_flags |= MMCI_BIT(RCRCE); + if (cmd->resp_type & MMC_RSP_136) + cmdr |= MMCI_BF(RSPTYP, 2); + else if (cmd->resp_type & MMC_RSP_BUSY) + cmdr |= MMCI_BF(RSPTYP, 3); + else if (cmd->resp_type & MMC_RSP_PRESENT) + cmdr |= MMCI_BF(RSPTYP, 1); + + return cmdr | MMCI_BF(CMDNB, cmd->cmdidx); +} + +/* Entered into function pointer in mci_send_cmd */ +static u32 mci_data_read(atmel_mci_t *mci, u32* data, u32 error_flags) +{ + u32 status; + + do { + status = readl(&mci->sr); + if (status & (error_flags | MMCI_BIT(OVRE))) + goto io_fail; + } while (!(status & MMCI_BIT(RXRDY))); + + if (status & MMCI_BIT(RXRDY)) { + *data = readl(&mci->rdr); + status = 0; + } +io_fail: + return status; +} + +/* Entered into function pointer in mci_send_cmd */ +static u32 mci_data_write(atmel_mci_t *mci, u32* data, u32 error_flags) +{ + u32 status; + + do { + status = readl(&mci->sr); + if (status & (error_flags | MMCI_BIT(UNRE))) + goto io_fail; + } while (!(status & MMCI_BIT(TXRDY))); + + if (status & MMCI_BIT(TXRDY)) { + writel(*data, &mci->tdr); + status = 0; + } +io_fail: + return status; +} + +/* + * Entered into mmc structure during driver init + * + * Sends a command out on the bus and deals with the block data. + * Takes the mmc pointer, a command pointer, and an optional data pointer. + */ +#ifdef CONFIG_DM_MMC +static int atmel_mci_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct atmel_mci_plat *plat = dev_get_plat(dev); + struct atmel_mci_priv *priv = dev_get_priv(dev); + atmel_mci_t *mci = plat->mci; +#else +static int +mci_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) +{ + struct atmel_mci_priv *priv = mmc->priv; + atmel_mci_t *mci = priv->mci; +#endif + u32 cmdr; + u32 error_flags = 0; + u32 status; + + if (!priv->initialized) { + puts ("MCI not initialized!\n"); + return -ECOMM; + } + + /* Figure out the transfer arguments */ + cmdr = mci_encode_cmd(cmd, data, &error_flags); + + mci_set_blklen(mci, data->blocksize); + + /* For multi blocks read/write, set the block register */ + if ((cmd->cmdidx == MMC_CMD_READ_MULTIPLE_BLOCK) + || (cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK)) + writel(data->blocks | MMCI_BF(BLKLEN, data->blocksize), + &mci->blkr); + + /* Send the command */ + writel(cmd->cmdarg, &mci->argr); + writel(cmdr, &mci->cmdr); + +#ifdef DEBUG + dump_cmd(cmdr, cmd->cmdarg, 0, "DEBUG"); +#endif + + /* Wait for the command to complete */ + while (!((status = readl(&mci->sr)) & MMCI_BIT(CMDRDY))); + + if ((status & error_flags) & MMCI_BIT(RTOE)) { + dump_cmd(cmdr, cmd->cmdarg, status, "Command Time Out"); + return -ETIMEDOUT; + } else if (status & error_flags) { + dump_cmd(cmdr, cmd->cmdarg, status, "Command Failed"); + return -ECOMM; + } + + /* Copy the response to the response buffer */ + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[0] = readl(&mci->rspr); + cmd->response[1] = readl(&mci->rspr1); + cmd->response[2] = readl(&mci->rspr2); + cmd->response[3] = readl(&mci->rspr3); + } else + cmd->response[0] = readl(&mci->rspr); + + /* transfer all of the blocks */ + if (data) { + u32 word_count, block_count; + u32* ioptr; + u32 i; + u32 (*mci_data_op) + (atmel_mci_t *mci, u32* data, u32 error_flags); + + if (data->flags & MMC_DATA_READ) { + mci_data_op = mci_data_read; + ioptr = (u32*)data->dest; + } else { + mci_data_op = mci_data_write; + ioptr = (u32*)data->src; + } + + status = 0; + for (block_count = 0; + block_count < data->blocks && !status; + block_count++) { + word_count = 0; + do { + status = mci_data_op(mci, ioptr, error_flags); + word_count++; + ioptr++; + } while (!status && word_count < (data->blocksize/4)); +#ifdef DEBUG + if (data->flags & MMC_DATA_READ) + { + u32 cnt = word_count * 4; + printf("Read Data:\n"); + print_buffer(0, data->dest + cnt * block_count, + 1, cnt, 0); + } +#endif + if (status) { + dump_cmd(cmdr, cmd->cmdarg, status, + "Data Transfer Failed"); + return -ECOMM; + } + } + + /* Wait for Transfer End */ + i = 0; + do { + status = readl(&mci->sr); + + if (status & error_flags) { + dump_cmd(cmdr, cmd->cmdarg, status, + "DTIP Wait Failed"); + return -ECOMM; + } + i++; + } while ((status & MMCI_BIT(DTIP)) && i < 10000); + if (status & MMCI_BIT(DTIP)) { + dump_cmd(cmdr, cmd->cmdarg, status, + "XFER DTIP never unset, ignoring"); + } + } + + /* + * After the switch command, wait for 8 clocks before the next + * command + */ + if (cmd->cmdidx == MMC_CMD_SWITCH) + udelay(8*1000000 / priv->curr_clk); /* 8 clk in us */ + + return 0; +} + +#ifdef CONFIG_DM_MMC +static int atmel_mci_set_ios(struct udevice *dev) +{ + struct atmel_mci_plat *plat = dev_get_plat(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + atmel_mci_t *mci = plat->mci; +#else +/* Entered into mmc structure during driver init */ +static int mci_set_ios(struct mmc *mmc) +{ + struct atmel_mci_priv *priv = mmc->priv; + atmel_mci_t *mci = priv->mci; +#endif + int bus_width = mmc->bus_width; + unsigned int version = atmel_mci_get_version(mci); + int busw; + + /* Set the clock speed */ +#ifdef CONFIG_DM_MMC + mci_set_mode(dev, mmc->clock, MMC_DEFAULT_BLKLEN); +#else + mci_set_mode(mmc, mmc->clock, MMC_DEFAULT_BLKLEN); +#endif + + /* + * set the bus width and select slot for this interface + * there is no capability for multiple slots on the same interface yet + */ + if ((version & 0xf00) >= 0x300) { + switch (bus_width) { + case 8: + busw = 3; + break; + case 4: + busw = 2; + break; + default: + busw = 0; + break; + } + + writel(busw << 6 | MMCI_BF(SCDSEL, MCI_BUS), &mci->sdcr); + } else { + busw = (bus_width == 4) ? 1 : 0; + + writel(busw << 7 | MMCI_BF(SCDSEL, MCI_BUS), &mci->sdcr); + } + + return 0; +} + +#ifdef CONFIG_DM_MMC +static int atmel_mci_hw_init(struct udevice *dev) +{ + struct atmel_mci_plat *plat = dev_get_plat(dev); + atmel_mci_t *mci = plat->mci; +#else +/* Entered into mmc structure during driver init */ +static int mci_init(struct mmc *mmc) +{ + struct atmel_mci_priv *priv = mmc->priv; + atmel_mci_t *mci = priv->mci; +#endif + + /* Initialize controller */ + writel(MMCI_BIT(SWRST), &mci->cr); /* soft reset */ + writel(MMCI_BIT(PWSDIS), &mci->cr); /* disable power save */ + writel(MMCI_BIT(MCIEN), &mci->cr); /* enable mci */ + writel(MMCI_BF(SCDSEL, MCI_BUS), &mci->sdcr); /* select port */ + + /* This delay can be optimized, but stick with max value */ + writel(0x7f, &mci->dtor); + /* Disable Interrupts */ + writel(~0UL, &mci->idr); + + /* Set default clocks and blocklen */ +#ifdef CONFIG_DM_MMC + mci_set_mode(dev, CONFIG_SYS_MMC_CLK_OD, MMC_DEFAULT_BLKLEN); +#else + mci_set_mode(mmc, CONFIG_SYS_MMC_CLK_OD, MMC_DEFAULT_BLKLEN); +#endif + + return 0; +} + +#ifndef CONFIG_DM_MMC +static const struct mmc_ops atmel_mci_ops = { + .send_cmd = mci_send_cmd, + .set_ios = mci_set_ios, + .init = mci_init, +}; + +/* + * This is the only exported function + * + * Call it with the MCI register base address + */ +int atmel_mci_init(void *regs) +{ + struct mmc *mmc; + struct mmc_config *cfg; + struct atmel_mci_priv *priv; + unsigned int version; + + priv = calloc(1, sizeof(*priv)); + if (!priv) + return -ENOMEM; + + cfg = &priv->cfg; + + cfg->name = "mci"; + cfg->ops = &atmel_mci_ops; + + priv->mci = (struct atmel_mci *)regs; + priv->initialized = 0; + + /* need to be able to pass these in on a board by board basis */ + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + version = atmel_mci_get_version(priv->mci); + if ((version & 0xf00) >= 0x300) { + cfg->host_caps = MMC_MODE_8BIT; + cfg->host_caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz; + } + + cfg->host_caps |= MMC_MODE_4BIT; + + /* + * min and max frequencies determined by + * max and min of clock divider + */ + cfg->f_min = get_mci_clk_rate() / (2*256); + cfg->f_max = get_mci_clk_rate() / (2*1); + + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + mmc = mmc_create(cfg, priv); + + if (mmc == NULL) { + free(priv); + return -ENODEV; + } + /* NOTE: possibly leaking the priv structure */ + + return 0; +} +#endif + +#ifdef CONFIG_DM_MMC +static const struct dm_mmc_ops atmel_mci_mmc_ops = { + .send_cmd = atmel_mci_send_cmd, + .set_ios = atmel_mci_set_ios, +}; + +static void atmel_mci_setup_cfg(struct udevice *dev) +{ + struct atmel_mci_plat *plat = dev_get_plat(dev); + struct atmel_mci_priv *priv = dev_get_priv(dev); + struct mmc_config *cfg; + u32 version; + + cfg = &plat->cfg; + cfg->name = "Atmel mci"; + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + + /* + * If the version is above 3.0, the capabilities of the 8-bit + * bus width and high speed are supported. + */ + version = atmel_mci_get_version(plat->mci); + if ((version & 0xf00) >= 0x300) { + cfg->host_caps = MMC_MODE_8BIT | + MMC_MODE_HS | MMC_MODE_HS_52MHz; + } + + cfg->host_caps |= MMC_MODE_4BIT; + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + cfg->f_min = priv->bus_clk_rate / (2 * 256); + cfg->f_max = priv->bus_clk_rate / 2; +} + +static int atmel_mci_enable_clk(struct udevice *dev) +{ + struct atmel_mci_priv *priv = dev_get_priv(dev); + struct clk clk; + ulong clk_rate; + int ret = 0; + + ret = clk_get_by_index(dev, 0, &clk); + if (ret) { + ret = -EINVAL; + goto failed; + } + + ret = clk_enable(&clk); + if (ret) + goto failed; + + clk_rate = clk_get_rate(&clk); + if (!clk_rate) { + ret = -EINVAL; + goto failed; + } + + priv->bus_clk_rate = clk_rate; + +failed: + clk_free(&clk); + + return ret; +} + +static int atmel_mci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct atmel_mci_plat *plat = dev_get_plat(dev); + struct mmc *mmc; + int ret; + + ret = atmel_mci_enable_clk(dev); + if (ret) + return ret; + + plat->mci = dev_read_addr_ptr(dev); + + atmel_mci_setup_cfg(dev); + + mmc = &plat->mmc; + mmc->cfg = &plat->cfg; + mmc->dev = dev; + upriv->mmc = mmc; + + atmel_mci_hw_init(dev); + + return 0; +} + +static int atmel_mci_bind(struct udevice *dev) +{ + struct atmel_mci_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id atmel_mci_ids[] = { + { .compatible = "atmel,hsmci" }, + { } +}; + +U_BOOT_DRIVER(atmel_mci) = { + .name = "atmel-mci", + .id = UCLASS_MMC, + .of_match = atmel_mci_ids, + .bind = atmel_mci_bind, + .probe = atmel_mci_probe, + .plat_auto = sizeof(struct atmel_mci_plat), + .priv_auto = sizeof(struct atmel_mci_priv), + .ops = &atmel_mci_mmc_ops, +}; +#endif diff --git a/roms/u-boot/drivers/mmc/hi6220_dw_mmc.c b/roms/u-boot/drivers/mmc/hi6220_dw_mmc.c new file mode 100644 index 000000000..2cec5b9ae --- /dev/null +++ b/roms/u-boot/drivers/mmc/hi6220_dw_mmc.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2015 Linaro + * peter.griffin <peter.griffin@linaro.org> + */ + +#include <common.h> +#include <dm.h> +#include <dwmmc.h> +#include <errno.h> +#include <fdtdec.h> +#include <malloc.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct hi6220_dwmmc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct hi6220_dwmmc_priv_data { + struct dwmci_host host; +}; + +struct hisi_mmc_data { + unsigned int clock; + bool use_fifo; +}; + +static int hi6220_dwmmc_of_to_plat(struct udevice *dev) +{ + struct hi6220_dwmmc_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + + host->name = dev->name; + host->ioaddr = dev_read_addr_ptr(dev); + host->buswidth = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), + "bus-width", 4); + + /* use non-removable property for differentiating SD card and eMMC */ + if (dev_read_bool(dev, "non-removable")) + host->dev_index = 0; + else + host->dev_index = 1; + + host->priv = priv; + + return 0; +} + +static int hi6220_dwmmc_probe(struct udevice *dev) +{ + struct hi6220_dwmmc_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct hi6220_dwmmc_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + struct hisi_mmc_data *mmc_data; + + mmc_data = (struct hisi_mmc_data *)dev_get_driver_data(dev); + + /* Use default bus speed due to absence of clk driver */ + host->bus_hz = mmc_data->clock; + + dwmci_setup_cfg(&plat->cfg, host, host->bus_hz, 400000); + host->mmc = &plat->mmc; + + host->fifo_mode = mmc_data->use_fifo; + host->mmc->priv = &priv->host; + upriv->mmc = host->mmc; + host->mmc->dev = dev; + + return dwmci_probe(dev); +} + +static int hi6220_dwmmc_bind(struct udevice *dev) +{ + struct hi6220_dwmmc_plat *plat = dev_get_plat(dev); + int ret; + + ret = dwmci_bind(dev, &plat->mmc, &plat->cfg); + if (ret) + return ret; + + return 0; +} + +static const struct hisi_mmc_data hi3660_mmc_data = { + .clock = 3200000, + .use_fifo = true, +}; + +static const struct hisi_mmc_data hi6220_mmc_data = { + .clock = 50000000, + .use_fifo = false, +}; + +static const struct udevice_id hi6220_dwmmc_ids[] = { + { .compatible = "hisilicon,hi6220-dw-mshc", + .data = (ulong)&hi6220_mmc_data }, + { .compatible = "hisilicon,hi3798cv200-dw-mshc", + .data = (ulong)&hi6220_mmc_data }, + { .compatible = "hisilicon,hi3660-dw-mshc", + .data = (ulong)&hi3660_mmc_data }, + { } +}; + +U_BOOT_DRIVER(hi6220_dwmmc_drv) = { + .name = "hi6220_dwmmc", + .id = UCLASS_MMC, + .of_match = hi6220_dwmmc_ids, + .of_to_plat = hi6220_dwmmc_of_to_plat, + .ops = &dm_dwmci_ops, + .bind = hi6220_dwmmc_bind, + .probe = hi6220_dwmmc_probe, + .priv_auto = sizeof(struct hi6220_dwmmc_priv_data), + .plat_auto = sizeof(struct hi6220_dwmmc_plat), +}; diff --git a/roms/u-boot/drivers/mmc/iproc_sdhci.c b/roms/u-boot/drivers/mmc/iproc_sdhci.c new file mode 100644 index 000000000..11d86ad65 --- /dev/null +++ b/roms/u-boot/drivers/mmc/iproc_sdhci.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2019 Broadcom. + * + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <malloc.h> +#include <sdhci.h> +#include <asm/global_data.h> +#include "mmc_private.h" +#include <linux/delay.h> + +#define MAX_TUNING_LOOP 40 + +DECLARE_GLOBAL_DATA_PTR; + +struct sdhci_iproc_host { + struct sdhci_host host; + u32 shadow_cmd; + u32 shadow_blk; +}; + +#define REG_OFFSET_IN_BITS(reg) ((reg) << 3 & 0x18) + +static inline struct sdhci_iproc_host *to_iproc(struct sdhci_host *host) +{ + return (struct sdhci_iproc_host *)host; +} + +#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS +static u32 sdhci_iproc_readl(struct sdhci_host *host, int reg) +{ + u32 val = readl(host->ioaddr + reg); +#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS_TRACE + printf("%s %d: readl [0x%02x] 0x%08x\n", + host->name, host->index, reg, val); +#endif + return val; +} + +static u16 sdhci_iproc_readw(struct sdhci_host *host, int reg) +{ + u32 val = sdhci_iproc_readl(host, (reg & ~3)); + u16 word = val >> REG_OFFSET_IN_BITS(reg) & 0xffff; + return word; +} + +static u8 sdhci_iproc_readb(struct sdhci_host *host, int reg) +{ + u32 val = sdhci_iproc_readl(host, (reg & ~3)); + u8 byte = val >> REG_OFFSET_IN_BITS(reg) & 0xff; + return byte; +} + +static void sdhci_iproc_writel(struct sdhci_host *host, u32 val, int reg) +{ + u32 clock = 0; +#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS_TRACE + printf("%s %d: writel [0x%02x] 0x%08x\n", + host->name, host->index, reg, val); +#endif + writel(val, host->ioaddr + reg); + + if (host->mmc) + clock = host->mmc->clock; + if (clock <= 400000) { + /* Round up to micro-second four SD clock delay */ + if (clock) + udelay((4 * 1000000 + clock - 1) / clock); + else + udelay(10); + } +} + +/* + * The Arasan has a bugette whereby it may lose the content of successive + * writes to the same register that are within two SD-card clock cycles of + * each other (a clock domain crossing problem). The data + * register does not have this problem, which is just as well - otherwise we'd + * have to nobble the DMA engine too. + * + * This wouldn't be a problem with the code except that we can only write the + * controller with 32-bit writes. So two different 16-bit registers are + * written back to back creates the problem. + * + * In reality, this only happens when SDHCI_BLOCK_SIZE and SDHCI_BLOCK_COUNT + * are written followed by SDHCI_TRANSFER_MODE and SDHCI_COMMAND. + * The BLOCK_SIZE and BLOCK_COUNT are meaningless until a command issued so + * the work around can be further optimized. We can keep shadow values of + * BLOCK_SIZE, BLOCK_COUNT, and TRANSFER_MODE until a COMMAND is issued. + * Then, write the BLOCK_SIZE+BLOCK_COUNT in a single 32-bit write followed + * by the TRANSFER+COMMAND in another 32-bit write. + */ +static void sdhci_iproc_writew(struct sdhci_host *host, u16 val, int reg) +{ + struct sdhci_iproc_host *iproc_host = to_iproc(host); + u32 word_shift = REG_OFFSET_IN_BITS(reg); + u32 mask = 0xffff << word_shift; + u32 oldval, newval; + + if (reg == SDHCI_COMMAND) { + /* Write the block now as we are issuing a command */ + if (iproc_host->shadow_blk != 0) { + sdhci_iproc_writel(host, iproc_host->shadow_blk, + SDHCI_BLOCK_SIZE); + iproc_host->shadow_blk = 0; + } + oldval = iproc_host->shadow_cmd; + } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) { + /* Block size and count are stored in shadow reg */ + oldval = iproc_host->shadow_blk; + } else { + /* Read reg, all other registers are not shadowed */ + oldval = sdhci_iproc_readl(host, (reg & ~3)); + } + newval = (oldval & ~mask) | (val << word_shift); + + if (reg == SDHCI_TRANSFER_MODE) { + /* Save the transfer mode until the command is issued */ + iproc_host->shadow_cmd = newval; + } else if (reg == SDHCI_BLOCK_SIZE || reg == SDHCI_BLOCK_COUNT) { + /* Save the block info until the command is issued */ + iproc_host->shadow_blk = newval; + } else { + /* Command or other regular 32-bit write */ + sdhci_iproc_writel(host, newval, reg & ~3); + } +} + +static void sdhci_iproc_writeb(struct sdhci_host *host, u8 val, int reg) +{ + u32 oldval = sdhci_iproc_readl(host, (reg & ~3)); + u32 byte_shift = REG_OFFSET_IN_BITS(reg); + u32 mask = 0xff << byte_shift; + u32 newval = (oldval & ~mask) | (val << byte_shift); + + sdhci_iproc_writel(host, newval, reg & ~3); +} +#endif + +static int sdhci_iproc_set_ios_post(struct sdhci_host *host) +{ + struct mmc *mmc = (struct mmc *)host->mmc; + u32 ctrl; + + if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180) { + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl |= SDHCI_CTRL_VDD_180; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + } + + sdhci_set_uhs_timing(host); + return 0; +} + +static void sdhci_start_tuning(struct sdhci_host *host) +{ + u32 ctrl; + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl |= SDHCI_CTRL_EXEC_TUNING; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + + sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_INT_ENABLE); + sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_SIGNAL_ENABLE); +} + +static void sdhci_end_tuning(struct sdhci_host *host) +{ + /* Enable only interrupts served by the SD controller */ + sdhci_writel(host, SDHCI_INT_DATA_MASK | SDHCI_INT_CMD_MASK, + SDHCI_INT_ENABLE); + /* Mask all sdhci interrupt sources */ + sdhci_writel(host, 0x0, SDHCI_SIGNAL_ENABLE); +} + +static int sdhci_iproc_execute_tuning(struct mmc *mmc, u8 opcode) +{ + struct mmc_cmd cmd; + u32 ctrl; + u32 blocksize = SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 64); + struct sdhci_host *host = dev_get_priv(mmc->dev); + char tuning_loop_counter = MAX_TUNING_LOOP; + int ret = 0; + + sdhci_start_tuning(host); + + cmd.cmdidx = opcode; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 0; + + if (opcode == MMC_CMD_SEND_TUNING_BLOCK_HS200 && mmc->bus_width == 8) + blocksize = SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, 128); + + sdhci_writew(host, blocksize, SDHCI_BLOCK_SIZE); + sdhci_writew(host, 1, SDHCI_BLOCK_COUNT); + sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE); + + do { + mmc_send_cmd(mmc, &cmd, NULL); + if (opcode == MMC_CMD_SEND_TUNING_BLOCK) + /* + * For tuning command, do not do busy loop. As tuning + * is happening (CLK-DATA latching for setup/hold time + * requirements), give time to complete + */ + udelay(1); + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + + if (tuning_loop_counter-- == 0) + break; + + } while (ctrl & SDHCI_CTRL_EXEC_TUNING); + + if (tuning_loop_counter < 0 || (!(ctrl & SDHCI_CTRL_TUNED_CLK))) { + ctrl &= ~(SDHCI_CTRL_TUNED_CLK | SDHCI_CTRL_EXEC_TUNING); + sdhci_writel(host, ctrl, SDHCI_HOST_CONTROL2); + printf("%s:Tuning failed, opcode = 0x%02x\n", __func__, opcode); + ret = -EIO; + } + + sdhci_end_tuning(host); + + return ret; +} + +static struct sdhci_ops sdhci_platform_ops = { +#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS + .read_l = sdhci_iproc_readl, + .read_w = sdhci_iproc_readw, + .read_b = sdhci_iproc_readb, + .write_l = sdhci_iproc_writel, + .write_w = sdhci_iproc_writew, + .write_b = sdhci_iproc_writeb, +#endif + .set_ios_post = sdhci_iproc_set_ios_post, + .platform_execute_tuning = sdhci_iproc_execute_tuning, +}; + +struct iproc_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +static int iproc_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct iproc_sdhci_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + struct sdhci_iproc_host *iproc_host; + int node = dev_of_offset(dev); + u32 f_min_max[2]; + int ret; + + iproc_host = malloc(sizeof(struct sdhci_iproc_host)); + if (!iproc_host) { + printf("%s: sdhci host malloc fail!\n", __func__); + return -ENOMEM; + } + iproc_host->shadow_cmd = 0; + iproc_host->shadow_blk = 0; + + host->name = dev->name; + host->ioaddr = dev_read_addr_ptr(dev); + host->quirks = SDHCI_QUIRK_BROKEN_R1B; + host->host_caps = MMC_MODE_DDR_52MHz; + host->index = fdtdec_get_uint(gd->fdt_blob, node, "index", 0); + host->ops = &sdhci_platform_ops; + host->version = sdhci_readw(host, SDHCI_HOST_VERSION); + ret = fdtdec_get_int_array(gd->fdt_blob, dev_of_offset(dev), + "clock-freq-min-max", f_min_max, 2); + if (ret) { + printf("sdhci: clock-freq-min-max not found\n"); + free(iproc_host); + return ret; + } + host->max_clk = f_min_max[1]; + host->bus_width = fdtdec_get_int(gd->fdt_blob, + dev_of_offset(dev), "bus-width", 4); + + /* Update host_caps for 8 bit bus width */ + if (host->bus_width == 8) + host->host_caps |= MMC_MODE_8BIT; + + memcpy(&iproc_host->host, host, sizeof(struct sdhci_host)); + + iproc_host->host.mmc = &plat->mmc; + iproc_host->host.mmc->dev = dev; + iproc_host->host.mmc->priv = &iproc_host->host; + upriv->mmc = iproc_host->host.mmc; + + ret = sdhci_setup_cfg(&plat->cfg, &iproc_host->host, + f_min_max[1], f_min_max[0]); + if (ret) { + free(iproc_host); + return ret; + } + + return sdhci_probe(dev); +} + +static int iproc_sdhci_bind(struct udevice *dev) +{ + struct iproc_sdhci_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id iproc_sdhci_ids[] = { + { .compatible = "brcm,iproc-sdhci" }, + { } +}; + +U_BOOT_DRIVER(iproc_sdhci_drv) = { + .name = "iproc_sdhci", + .id = UCLASS_MMC, + .of_match = iproc_sdhci_ids, + .ops = &sdhci_ops, + .bind = iproc_sdhci_bind, + .probe = iproc_sdhci_probe, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct iproc_sdhci_plat), +}; diff --git a/roms/u-boot/drivers/mmc/jz_mmc.c b/roms/u-boot/drivers/mmc/jz_mmc.c new file mode 100644 index 000000000..61e48ee0f --- /dev/null +++ b/roms/u-boot/drivers/mmc/jz_mmc.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Ingenic JZ MMC driver + * + * Copyright (c) 2013 Imagination Technologies + * Author: Paul Burton <paul.burton@imgtec.com> + */ + +#include <common.h> +#include <malloc.h> +#include <mmc.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <asm/unaligned.h> +#include <errno.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <mach/jz4780.h> +#include <wait_bit.h> + +/* Registers */ +#define MSC_STRPCL 0x000 +#define MSC_STAT 0x004 +#define MSC_CLKRT 0x008 +#define MSC_CMDAT 0x00c +#define MSC_RESTO 0x010 +#define MSC_RDTO 0x014 +#define MSC_BLKLEN 0x018 +#define MSC_NOB 0x01c +#define MSC_SNOB 0x020 +#define MSC_IMASK 0x024 +#define MSC_IREG 0x028 +#define MSC_CMD 0x02c +#define MSC_ARG 0x030 +#define MSC_RES 0x034 +#define MSC_RXFIFO 0x038 +#define MSC_TXFIFO 0x03c +#define MSC_LPM 0x040 +#define MSC_DMAC 0x044 +#define MSC_DMANDA 0x048 +#define MSC_DMADA 0x04c +#define MSC_DMALEN 0x050 +#define MSC_DMACMD 0x054 +#define MSC_CTRL2 0x058 +#define MSC_RTCNT 0x05c +#define MSC_DBG 0x0fc + +/* MSC Clock and Control Register (MSC_STRPCL) */ +#define MSC_STRPCL_EXIT_MULTIPLE BIT(7) +#define MSC_STRPCL_EXIT_TRANSFER BIT(6) +#define MSC_STRPCL_START_READWAIT BIT(5) +#define MSC_STRPCL_STOP_READWAIT BIT(4) +#define MSC_STRPCL_RESET BIT(3) +#define MSC_STRPCL_START_OP BIT(2) +#define MSC_STRPCL_CLOCK_CONTROL_STOP BIT(0) +#define MSC_STRPCL_CLOCK_CONTROL_START BIT(1) + +/* MSC Status Register (MSC_STAT) */ +#define MSC_STAT_AUTO_CMD_DONE BIT(31) +#define MSC_STAT_IS_RESETTING BIT(15) +#define MSC_STAT_SDIO_INT_ACTIVE BIT(14) +#define MSC_STAT_PRG_DONE BIT(13) +#define MSC_STAT_DATA_TRAN_DONE BIT(12) +#define MSC_STAT_END_CMD_RES BIT(11) +#define MSC_STAT_DATA_FIFO_AFULL BIT(10) +#define MSC_STAT_IS_READWAIT BIT(9) +#define MSC_STAT_CLK_EN BIT(8) +#define MSC_STAT_DATA_FIFO_FULL BIT(7) +#define MSC_STAT_DATA_FIFO_EMPTY BIT(6) +#define MSC_STAT_CRC_RES_ERR BIT(5) +#define MSC_STAT_CRC_READ_ERROR BIT(4) +#define MSC_STAT_CRC_WRITE_ERROR BIT(2) +#define MSC_STAT_CRC_WRITE_ERROR_NOSTS BIT(4) +#define MSC_STAT_TIME_OUT_RES BIT(1) +#define MSC_STAT_TIME_OUT_READ BIT(0) + +/* MSC Bus Clock Control Register (MSC_CLKRT) */ +#define MSC_CLKRT_CLK_RATE_MASK 0x7 + +/* MSC Command Sequence Control Register (MSC_CMDAT) */ +#define MSC_CMDAT_IO_ABORT BIT(11) +#define MSC_CMDAT_BUS_WIDTH_1BIT (0x0 << 9) +#define MSC_CMDAT_BUS_WIDTH_4BIT (0x2 << 9) +#define MSC_CMDAT_DMA_EN BIT(8) +#define MSC_CMDAT_INIT BIT(7) +#define MSC_CMDAT_BUSY BIT(6) +#define MSC_CMDAT_STREAM_BLOCK BIT(5) +#define MSC_CMDAT_WRITE BIT(4) +#define MSC_CMDAT_DATA_EN BIT(3) +#define MSC_CMDAT_RESPONSE_MASK (0x7 << 0) +#define MSC_CMDAT_RESPONSE_NONE (0x0 << 0) /* No response */ +#define MSC_CMDAT_RESPONSE_R1 (0x1 << 0) /* Format R1 and R1b */ +#define MSC_CMDAT_RESPONSE_R2 (0x2 << 0) /* Format R2 */ +#define MSC_CMDAT_RESPONSE_R3 (0x3 << 0) /* Format R3 */ +#define MSC_CMDAT_RESPONSE_R4 (0x4 << 0) /* Format R4 */ +#define MSC_CMDAT_RESPONSE_R5 (0x5 << 0) /* Format R5 */ +#define MSC_CMDAT_RESPONSE_R6 (0x6 << 0) /* Format R6 */ + +/* MSC Interrupts Mask Register (MSC_IMASK) */ +#define MSC_IMASK_TIME_OUT_RES BIT(9) +#define MSC_IMASK_TIME_OUT_READ BIT(8) +#define MSC_IMASK_SDIO BIT(7) +#define MSC_IMASK_TXFIFO_WR_REQ BIT(6) +#define MSC_IMASK_RXFIFO_RD_REQ BIT(5) +#define MSC_IMASK_END_CMD_RES BIT(2) +#define MSC_IMASK_PRG_DONE BIT(1) +#define MSC_IMASK_DATA_TRAN_DONE BIT(0) + +/* MSC Interrupts Status Register (MSC_IREG) */ +#define MSC_IREG_TIME_OUT_RES BIT(9) +#define MSC_IREG_TIME_OUT_READ BIT(8) +#define MSC_IREG_SDIO BIT(7) +#define MSC_IREG_TXFIFO_WR_REQ BIT(6) +#define MSC_IREG_RXFIFO_RD_REQ BIT(5) +#define MSC_IREG_END_CMD_RES BIT(2) +#define MSC_IREG_PRG_DONE BIT(1) +#define MSC_IREG_DATA_TRAN_DONE BIT(0) + +struct jz_mmc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct jz_mmc_priv { + void __iomem *regs; + u32 flags; +/* priv flags */ +#define JZ_MMC_BUS_WIDTH_MASK 0x3 +#define JZ_MMC_BUS_WIDTH_1 0x0 +#define JZ_MMC_BUS_WIDTH_4 0x2 +#define JZ_MMC_BUS_WIDTH_8 0x3 +#define JZ_MMC_SENT_INIT BIT(2) +}; + +static int jz_mmc_clock_rate(void) +{ + return 24000000; +} + +#if CONFIG_IS_ENABLED(MMC_WRITE) +static inline void jz_mmc_write_data(struct jz_mmc_priv *priv, struct mmc_data *data) +{ + int sz = DIV_ROUND_UP(data->blocks * data->blocksize, 4); + const void *buf = data->src; + + while (sz--) { + u32 val = get_unaligned_le32(buf); + + wait_for_bit_le32(priv->regs + MSC_IREG, + MSC_IREG_TXFIFO_WR_REQ, + true, 10000, false); + writel(val, priv->regs + MSC_TXFIFO); + buf += 4; + } +} +#else +static void jz_mmc_write_data(struct jz_mmc_priv *priv, struct mmc_data *data) +{} +#endif + +static inline int jz_mmc_read_data(struct jz_mmc_priv *priv, struct mmc_data *data) +{ + int sz = data->blocks * data->blocksize; + void *buf = data->dest; + u32 stat, val; + + do { + stat = readl(priv->regs + MSC_STAT); + + if (stat & MSC_STAT_TIME_OUT_READ) + return -ETIMEDOUT; + if (stat & MSC_STAT_CRC_READ_ERROR) + return -EINVAL; + if (stat & MSC_STAT_DATA_FIFO_EMPTY) { + udelay(10); + continue; + } + do { + val = readl(priv->regs + MSC_RXFIFO); + if (sz == 1) + *(u8 *)buf = (u8)val; + else if (sz == 2) + put_unaligned_le16(val, buf); + else if (sz >= 4) + put_unaligned_le32(val, buf); + buf += 4; + sz -= 4; + stat = readl(priv->regs + MSC_STAT); + } while (!(stat & MSC_STAT_DATA_FIFO_EMPTY)); + } while (!(stat & MSC_STAT_DATA_TRAN_DONE)); + return 0; +} + +static int jz_mmc_send_cmd(struct mmc *mmc, struct jz_mmc_priv *priv, + struct mmc_cmd *cmd, struct mmc_data *data) +{ + u32 stat, mask, cmdat = 0; + int i, ret; + + /* stop the clock */ + writel(MSC_STRPCL_CLOCK_CONTROL_STOP, priv->regs + MSC_STRPCL); + ret = wait_for_bit_le32(priv->regs + MSC_STAT, + MSC_STAT_CLK_EN, false, 10000, false); + if (ret) + return ret; + + writel(0, priv->regs + MSC_DMAC); + + /* setup command */ + writel(cmd->cmdidx, priv->regs + MSC_CMD); + writel(cmd->cmdarg, priv->regs + MSC_ARG); + + if (data) { + /* setup data */ + cmdat |= MSC_CMDAT_DATA_EN; + if (data->flags & MMC_DATA_WRITE) + cmdat |= MSC_CMDAT_WRITE; + + writel(data->blocks, priv->regs + MSC_NOB); + writel(data->blocksize, priv->regs + MSC_BLKLEN); + } else { + writel(0, priv->regs + MSC_NOB); + writel(0, priv->regs + MSC_BLKLEN); + } + + /* setup response */ + switch (cmd->resp_type) { + case MMC_RSP_NONE: + break; + case MMC_RSP_R1: + case MMC_RSP_R1b: + cmdat |= MSC_CMDAT_RESPONSE_R1; + break; + case MMC_RSP_R2: + cmdat |= MSC_CMDAT_RESPONSE_R2; + break; + case MMC_RSP_R3: + cmdat |= MSC_CMDAT_RESPONSE_R3; + break; + default: + break; + } + + if (cmd->resp_type & MMC_RSP_BUSY) + cmdat |= MSC_CMDAT_BUSY; + + /* set init for the first command only */ + if (!(priv->flags & JZ_MMC_SENT_INIT)) { + cmdat |= MSC_CMDAT_INIT; + priv->flags |= JZ_MMC_SENT_INIT; + } + + cmdat |= (priv->flags & JZ_MMC_BUS_WIDTH_MASK) << 9; + + /* write the data setup */ + writel(cmdat, priv->regs + MSC_CMDAT); + + /* unmask interrupts */ + mask = 0xffffffff & ~(MSC_IMASK_END_CMD_RES | MSC_IMASK_TIME_OUT_RES); + if (data) { + mask &= ~MSC_IMASK_DATA_TRAN_DONE; + if (data->flags & MMC_DATA_WRITE) { + mask &= ~MSC_IMASK_TXFIFO_WR_REQ; + } else { + mask &= ~(MSC_IMASK_RXFIFO_RD_REQ | + MSC_IMASK_TIME_OUT_READ); + } + } + writel(mask, priv->regs + MSC_IMASK); + + /* clear interrupts */ + writel(0xffffffff, priv->regs + MSC_IREG); + + /* start the command (& the clock) */ + writel(MSC_STRPCL_START_OP | MSC_STRPCL_CLOCK_CONTROL_START, + priv->regs + MSC_STRPCL); + + /* wait for completion */ + for (i = 0; i < 100; i++) { + stat = readl(priv->regs + MSC_IREG); + stat &= MSC_IREG_END_CMD_RES | MSC_IREG_TIME_OUT_RES; + if (stat) + break; + mdelay(1); + } + writel(stat, priv->regs + MSC_IREG); + if (stat & MSC_IREG_TIME_OUT_RES) + return -ETIMEDOUT; + + if (cmd->resp_type & MMC_RSP_PRESENT) { + /* read the response */ + if (cmd->resp_type & MMC_RSP_136) { + u16 a, b, c, i; + + a = readw(priv->regs + MSC_RES); + for (i = 0; i < 4; i++) { + b = readw(priv->regs + MSC_RES); + c = readw(priv->regs + MSC_RES); + cmd->response[i] = + (a << 24) | (b << 8) | (c >> 8); + a = c; + } + } else { + cmd->response[0] = readw(priv->regs + MSC_RES) << 24; + cmd->response[0] |= readw(priv->regs + MSC_RES) << 8; + cmd->response[0] |= readw(priv->regs + MSC_RES) & 0xff; + } + } + if (data) { + if (data->flags & MMC_DATA_WRITE) + jz_mmc_write_data(priv, data); + else if (data->flags & MMC_DATA_READ) { + ret = jz_mmc_read_data(priv, data); + if (ret) + return ret; + } + } + + return 0; +} + +static int jz_mmc_set_ios(struct mmc *mmc, struct jz_mmc_priv *priv) +{ + u32 real_rate = jz_mmc_clock_rate(); + u8 clk_div = 0; + + /* calculate clock divide */ + while ((real_rate > mmc->clock) && (clk_div < 7)) { + real_rate >>= 1; + clk_div++; + } + writel(clk_div & MSC_CLKRT_CLK_RATE_MASK, priv->regs + MSC_CLKRT); + + /* set the bus width for the next command */ + priv->flags &= ~JZ_MMC_BUS_WIDTH_MASK; + if (mmc->bus_width == 8) + priv->flags |= JZ_MMC_BUS_WIDTH_8; + else if (mmc->bus_width == 4) + priv->flags |= JZ_MMC_BUS_WIDTH_4; + else + priv->flags |= JZ_MMC_BUS_WIDTH_1; + + return 0; +} + +static int jz_mmc_core_init(struct mmc *mmc) +{ + struct jz_mmc_priv *priv = mmc->priv; + int ret; + + /* Reset */ + writel(MSC_STRPCL_RESET, priv->regs + MSC_STRPCL); + ret = wait_for_bit_le32(priv->regs + MSC_STAT, + MSC_STAT_IS_RESETTING, false, 10000, false); + if (ret) + return ret; + + /* Maximum timeouts */ + writel(0xffff, priv->regs + MSC_RESTO); + writel(0xffffffff, priv->regs + MSC_RDTO); + + /* Enable low power mode */ + writel(0x1, priv->regs + MSC_LPM); + + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) + +static int jz_mmc_legacy_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct jz_mmc_priv *priv = mmc->priv; + + return jz_mmc_send_cmd(mmc, priv, cmd, data); +} + +static int jz_mmc_legacy_set_ios(struct mmc *mmc) +{ + struct jz_mmc_priv *priv = mmc->priv; + + return jz_mmc_set_ios(mmc, priv); +}; + +static const struct mmc_ops jz_msc_ops = { + .send_cmd = jz_mmc_legacy_send_cmd, + .set_ios = jz_mmc_legacy_set_ios, + .init = jz_mmc_core_init, +}; + +static struct jz_mmc_priv jz_mmc_priv_static; +static struct jz_mmc_plat jz_mmc_plat_static = { + .cfg = { + .name = "MSC", + .ops = &jz_msc_ops, + + .voltages = MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30 | + MMC_VDD_30_31 | MMC_VDD_31_32 | MMC_VDD_32_33 | + MMC_VDD_33_34 | MMC_VDD_34_35 | MMC_VDD_35_36, + .host_caps = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS, + + .f_min = 375000, + .f_max = 48000000, + .b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT, + }, +}; + +int jz_mmc_init(void __iomem *base) +{ + struct mmc *mmc; + + jz_mmc_priv_static.regs = base; + + mmc = mmc_create(&jz_mmc_plat_static.cfg, &jz_mmc_priv_static); + + return mmc ? 0 : -ENODEV; +} + +#else /* CONFIG_DM_MMC */ + +#include <dm.h> +DECLARE_GLOBAL_DATA_PTR; + +static int jz_mmc_dm_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct jz_mmc_priv *priv = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + + return jz_mmc_send_cmd(mmc, priv, cmd, data); +} + +static int jz_mmc_dm_set_ios(struct udevice *dev) +{ + struct jz_mmc_priv *priv = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + + return jz_mmc_set_ios(mmc, priv); +}; + +static const struct dm_mmc_ops jz_msc_ops = { + .send_cmd = jz_mmc_dm_send_cmd, + .set_ios = jz_mmc_dm_set_ios, +}; + +static int jz_mmc_of_to_plat(struct udevice *dev) +{ + struct jz_mmc_priv *priv = dev_get_priv(dev); + struct jz_mmc_plat *plat = dev_get_plat(dev); + struct mmc_config *cfg; + int ret; + + priv->regs = map_physmem(dev_read_addr(dev), 0x100, MAP_NOCACHE); + cfg = &plat->cfg; + + cfg->name = "MSC"; + cfg->host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS; + + ret = mmc_of_parse(dev, cfg); + if (ret < 0) { + dev_err(dev, "failed to parse host caps\n"); + return ret; + } + + cfg->f_min = 400000; + cfg->f_max = 52000000; + + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + return 0; +} + +static int jz_mmc_bind(struct udevice *dev) +{ + struct jz_mmc_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static int jz_mmc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct jz_mmc_priv *priv = dev_get_priv(dev); + struct jz_mmc_plat *plat = dev_get_plat(dev); + + plat->mmc.priv = priv; + upriv->mmc = &plat->mmc; + return jz_mmc_core_init(&plat->mmc); +} + +static const struct udevice_id jz_mmc_ids[] = { + { .compatible = "ingenic,jz4780-mmc" }, + { } +}; + +U_BOOT_DRIVER(jz_mmc_drv) = { + .name = "jz_mmc", + .id = UCLASS_MMC, + .of_match = jz_mmc_ids, + .of_to_plat = jz_mmc_of_to_plat, + .bind = jz_mmc_bind, + .probe = jz_mmc_probe, + .priv_auto = sizeof(struct jz_mmc_priv), + .plat_auto = sizeof(struct jz_mmc_plat), + .ops = &jz_msc_ops, +}; +#endif /* CONFIG_DM_MMC */ diff --git a/roms/u-boot/drivers/mmc/kona_sdhci.c b/roms/u-boot/drivers/mmc/kona_sdhci.c new file mode 100644 index 000000000..2bbe673b9 --- /dev/null +++ b/roms/u-boot/drivers/mmc/kona_sdhci.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2013 Broadcom Corporation. + */ + +#include <common.h> +#include <malloc.h> +#include <sdhci.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <asm/kona-common/clk.h> + +#define SDHCI_CORECTRL_OFFSET 0x00008000 +#define SDHCI_CORECTRL_EN 0x01 +#define SDHCI_CORECTRL_RESET 0x02 + +#define SDHCI_CORESTAT_OFFSET 0x00008004 +#define SDHCI_CORESTAT_CD_SW 0x01 + +#define SDHCI_COREIMR_OFFSET 0x00008008 +#define SDHCI_COREIMR_IP 0x01 + +static int init_kona_mmc_core(struct sdhci_host *host) +{ + unsigned int mask; + unsigned int timeout; + + if (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & SDHCI_RESET_ALL) { + printf("%s: sd host controller reset error\n", __func__); + return -EBUSY; + } + + /* For kona a hardware reset before anything else. */ + mask = sdhci_readl(host, SDHCI_CORECTRL_OFFSET) | SDHCI_CORECTRL_RESET; + sdhci_writel(host, mask, SDHCI_CORECTRL_OFFSET); + + /* Wait max 100 ms */ + timeout = 1000; + do { + if (timeout == 0) { + printf("%s: reset timeout error\n", __func__); + return -ETIMEDOUT; + } + timeout--; + udelay(100); + } while (0 == + (sdhci_readl(host, SDHCI_CORECTRL_OFFSET) & + SDHCI_CORECTRL_RESET)); + + /* Clear the reset bit. */ + mask = mask & ~SDHCI_CORECTRL_RESET; + sdhci_writel(host, mask, SDHCI_CORECTRL_OFFSET); + + /* Enable AHB clock */ + mask = sdhci_readl(host, SDHCI_CORECTRL_OFFSET); + sdhci_writel(host, mask | SDHCI_CORECTRL_EN, SDHCI_CORECTRL_OFFSET); + + /* Enable interrupts */ + sdhci_writel(host, SDHCI_COREIMR_IP, SDHCI_COREIMR_OFFSET); + + /* Make sure Card is detected in controller */ + mask = sdhci_readl(host, SDHCI_CORESTAT_OFFSET); + sdhci_writel(host, mask | SDHCI_CORESTAT_CD_SW, SDHCI_CORESTAT_OFFSET); + + /* Wait max 100 ms */ + timeout = 1000; + while (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { + if (timeout == 0) { + printf("%s: CARD DETECT timeout error\n", __func__); + return -ETIMEDOUT; + } + timeout--; + udelay(100); + } + return 0; +} + +int kona_sdhci_init(int dev_index, u32 min_clk, u32 quirks) +{ + int ret = 0; + u32 max_clk; + void *reg_base; + struct sdhci_host *host = NULL; + + host = (struct sdhci_host *)malloc(sizeof(struct sdhci_host)); + if (!host) { + printf("%s: sdhci host malloc fail!\n", __func__); + return -ENOMEM; + } + switch (dev_index) { + case 0: + reg_base = (void *)CONFIG_SYS_SDIO_BASE0; + ret = clk_sdio_enable(reg_base, CONFIG_SYS_SDIO0_MAX_CLK, + &max_clk); + break; + case 1: + reg_base = (void *)CONFIG_SYS_SDIO_BASE1; + ret = clk_sdio_enable(reg_base, CONFIG_SYS_SDIO1_MAX_CLK, + &max_clk); + break; + case 2: + reg_base = (void *)CONFIG_SYS_SDIO_BASE2; + ret = clk_sdio_enable(reg_base, CONFIG_SYS_SDIO2_MAX_CLK, + &max_clk); + break; + case 3: + reg_base = (void *)CONFIG_SYS_SDIO_BASE3; + ret = clk_sdio_enable(reg_base, CONFIG_SYS_SDIO3_MAX_CLK, + &max_clk); + break; + default: + printf("%s: sdio dev index %d not supported\n", + __func__, dev_index); + ret = -EINVAL; + } + if (ret) { + free(host); + return ret; + } + + host->name = "kona-sdhci"; + host->ioaddr = reg_base; + host->quirks = quirks; + host->max_clk = max_clk; + + if (init_kona_mmc_core(host)) { + free(host); + return -EINVAL; + } + + add_sdhci(host, 0, min_clk); + return ret; +} diff --git a/roms/u-boot/drivers/mmc/meson_gx_mmc.c b/roms/u-boot/drivers/mmc/meson_gx_mmc.c new file mode 100644 index 000000000..fcf4f03d1 --- /dev/null +++ b/roms/u-boot/drivers/mmc/meson_gx_mmc.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2016 Carlo Caione <carlo@caione.org> + */ + +#include <common.h> +#include <clk.h> +#include <cpu_func.h> +#include <dm.h> +#include <fdtdec.h> +#include <malloc.h> +#include <pwrseq.h> +#include <mmc.h> +#include <asm/io.h> +#include <asm/gpio.h> +#include <linux/delay.h> +#include <linux/log2.h> +#include "meson_gx_mmc.h" + +bool meson_gx_mmc_is_compatible(struct udevice *dev, + enum meson_gx_mmc_compatible family) +{ + enum meson_gx_mmc_compatible compat = dev_get_driver_data(dev); + + return compat == family; +} + +static inline void *get_regbase(const struct mmc *mmc) +{ + struct meson_mmc_plat *pdata = mmc->priv; + + return pdata->regbase; +} + +static inline uint32_t meson_read(struct mmc *mmc, int offset) +{ + return readl(get_regbase(mmc) + offset); +} + +static inline void meson_write(struct mmc *mmc, uint32_t val, int offset) +{ + writel(val, get_regbase(mmc) + offset); +} + +static void meson_mmc_config_clock(struct mmc *mmc) +{ + uint32_t meson_mmc_clk = 0; + unsigned int clk, clk_src, clk_div; + + if (!mmc->clock) + return; + + /* TOFIX This should use the proper clock taken from DT */ + + /* 1GHz / CLK_MAX_DIV = 15,9 MHz */ + if (mmc->clock > 16000000) { + clk = SD_EMMC_CLKSRC_DIV2; + clk_src = CLK_SRC_DIV2; + } else { + clk = SD_EMMC_CLKSRC_24M; + clk_src = CLK_SRC_24M; + } + clk_div = DIV_ROUND_UP(clk, mmc->clock); + + /* + * SM1 SoCs doesn't work fine over 50MHz with CLK_CO_PHASE_180 + * If CLK_CO_PHASE_270 is used, it's more stable than other. + * Other SoCs use CLK_CO_PHASE_180 by default. + * It needs to find what is a proper value about each SoCs. + */ + if (meson_gx_mmc_is_compatible(mmc->dev, MMC_COMPATIBLE_SM1)) + meson_mmc_clk |= CLK_CO_PHASE_270; + else + meson_mmc_clk |= CLK_CO_PHASE_180; + + /* 180 phase tx clock */ + meson_mmc_clk |= CLK_TX_PHASE_000; + + /* clock settings */ + meson_mmc_clk |= clk_src; + meson_mmc_clk |= clk_div; + + meson_write(mmc, meson_mmc_clk, MESON_SD_EMMC_CLOCK); +} + +static int meson_dm_mmc_set_ios(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + uint32_t meson_mmc_cfg; + + meson_mmc_config_clock(mmc); + + meson_mmc_cfg = meson_read(mmc, MESON_SD_EMMC_CFG); + + meson_mmc_cfg &= ~CFG_BUS_WIDTH_MASK; + if (mmc->bus_width == 1) + meson_mmc_cfg |= CFG_BUS_WIDTH_1; + else if (mmc->bus_width == 4) + meson_mmc_cfg |= CFG_BUS_WIDTH_4; + else if (mmc->bus_width == 8) + meson_mmc_cfg |= CFG_BUS_WIDTH_8; + else + return -EINVAL; + + /* 512 bytes block length */ + meson_mmc_cfg &= ~CFG_BL_LEN_MASK; + meson_mmc_cfg |= CFG_BL_LEN_512; + + /* Response timeout 256 clk */ + meson_mmc_cfg &= ~CFG_RESP_TIMEOUT_MASK; + meson_mmc_cfg |= CFG_RESP_TIMEOUT_256; + + /* Command-command gap 16 clk */ + meson_mmc_cfg &= ~CFG_RC_CC_MASK; + meson_mmc_cfg |= CFG_RC_CC_16; + + meson_write(mmc, meson_mmc_cfg, MESON_SD_EMMC_CFG); + + return 0; +} + +static void meson_mmc_setup_cmd(struct mmc *mmc, struct mmc_data *data, + struct mmc_cmd *cmd) +{ + uint32_t meson_mmc_cmd = 0, cfg; + + meson_mmc_cmd |= cmd->cmdidx << CMD_CFG_CMD_INDEX_SHIFT; + + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) + meson_mmc_cmd |= CMD_CFG_RESP_128; + + if (cmd->resp_type & MMC_RSP_BUSY) + meson_mmc_cmd |= CMD_CFG_R1B; + + if (!(cmd->resp_type & MMC_RSP_CRC)) + meson_mmc_cmd |= CMD_CFG_RESP_NOCRC; + } else { + meson_mmc_cmd |= CMD_CFG_NO_RESP; + } + + if (data) { + cfg = meson_read(mmc, MESON_SD_EMMC_CFG); + cfg &= ~CFG_BL_LEN_MASK; + cfg |= ilog2(data->blocksize) << CFG_BL_LEN_SHIFT; + meson_write(mmc, cfg, MESON_SD_EMMC_CFG); + + if (data->flags == MMC_DATA_WRITE) + meson_mmc_cmd |= CMD_CFG_DATA_WR; + + meson_mmc_cmd |= CMD_CFG_DATA_IO | CMD_CFG_BLOCK_MODE | + data->blocks; + } + + meson_mmc_cmd |= CMD_CFG_TIMEOUT_4S | CMD_CFG_OWNER | + CMD_CFG_END_OF_CHAIN; + + meson_write(mmc, meson_mmc_cmd, MESON_SD_EMMC_CMD_CFG); +} + +static void meson_mmc_setup_addr(struct mmc *mmc, struct mmc_data *data) +{ + struct meson_mmc_plat *pdata = mmc->priv; + unsigned int data_size; + uint32_t data_addr = 0; + + if (data) { + data_size = data->blocks * data->blocksize; + + if (data->flags == MMC_DATA_READ) { + data_addr = (ulong) data->dest; + invalidate_dcache_range(data_addr, + data_addr + data_size); + } else { + pdata->w_buf = calloc(data_size, sizeof(char)); + data_addr = (ulong) pdata->w_buf; + memcpy(pdata->w_buf, data->src, data_size); + flush_dcache_range(data_addr, data_addr + data_size); + } + } + + meson_write(mmc, data_addr, MESON_SD_EMMC_CMD_DAT); +} + +static void meson_mmc_read_response(struct mmc *mmc, struct mmc_cmd *cmd) +{ + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[0] = meson_read(mmc, MESON_SD_EMMC_CMD_RSP3); + cmd->response[1] = meson_read(mmc, MESON_SD_EMMC_CMD_RSP2); + cmd->response[2] = meson_read(mmc, MESON_SD_EMMC_CMD_RSP1); + cmd->response[3] = meson_read(mmc, MESON_SD_EMMC_CMD_RSP); + } else { + cmd->response[0] = meson_read(mmc, MESON_SD_EMMC_CMD_RSP); + } +} + +static int meson_dm_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct meson_mmc_plat *pdata = mmc->priv; + uint32_t status; + ulong start; + int ret = 0; + + /* max block size supported by chip is 512 byte */ + if (data && data->blocksize > 512) + return -EINVAL; + + meson_mmc_setup_cmd(mmc, data, cmd); + meson_mmc_setup_addr(mmc, data); + + meson_write(mmc, cmd->cmdarg, MESON_SD_EMMC_CMD_ARG); + + /* use 10s timeout */ + start = get_timer(0); + do { + status = meson_read(mmc, MESON_SD_EMMC_STATUS); + } while(!(status & STATUS_END_OF_CHAIN) && get_timer(start) < 10000); + + if (!(status & STATUS_END_OF_CHAIN)) + ret = -ETIMEDOUT; + else if (status & STATUS_RESP_TIMEOUT) + ret = -ETIMEDOUT; + else if (status & STATUS_ERR_MASK) + ret = -EIO; + + meson_mmc_read_response(mmc, cmd); + + if (data && data->flags == MMC_DATA_WRITE) + free(pdata->w_buf); + + /* reset status bits */ + meson_write(mmc, STATUS_MASK, MESON_SD_EMMC_STATUS); + + return ret; +} + +static const struct dm_mmc_ops meson_dm_mmc_ops = { + .send_cmd = meson_dm_mmc_send_cmd, + .set_ios = meson_dm_mmc_set_ios, +}; + +static int meson_mmc_of_to_plat(struct udevice *dev) +{ + struct meson_mmc_plat *pdata = dev_get_plat(dev); + fdt_addr_t addr; + + addr = dev_read_addr(dev); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + + pdata->regbase = (void *)addr; + + return 0; +} + +static int meson_mmc_probe(struct udevice *dev) +{ + struct meson_mmc_plat *pdata = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct mmc *mmc = &pdata->mmc; + struct mmc_config *cfg = &pdata->cfg; + struct clk_bulk clocks; + uint32_t val; + int ret; + + /* Enable the clocks feeding the MMC controller */ + ret = clk_get_bulk(dev, &clocks); + if (ret) + return ret; + + ret = clk_enable_bulk(&clocks); + if (ret) + return ret; + + cfg->voltages = MMC_VDD_33_34 | MMC_VDD_32_33 | + MMC_VDD_31_32 | MMC_VDD_165_195; + cfg->host_caps = MMC_MODE_8BIT | MMC_MODE_4BIT | + MMC_MODE_HS_52MHz | MMC_MODE_HS; + cfg->f_min = DIV_ROUND_UP(SD_EMMC_CLKSRC_24M, CLK_MAX_DIV); + cfg->f_max = 100000000; /* 100 MHz */ + cfg->b_max = 511; /* max 512 - 1 blocks */ + cfg->name = dev->name; + + mmc->priv = pdata; + upriv->mmc = mmc; + + mmc_set_clock(mmc, cfg->f_min, MMC_CLK_ENABLE); + +#ifdef CONFIG_MMC_PWRSEQ + /* Enable power if needed */ + ret = mmc_pwrseq_get_power(dev, cfg); + if (!ret) { + ret = pwrseq_set_power(cfg->pwr_dev, true); + if (ret) + return ret; + } +#endif + + /* reset all status bits */ + meson_write(mmc, STATUS_MASK, MESON_SD_EMMC_STATUS); + + /* disable interrupts */ + meson_write(mmc, 0, MESON_SD_EMMC_IRQ_EN); + + /* enable auto clock mode */ + val = meson_read(mmc, MESON_SD_EMMC_CFG); + val &= ~CFG_SDCLK_ALWAYS_ON; + val |= CFG_AUTO_CLK; + meson_write(mmc, val, MESON_SD_EMMC_CFG); + + return 0; +} + +int meson_mmc_bind(struct udevice *dev) +{ + struct meson_mmc_plat *pdata = dev_get_plat(dev); + + return mmc_bind(dev, &pdata->mmc, &pdata->cfg); +} + +static const struct udevice_id meson_mmc_match[] = { + { .compatible = "amlogic,meson-gx-mmc", .data = MMC_COMPATIBLE_GX }, + { .compatible = "amlogic,meson-axg-mmc", .data = MMC_COMPATIBLE_GX }, + { .compatible = "amlogic,meson-sm1-mmc", .data = MMC_COMPATIBLE_SM1 }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(meson_mmc) = { + .name = "meson_gx_mmc", + .id = UCLASS_MMC, + .of_match = meson_mmc_match, + .ops = &meson_dm_mmc_ops, + .probe = meson_mmc_probe, + .bind = meson_mmc_bind, + .of_to_plat = meson_mmc_of_to_plat, + .plat_auto = sizeof(struct meson_mmc_plat), +}; diff --git a/roms/u-boot/drivers/mmc/meson_gx_mmc.h b/roms/u-boot/drivers/mmc/meson_gx_mmc.h new file mode 100644 index 000000000..8974b78f5 --- /dev/null +++ b/roms/u-boot/drivers/mmc/meson_gx_mmc.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2016 Carlo Caione <carlo@caione.org> + */ + +#ifndef __MESON_GX_MMC_H__ +#define __MESON_GX_MMC_H__ + +#include <mmc.h> +#include <linux/bitops.h> + +enum meson_gx_mmc_compatible { + MMC_COMPATIBLE_GX, + MMC_COMPATIBLE_SM1, +}; + +#define SDIO_PORT_A 0 +#define SDIO_PORT_B 1 +#define SDIO_PORT_C 2 + +#define SD_EMMC_CLKSRC_24M 24000000 /* 24 MHz */ +#define SD_EMMC_CLKSRC_DIV2 1000000000 /* 1 GHz */ + +#define MESON_SD_EMMC_CLOCK 0x00 +#define CLK_MAX_DIV 63 +#define CLK_SRC_24M (0 << 6) +#define CLK_SRC_DIV2 (1 << 6) +#define CLK_CO_PHASE_000 (0 << 8) +#define CLK_CO_PHASE_090 (1 << 8) +#define CLK_CO_PHASE_180 (2 << 8) +#define CLK_CO_PHASE_270 (3 << 8) +#define CLK_TX_PHASE_000 (0 << 10) +#define CLK_TX_PHASE_090 (1 << 10) +#define CLK_TX_PHASE_180 (2 << 10) +#define CLK_TX_PHASE_270 (3 << 10) +#define CLK_ALWAYS_ON BIT(24) + +#define MESON_SD_EMMC_CFG 0x44 +#define CFG_BUS_WIDTH_MASK GENMASK(1, 0) +#define CFG_BUS_WIDTH_1 0 +#define CFG_BUS_WIDTH_4 1 +#define CFG_BUS_WIDTH_8 2 +#define CFG_BL_LEN_MASK GENMASK(7, 4) +#define CFG_BL_LEN_SHIFT 4 +#define CFG_BL_LEN_512 (9 << 4) +#define CFG_RESP_TIMEOUT_MASK GENMASK(11, 8) +#define CFG_RESP_TIMEOUT_256 (8 << 8) +#define CFG_RC_CC_MASK GENMASK(15, 12) +#define CFG_RC_CC_16 (4 << 12) +#define CFG_SDCLK_ALWAYS_ON BIT(18) +#define CFG_AUTO_CLK BIT(23) + +#define MESON_SD_EMMC_STATUS 0x48 +#define STATUS_MASK GENMASK(15, 0) +#define STATUS_ERR_MASK GENMASK(12, 0) +#define STATUS_RXD_ERR_MASK GENMASK(7, 0) +#define STATUS_TXD_ERR BIT(8) +#define STATUS_DESC_ERR BIT(9) +#define STATUS_RESP_ERR BIT(10) +#define STATUS_RESP_TIMEOUT BIT(11) +#define STATUS_DESC_TIMEOUT BIT(12) +#define STATUS_END_OF_CHAIN BIT(13) + +#define MESON_SD_EMMC_IRQ_EN 0x4c + +#define MESON_SD_EMMC_CMD_CFG 0x50 +#define CMD_CFG_LENGTH_MASK GENMASK(8, 0) +#define CMD_CFG_BLOCK_MODE BIT(9) +#define CMD_CFG_R1B BIT(10) +#define CMD_CFG_END_OF_CHAIN BIT(11) +#define CMD_CFG_TIMEOUT_4S (12 << 12) +#define CMD_CFG_NO_RESP BIT(16) +#define CMD_CFG_DATA_IO BIT(18) +#define CMD_CFG_DATA_WR BIT(19) +#define CMD_CFG_RESP_NOCRC BIT(20) +#define CMD_CFG_RESP_128 BIT(21) +#define CMD_CFG_CMD_INDEX_SHIFT 24 +#define CMD_CFG_OWNER BIT(31) + +#define MESON_SD_EMMC_CMD_ARG 0x54 +#define MESON_SD_EMMC_CMD_DAT 0x58 +#define MESON_SD_EMMC_CMD_RSP 0x5c +#define MESON_SD_EMMC_CMD_RSP1 0x60 +#define MESON_SD_EMMC_CMD_RSP2 0x64 +#define MESON_SD_EMMC_CMD_RSP3 0x68 + +struct meson_mmc_plat { + struct mmc_config cfg; + struct mmc mmc; + void *regbase; + void *w_buf; +}; + +#endif diff --git a/roms/u-boot/drivers/mmc/mmc-pwrseq.c b/roms/u-boot/drivers/mmc/mmc-pwrseq.c new file mode 100644 index 000000000..2539f6132 --- /dev/null +++ b/roms/u-boot/drivers/mmc/mmc-pwrseq.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2021 SAMSUNG Electronics + * Jaehoon Chung <jh80.chung@samsung.com> + */ + +#include <common.h> +#include <dm.h> +#include <mmc.h> +#include <pwrseq.h> +#include <asm/gpio.h> +#include <linux/delay.h> + +int mmc_pwrseq_get_power(struct udevice *dev, struct mmc_config *cfg) +{ + /* Enable power if needed */ + return uclass_get_device_by_phandle(UCLASS_PWRSEQ, dev, "mmc-pwrseq", + &cfg->pwr_dev); +} + +static int mmc_pwrseq_set_power(struct udevice *dev, bool enable) +{ + struct gpio_desc reset; + int ret; + + ret = gpio_request_by_name(dev, "reset-gpios", 0, &reset, GPIOD_IS_OUT); + if (ret) + return ret; + dm_gpio_set_value(&reset, 1); + udelay(1); + dm_gpio_set_value(&reset, 0); + udelay(200); + + return 0; +} + +static const struct pwrseq_ops mmc_pwrseq_ops = { + .set_power = mmc_pwrseq_set_power, +}; + +static const struct udevice_id mmc_pwrseq_ids[] = { + { .compatible = "mmc-pwrseq-emmc" }, + { } +}; + +U_BOOT_DRIVER(mmc_pwrseq_drv) = { + .name = "mmc_pwrseq_emmc", + .id = UCLASS_PWRSEQ, + .of_match = mmc_pwrseq_ids, + .ops = &mmc_pwrseq_ops, +}; diff --git a/roms/u-boot/drivers/mmc/mmc-uclass.c b/roms/u-boot/drivers/mmc/mmc-uclass.c new file mode 100644 index 000000000..579d7a140 --- /dev/null +++ b/roms/u-boot/drivers/mmc/mmc-uclass.c @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015 Google, Inc + * Copyright 2020 NXP + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <log.h> +#include <mmc.h> +#include <dm.h> +#include <dm/device-internal.h> +#include <dm/device_compat.h> +#include <dm/lists.h> +#include <linux/compat.h> +#include "mmc_private.h" + +static int dm_mmc_get_b_max(struct udevice *dev, void *dst, lbaint_t blkcnt) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + + if (ops->get_b_max) + return ops->get_b_max(dev, dst, blkcnt); + else + return mmc->cfg->b_max; +} + +int mmc_get_b_max(struct mmc *mmc, void *dst, lbaint_t blkcnt) +{ + return dm_mmc_get_b_max(mmc->dev, dst, blkcnt); +} + +static int dm_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct dm_mmc_ops *ops = mmc_get_ops(dev); + int ret; + + mmmc_trace_before_send(mmc, cmd); + if (ops->send_cmd) + ret = ops->send_cmd(dev, cmd, data); + else + ret = -ENOSYS; + mmmc_trace_after_send(mmc, cmd, ret); + + return ret; +} + +int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) +{ + return dm_mmc_send_cmd(mmc->dev, cmd, data); +} + +static int dm_mmc_set_ios(struct udevice *dev) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + + if (!ops->set_ios) + return -ENOSYS; + return ops->set_ios(dev); +} + +int mmc_set_ios(struct mmc *mmc) +{ + return dm_mmc_set_ios(mmc->dev); +} + +static int dm_mmc_wait_dat0(struct udevice *dev, int state, int timeout_us) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + + if (!ops->wait_dat0) + return -ENOSYS; + return ops->wait_dat0(dev, state, timeout_us); +} + +int mmc_wait_dat0(struct mmc *mmc, int state, int timeout_us) +{ + return dm_mmc_wait_dat0(mmc->dev, state, timeout_us); +} + +static int dm_mmc_get_wp(struct udevice *dev) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + + if (!ops->get_wp) + return -ENOSYS; + return ops->get_wp(dev); +} + +int mmc_getwp(struct mmc *mmc) +{ + return dm_mmc_get_wp(mmc->dev); +} + +static int dm_mmc_get_cd(struct udevice *dev) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + + if (!ops->get_cd) + return -ENOSYS; + return ops->get_cd(dev); +} + +int mmc_getcd(struct mmc *mmc) +{ + return dm_mmc_get_cd(mmc->dev); +} + +#ifdef MMC_SUPPORTS_TUNING +static int dm_mmc_execute_tuning(struct udevice *dev, uint opcode) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + + if (!ops->execute_tuning) + return -ENOSYS; + return ops->execute_tuning(dev, opcode); +} + +int mmc_execute_tuning(struct mmc *mmc, uint opcode) +{ + return dm_mmc_execute_tuning(mmc->dev, opcode); +} +#endif + +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT) +static int dm_mmc_set_enhanced_strobe(struct udevice *dev) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + + if (ops->set_enhanced_strobe) + return ops->set_enhanced_strobe(dev); + + return -ENOTSUPP; +} + +int mmc_set_enhanced_strobe(struct mmc *mmc) +{ + return dm_mmc_set_enhanced_strobe(mmc->dev); +} +#endif + +static int dm_mmc_hs400_prepare_ddr(struct udevice *dev) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + + if (ops->hs400_prepare_ddr) + return ops->hs400_prepare_ddr(dev); + + return 0; +} + +int mmc_hs400_prepare_ddr(struct mmc *mmc) +{ + return dm_mmc_hs400_prepare_ddr(mmc->dev); +} + +static int dm_mmc_host_power_cycle(struct udevice *dev) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + + if (ops->host_power_cycle) + return ops->host_power_cycle(dev); + return 0; +} + +int mmc_host_power_cycle(struct mmc *mmc) +{ + return dm_mmc_host_power_cycle(mmc->dev); +} + +static int dm_mmc_deferred_probe(struct udevice *dev) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + + if (ops->deferred_probe) + return ops->deferred_probe(dev); + + return 0; +} + +int mmc_deferred_probe(struct mmc *mmc) +{ + return dm_mmc_deferred_probe(mmc->dev); +} + +static int dm_mmc_reinit(struct udevice *dev) +{ + struct dm_mmc_ops *ops = mmc_get_ops(dev); + + if (ops->reinit) + return ops->reinit(dev); + + return 0; +} + +int mmc_reinit(struct mmc *mmc) +{ + return dm_mmc_reinit(mmc->dev); +} + +int mmc_of_parse(struct udevice *dev, struct mmc_config *cfg) +{ + int val; + + val = dev_read_u32_default(dev, "bus-width", 1); + + switch (val) { + case 0x8: + cfg->host_caps |= MMC_MODE_8BIT; + /* fall through */ + case 0x4: + cfg->host_caps |= MMC_MODE_4BIT; + /* fall through */ + case 0x1: + cfg->host_caps |= MMC_MODE_1BIT; + break; + default: + dev_err(dev, "Invalid \"bus-width\" value %u!\n", val); + return -EINVAL; + } + + /* f_max is obtained from the optional "max-frequency" property */ + dev_read_u32(dev, "max-frequency", &cfg->f_max); + + if (dev_read_bool(dev, "cap-sd-highspeed")) + cfg->host_caps |= MMC_CAP(SD_HS); + if (dev_read_bool(dev, "cap-mmc-highspeed")) + cfg->host_caps |= MMC_CAP(MMC_HS) | MMC_CAP(MMC_HS_52); + if (dev_read_bool(dev, "sd-uhs-sdr12")) + cfg->host_caps |= MMC_CAP(UHS_SDR12); + if (dev_read_bool(dev, "sd-uhs-sdr25")) + cfg->host_caps |= MMC_CAP(UHS_SDR25); + if (dev_read_bool(dev, "sd-uhs-sdr50")) + cfg->host_caps |= MMC_CAP(UHS_SDR50); + if (dev_read_bool(dev, "sd-uhs-sdr104")) + cfg->host_caps |= MMC_CAP(UHS_SDR104); + if (dev_read_bool(dev, "sd-uhs-ddr50")) + cfg->host_caps |= MMC_CAP(UHS_DDR50); + if (dev_read_bool(dev, "mmc-ddr-1_8v")) + cfg->host_caps |= MMC_CAP(MMC_DDR_52); + if (dev_read_bool(dev, "mmc-ddr-1_2v")) + cfg->host_caps |= MMC_CAP(MMC_DDR_52); + if (dev_read_bool(dev, "mmc-hs200-1_8v")) + cfg->host_caps |= MMC_CAP(MMC_HS_200); + if (dev_read_bool(dev, "mmc-hs200-1_2v")) + cfg->host_caps |= MMC_CAP(MMC_HS_200); + if (dev_read_bool(dev, "mmc-hs400-1_8v")) + cfg->host_caps |= MMC_CAP(MMC_HS_400); + if (dev_read_bool(dev, "mmc-hs400-1_2v")) + cfg->host_caps |= MMC_CAP(MMC_HS_400); + if (dev_read_bool(dev, "mmc-hs400-enhanced-strobe")) + cfg->host_caps |= MMC_CAP(MMC_HS_400_ES); + + if (dev_read_bool(dev, "non-removable")) { + cfg->host_caps |= MMC_CAP_NONREMOVABLE; + } else { + if (dev_read_bool(dev, "cd-inverted")) + cfg->host_caps |= MMC_CAP_CD_ACTIVE_HIGH; + if (dev_read_bool(dev, "broken-cd")) + cfg->host_caps |= MMC_CAP_NEEDS_POLL; + } + + if (dev_read_bool(dev, "no-1-8-v")) { + cfg->host_caps &= ~(UHS_CAPS | MMC_MODE_HS200 | + MMC_MODE_HS400 | MMC_MODE_HS400_ES); + } + + return 0; +} + +struct mmc *mmc_get_mmc_dev(const struct udevice *dev) +{ + struct mmc_uclass_priv *upriv; + + if (!device_active(dev)) + return NULL; + upriv = dev_get_uclass_priv(dev); + return upriv->mmc; +} + +#if CONFIG_IS_ENABLED(BLK) +struct mmc *find_mmc_device(int dev_num) +{ + struct udevice *dev, *mmc_dev; + int ret; + + ret = blk_find_device(IF_TYPE_MMC, dev_num, &dev); + + if (ret) { +#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) + printf("MMC Device %d not found\n", dev_num); +#endif + return NULL; + } + + mmc_dev = dev_get_parent(dev); + + struct mmc *mmc = mmc_get_mmc_dev(mmc_dev); + + return mmc; +} + +int get_mmc_num(void) +{ + return max((blk_find_max_devnum(IF_TYPE_MMC) + 1), 0); +} + +int mmc_get_next_devnum(void) +{ + return blk_find_max_devnum(IF_TYPE_MMC); +} + +struct blk_desc *mmc_get_blk_desc(struct mmc *mmc) +{ + struct blk_desc *desc; + struct udevice *dev; + + device_find_first_child(mmc->dev, &dev); + if (!dev) + return NULL; + desc = dev_get_uclass_plat(dev); + + return desc; +} + +void mmc_do_preinit(void) +{ + struct udevice *dev; + struct uclass *uc; + int ret; + + ret = uclass_get(UCLASS_MMC, &uc); + if (ret) + return; + uclass_foreach_dev(dev, uc) { + struct mmc *m = mmc_get_mmc_dev(dev); + + if (!m) + continue; + if (m->preinit) + mmc_start_init(m); + } +} + +#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) +void print_mmc_devices(char separator) +{ + struct udevice *dev; + char *mmc_type; + bool first = true; + + for (uclass_first_device(UCLASS_MMC, &dev); + dev; + uclass_next_device(&dev), first = false) { + struct mmc *m = mmc_get_mmc_dev(dev); + + if (!first) { + printf("%c", separator); + if (separator != '\n') + puts(" "); + } + if (m->has_init) + mmc_type = IS_SD(m) ? "SD" : "eMMC"; + else + mmc_type = NULL; + + printf("%s: %d", m->cfg->name, mmc_get_blk_desc(m)->devnum); + if (mmc_type) + printf(" (%s)", mmc_type); + } + + printf("\n"); +} + +#else +void print_mmc_devices(char separator) { } +#endif + +int mmc_bind(struct udevice *dev, struct mmc *mmc, const struct mmc_config *cfg) +{ + struct blk_desc *bdesc; + struct udevice *bdev; + int ret; + + if (!mmc_get_ops(dev)) + return -ENOSYS; + + /* Use the fixed index with aliases node's index */ + debug("%s: alias devnum=%d\n", __func__, dev_seq(dev)); + + ret = blk_create_devicef(dev, "mmc_blk", "blk", IF_TYPE_MMC, + dev_seq(dev), 512, 0, &bdev); + if (ret) { + debug("Cannot create block device\n"); + return ret; + } + bdesc = dev_get_uclass_plat(bdev); + mmc->cfg = cfg; + mmc->priv = dev; + + /* the following chunk was from mmc_register() */ + + /* Setup dsr related values */ + mmc->dsr_imp = 0; + mmc->dsr = 0xffffffff; + /* Setup the universal parts of the block interface just once */ + bdesc->removable = 1; + + /* setup initial part type */ + bdesc->part_type = cfg->part_type; + mmc->dev = dev; + + return 0; +} + +int mmc_unbind(struct udevice *dev) +{ + struct udevice *bdev; + + device_find_first_child(dev, &bdev); + if (bdev) { + device_remove(bdev, DM_REMOVE_NORMAL); + device_unbind(bdev); + } + + return 0; +} + +static int mmc_select_hwpart(struct udevice *bdev, int hwpart) +{ + struct udevice *mmc_dev = dev_get_parent(bdev); + struct mmc *mmc = mmc_get_mmc_dev(mmc_dev); + struct blk_desc *desc = dev_get_uclass_plat(bdev); + int ret; + + if (desc->hwpart == hwpart) + return 0; + + if (mmc->part_config == MMCPART_NOAVAILABLE) + return -EMEDIUMTYPE; + + ret = mmc_switch_part(mmc, hwpart); + if (!ret) + blkcache_invalidate(desc->if_type, desc->devnum); + + return ret; +} + +static int mmc_blk_probe(struct udevice *dev) +{ + struct udevice *mmc_dev = dev_get_parent(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(mmc_dev); + struct mmc *mmc = upriv->mmc; + int ret; + + ret = mmc_init(mmc); + if (ret) { + debug("%s: mmc_init() failed (err=%d)\n", __func__, ret); + return ret; + } + + return 0; +} + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) +static int mmc_blk_remove(struct udevice *dev) +{ + struct udevice *mmc_dev = dev_get_parent(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(mmc_dev); + struct mmc *mmc = upriv->mmc; + + return mmc_deinit(mmc); +} +#endif + +static const struct blk_ops mmc_blk_ops = { + .read = mmc_bread, +#if CONFIG_IS_ENABLED(MMC_WRITE) + .write = mmc_bwrite, + .erase = mmc_berase, +#endif + .select_hwpart = mmc_select_hwpart, +}; + +U_BOOT_DRIVER(mmc_blk) = { + .name = "mmc_blk", + .id = UCLASS_BLK, + .ops = &mmc_blk_ops, + .probe = mmc_blk_probe, +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + .remove = mmc_blk_remove, + .flags = DM_FLAG_OS_PREPARE, +#endif +}; +#endif /* CONFIG_BLK */ + + +UCLASS_DRIVER(mmc) = { + .id = UCLASS_MMC, + .name = "mmc", + .flags = DM_UC_FLAG_SEQ_ALIAS, + .per_device_auto = sizeof(struct mmc_uclass_priv), +}; diff --git a/roms/u-boot/drivers/mmc/mmc.c b/roms/u-boot/drivers/mmc/mmc.c new file mode 100644 index 000000000..1e8300728 --- /dev/null +++ b/roms/u-boot/drivers/mmc/mmc.c @@ -0,0 +1,3112 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2008, Freescale Semiconductor, Inc + * Copyright 2020 NXP + * Andy Fleming + * + * Based vaguely on the Linux code + */ + +#include <config.h> +#include <common.h> +#include <blk.h> +#include <command.h> +#include <dm.h> +#include <log.h> +#include <dm/device-internal.h> +#include <errno.h> +#include <mmc.h> +#include <part.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <power/regulator.h> +#include <malloc.h> +#include <memalign.h> +#include <linux/list.h> +#include <div64.h> +#include "mmc_private.h" + +#define DEFAULT_CMD6_TIMEOUT_MS 500 + +static int mmc_set_signal_voltage(struct mmc *mmc, uint signal_voltage); + +#if !CONFIG_IS_ENABLED(DM_MMC) + +static int mmc_wait_dat0(struct mmc *mmc, int state, int timeout_us) +{ + return -ENOSYS; +} + +__weak int board_mmc_getwp(struct mmc *mmc) +{ + return -1; +} + +int mmc_getwp(struct mmc *mmc) +{ + int wp; + + wp = board_mmc_getwp(mmc); + + if (wp < 0) { + if (mmc->cfg->ops->getwp) + wp = mmc->cfg->ops->getwp(mmc); + else + wp = 0; + } + + return wp; +} + +__weak int board_mmc_getcd(struct mmc *mmc) +{ + return -1; +} +#endif + +#ifdef CONFIG_MMC_TRACE +void mmmc_trace_before_send(struct mmc *mmc, struct mmc_cmd *cmd) +{ + printf("CMD_SEND:%d\n", cmd->cmdidx); + printf("\t\tARG\t\t\t 0x%08x\n", cmd->cmdarg); +} + +void mmmc_trace_after_send(struct mmc *mmc, struct mmc_cmd *cmd, int ret) +{ + int i; + u8 *ptr; + + if (ret) { + printf("\t\tRET\t\t\t %d\n", ret); + } else { + switch (cmd->resp_type) { + case MMC_RSP_NONE: + printf("\t\tMMC_RSP_NONE\n"); + break; + case MMC_RSP_R1: + printf("\t\tMMC_RSP_R1,5,6,7 \t 0x%08x \n", + cmd->response[0]); + break; + case MMC_RSP_R1b: + printf("\t\tMMC_RSP_R1b\t\t 0x%08x \n", + cmd->response[0]); + break; + case MMC_RSP_R2: + printf("\t\tMMC_RSP_R2\t\t 0x%08x \n", + cmd->response[0]); + printf("\t\t \t\t 0x%08x \n", + cmd->response[1]); + printf("\t\t \t\t 0x%08x \n", + cmd->response[2]); + printf("\t\t \t\t 0x%08x \n", + cmd->response[3]); + printf("\n"); + printf("\t\t\t\t\tDUMPING DATA\n"); + for (i = 0; i < 4; i++) { + int j; + printf("\t\t\t\t\t%03d - ", i*4); + ptr = (u8 *)&cmd->response[i]; + ptr += 3; + for (j = 0; j < 4; j++) + printf("%02x ", *ptr--); + printf("\n"); + } + break; + case MMC_RSP_R3: + printf("\t\tMMC_RSP_R3,4\t\t 0x%08x \n", + cmd->response[0]); + break; + default: + printf("\t\tERROR MMC rsp not supported\n"); + break; + } + } +} + +void mmc_trace_state(struct mmc *mmc, struct mmc_cmd *cmd) +{ + int status; + + status = (cmd->response[0] & MMC_STATUS_CURR_STATE) >> 9; + printf("CURR STATE:%d\n", status); +} +#endif + +#if CONFIG_IS_ENABLED(MMC_VERBOSE) || defined(DEBUG) +const char *mmc_mode_name(enum bus_mode mode) +{ + static const char *const names[] = { + [MMC_LEGACY] = "MMC legacy", + [MMC_HS] = "MMC High Speed (26MHz)", + [SD_HS] = "SD High Speed (50MHz)", + [UHS_SDR12] = "UHS SDR12 (25MHz)", + [UHS_SDR25] = "UHS SDR25 (50MHz)", + [UHS_SDR50] = "UHS SDR50 (100MHz)", + [UHS_SDR104] = "UHS SDR104 (208MHz)", + [UHS_DDR50] = "UHS DDR50 (50MHz)", + [MMC_HS_52] = "MMC High Speed (52MHz)", + [MMC_DDR_52] = "MMC DDR52 (52MHz)", + [MMC_HS_200] = "HS200 (200MHz)", + [MMC_HS_400] = "HS400 (200MHz)", + [MMC_HS_400_ES] = "HS400ES (200MHz)", + }; + + if (mode >= MMC_MODES_END) + return "Unknown mode"; + else + return names[mode]; +} +#endif + +static uint mmc_mode2freq(struct mmc *mmc, enum bus_mode mode) +{ + static const int freqs[] = { + [MMC_LEGACY] = 25000000, + [MMC_HS] = 26000000, + [SD_HS] = 50000000, + [MMC_HS_52] = 52000000, + [MMC_DDR_52] = 52000000, + [UHS_SDR12] = 25000000, + [UHS_SDR25] = 50000000, + [UHS_SDR50] = 100000000, + [UHS_DDR50] = 50000000, + [UHS_SDR104] = 208000000, + [MMC_HS_200] = 200000000, + [MMC_HS_400] = 200000000, + [MMC_HS_400_ES] = 200000000, + }; + + if (mode == MMC_LEGACY) + return mmc->legacy_speed; + else if (mode >= MMC_MODES_END) + return 0; + else + return freqs[mode]; +} + +static int mmc_select_mode(struct mmc *mmc, enum bus_mode mode) +{ + mmc->selected_mode = mode; + mmc->tran_speed = mmc_mode2freq(mmc, mode); + mmc->ddr_mode = mmc_is_mode_ddr(mode); + pr_debug("selecting mode %s (freq : %d MHz)\n", mmc_mode_name(mode), + mmc->tran_speed / 1000000); + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) +{ + int ret; + + mmmc_trace_before_send(mmc, cmd); + ret = mmc->cfg->ops->send_cmd(mmc, cmd, data); + mmmc_trace_after_send(mmc, cmd, ret); + + return ret; +} +#endif + +/** + * mmc_send_cmd_retry() - send a command to the mmc device, retrying on error + * + * @dev: device to receive the command + * @cmd: command to send + * @data: additional data to send/receive + * @retries: how many times to retry; mmc_send_cmd is always called at least + * once + * @return 0 if ok, -ve on error + */ +static int mmc_send_cmd_retry(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data, uint retries) +{ + int ret; + + do { + ret = mmc_send_cmd(mmc, cmd, data); + } while (ret && retries--); + + return ret; +} + +/** + * mmc_send_cmd_quirks() - send a command to the mmc device, retrying if a + * specific quirk is enabled + * + * @dev: device to receive the command + * @cmd: command to send + * @data: additional data to send/receive + * @quirk: retry only if this quirk is enabled + * @retries: how many times to retry; mmc_send_cmd is always called at least + * once + * @return 0 if ok, -ve on error + */ +static int mmc_send_cmd_quirks(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data, u32 quirk, uint retries) +{ + if (CONFIG_IS_ENABLED(MMC_QUIRKS) && mmc->quirks & quirk) + return mmc_send_cmd_retry(mmc, cmd, data, retries); + else + return mmc_send_cmd(mmc, cmd, data); +} + +int mmc_send_status(struct mmc *mmc, unsigned int *status) +{ + struct mmc_cmd cmd; + int ret; + + cmd.cmdidx = MMC_CMD_SEND_STATUS; + cmd.resp_type = MMC_RSP_R1; + if (!mmc_host_is_spi(mmc)) + cmd.cmdarg = mmc->rca << 16; + + ret = mmc_send_cmd_retry(mmc, &cmd, NULL, 4); + mmc_trace_state(mmc, &cmd); + if (!ret) + *status = cmd.response[0]; + + return ret; +} + +int mmc_poll_for_busy(struct mmc *mmc, int timeout_ms) +{ + unsigned int status; + int err; + + err = mmc_wait_dat0(mmc, 1, timeout_ms * 1000); + if (err != -ENOSYS) + return err; + + while (1) { + err = mmc_send_status(mmc, &status); + if (err) + return err; + + if ((status & MMC_STATUS_RDY_FOR_DATA) && + (status & MMC_STATUS_CURR_STATE) != + MMC_STATE_PRG) + break; + + if (status & MMC_STATUS_MASK) { +#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) + pr_err("Status Error: 0x%08x\n", status); +#endif + return -ECOMM; + } + + if (timeout_ms-- <= 0) + break; + + udelay(1000); + } + + if (timeout_ms <= 0) { +#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) + pr_err("Timeout waiting card ready\n"); +#endif + return -ETIMEDOUT; + } + + return 0; +} + +int mmc_set_blocklen(struct mmc *mmc, int len) +{ + struct mmc_cmd cmd; + + if (mmc->ddr_mode) + return 0; + + cmd.cmdidx = MMC_CMD_SET_BLOCKLEN; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = len; + + return mmc_send_cmd_quirks(mmc, &cmd, NULL, + MMC_QUIRK_RETRY_SET_BLOCKLEN, 4); +} + +#ifdef MMC_SUPPORTS_TUNING +static const u8 tuning_blk_pattern_4bit[] = { + 0xff, 0x0f, 0xff, 0x00, 0xff, 0xcc, 0xc3, 0xcc, + 0xc3, 0x3c, 0xcc, 0xff, 0xfe, 0xff, 0xfe, 0xef, + 0xff, 0xdf, 0xff, 0xdd, 0xff, 0xfb, 0xff, 0xfb, + 0xbf, 0xff, 0x7f, 0xff, 0x77, 0xf7, 0xbd, 0xef, + 0xff, 0xf0, 0xff, 0xf0, 0x0f, 0xfc, 0xcc, 0x3c, + 0xcc, 0x33, 0xcc, 0xcf, 0xff, 0xef, 0xff, 0xee, + 0xff, 0xfd, 0xff, 0xfd, 0xdf, 0xff, 0xbf, 0xff, + 0xbb, 0xff, 0xf7, 0xff, 0xf7, 0x7f, 0x7b, 0xde, +}; + +static const u8 tuning_blk_pattern_8bit[] = { + 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, + 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc, + 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff, + 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff, + 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd, + 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, + 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff, + 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, + 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, + 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, + 0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, + 0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, + 0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, + 0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, +}; + +int mmc_send_tuning(struct mmc *mmc, u32 opcode, int *cmd_error) +{ + struct mmc_cmd cmd; + struct mmc_data data; + const u8 *tuning_block_pattern; + int size, err; + + if (mmc->bus_width == 8) { + tuning_block_pattern = tuning_blk_pattern_8bit; + size = sizeof(tuning_blk_pattern_8bit); + } else if (mmc->bus_width == 4) { + tuning_block_pattern = tuning_blk_pattern_4bit; + size = sizeof(tuning_blk_pattern_4bit); + } else { + return -EINVAL; + } + + ALLOC_CACHE_ALIGN_BUFFER(u8, data_buf, size); + + cmd.cmdidx = opcode; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1; + + data.dest = (void *)data_buf; + data.blocks = 1; + data.blocksize = size; + data.flags = MMC_DATA_READ; + + err = mmc_send_cmd(mmc, &cmd, &data); + if (err) + return err; + + if (memcmp(data_buf, tuning_block_pattern, size)) + return -EIO; + + return 0; +} +#endif + +static int mmc_read_blocks(struct mmc *mmc, void *dst, lbaint_t start, + lbaint_t blkcnt) +{ + struct mmc_cmd cmd; + struct mmc_data data; + + if (blkcnt > 1) + cmd.cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK; + else + cmd.cmdidx = MMC_CMD_READ_SINGLE_BLOCK; + + if (mmc->high_capacity) + cmd.cmdarg = start; + else + cmd.cmdarg = start * mmc->read_bl_len; + + cmd.resp_type = MMC_RSP_R1; + + data.dest = dst; + data.blocks = blkcnt; + data.blocksize = mmc->read_bl_len; + data.flags = MMC_DATA_READ; + + if (mmc_send_cmd(mmc, &cmd, &data)) + return 0; + + if (blkcnt > 1) { + cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1b; + if (mmc_send_cmd(mmc, &cmd, NULL)) { +#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) + pr_err("mmc fail to send stop cmd\n"); +#endif + return 0; + } + } + + return blkcnt; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +static int mmc_get_b_max(struct mmc *mmc, void *dst, lbaint_t blkcnt) +{ + if (mmc->cfg->ops->get_b_max) + return mmc->cfg->ops->get_b_max(mmc, dst, blkcnt); + else + return mmc->cfg->b_max; +} +#endif + +#if CONFIG_IS_ENABLED(BLK) +ulong mmc_bread(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, void *dst) +#else +ulong mmc_bread(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt, + void *dst) +#endif +{ +#if CONFIG_IS_ENABLED(BLK) + struct blk_desc *block_dev = dev_get_uclass_plat(dev); +#endif + int dev_num = block_dev->devnum; + int err; + lbaint_t cur, blocks_todo = blkcnt; + uint b_max; + + if (blkcnt == 0) + return 0; + + struct mmc *mmc = find_mmc_device(dev_num); + if (!mmc) + return 0; + + if (CONFIG_IS_ENABLED(MMC_TINY)) + err = mmc_switch_part(mmc, block_dev->hwpart); + else + err = blk_dselect_hwpart(block_dev, block_dev->hwpart); + + if (err < 0) + return 0; + + if ((start + blkcnt) > block_dev->lba) { +#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) + pr_err("MMC: block number 0x" LBAF " exceeds max(0x" LBAF ")\n", + start + blkcnt, block_dev->lba); +#endif + return 0; + } + + if (mmc_set_blocklen(mmc, mmc->read_bl_len)) { + pr_debug("%s: Failed to set blocklen\n", __func__); + return 0; + } + + b_max = mmc_get_b_max(mmc, dst, blkcnt); + + do { + cur = (blocks_todo > b_max) ? b_max : blocks_todo; + if (mmc_read_blocks(mmc, dst, start, cur) != cur) { + pr_debug("%s: Failed to read blocks\n", __func__); + return 0; + } + blocks_todo -= cur; + start += cur; + dst += cur * mmc->read_bl_len; + } while (blocks_todo > 0); + + return blkcnt; +} + +static int mmc_go_idle(struct mmc *mmc) +{ + struct mmc_cmd cmd; + int err; + + udelay(1000); + + cmd.cmdidx = MMC_CMD_GO_IDLE_STATE; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_NONE; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + + udelay(2000); + + return 0; +} + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) +static int mmc_switch_voltage(struct mmc *mmc, int signal_voltage) +{ + struct mmc_cmd cmd; + int err = 0; + + /* + * Send CMD11 only if the request is to switch the card to + * 1.8V signalling. + */ + if (signal_voltage == MMC_SIGNAL_VOLTAGE_330) + return mmc_set_signal_voltage(mmc, signal_voltage); + + cmd.cmdidx = SD_CMD_SWITCH_UHS18V; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1; + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) + return err; + + if (!mmc_host_is_spi(mmc) && (cmd.response[0] & MMC_STATUS_ERROR)) + return -EIO; + + /* + * The card should drive cmd and dat[0:3] low immediately + * after the response of cmd11, but wait 100 us to be sure + */ + err = mmc_wait_dat0(mmc, 0, 100); + if (err == -ENOSYS) + udelay(100); + else if (err) + return -ETIMEDOUT; + + /* + * During a signal voltage level switch, the clock must be gated + * for 5 ms according to the SD spec + */ + mmc_set_clock(mmc, mmc->clock, MMC_CLK_DISABLE); + + err = mmc_set_signal_voltage(mmc, signal_voltage); + if (err) + return err; + + /* Keep clock gated for at least 10 ms, though spec only says 5 ms */ + mdelay(10); + mmc_set_clock(mmc, mmc->clock, MMC_CLK_ENABLE); + + /* + * Failure to switch is indicated by the card holding + * dat[0:3] low. Wait for at least 1 ms according to spec + */ + err = mmc_wait_dat0(mmc, 1, 1000); + if (err == -ENOSYS) + udelay(1000); + else if (err) + return -ETIMEDOUT; + + return 0; +} +#endif + +static int sd_send_op_cond(struct mmc *mmc, bool uhs_en) +{ + int timeout = 1000; + int err; + struct mmc_cmd cmd; + + while (1) { + cmd.cmdidx = MMC_CMD_APP_CMD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 0; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + + cmd.cmdidx = SD_CMD_APP_SEND_OP_COND; + cmd.resp_type = MMC_RSP_R3; + + /* + * Most cards do not answer if some reserved bits + * in the ocr are set. However, Some controller + * can set bit 7 (reserved for low voltages), but + * how to manage low voltages SD card is not yet + * specified. + */ + cmd.cmdarg = mmc_host_is_spi(mmc) ? 0 : + (mmc->cfg->voltages & 0xff8000); + + if (mmc->version == SD_VERSION_2) + cmd.cmdarg |= OCR_HCS; + + if (uhs_en) + cmd.cmdarg |= OCR_S18R; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + + if (cmd.response[0] & OCR_BUSY) + break; + + if (timeout-- <= 0) + return -EOPNOTSUPP; + + udelay(1000); + } + + if (mmc->version != SD_VERSION_2) + mmc->version = SD_VERSION_1_0; + + if (mmc_host_is_spi(mmc)) { /* read OCR for spi */ + cmd.cmdidx = MMC_CMD_SPI_READ_OCR; + cmd.resp_type = MMC_RSP_R3; + cmd.cmdarg = 0; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + } + + mmc->ocr = cmd.response[0]; + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) + if (uhs_en && !(mmc_host_is_spi(mmc)) && (cmd.response[0] & 0x41000000) + == 0x41000000) { + err = mmc_switch_voltage(mmc, MMC_SIGNAL_VOLTAGE_180); + if (err) + return err; + } +#endif + + mmc->high_capacity = ((mmc->ocr & OCR_HCS) == OCR_HCS); + mmc->rca = 0; + + return 0; +} + +static int mmc_send_op_cond_iter(struct mmc *mmc, int use_arg) +{ + struct mmc_cmd cmd; + int err; + + cmd.cmdidx = MMC_CMD_SEND_OP_COND; + cmd.resp_type = MMC_RSP_R3; + cmd.cmdarg = 0; + if (use_arg && !mmc_host_is_spi(mmc)) + cmd.cmdarg = OCR_HCS | + (mmc->cfg->voltages & + (mmc->ocr & OCR_VOLTAGE_MASK)) | + (mmc->ocr & OCR_ACCESS_MODE); + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) + return err; + mmc->ocr = cmd.response[0]; + return 0; +} + +static int mmc_send_op_cond(struct mmc *mmc) +{ + int err, i; + int timeout = 1000; + uint start; + + /* Some cards seem to need this */ + mmc_go_idle(mmc); + + start = get_timer(0); + /* Asking to the card its capabilities */ + for (i = 0; ; i++) { + err = mmc_send_op_cond_iter(mmc, i != 0); + if (err) + return err; + + /* exit if not busy (flag seems to be inverted) */ + if (mmc->ocr & OCR_BUSY) + break; + + if (get_timer(start) > timeout) + return -ETIMEDOUT; + udelay(100); + } + mmc->op_cond_pending = 1; + return 0; +} + +static int mmc_complete_op_cond(struct mmc *mmc) +{ + struct mmc_cmd cmd; + int timeout = 1000; + ulong start; + int err; + + mmc->op_cond_pending = 0; + if (!(mmc->ocr & OCR_BUSY)) { + /* Some cards seem to need this */ + mmc_go_idle(mmc); + + start = get_timer(0); + while (1) { + err = mmc_send_op_cond_iter(mmc, 1); + if (err) + return err; + if (mmc->ocr & OCR_BUSY) + break; + if (get_timer(start) > timeout) + return -EOPNOTSUPP; + udelay(100); + } + } + + if (mmc_host_is_spi(mmc)) { /* read OCR for spi */ + cmd.cmdidx = MMC_CMD_SPI_READ_OCR; + cmd.resp_type = MMC_RSP_R3; + cmd.cmdarg = 0; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + + mmc->ocr = cmd.response[0]; + } + + mmc->version = MMC_VERSION_UNKNOWN; + + mmc->high_capacity = ((mmc->ocr & OCR_HCS) == OCR_HCS); + mmc->rca = 1; + + return 0; +} + + +int mmc_send_ext_csd(struct mmc *mmc, u8 *ext_csd) +{ + struct mmc_cmd cmd; + struct mmc_data data; + int err; + + /* Get the Card Status Register */ + cmd.cmdidx = MMC_CMD_SEND_EXT_CSD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 0; + + data.dest = (char *)ext_csd; + data.blocks = 1; + data.blocksize = MMC_MAX_BLOCK_LEN; + data.flags = MMC_DATA_READ; + + err = mmc_send_cmd(mmc, &cmd, &data); + + return err; +} + +static int __mmc_switch(struct mmc *mmc, u8 set, u8 index, u8 value, + bool send_status) +{ + unsigned int status, start; + struct mmc_cmd cmd; + int timeout_ms = DEFAULT_CMD6_TIMEOUT_MS; + bool is_part_switch = (set == EXT_CSD_CMD_SET_NORMAL) && + (index == EXT_CSD_PART_CONF); + int ret; + + if (mmc->gen_cmd6_time) + timeout_ms = mmc->gen_cmd6_time * 10; + + if (is_part_switch && mmc->part_switch_time) + timeout_ms = mmc->part_switch_time * 10; + + cmd.cmdidx = MMC_CMD_SWITCH; + cmd.resp_type = MMC_RSP_R1b; + cmd.cmdarg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | + (index << 16) | + (value << 8); + + ret = mmc_send_cmd_retry(mmc, &cmd, NULL, 3); + if (ret) + return ret; + + start = get_timer(0); + + /* poll dat0 for rdy/buys status */ + ret = mmc_wait_dat0(mmc, 1, timeout_ms * 1000); + if (ret && ret != -ENOSYS) + return ret; + + /* + * In cases when not allowed to poll by using CMD13 or because we aren't + * capable of polling by using mmc_wait_dat0, then rely on waiting the + * stated timeout to be sufficient. + */ + if (ret == -ENOSYS && !send_status) { + mdelay(timeout_ms); + return 0; + } + + /* Finally wait until the card is ready or indicates a failure + * to switch. It doesn't hurt to use CMD13 here even if send_status + * is false, because by now (after 'timeout_ms' ms) the bus should be + * reliable. + */ + do { + ret = mmc_send_status(mmc, &status); + + if (!ret && (status & MMC_STATUS_SWITCH_ERROR)) { + pr_debug("switch failed %d/%d/0x%x !\n", set, index, + value); + return -EIO; + } + if (!ret && (status & MMC_STATUS_RDY_FOR_DATA) && + (status & MMC_STATUS_CURR_STATE) == MMC_STATE_TRANS) + return 0; + udelay(100); + } while (get_timer(start) < timeout_ms); + + return -ETIMEDOUT; +} + +int mmc_switch(struct mmc *mmc, u8 set, u8 index, u8 value) +{ + return __mmc_switch(mmc, set, index, value, true); +} + +int mmc_boot_wp(struct mmc *mmc) +{ + return mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BOOT_WP, 1); +} + +#if !CONFIG_IS_ENABLED(MMC_TINY) +static int mmc_set_card_speed(struct mmc *mmc, enum bus_mode mode, + bool hsdowngrade) +{ + int err; + int speed_bits; + + ALLOC_CACHE_ALIGN_BUFFER(u8, test_csd, MMC_MAX_BLOCK_LEN); + + switch (mode) { + case MMC_HS: + case MMC_HS_52: + case MMC_DDR_52: + speed_bits = EXT_CSD_TIMING_HS; + break; +#if CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) + case MMC_HS_200: + speed_bits = EXT_CSD_TIMING_HS200; + break; +#endif +#if CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + case MMC_HS_400: + speed_bits = EXT_CSD_TIMING_HS400; + break; +#endif +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT) + case MMC_HS_400_ES: + speed_bits = EXT_CSD_TIMING_HS400; + break; +#endif + case MMC_LEGACY: + speed_bits = EXT_CSD_TIMING_LEGACY; + break; + default: + return -EINVAL; + } + + err = __mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, + speed_bits, !hsdowngrade); + if (err) + return err; + +#if CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + /* + * In case the eMMC is in HS200/HS400 mode and we are downgrading + * to HS mode, the card clock are still running much faster than + * the supported HS mode clock, so we can not reliably read out + * Extended CSD. Reconfigure the controller to run at HS mode. + */ + if (hsdowngrade) { + mmc_select_mode(mmc, MMC_HS); + mmc_set_clock(mmc, mmc_mode2freq(mmc, MMC_HS), false); + } +#endif + + if ((mode == MMC_HS) || (mode == MMC_HS_52)) { + /* Now check to see that it worked */ + err = mmc_send_ext_csd(mmc, test_csd); + if (err) + return err; + + /* No high-speed support */ + if (!test_csd[EXT_CSD_HS_TIMING]) + return -ENOTSUPP; + } + + return 0; +} + +static int mmc_get_capabilities(struct mmc *mmc) +{ + u8 *ext_csd = mmc->ext_csd; + char cardtype; + + mmc->card_caps = MMC_MODE_1BIT | MMC_CAP(MMC_LEGACY); + + if (mmc_host_is_spi(mmc)) + return 0; + + /* Only version 4 supports high-speed */ + if (mmc->version < MMC_VERSION_4) + return 0; + + if (!ext_csd) { + pr_err("No ext_csd found!\n"); /* this should enver happen */ + return -ENOTSUPP; + } + + mmc->card_caps |= MMC_MODE_4BIT | MMC_MODE_8BIT; + + cardtype = ext_csd[EXT_CSD_CARD_TYPE]; + mmc->cardtype = cardtype; + +#if CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) + if (cardtype & (EXT_CSD_CARD_TYPE_HS200_1_2V | + EXT_CSD_CARD_TYPE_HS200_1_8V)) { + mmc->card_caps |= MMC_MODE_HS200; + } +#endif +#if CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT) + if (cardtype & (EXT_CSD_CARD_TYPE_HS400_1_2V | + EXT_CSD_CARD_TYPE_HS400_1_8V)) { + mmc->card_caps |= MMC_MODE_HS400; + } +#endif + if (cardtype & EXT_CSD_CARD_TYPE_52) { + if (cardtype & EXT_CSD_CARD_TYPE_DDR_52) + mmc->card_caps |= MMC_MODE_DDR_52MHz; + mmc->card_caps |= MMC_MODE_HS_52MHz; + } + if (cardtype & EXT_CSD_CARD_TYPE_26) + mmc->card_caps |= MMC_MODE_HS; + +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT) + if (ext_csd[EXT_CSD_STROBE_SUPPORT] && + (mmc->card_caps & MMC_MODE_HS400)) { + mmc->card_caps |= MMC_MODE_HS400_ES; + } +#endif + + return 0; +} +#endif + +static int mmc_set_capacity(struct mmc *mmc, int part_num) +{ + switch (part_num) { + case 0: + mmc->capacity = mmc->capacity_user; + break; + case 1: + case 2: + mmc->capacity = mmc->capacity_boot; + break; + case 3: + mmc->capacity = mmc->capacity_rpmb; + break; + case 4: + case 5: + case 6: + case 7: + mmc->capacity = mmc->capacity_gp[part_num - 4]; + break; + default: + return -1; + } + + mmc_get_blk_desc(mmc)->lba = lldiv(mmc->capacity, mmc->read_bl_len); + + return 0; +} + +int mmc_switch_part(struct mmc *mmc, unsigned int part_num) +{ + int ret; + int retry = 3; + + do { + ret = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_PART_CONF, + (mmc->part_config & ~PART_ACCESS_MASK) + | (part_num & PART_ACCESS_MASK)); + } while (ret && retry--); + + /* + * Set the capacity if the switch succeeded or was intended + * to return to representing the raw device. + */ + if ((ret == 0) || ((ret == -ENODEV) && (part_num == 0))) { + ret = mmc_set_capacity(mmc, part_num); + mmc_get_blk_desc(mmc)->hwpart = part_num; + } + + return ret; +} + +#if CONFIG_IS_ENABLED(MMC_HW_PARTITIONING) +int mmc_hwpart_config(struct mmc *mmc, + const struct mmc_hwpart_conf *conf, + enum mmc_hwpart_conf_mode mode) +{ + u8 part_attrs = 0; + u32 enh_size_mult; + u32 enh_start_addr; + u32 gp_size_mult[4]; + u32 max_enh_size_mult; + u32 tot_enh_size_mult = 0; + u8 wr_rel_set; + int i, pidx, err; + ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN); + + if (mode < MMC_HWPART_CONF_CHECK || mode > MMC_HWPART_CONF_COMPLETE) + return -EINVAL; + + if (IS_SD(mmc) || (mmc->version < MMC_VERSION_4_41)) { + pr_err("eMMC >= 4.4 required for enhanced user data area\n"); + return -EMEDIUMTYPE; + } + + if (!(mmc->part_support & PART_SUPPORT)) { + pr_err("Card does not support partitioning\n"); + return -EMEDIUMTYPE; + } + + if (!mmc->hc_wp_grp_size) { + pr_err("Card does not define HC WP group size\n"); + return -EMEDIUMTYPE; + } + + /* check partition alignment and total enhanced size */ + if (conf->user.enh_size) { + if (conf->user.enh_size % mmc->hc_wp_grp_size || + conf->user.enh_start % mmc->hc_wp_grp_size) { + pr_err("User data enhanced area not HC WP group " + "size aligned\n"); + return -EINVAL; + } + part_attrs |= EXT_CSD_ENH_USR; + enh_size_mult = conf->user.enh_size / mmc->hc_wp_grp_size; + if (mmc->high_capacity) { + enh_start_addr = conf->user.enh_start; + } else { + enh_start_addr = (conf->user.enh_start << 9); + } + } else { + enh_size_mult = 0; + enh_start_addr = 0; + } + tot_enh_size_mult += enh_size_mult; + + for (pidx = 0; pidx < 4; pidx++) { + if (conf->gp_part[pidx].size % mmc->hc_wp_grp_size) { + pr_err("GP%i partition not HC WP group size " + "aligned\n", pidx+1); + return -EINVAL; + } + gp_size_mult[pidx] = conf->gp_part[pidx].size / mmc->hc_wp_grp_size; + if (conf->gp_part[pidx].size && conf->gp_part[pidx].enhanced) { + part_attrs |= EXT_CSD_ENH_GP(pidx); + tot_enh_size_mult += gp_size_mult[pidx]; + } + } + + if (part_attrs && ! (mmc->part_support & ENHNCD_SUPPORT)) { + pr_err("Card does not support enhanced attribute\n"); + return -EMEDIUMTYPE; + } + + err = mmc_send_ext_csd(mmc, ext_csd); + if (err) + return err; + + max_enh_size_mult = + (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT+2] << 16) + + (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT+1] << 8) + + ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT]; + if (tot_enh_size_mult > max_enh_size_mult) { + pr_err("Total enhanced size exceeds maximum (%u > %u)\n", + tot_enh_size_mult, max_enh_size_mult); + return -EMEDIUMTYPE; + } + + /* The default value of EXT_CSD_WR_REL_SET is device + * dependent, the values can only be changed if the + * EXT_CSD_HS_CTRL_REL bit is set. The values can be + * changed only once and before partitioning is completed. */ + wr_rel_set = ext_csd[EXT_CSD_WR_REL_SET]; + if (conf->user.wr_rel_change) { + if (conf->user.wr_rel_set) + wr_rel_set |= EXT_CSD_WR_DATA_REL_USR; + else + wr_rel_set &= ~EXT_CSD_WR_DATA_REL_USR; + } + for (pidx = 0; pidx < 4; pidx++) { + if (conf->gp_part[pidx].wr_rel_change) { + if (conf->gp_part[pidx].wr_rel_set) + wr_rel_set |= EXT_CSD_WR_DATA_REL_GP(pidx); + else + wr_rel_set &= ~EXT_CSD_WR_DATA_REL_GP(pidx); + } + } + + if (wr_rel_set != ext_csd[EXT_CSD_WR_REL_SET] && + !(ext_csd[EXT_CSD_WR_REL_PARAM] & EXT_CSD_HS_CTRL_REL)) { + puts("Card does not support host controlled partition write " + "reliability settings\n"); + return -EMEDIUMTYPE; + } + + if (ext_csd[EXT_CSD_PARTITION_SETTING] & + EXT_CSD_PARTITION_SETTING_COMPLETED) { + pr_err("Card already partitioned\n"); + return -EPERM; + } + + if (mode == MMC_HWPART_CONF_CHECK) + return 0; + + /* Partitioning requires high-capacity size definitions */ + if (!(ext_csd[EXT_CSD_ERASE_GROUP_DEF] & 0x01)) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_ERASE_GROUP_DEF, 1); + + if (err) + return err; + + ext_csd[EXT_CSD_ERASE_GROUP_DEF] = 1; + +#if CONFIG_IS_ENABLED(MMC_WRITE) + /* update erase group size to be high-capacity */ + mmc->erase_grp_size = + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * 1024; +#endif + + } + + /* all OK, write the configuration */ + for (i = 0; i < 4; i++) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_ENH_START_ADDR+i, + (enh_start_addr >> (i*8)) & 0xFF); + if (err) + return err; + } + for (i = 0; i < 3; i++) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_ENH_SIZE_MULT+i, + (enh_size_mult >> (i*8)) & 0xFF); + if (err) + return err; + } + for (pidx = 0; pidx < 4; pidx++) { + for (i = 0; i < 3; i++) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_GP_SIZE_MULT+pidx*3+i, + (gp_size_mult[pidx] >> (i*8)) & 0xFF); + if (err) + return err; + } + } + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_PARTITIONS_ATTRIBUTE, part_attrs); + if (err) + return err; + + if (mode == MMC_HWPART_CONF_SET) + return 0; + + /* The WR_REL_SET is a write-once register but shall be + * written before setting PART_SETTING_COMPLETED. As it is + * write-once we can only write it when completing the + * partitioning. */ + if (wr_rel_set != ext_csd[EXT_CSD_WR_REL_SET]) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_WR_REL_SET, wr_rel_set); + if (err) + return err; + } + + /* Setting PART_SETTING_COMPLETED confirms the partition + * configuration but it only becomes effective after power + * cycle, so we do not adjust the partition related settings + * in the mmc struct. */ + + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_PARTITION_SETTING, + EXT_CSD_PARTITION_SETTING_COMPLETED); + if (err) + return err; + + return 0; +} +#endif + +#if !CONFIG_IS_ENABLED(DM_MMC) +int mmc_getcd(struct mmc *mmc) +{ + int cd; + + cd = board_mmc_getcd(mmc); + + if (cd < 0) { + if (mmc->cfg->ops->getcd) + cd = mmc->cfg->ops->getcd(mmc); + else + cd = 1; + } + + return cd; +} +#endif + +#if !CONFIG_IS_ENABLED(MMC_TINY) +static int sd_switch(struct mmc *mmc, int mode, int group, u8 value, u8 *resp) +{ + struct mmc_cmd cmd; + struct mmc_data data; + + /* Switch the frequency */ + cmd.cmdidx = SD_CMD_SWITCH_FUNC; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = (mode << 31) | 0xffffff; + cmd.cmdarg &= ~(0xf << (group * 4)); + cmd.cmdarg |= value << (group * 4); + + data.dest = (char *)resp; + data.blocksize = 64; + data.blocks = 1; + data.flags = MMC_DATA_READ; + + return mmc_send_cmd(mmc, &cmd, &data); +} + +static int sd_get_capabilities(struct mmc *mmc) +{ + int err; + struct mmc_cmd cmd; + ALLOC_CACHE_ALIGN_BUFFER(__be32, scr, 2); + ALLOC_CACHE_ALIGN_BUFFER(__be32, switch_status, 16); + struct mmc_data data; + int timeout; +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) + u32 sd3_bus_mode; +#endif + + mmc->card_caps = MMC_MODE_1BIT | MMC_CAP(MMC_LEGACY); + + if (mmc_host_is_spi(mmc)) + return 0; + + /* Read the SCR to find out if this card supports higher speeds */ + cmd.cmdidx = MMC_CMD_APP_CMD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = mmc->rca << 16; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + + cmd.cmdidx = SD_CMD_APP_SEND_SCR; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 0; + + data.dest = (char *)scr; + data.blocksize = 8; + data.blocks = 1; + data.flags = MMC_DATA_READ; + + err = mmc_send_cmd_retry(mmc, &cmd, &data, 3); + + if (err) + return err; + + mmc->scr[0] = __be32_to_cpu(scr[0]); + mmc->scr[1] = __be32_to_cpu(scr[1]); + + switch ((mmc->scr[0] >> 24) & 0xf) { + case 0: + mmc->version = SD_VERSION_1_0; + break; + case 1: + mmc->version = SD_VERSION_1_10; + break; + case 2: + mmc->version = SD_VERSION_2; + if ((mmc->scr[0] >> 15) & 0x1) + mmc->version = SD_VERSION_3; + break; + default: + mmc->version = SD_VERSION_1_0; + break; + } + + if (mmc->scr[0] & SD_DATA_4BIT) + mmc->card_caps |= MMC_MODE_4BIT; + + /* Version 1.0 doesn't support switching */ + if (mmc->version == SD_VERSION_1_0) + return 0; + + timeout = 4; + while (timeout--) { + err = sd_switch(mmc, SD_SWITCH_CHECK, 0, 1, + (u8 *)switch_status); + + if (err) + return err; + + /* The high-speed function is busy. Try again */ + if (!(__be32_to_cpu(switch_status[7]) & SD_HIGHSPEED_BUSY)) + break; + } + + /* If high-speed isn't supported, we return */ + if (__be32_to_cpu(switch_status[3]) & SD_HIGHSPEED_SUPPORTED) + mmc->card_caps |= MMC_CAP(SD_HS); + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) + /* Version before 3.0 don't support UHS modes */ + if (mmc->version < SD_VERSION_3) + return 0; + + sd3_bus_mode = __be32_to_cpu(switch_status[3]) >> 16 & 0x1f; + if (sd3_bus_mode & SD_MODE_UHS_SDR104) + mmc->card_caps |= MMC_CAP(UHS_SDR104); + if (sd3_bus_mode & SD_MODE_UHS_SDR50) + mmc->card_caps |= MMC_CAP(UHS_SDR50); + if (sd3_bus_mode & SD_MODE_UHS_SDR25) + mmc->card_caps |= MMC_CAP(UHS_SDR25); + if (sd3_bus_mode & SD_MODE_UHS_SDR12) + mmc->card_caps |= MMC_CAP(UHS_SDR12); + if (sd3_bus_mode & SD_MODE_UHS_DDR50) + mmc->card_caps |= MMC_CAP(UHS_DDR50); +#endif + + return 0; +} + +static int sd_set_card_speed(struct mmc *mmc, enum bus_mode mode) +{ + int err; + + ALLOC_CACHE_ALIGN_BUFFER(uint, switch_status, 16); + int speed; + + /* SD version 1.00 and 1.01 does not support CMD 6 */ + if (mmc->version == SD_VERSION_1_0) + return 0; + + switch (mode) { + case MMC_LEGACY: + speed = UHS_SDR12_BUS_SPEED; + break; + case SD_HS: + speed = HIGH_SPEED_BUS_SPEED; + break; +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) + case UHS_SDR12: + speed = UHS_SDR12_BUS_SPEED; + break; + case UHS_SDR25: + speed = UHS_SDR25_BUS_SPEED; + break; + case UHS_SDR50: + speed = UHS_SDR50_BUS_SPEED; + break; + case UHS_DDR50: + speed = UHS_DDR50_BUS_SPEED; + break; + case UHS_SDR104: + speed = UHS_SDR104_BUS_SPEED; + break; +#endif + default: + return -EINVAL; + } + + err = sd_switch(mmc, SD_SWITCH_SWITCH, 0, speed, (u8 *)switch_status); + if (err) + return err; + + if (((__be32_to_cpu(switch_status[4]) >> 24) & 0xF) != speed) + return -ENOTSUPP; + + return 0; +} + +static int sd_select_bus_width(struct mmc *mmc, int w) +{ + int err; + struct mmc_cmd cmd; + + if ((w != 4) && (w != 1)) + return -EINVAL; + + cmd.cmdidx = MMC_CMD_APP_CMD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = mmc->rca << 16; + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) + return err; + + cmd.cmdidx = SD_CMD_APP_SET_BUS_WIDTH; + cmd.resp_type = MMC_RSP_R1; + if (w == 4) + cmd.cmdarg = 2; + else if (w == 1) + cmd.cmdarg = 0; + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) + return err; + + return 0; +} +#endif + +#if CONFIG_IS_ENABLED(MMC_WRITE) +static int sd_read_ssr(struct mmc *mmc) +{ + static const unsigned int sd_au_size[] = { + 0, SZ_16K / 512, SZ_32K / 512, + SZ_64K / 512, SZ_128K / 512, SZ_256K / 512, + SZ_512K / 512, SZ_1M / 512, SZ_2M / 512, + SZ_4M / 512, SZ_8M / 512, (SZ_8M + SZ_4M) / 512, + SZ_16M / 512, (SZ_16M + SZ_8M) / 512, SZ_32M / 512, + SZ_64M / 512, + }; + int err, i; + struct mmc_cmd cmd; + ALLOC_CACHE_ALIGN_BUFFER(uint, ssr, 16); + struct mmc_data data; + unsigned int au, eo, et, es; + + cmd.cmdidx = MMC_CMD_APP_CMD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = mmc->rca << 16; + + err = mmc_send_cmd_quirks(mmc, &cmd, NULL, MMC_QUIRK_RETRY_APP_CMD, 4); + if (err) + return err; + + cmd.cmdidx = SD_CMD_APP_SD_STATUS; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 0; + + data.dest = (char *)ssr; + data.blocksize = 64; + data.blocks = 1; + data.flags = MMC_DATA_READ; + + err = mmc_send_cmd_retry(mmc, &cmd, &data, 3); + if (err) + return err; + + for (i = 0; i < 16; i++) + ssr[i] = be32_to_cpu(ssr[i]); + + au = (ssr[2] >> 12) & 0xF; + if ((au <= 9) || (mmc->version == SD_VERSION_3)) { + mmc->ssr.au = sd_au_size[au]; + es = (ssr[3] >> 24) & 0xFF; + es |= (ssr[2] & 0xFF) << 8; + et = (ssr[3] >> 18) & 0x3F; + if (es && et) { + eo = (ssr[3] >> 16) & 0x3; + mmc->ssr.erase_timeout = (et * 1000) / es; + mmc->ssr.erase_offset = eo * 1000; + } + } else { + pr_debug("Invalid Allocation Unit Size.\n"); + } + + return 0; +} +#endif +/* frequency bases */ +/* divided by 10 to be nice to platforms without floating point */ +static const int fbase[] = { + 10000, + 100000, + 1000000, + 10000000, +}; + +/* Multiplier values for TRAN_SPEED. Multiplied by 10 to be nice + * to platforms without floating point. + */ +static const u8 multipliers[] = { + 0, /* reserved */ + 10, + 12, + 13, + 15, + 20, + 25, + 30, + 35, + 40, + 45, + 50, + 55, + 60, + 70, + 80, +}; + +static inline int bus_width(uint cap) +{ + if (cap == MMC_MODE_8BIT) + return 8; + if (cap == MMC_MODE_4BIT) + return 4; + if (cap == MMC_MODE_1BIT) + return 1; + pr_warn("invalid bus witdh capability 0x%x\n", cap); + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +#ifdef MMC_SUPPORTS_TUNING +static int mmc_execute_tuning(struct mmc *mmc, uint opcode) +{ + return -ENOTSUPP; +} +#endif + +static int mmc_set_ios(struct mmc *mmc) +{ + int ret = 0; + + if (mmc->cfg->ops->set_ios) + ret = mmc->cfg->ops->set_ios(mmc); + + return ret; +} + +static int mmc_host_power_cycle(struct mmc *mmc) +{ + int ret = 0; + + if (mmc->cfg->ops->host_power_cycle) + ret = mmc->cfg->ops->host_power_cycle(mmc); + + return ret; +} +#endif + +int mmc_set_clock(struct mmc *mmc, uint clock, bool disable) +{ + if (!disable) { + if (clock > mmc->cfg->f_max) + clock = mmc->cfg->f_max; + + if (clock < mmc->cfg->f_min) + clock = mmc->cfg->f_min; + } + + mmc->clock = clock; + mmc->clk_disable = disable; + + debug("clock is %s (%dHz)\n", disable ? "disabled" : "enabled", clock); + + return mmc_set_ios(mmc); +} + +static int mmc_set_bus_width(struct mmc *mmc, uint width) +{ + mmc->bus_width = width; + + return mmc_set_ios(mmc); +} + +#if CONFIG_IS_ENABLED(MMC_VERBOSE) || defined(DEBUG) +/* + * helper function to display the capabilities in a human + * friendly manner. The capabilities include bus width and + * supported modes. + */ +void mmc_dump_capabilities(const char *text, uint caps) +{ + enum bus_mode mode; + + pr_debug("%s: widths [", text); + if (caps & MMC_MODE_8BIT) + pr_debug("8, "); + if (caps & MMC_MODE_4BIT) + pr_debug("4, "); + if (caps & MMC_MODE_1BIT) + pr_debug("1, "); + pr_debug("\b\b] modes ["); + for (mode = MMC_LEGACY; mode < MMC_MODES_END; mode++) + if (MMC_CAP(mode) & caps) + pr_debug("%s, ", mmc_mode_name(mode)); + pr_debug("\b\b]\n"); +} +#endif + +struct mode_width_tuning { + enum bus_mode mode; + uint widths; +#ifdef MMC_SUPPORTS_TUNING + uint tuning; +#endif +}; + +#if CONFIG_IS_ENABLED(MMC_IO_VOLTAGE) +int mmc_voltage_to_mv(enum mmc_voltage voltage) +{ + switch (voltage) { + case MMC_SIGNAL_VOLTAGE_000: return 0; + case MMC_SIGNAL_VOLTAGE_330: return 3300; + case MMC_SIGNAL_VOLTAGE_180: return 1800; + case MMC_SIGNAL_VOLTAGE_120: return 1200; + } + return -EINVAL; +} + +static int mmc_set_signal_voltage(struct mmc *mmc, uint signal_voltage) +{ + int err; + + if (mmc->signal_voltage == signal_voltage) + return 0; + + mmc->signal_voltage = signal_voltage; + err = mmc_set_ios(mmc); + if (err) + pr_debug("unable to set voltage (err %d)\n", err); + + return err; +} +#else +static inline int mmc_set_signal_voltage(struct mmc *mmc, uint signal_voltage) +{ + return 0; +} +#endif + +#if !CONFIG_IS_ENABLED(MMC_TINY) +static const struct mode_width_tuning sd_modes_by_pref[] = { +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) +#ifdef MMC_SUPPORTS_TUNING + { + .mode = UHS_SDR104, + .widths = MMC_MODE_4BIT | MMC_MODE_1BIT, + .tuning = MMC_CMD_SEND_TUNING_BLOCK + }, +#endif + { + .mode = UHS_SDR50, + .widths = MMC_MODE_4BIT | MMC_MODE_1BIT, + }, + { + .mode = UHS_DDR50, + .widths = MMC_MODE_4BIT | MMC_MODE_1BIT, + }, + { + .mode = UHS_SDR25, + .widths = MMC_MODE_4BIT | MMC_MODE_1BIT, + }, +#endif + { + .mode = SD_HS, + .widths = MMC_MODE_4BIT | MMC_MODE_1BIT, + }, +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) + { + .mode = UHS_SDR12, + .widths = MMC_MODE_4BIT | MMC_MODE_1BIT, + }, +#endif + { + .mode = MMC_LEGACY, + .widths = MMC_MODE_4BIT | MMC_MODE_1BIT, + } +}; + +#define for_each_sd_mode_by_pref(caps, mwt) \ + for (mwt = sd_modes_by_pref;\ + mwt < sd_modes_by_pref + ARRAY_SIZE(sd_modes_by_pref);\ + mwt++) \ + if (caps & MMC_CAP(mwt->mode)) + +static int sd_select_mode_and_width(struct mmc *mmc, uint card_caps) +{ + int err; + uint widths[] = {MMC_MODE_4BIT, MMC_MODE_1BIT}; + const struct mode_width_tuning *mwt; +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) + bool uhs_en = (mmc->ocr & OCR_S18R) ? true : false; +#else + bool uhs_en = false; +#endif + uint caps; + +#ifdef DEBUG + mmc_dump_capabilities("sd card", card_caps); + mmc_dump_capabilities("host", mmc->host_caps); +#endif + + if (mmc_host_is_spi(mmc)) { + mmc_set_bus_width(mmc, 1); + mmc_select_mode(mmc, MMC_LEGACY); + mmc_set_clock(mmc, mmc->tran_speed, MMC_CLK_ENABLE); +#if CONFIG_IS_ENABLED(MMC_WRITE) + err = sd_read_ssr(mmc); + if (err) + pr_warn("unable to read ssr\n"); +#endif + return 0; + } + + /* Restrict card's capabilities by what the host can do */ + caps = card_caps & mmc->host_caps; + + if (!uhs_en) + caps &= ~UHS_CAPS; + + for_each_sd_mode_by_pref(caps, mwt) { + uint *w; + + for (w = widths; w < widths + ARRAY_SIZE(widths); w++) { + if (*w & caps & mwt->widths) { + pr_debug("trying mode %s width %d (at %d MHz)\n", + mmc_mode_name(mwt->mode), + bus_width(*w), + mmc_mode2freq(mmc, mwt->mode) / 1000000); + + /* configure the bus width (card + host) */ + err = sd_select_bus_width(mmc, bus_width(*w)); + if (err) + goto error; + mmc_set_bus_width(mmc, bus_width(*w)); + + /* configure the bus mode (card) */ + err = sd_set_card_speed(mmc, mwt->mode); + if (err) + goto error; + + /* configure the bus mode (host) */ + mmc_select_mode(mmc, mwt->mode); + mmc_set_clock(mmc, mmc->tran_speed, + MMC_CLK_ENABLE); + +#ifdef MMC_SUPPORTS_TUNING + /* execute tuning if needed */ + if (mwt->tuning && !mmc_host_is_spi(mmc)) { + err = mmc_execute_tuning(mmc, + mwt->tuning); + if (err) { + pr_debug("tuning failed\n"); + goto error; + } + } +#endif + +#if CONFIG_IS_ENABLED(MMC_WRITE) + err = sd_read_ssr(mmc); + if (err) + pr_warn("unable to read ssr\n"); +#endif + if (!err) + return 0; + +error: + /* revert to a safer bus speed */ + mmc_select_mode(mmc, MMC_LEGACY); + mmc_set_clock(mmc, mmc->tran_speed, + MMC_CLK_ENABLE); + } + } + } + + pr_err("unable to select a mode\n"); + return -ENOTSUPP; +} + +/* + * read the compare the part of ext csd that is constant. + * This can be used to check that the transfer is working + * as expected. + */ +static int mmc_read_and_compare_ext_csd(struct mmc *mmc) +{ + int err; + const u8 *ext_csd = mmc->ext_csd; + ALLOC_CACHE_ALIGN_BUFFER(u8, test_csd, MMC_MAX_BLOCK_LEN); + + if (mmc->version < MMC_VERSION_4) + return 0; + + err = mmc_send_ext_csd(mmc, test_csd); + if (err) + return err; + + /* Only compare read only fields */ + if (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] + == test_csd[EXT_CSD_PARTITIONING_SUPPORT] && + ext_csd[EXT_CSD_HC_WP_GRP_SIZE] + == test_csd[EXT_CSD_HC_WP_GRP_SIZE] && + ext_csd[EXT_CSD_REV] + == test_csd[EXT_CSD_REV] && + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] + == test_csd[EXT_CSD_HC_ERASE_GRP_SIZE] && + memcmp(&ext_csd[EXT_CSD_SEC_CNT], + &test_csd[EXT_CSD_SEC_CNT], 4) == 0) + return 0; + + return -EBADMSG; +} + +#if CONFIG_IS_ENABLED(MMC_IO_VOLTAGE) +static int mmc_set_lowest_voltage(struct mmc *mmc, enum bus_mode mode, + uint32_t allowed_mask) +{ + u32 card_mask = 0; + + switch (mode) { + case MMC_HS_400_ES: + case MMC_HS_400: + case MMC_HS_200: + if (mmc->cardtype & (EXT_CSD_CARD_TYPE_HS200_1_8V | + EXT_CSD_CARD_TYPE_HS400_1_8V)) + card_mask |= MMC_SIGNAL_VOLTAGE_180; + if (mmc->cardtype & (EXT_CSD_CARD_TYPE_HS200_1_2V | + EXT_CSD_CARD_TYPE_HS400_1_2V)) + card_mask |= MMC_SIGNAL_VOLTAGE_120; + break; + case MMC_DDR_52: + if (mmc->cardtype & EXT_CSD_CARD_TYPE_DDR_1_8V) + card_mask |= MMC_SIGNAL_VOLTAGE_330 | + MMC_SIGNAL_VOLTAGE_180; + if (mmc->cardtype & EXT_CSD_CARD_TYPE_DDR_1_2V) + card_mask |= MMC_SIGNAL_VOLTAGE_120; + break; + default: + card_mask |= MMC_SIGNAL_VOLTAGE_330; + break; + } + + while (card_mask & allowed_mask) { + enum mmc_voltage best_match; + + best_match = 1 << (ffs(card_mask & allowed_mask) - 1); + if (!mmc_set_signal_voltage(mmc, best_match)) + return 0; + + allowed_mask &= ~best_match; + } + + return -ENOTSUPP; +} +#else +static inline int mmc_set_lowest_voltage(struct mmc *mmc, enum bus_mode mode, + uint32_t allowed_mask) +{ + return 0; +} +#endif + +static const struct mode_width_tuning mmc_modes_by_pref[] = { +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT) + { + .mode = MMC_HS_400_ES, + .widths = MMC_MODE_8BIT, + }, +#endif +#if CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + { + .mode = MMC_HS_400, + .widths = MMC_MODE_8BIT, + .tuning = MMC_CMD_SEND_TUNING_BLOCK_HS200 + }, +#endif +#if CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) + { + .mode = MMC_HS_200, + .widths = MMC_MODE_8BIT | MMC_MODE_4BIT, + .tuning = MMC_CMD_SEND_TUNING_BLOCK_HS200 + }, +#endif + { + .mode = MMC_DDR_52, + .widths = MMC_MODE_8BIT | MMC_MODE_4BIT, + }, + { + .mode = MMC_HS_52, + .widths = MMC_MODE_8BIT | MMC_MODE_4BIT | MMC_MODE_1BIT, + }, + { + .mode = MMC_HS, + .widths = MMC_MODE_8BIT | MMC_MODE_4BIT | MMC_MODE_1BIT, + }, + { + .mode = MMC_LEGACY, + .widths = MMC_MODE_8BIT | MMC_MODE_4BIT | MMC_MODE_1BIT, + } +}; + +#define for_each_mmc_mode_by_pref(caps, mwt) \ + for (mwt = mmc_modes_by_pref;\ + mwt < mmc_modes_by_pref + ARRAY_SIZE(mmc_modes_by_pref);\ + mwt++) \ + if (caps & MMC_CAP(mwt->mode)) + +static const struct ext_csd_bus_width { + uint cap; + bool is_ddr; + uint ext_csd_bits; +} ext_csd_bus_width[] = { + {MMC_MODE_8BIT, true, EXT_CSD_DDR_BUS_WIDTH_8}, + {MMC_MODE_4BIT, true, EXT_CSD_DDR_BUS_WIDTH_4}, + {MMC_MODE_8BIT, false, EXT_CSD_BUS_WIDTH_8}, + {MMC_MODE_4BIT, false, EXT_CSD_BUS_WIDTH_4}, + {MMC_MODE_1BIT, false, EXT_CSD_BUS_WIDTH_1}, +}; + +#if CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) +static int mmc_select_hs400(struct mmc *mmc) +{ + int err; + + /* Set timing to HS200 for tuning */ + err = mmc_set_card_speed(mmc, MMC_HS_200, false); + if (err) + return err; + + /* configure the bus mode (host) */ + mmc_select_mode(mmc, MMC_HS_200); + mmc_set_clock(mmc, mmc->tran_speed, false); + + /* execute tuning if needed */ + mmc->hs400_tuning = 1; + err = mmc_execute_tuning(mmc, MMC_CMD_SEND_TUNING_BLOCK_HS200); + mmc->hs400_tuning = 0; + if (err) { + debug("tuning failed\n"); + return err; + } + + /* Set back to HS */ + mmc_set_card_speed(mmc, MMC_HS, true); + + err = mmc_hs400_prepare_ddr(mmc); + if (err) + return err; + + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, + EXT_CSD_BUS_WIDTH_8 | EXT_CSD_DDR_FLAG); + if (err) + return err; + + err = mmc_set_card_speed(mmc, MMC_HS_400, false); + if (err) + return err; + + mmc_select_mode(mmc, MMC_HS_400); + err = mmc_set_clock(mmc, mmc->tran_speed, false); + if (err) + return err; + + return 0; +} +#else +static int mmc_select_hs400(struct mmc *mmc) +{ + return -ENOTSUPP; +} +#endif + +#if CONFIG_IS_ENABLED(MMC_HS400_ES_SUPPORT) +#if !CONFIG_IS_ENABLED(DM_MMC) +static int mmc_set_enhanced_strobe(struct mmc *mmc) +{ + return -ENOTSUPP; +} +#endif +static int mmc_select_hs400es(struct mmc *mmc) +{ + int err; + + err = mmc_set_card_speed(mmc, MMC_HS, true); + if (err) + return err; + + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, + EXT_CSD_BUS_WIDTH_8 | EXT_CSD_DDR_FLAG | + EXT_CSD_BUS_WIDTH_STROBE); + if (err) { + printf("switch to bus width for hs400 failed\n"); + return err; + } + /* TODO: driver strength */ + err = mmc_set_card_speed(mmc, MMC_HS_400_ES, false); + if (err) + return err; + + mmc_select_mode(mmc, MMC_HS_400_ES); + err = mmc_set_clock(mmc, mmc->tran_speed, false); + if (err) + return err; + + return mmc_set_enhanced_strobe(mmc); +} +#else +static int mmc_select_hs400es(struct mmc *mmc) +{ + return -ENOTSUPP; +} +#endif + +#define for_each_supported_width(caps, ddr, ecbv) \ + for (ecbv = ext_csd_bus_width;\ + ecbv < ext_csd_bus_width + ARRAY_SIZE(ext_csd_bus_width);\ + ecbv++) \ + if ((ddr == ecbv->is_ddr) && (caps & ecbv->cap)) + +static int mmc_select_mode_and_width(struct mmc *mmc, uint card_caps) +{ + int err = 0; + const struct mode_width_tuning *mwt; + const struct ext_csd_bus_width *ecbw; + +#ifdef DEBUG + mmc_dump_capabilities("mmc", card_caps); + mmc_dump_capabilities("host", mmc->host_caps); +#endif + + if (mmc_host_is_spi(mmc)) { + mmc_set_bus_width(mmc, 1); + mmc_select_mode(mmc, MMC_LEGACY); + mmc_set_clock(mmc, mmc->tran_speed, MMC_CLK_ENABLE); + return 0; + } + + /* Restrict card's capabilities by what the host can do */ + card_caps &= mmc->host_caps; + + /* Only version 4 of MMC supports wider bus widths */ + if (mmc->version < MMC_VERSION_4) + return 0; + + if (!mmc->ext_csd) { + pr_debug("No ext_csd found!\n"); /* this should enver happen */ + return -ENOTSUPP; + } + +#if CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + /* + * In case the eMMC is in HS200/HS400 mode, downgrade to HS mode + * before doing anything else, since a transition from either of + * the HS200/HS400 mode directly to legacy mode is not supported. + */ + if (mmc->selected_mode == MMC_HS_200 || + mmc->selected_mode == MMC_HS_400) + mmc_set_card_speed(mmc, MMC_HS, true); + else +#endif + mmc_set_clock(mmc, mmc->legacy_speed, MMC_CLK_ENABLE); + + for_each_mmc_mode_by_pref(card_caps, mwt) { + for_each_supported_width(card_caps & mwt->widths, + mmc_is_mode_ddr(mwt->mode), ecbw) { + enum mmc_voltage old_voltage; + pr_debug("trying mode %s width %d (at %d MHz)\n", + mmc_mode_name(mwt->mode), + bus_width(ecbw->cap), + mmc_mode2freq(mmc, mwt->mode) / 1000000); + old_voltage = mmc->signal_voltage; + err = mmc_set_lowest_voltage(mmc, mwt->mode, + MMC_ALL_SIGNAL_VOLTAGE); + if (err) + continue; + + /* configure the bus width (card + host) */ + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, + ecbw->ext_csd_bits & ~EXT_CSD_DDR_FLAG); + if (err) + goto error; + mmc_set_bus_width(mmc, bus_width(ecbw->cap)); + + if (mwt->mode == MMC_HS_400) { + err = mmc_select_hs400(mmc); + if (err) { + printf("Select HS400 failed %d\n", err); + goto error; + } + } else if (mwt->mode == MMC_HS_400_ES) { + err = mmc_select_hs400es(mmc); + if (err) { + printf("Select HS400ES failed %d\n", + err); + goto error; + } + } else { + /* configure the bus speed (card) */ + err = mmc_set_card_speed(mmc, mwt->mode, false); + if (err) + goto error; + + /* + * configure the bus width AND the ddr mode + * (card). The host side will be taken care + * of in the next step + */ + if (ecbw->ext_csd_bits & EXT_CSD_DDR_FLAG) { + err = mmc_switch(mmc, + EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, + ecbw->ext_csd_bits); + if (err) + goto error; + } + + /* configure the bus mode (host) */ + mmc_select_mode(mmc, mwt->mode); + mmc_set_clock(mmc, mmc->tran_speed, + MMC_CLK_ENABLE); +#ifdef MMC_SUPPORTS_TUNING + + /* execute tuning if needed */ + if (mwt->tuning) { + err = mmc_execute_tuning(mmc, + mwt->tuning); + if (err) { + pr_debug("tuning failed : %d\n", err); + goto error; + } + } +#endif + } + + /* do a transfer to check the configuration */ + err = mmc_read_and_compare_ext_csd(mmc); + if (!err) + return 0; +error: + mmc_set_signal_voltage(mmc, old_voltage); + /* if an error occurred, revert to a safer bus mode */ + mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_1); + mmc_select_mode(mmc, MMC_LEGACY); + mmc_set_bus_width(mmc, 1); + } + } + + pr_err("unable to select a mode : %d\n", err); + + return -ENOTSUPP; +} +#endif + +#if CONFIG_IS_ENABLED(MMC_TINY) +DEFINE_CACHE_ALIGN_BUFFER(u8, ext_csd_bkup, MMC_MAX_BLOCK_LEN); +#endif + +static int mmc_startup_v4(struct mmc *mmc) +{ + int err, i; + u64 capacity; + bool has_parts = false; + bool part_completed; + static const u32 mmc_versions[] = { + MMC_VERSION_4, + MMC_VERSION_4_1, + MMC_VERSION_4_2, + MMC_VERSION_4_3, + MMC_VERSION_4_4, + MMC_VERSION_4_41, + MMC_VERSION_4_5, + MMC_VERSION_5_0, + MMC_VERSION_5_1 + }; + +#if CONFIG_IS_ENABLED(MMC_TINY) + u8 *ext_csd = ext_csd_bkup; + + if (IS_SD(mmc) || mmc->version < MMC_VERSION_4) + return 0; + + if (!mmc->ext_csd) + memset(ext_csd_bkup, 0, sizeof(ext_csd_bkup)); + + err = mmc_send_ext_csd(mmc, ext_csd); + if (err) + goto error; + + /* store the ext csd for future reference */ + if (!mmc->ext_csd) + mmc->ext_csd = ext_csd; +#else + ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN); + + if (IS_SD(mmc) || (mmc->version < MMC_VERSION_4)) + return 0; + + /* check ext_csd version and capacity */ + err = mmc_send_ext_csd(mmc, ext_csd); + if (err) + goto error; + + /* store the ext csd for future reference */ + if (!mmc->ext_csd) + mmc->ext_csd = malloc(MMC_MAX_BLOCK_LEN); + if (!mmc->ext_csd) + return -ENOMEM; + memcpy(mmc->ext_csd, ext_csd, MMC_MAX_BLOCK_LEN); +#endif + if (ext_csd[EXT_CSD_REV] >= ARRAY_SIZE(mmc_versions)) + return -EINVAL; + + mmc->version = mmc_versions[ext_csd[EXT_CSD_REV]]; + + if (mmc->version >= MMC_VERSION_4_2) { + /* + * According to the JEDEC Standard, the value of + * ext_csd's capacity is valid if the value is more + * than 2GB + */ + capacity = ext_csd[EXT_CSD_SEC_CNT] << 0 + | ext_csd[EXT_CSD_SEC_CNT + 1] << 8 + | ext_csd[EXT_CSD_SEC_CNT + 2] << 16 + | ext_csd[EXT_CSD_SEC_CNT + 3] << 24; + capacity *= MMC_MAX_BLOCK_LEN; + if ((capacity >> 20) > 2 * 1024) + mmc->capacity_user = capacity; + } + + if (mmc->version >= MMC_VERSION_4_5) + mmc->gen_cmd6_time = ext_csd[EXT_CSD_GENERIC_CMD6_TIME]; + + /* The partition data may be non-zero but it is only + * effective if PARTITION_SETTING_COMPLETED is set in + * EXT_CSD, so ignore any data if this bit is not set, + * except for enabling the high-capacity group size + * definition (see below). + */ + part_completed = !!(ext_csd[EXT_CSD_PARTITION_SETTING] & + EXT_CSD_PARTITION_SETTING_COMPLETED); + + mmc->part_switch_time = ext_csd[EXT_CSD_PART_SWITCH_TIME]; + /* Some eMMC set the value too low so set a minimum */ + if (mmc->part_switch_time < MMC_MIN_PART_SWITCH_TIME && mmc->part_switch_time) + mmc->part_switch_time = MMC_MIN_PART_SWITCH_TIME; + + /* store the partition info of emmc */ + mmc->part_support = ext_csd[EXT_CSD_PARTITIONING_SUPPORT]; + if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) || + ext_csd[EXT_CSD_BOOT_MULT]) + mmc->part_config = ext_csd[EXT_CSD_PART_CONF]; + if (part_completed && + (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & ENHNCD_SUPPORT)) + mmc->part_attr = ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE]; + + mmc->capacity_boot = ext_csd[EXT_CSD_BOOT_MULT] << 17; + + mmc->capacity_rpmb = ext_csd[EXT_CSD_RPMB_MULT] << 17; + + for (i = 0; i < 4; i++) { + int idx = EXT_CSD_GP_SIZE_MULT + i * 3; + uint mult = (ext_csd[idx + 2] << 16) + + (ext_csd[idx + 1] << 8) + ext_csd[idx]; + if (mult) + has_parts = true; + if (!part_completed) + continue; + mmc->capacity_gp[i] = mult; + mmc->capacity_gp[i] *= + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; + mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + mmc->capacity_gp[i] <<= 19; + } + +#ifndef CONFIG_SPL_BUILD + if (part_completed) { + mmc->enh_user_size = + (ext_csd[EXT_CSD_ENH_SIZE_MULT + 2] << 16) + + (ext_csd[EXT_CSD_ENH_SIZE_MULT + 1] << 8) + + ext_csd[EXT_CSD_ENH_SIZE_MULT]; + mmc->enh_user_size *= ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; + mmc->enh_user_size *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + mmc->enh_user_size <<= 19; + mmc->enh_user_start = + (ext_csd[EXT_CSD_ENH_START_ADDR + 3] << 24) + + (ext_csd[EXT_CSD_ENH_START_ADDR + 2] << 16) + + (ext_csd[EXT_CSD_ENH_START_ADDR + 1] << 8) + + ext_csd[EXT_CSD_ENH_START_ADDR]; + if (mmc->high_capacity) + mmc->enh_user_start <<= 9; + } +#endif + + /* + * Host needs to enable ERASE_GRP_DEF bit if device is + * partitioned. This bit will be lost every time after a reset + * or power off. This will affect erase size. + */ + if (part_completed) + has_parts = true; + if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) && + (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & PART_ENH_ATTRIB)) + has_parts = true; + if (has_parts) { + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_ERASE_GROUP_DEF, 1); + + if (err) + goto error; + + ext_csd[EXT_CSD_ERASE_GROUP_DEF] = 1; + } + + if (ext_csd[EXT_CSD_ERASE_GROUP_DEF] & 0x01) { +#if CONFIG_IS_ENABLED(MMC_WRITE) + /* Read out group size from ext_csd */ + mmc->erase_grp_size = + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * 1024; +#endif + /* + * if high capacity and partition setting completed + * SEC_COUNT is valid even if it is smaller than 2 GiB + * JEDEC Standard JESD84-B45, 6.2.4 + */ + if (mmc->high_capacity && part_completed) { + capacity = (ext_csd[EXT_CSD_SEC_CNT]) | + (ext_csd[EXT_CSD_SEC_CNT + 1] << 8) | + (ext_csd[EXT_CSD_SEC_CNT + 2] << 16) | + (ext_csd[EXT_CSD_SEC_CNT + 3] << 24); + capacity *= MMC_MAX_BLOCK_LEN; + mmc->capacity_user = capacity; + } + } +#if CONFIG_IS_ENABLED(MMC_WRITE) + else { + /* Calculate the group size from the csd value. */ + int erase_gsz, erase_gmul; + + erase_gsz = (mmc->csd[2] & 0x00007c00) >> 10; + erase_gmul = (mmc->csd[2] & 0x000003e0) >> 5; + mmc->erase_grp_size = (erase_gsz + 1) + * (erase_gmul + 1); + } +#endif +#if CONFIG_IS_ENABLED(MMC_HW_PARTITIONING) + mmc->hc_wp_grp_size = 1024 + * ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] + * ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; +#endif + + mmc->wr_rel_set = ext_csd[EXT_CSD_WR_REL_SET]; + + return 0; +error: + if (mmc->ext_csd) { +#if !CONFIG_IS_ENABLED(MMC_TINY) + free(mmc->ext_csd); +#endif + mmc->ext_csd = NULL; + } + return err; +} + +static int mmc_startup(struct mmc *mmc) +{ + int err, i; + uint mult, freq; + u64 cmult, csize; + struct mmc_cmd cmd; + struct blk_desc *bdesc; + +#ifdef CONFIG_MMC_SPI_CRC_ON + if (mmc_host_is_spi(mmc)) { /* enable CRC check for spi */ + cmd.cmdidx = MMC_CMD_SPI_CRC_ON_OFF; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 1; + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) + return err; + } +#endif + + /* Put the Card in Identify Mode */ + cmd.cmdidx = mmc_host_is_spi(mmc) ? MMC_CMD_SEND_CID : + MMC_CMD_ALL_SEND_CID; /* cmd not supported in spi */ + cmd.resp_type = MMC_RSP_R2; + cmd.cmdarg = 0; + + err = mmc_send_cmd_quirks(mmc, &cmd, NULL, MMC_QUIRK_RETRY_SEND_CID, 4); + if (err) + return err; + + memcpy(mmc->cid, cmd.response, 16); + + /* + * For MMC cards, set the Relative Address. + * For SD cards, get the Relatvie Address. + * This also puts the cards into Standby State + */ + if (!mmc_host_is_spi(mmc)) { /* cmd not supported in spi */ + cmd.cmdidx = SD_CMD_SEND_RELATIVE_ADDR; + cmd.cmdarg = mmc->rca << 16; + cmd.resp_type = MMC_RSP_R6; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + + if (IS_SD(mmc)) + mmc->rca = (cmd.response[0] >> 16) & 0xffff; + } + + /* Get the Card-Specific Data */ + cmd.cmdidx = MMC_CMD_SEND_CSD; + cmd.resp_type = MMC_RSP_R2; + cmd.cmdarg = mmc->rca << 16; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + + mmc->csd[0] = cmd.response[0]; + mmc->csd[1] = cmd.response[1]; + mmc->csd[2] = cmd.response[2]; + mmc->csd[3] = cmd.response[3]; + + if (mmc->version == MMC_VERSION_UNKNOWN) { + int version = (cmd.response[0] >> 26) & 0xf; + + switch (version) { + case 0: + mmc->version = MMC_VERSION_1_2; + break; + case 1: + mmc->version = MMC_VERSION_1_4; + break; + case 2: + mmc->version = MMC_VERSION_2_2; + break; + case 3: + mmc->version = MMC_VERSION_3; + break; + case 4: + mmc->version = MMC_VERSION_4; + break; + default: + mmc->version = MMC_VERSION_1_2; + break; + } + } + + /* divide frequency by 10, since the mults are 10x bigger */ + freq = fbase[(cmd.response[0] & 0x7)]; + mult = multipliers[((cmd.response[0] >> 3) & 0xf)]; + + mmc->legacy_speed = freq * mult; + mmc_select_mode(mmc, MMC_LEGACY); + + mmc->dsr_imp = ((cmd.response[1] >> 12) & 0x1); + mmc->read_bl_len = 1 << ((cmd.response[1] >> 16) & 0xf); +#if CONFIG_IS_ENABLED(MMC_WRITE) + + if (IS_SD(mmc)) + mmc->write_bl_len = mmc->read_bl_len; + else + mmc->write_bl_len = 1 << ((cmd.response[3] >> 22) & 0xf); +#endif + + if (mmc->high_capacity) { + csize = (mmc->csd[1] & 0x3f) << 16 + | (mmc->csd[2] & 0xffff0000) >> 16; + cmult = 8; + } else { + csize = (mmc->csd[1] & 0x3ff) << 2 + | (mmc->csd[2] & 0xc0000000) >> 30; + cmult = (mmc->csd[2] & 0x00038000) >> 15; + } + + mmc->capacity_user = (csize + 1) << (cmult + 2); + mmc->capacity_user *= mmc->read_bl_len; + mmc->capacity_boot = 0; + mmc->capacity_rpmb = 0; + for (i = 0; i < 4; i++) + mmc->capacity_gp[i] = 0; + + if (mmc->read_bl_len > MMC_MAX_BLOCK_LEN) + mmc->read_bl_len = MMC_MAX_BLOCK_LEN; + +#if CONFIG_IS_ENABLED(MMC_WRITE) + if (mmc->write_bl_len > MMC_MAX_BLOCK_LEN) + mmc->write_bl_len = MMC_MAX_BLOCK_LEN; +#endif + + if ((mmc->dsr_imp) && (0xffffffff != mmc->dsr)) { + cmd.cmdidx = MMC_CMD_SET_DSR; + cmd.cmdarg = (mmc->dsr & 0xffff) << 16; + cmd.resp_type = MMC_RSP_NONE; + if (mmc_send_cmd(mmc, &cmd, NULL)) + pr_warn("MMC: SET_DSR failed\n"); + } + + /* Select the card, and put it into Transfer Mode */ + if (!mmc_host_is_spi(mmc)) { /* cmd not supported in spi */ + cmd.cmdidx = MMC_CMD_SELECT_CARD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = mmc->rca << 16; + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + } + + /* + * For SD, its erase group is always one sector + */ +#if CONFIG_IS_ENABLED(MMC_WRITE) + mmc->erase_grp_size = 1; +#endif + mmc->part_config = MMCPART_NOAVAILABLE; + + err = mmc_startup_v4(mmc); + if (err) + return err; + + err = mmc_set_capacity(mmc, mmc_get_blk_desc(mmc)->hwpart); + if (err) + return err; + +#if CONFIG_IS_ENABLED(MMC_TINY) + mmc_set_clock(mmc, mmc->legacy_speed, false); + mmc_select_mode(mmc, MMC_LEGACY); + mmc_set_bus_width(mmc, 1); +#else + if (IS_SD(mmc)) { + err = sd_get_capabilities(mmc); + if (err) + return err; + err = sd_select_mode_and_width(mmc, mmc->card_caps); + } else { + err = mmc_get_capabilities(mmc); + if (err) + return err; + err = mmc_select_mode_and_width(mmc, mmc->card_caps); + } +#endif + if (err) + return err; + + mmc->best_mode = mmc->selected_mode; + + /* Fix the block length for DDR mode */ + if (mmc->ddr_mode) { + mmc->read_bl_len = MMC_MAX_BLOCK_LEN; +#if CONFIG_IS_ENABLED(MMC_WRITE) + mmc->write_bl_len = MMC_MAX_BLOCK_LEN; +#endif + } + + /* fill in device description */ + bdesc = mmc_get_blk_desc(mmc); + bdesc->lun = 0; + bdesc->hwpart = 0; + bdesc->type = 0; + bdesc->blksz = mmc->read_bl_len; + bdesc->log2blksz = LOG2(bdesc->blksz); + bdesc->lba = lldiv(mmc->capacity, mmc->read_bl_len); +#if !defined(CONFIG_SPL_BUILD) || \ + (defined(CONFIG_SPL_LIBCOMMON_SUPPORT) && \ + !CONFIG_IS_ENABLED(USE_TINY_PRINTF)) + sprintf(bdesc->vendor, "Man %06x Snr %04x%04x", + mmc->cid[0] >> 24, (mmc->cid[2] & 0xffff), + (mmc->cid[3] >> 16) & 0xffff); + sprintf(bdesc->product, "%c%c%c%c%c%c", mmc->cid[0] & 0xff, + (mmc->cid[1] >> 24), (mmc->cid[1] >> 16) & 0xff, + (mmc->cid[1] >> 8) & 0xff, mmc->cid[1] & 0xff, + (mmc->cid[2] >> 24) & 0xff); + sprintf(bdesc->revision, "%d.%d", (mmc->cid[2] >> 20) & 0xf, + (mmc->cid[2] >> 16) & 0xf); +#else + bdesc->vendor[0] = 0; + bdesc->product[0] = 0; + bdesc->revision[0] = 0; +#endif + +#if !defined(CONFIG_DM_MMC) && (!defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBDISK_SUPPORT)) + part_init(bdesc); +#endif + + return 0; +} + +static int mmc_send_if_cond(struct mmc *mmc) +{ + struct mmc_cmd cmd; + int err; + + cmd.cmdidx = SD_CMD_SEND_IF_COND; + /* We set the bit if the host supports voltages between 2.7 and 3.6 V */ + cmd.cmdarg = ((mmc->cfg->voltages & 0xff8000) != 0) << 8 | 0xaa; + cmd.resp_type = MMC_RSP_R7; + + err = mmc_send_cmd(mmc, &cmd, NULL); + + if (err) + return err; + + if ((cmd.response[0] & 0xff) != 0xaa) + return -EOPNOTSUPP; + else + mmc->version = SD_VERSION_2; + + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +/* board-specific MMC power initializations. */ +__weak void board_mmc_power_init(void) +{ +} +#endif + +static int mmc_power_init(struct mmc *mmc) +{ +#if CONFIG_IS_ENABLED(DM_MMC) +#if CONFIG_IS_ENABLED(DM_REGULATOR) + int ret; + + ret = device_get_supply_regulator(mmc->dev, "vmmc-supply", + &mmc->vmmc_supply); + if (ret) + pr_debug("%s: No vmmc supply\n", mmc->dev->name); + + ret = device_get_supply_regulator(mmc->dev, "vqmmc-supply", + &mmc->vqmmc_supply); + if (ret) + pr_debug("%s: No vqmmc supply\n", mmc->dev->name); +#endif +#else /* !CONFIG_DM_MMC */ + /* + * Driver model should use a regulator, as above, rather than calling + * out to board code. + */ + board_mmc_power_init(); +#endif + return 0; +} + +/* + * put the host in the initial state: + * - turn on Vdd (card power supply) + * - configure the bus width and clock to minimal values + */ +static void mmc_set_initial_state(struct mmc *mmc) +{ + int err; + + /* First try to set 3.3V. If it fails set to 1.8V */ + err = mmc_set_signal_voltage(mmc, MMC_SIGNAL_VOLTAGE_330); + if (err != 0) + err = mmc_set_signal_voltage(mmc, MMC_SIGNAL_VOLTAGE_180); + if (err != 0) + pr_warn("mmc: failed to set signal voltage\n"); + + mmc_select_mode(mmc, MMC_LEGACY); + mmc_set_bus_width(mmc, 1); + mmc_set_clock(mmc, 0, MMC_CLK_ENABLE); +} + +static int mmc_power_on(struct mmc *mmc) +{ +#if CONFIG_IS_ENABLED(DM_MMC) && CONFIG_IS_ENABLED(DM_REGULATOR) + if (mmc->vmmc_supply) { + int ret = regulator_set_enable(mmc->vmmc_supply, true); + + if (ret && ret != -EACCES) { + printf("Error enabling VMMC supply : %d\n", ret); + return ret; + } + } +#endif + return 0; +} + +static int mmc_power_off(struct mmc *mmc) +{ + mmc_set_clock(mmc, 0, MMC_CLK_DISABLE); +#if CONFIG_IS_ENABLED(DM_MMC) && CONFIG_IS_ENABLED(DM_REGULATOR) + if (mmc->vmmc_supply) { + int ret = regulator_set_enable(mmc->vmmc_supply, false); + + if (ret && ret != -EACCES) { + pr_debug("Error disabling VMMC supply : %d\n", ret); + return ret; + } + } +#endif + return 0; +} + +static int mmc_power_cycle(struct mmc *mmc) +{ + int ret; + + ret = mmc_power_off(mmc); + if (ret) + return ret; + + ret = mmc_host_power_cycle(mmc); + if (ret) + return ret; + + /* + * SD spec recommends at least 1ms of delay. Let's wait for 2ms + * to be on the safer side. + */ + udelay(2000); + return mmc_power_on(mmc); +} + +int mmc_get_op_cond(struct mmc *mmc) +{ + bool uhs_en = supports_uhs(mmc->cfg->host_caps); + int err; + + if (mmc->has_init) + return 0; + + err = mmc_power_init(mmc); + if (err) + return err; + +#ifdef CONFIG_MMC_QUIRKS + mmc->quirks = MMC_QUIRK_RETRY_SET_BLOCKLEN | + MMC_QUIRK_RETRY_SEND_CID | + MMC_QUIRK_RETRY_APP_CMD; +#endif + + err = mmc_power_cycle(mmc); + if (err) { + /* + * if power cycling is not supported, we should not try + * to use the UHS modes, because we wouldn't be able to + * recover from an error during the UHS initialization. + */ + pr_debug("Unable to do a full power cycle. Disabling the UHS modes for safety\n"); + uhs_en = false; + mmc->host_caps &= ~UHS_CAPS; + err = mmc_power_on(mmc); + } + if (err) + return err; + +#if CONFIG_IS_ENABLED(DM_MMC) + /* + * Re-initialization is needed to clear old configuration for + * mmc rescan. + */ + err = mmc_reinit(mmc); +#else + /* made sure it's not NULL earlier */ + err = mmc->cfg->ops->init(mmc); +#endif + if (err) + return err; + mmc->ddr_mode = 0; + +retry: + mmc_set_initial_state(mmc); + + /* Reset the Card */ + err = mmc_go_idle(mmc); + + if (err) + return err; + + /* The internal partition reset to user partition(0) at every CMD0 */ + mmc_get_blk_desc(mmc)->hwpart = 0; + + /* Test for SD version 2 */ + err = mmc_send_if_cond(mmc); + + /* Now try to get the SD card's operating condition */ + err = sd_send_op_cond(mmc, uhs_en); + if (err && uhs_en) { + uhs_en = false; + mmc_power_cycle(mmc); + goto retry; + } + + /* If the command timed out, we check for an MMC card */ + if (err == -ETIMEDOUT) { + err = mmc_send_op_cond(mmc); + + if (err) { +#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) + pr_err("Card did not respond to voltage select! : %d\n", err); +#endif + return -EOPNOTSUPP; + } + } + + return err; +} + +int mmc_start_init(struct mmc *mmc) +{ + bool no_card; + int err = 0; + + /* + * all hosts are capable of 1 bit bus-width and able to use the legacy + * timings. + */ + mmc->host_caps = mmc->cfg->host_caps | MMC_CAP(MMC_LEGACY) | + MMC_CAP(MMC_LEGACY) | MMC_MODE_1BIT; +#if CONFIG_IS_ENABLED(DM_MMC) + mmc_deferred_probe(mmc); +#endif +#if !defined(CONFIG_MMC_BROKEN_CD) + no_card = mmc_getcd(mmc) == 0; +#else + no_card = 0; +#endif +#if !CONFIG_IS_ENABLED(DM_MMC) + /* we pretend there's no card when init is NULL */ + no_card = no_card || (mmc->cfg->ops->init == NULL); +#endif + if (no_card) { + mmc->has_init = 0; +#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) + pr_err("MMC: no card present\n"); +#endif + return -ENOMEDIUM; + } + + err = mmc_get_op_cond(mmc); + + if (!err) + mmc->init_in_progress = 1; + + return err; +} + +static int mmc_complete_init(struct mmc *mmc) +{ + int err = 0; + + mmc->init_in_progress = 0; + if (mmc->op_cond_pending) + err = mmc_complete_op_cond(mmc); + + if (!err) + err = mmc_startup(mmc); + if (err) + mmc->has_init = 0; + else + mmc->has_init = 1; + return err; +} + +int mmc_init(struct mmc *mmc) +{ + int err = 0; + __maybe_unused ulong start; +#if CONFIG_IS_ENABLED(DM_MMC) + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(mmc->dev); + + upriv->mmc = mmc; +#endif + if (mmc->has_init) + return 0; + + start = get_timer(0); + + if (!mmc->init_in_progress) + err = mmc_start_init(mmc); + + if (!err) + err = mmc_complete_init(mmc); + if (err) + pr_info("%s: %d, time %lu\n", __func__, err, get_timer(start)); + + return err; +} + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) +int mmc_deinit(struct mmc *mmc) +{ + u32 caps_filtered; + + if (!mmc->has_init) + return 0; + + if (IS_SD(mmc)) { + caps_filtered = mmc->card_caps & + ~(MMC_CAP(UHS_SDR12) | MMC_CAP(UHS_SDR25) | + MMC_CAP(UHS_SDR50) | MMC_CAP(UHS_DDR50) | + MMC_CAP(UHS_SDR104)); + + return sd_select_mode_and_width(mmc, caps_filtered); + } else { + caps_filtered = mmc->card_caps & + ~(MMC_CAP(MMC_HS_200) | MMC_CAP(MMC_HS_400)); + + return mmc_select_mode_and_width(mmc, caps_filtered); + } +} +#endif + +int mmc_set_dsr(struct mmc *mmc, u16 val) +{ + mmc->dsr = val; + return 0; +} + +/* CPU-specific MMC initializations */ +__weak int cpu_mmc_init(struct bd_info *bis) +{ + return -1; +} + +/* board-specific MMC initializations. */ +__weak int board_mmc_init(struct bd_info *bis) +{ + return -1; +} + +void mmc_set_preinit(struct mmc *mmc, int preinit) +{ + mmc->preinit = preinit; +} + +#if CONFIG_IS_ENABLED(DM_MMC) +static int mmc_probe(struct bd_info *bis) +{ + int ret, i; + struct uclass *uc; + struct udevice *dev; + + ret = uclass_get(UCLASS_MMC, &uc); + if (ret) + return ret; + + /* + * Try to add them in sequence order. Really with driver model we + * should allow holes, but the current MMC list does not allow that. + * So if we request 0, 1, 3 we will get 0, 1, 2. + */ + for (i = 0; ; i++) { + ret = uclass_get_device_by_seq(UCLASS_MMC, i, &dev); + if (ret == -ENODEV) + break; + } + uclass_foreach_dev(dev, uc) { + ret = device_probe(dev); + if (ret) + pr_err("%s - probe failed: %d\n", dev->name, ret); + } + + return 0; +} +#else +static int mmc_probe(struct bd_info *bis) +{ + if (board_mmc_init(bis) < 0) + cpu_mmc_init(bis); + + return 0; +} +#endif + +int mmc_initialize(struct bd_info *bis) +{ + static int initialized = 0; + int ret; + if (initialized) /* Avoid initializing mmc multiple times */ + return 0; + initialized = 1; + +#if !CONFIG_IS_ENABLED(BLK) +#if !CONFIG_IS_ENABLED(MMC_TINY) + mmc_list_init(); +#endif +#endif + ret = mmc_probe(bis); + if (ret) + return ret; + +#ifndef CONFIG_SPL_BUILD + print_mmc_devices(','); +#endif + + mmc_do_preinit(); + return 0; +} + +#if CONFIG_IS_ENABLED(DM_MMC) +int mmc_init_device(int num) +{ + struct udevice *dev; + struct mmc *m; + int ret; + + if (uclass_get_device_by_seq(UCLASS_MMC, num, &dev)) { + ret = uclass_get_device(UCLASS_MMC, num, &dev); + if (ret) + return ret; + } + + m = mmc_get_mmc_dev(dev); + if (!m) + return 0; + if (m->preinit) + mmc_start_init(m); + + return 0; +} +#endif + +#ifdef CONFIG_CMD_BKOPS_ENABLE +int mmc_set_bkops_enable(struct mmc *mmc) +{ + int err; + ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN); + + err = mmc_send_ext_csd(mmc, ext_csd); + if (err) { + puts("Could not get ext_csd register values\n"); + return err; + } + + if (!(ext_csd[EXT_CSD_BKOPS_SUPPORT] & 0x1)) { + puts("Background operations not supported on device\n"); + return -EMEDIUMTYPE; + } + + if (ext_csd[EXT_CSD_BKOPS_EN] & 0x1) { + puts("Background operations already enabled\n"); + return 0; + } + + err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BKOPS_EN, 1); + if (err) { + puts("Failed to enable manual background operations\n"); + return err; + } + + puts("Enabled manual background operations\n"); + + return 0; +} +#endif + +__weak int mmc_get_env_dev(void) +{ +#ifdef CONFIG_SYS_MMC_ENV_DEV + return CONFIG_SYS_MMC_ENV_DEV; +#else + return 0; +#endif +} diff --git a/roms/u-boot/drivers/mmc/mmc_boot.c b/roms/u-boot/drivers/mmc/mmc_boot.c new file mode 100644 index 000000000..0a74b1fb7 --- /dev/null +++ b/roms/u-boot/drivers/mmc/mmc_boot.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Google, Inc + * Written by Amar <amarendra.xt@samsung.com> + */ + +#include <common.h> +#include <log.h> +#include <mmc.h> +#include "mmc_private.h" + +/* + * This function changes the size of boot partition and the size of rpmb + * partition present on EMMC devices. + * + * Input Parameters: + * struct *mmc: pointer for the mmc device strcuture + * bootsize: size of boot partition + * rpmbsize: size of rpmb partition + * + * Returns 0 on success. + */ + +int mmc_boot_partition_size_change(struct mmc *mmc, unsigned long bootsize, + unsigned long rpmbsize) +{ + int err; + struct mmc_cmd cmd; + + /* Only use this command for raw EMMC moviNAND. Enter backdoor mode */ + cmd.cmdidx = MMC_CMD_RES_MAN; + cmd.resp_type = MMC_RSP_R1b; + cmd.cmdarg = MMC_CMD62_ARG1; + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) { + debug("mmc_boot_partition_size_change: Error1 = %d\n", err); + return err; + } + + /* Boot partition changing mode */ + cmd.cmdidx = MMC_CMD_RES_MAN; + cmd.resp_type = MMC_RSP_R1b; + cmd.cmdarg = MMC_CMD62_ARG2; + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) { + debug("mmc_boot_partition_size_change: Error2 = %d\n", err); + return err; + } + /* boot partition size is multiple of 128KB */ + bootsize = (bootsize * 1024) / 128; + + /* Arg: boot partition size */ + cmd.cmdidx = MMC_CMD_RES_MAN; + cmd.resp_type = MMC_RSP_R1b; + cmd.cmdarg = bootsize; + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) { + debug("mmc_boot_partition_size_change: Error3 = %d\n", err); + return err; + } + /* RPMB partition size is multiple of 128KB */ + rpmbsize = (rpmbsize * 1024) / 128; + /* Arg: RPMB partition size */ + cmd.cmdidx = MMC_CMD_RES_MAN; + cmd.resp_type = MMC_RSP_R1b; + cmd.cmdarg = rpmbsize; + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) { + debug("mmc_boot_partition_size_change: Error4 = %d\n", err); + return err; + } + return 0; +} + +/* + * Modify EXT_CSD[177] which is BOOT_BUS_WIDTH + * based on the passed in values for BOOT_BUS_WIDTH, RESET_BOOT_BUS_WIDTH + * and BOOT_MODE. + * + * Returns 0 on success. + */ +int mmc_set_boot_bus_width(struct mmc *mmc, u8 width, u8 reset, u8 mode) +{ + return mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BOOT_BUS_WIDTH, + EXT_CSD_BOOT_BUS_WIDTH_MODE(mode) | + EXT_CSD_BOOT_BUS_WIDTH_RESET(reset) | + EXT_CSD_BOOT_BUS_WIDTH_WIDTH(width)); +} + +/* + * Modify EXT_CSD[179] which is PARTITION_CONFIG (formerly BOOT_CONFIG) + * based on the passed in values for BOOT_ACK, BOOT_PARTITION_ENABLE and + * PARTITION_ACCESS. + * + * Returns 0 on success. + */ +int mmc_set_part_conf(struct mmc *mmc, u8 ack, u8 part_num, u8 access) +{ + int ret; + u8 part_conf; + + part_conf = EXT_CSD_BOOT_ACK(ack) | + EXT_CSD_BOOT_PART_NUM(part_num) | + EXT_CSD_PARTITION_ACCESS(access); + + ret = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_PART_CONF, + part_conf); + if (!ret) + mmc->part_config = part_conf; + + return ret; +} + +/* + * Modify EXT_CSD[162] which is RST_n_FUNCTION based on the given value + * for enable. Note that this is a write-once field for non-zero values. + * + * Returns 0 on success. + */ +int mmc_set_rst_n_function(struct mmc *mmc, u8 enable) +{ + return mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_RST_N_FUNCTION, + enable); +} diff --git a/roms/u-boot/drivers/mmc/mmc_legacy.c b/roms/u-boot/drivers/mmc/mmc_legacy.c new file mode 100644 index 000000000..a05da6c2e --- /dev/null +++ b/roms/u-boot/drivers/mmc/mmc_legacy.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Google, Inc + * Copyright 2020 NXP + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <log.h> +#include <malloc.h> +#include <mmc.h> +#include "mmc_private.h" + +static struct list_head mmc_devices; +static int cur_dev_num = -1; + +#if CONFIG_IS_ENABLED(MMC_TINY) +static struct mmc mmc_static; +struct mmc *find_mmc_device(int dev_num) +{ + return &mmc_static; +} + +void mmc_do_preinit(void) +{ + struct mmc *m = &mmc_static; + if (m->preinit) + mmc_start_init(m); +} + +struct blk_desc *mmc_get_blk_desc(struct mmc *mmc) +{ + return &mmc->block_dev; +} +#else +struct mmc *find_mmc_device(int dev_num) +{ + struct mmc *m; + struct list_head *entry; + + list_for_each(entry, &mmc_devices) { + m = list_entry(entry, struct mmc, link); + + if (m->block_dev.devnum == dev_num) + return m; + } + +#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) + printf("MMC Device %d not found\n", dev_num); +#endif + + return NULL; +} + +int mmc_get_next_devnum(void) +{ + return cur_dev_num++; +} + +struct blk_desc *mmc_get_blk_desc(struct mmc *mmc) +{ + return &mmc->block_dev; +} + +int get_mmc_num(void) +{ + return cur_dev_num; +} + +void mmc_do_preinit(void) +{ + struct mmc *m; + struct list_head *entry; + + list_for_each(entry, &mmc_devices) { + m = list_entry(entry, struct mmc, link); + + if (m->preinit) + mmc_start_init(m); + } +} +#endif + +void mmc_list_init(void) +{ + INIT_LIST_HEAD(&mmc_devices); + cur_dev_num = 0; +} + +void mmc_list_add(struct mmc *mmc) +{ + INIT_LIST_HEAD(&mmc->link); + + list_add_tail(&mmc->link, &mmc_devices); +} + +#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_LIBCOMMON_SUPPORT) +void print_mmc_devices(char separator) +{ + struct mmc *m; + struct list_head *entry; + char *mmc_type; + + list_for_each(entry, &mmc_devices) { + m = list_entry(entry, struct mmc, link); + + if (m->has_init) + mmc_type = IS_SD(m) ? "SD" : "eMMC"; + else + mmc_type = NULL; + + printf("%s: %d", m->cfg->name, m->block_dev.devnum); + if (mmc_type) + printf(" (%s)", mmc_type); + + if (entry->next != &mmc_devices) { + printf("%c", separator); + if (separator != '\n') + puts(" "); + } + } + + printf("\n"); +} + +#else +void print_mmc_devices(char separator) { } +#endif + +#if CONFIG_IS_ENABLED(MMC_TINY) +static struct mmc mmc_static = { + .dsr_imp = 0, + .dsr = 0xffffffff, + .block_dev = { + .if_type = IF_TYPE_MMC, + .removable = 1, + .devnum = 0, + .block_read = mmc_bread, + .block_write = mmc_bwrite, + .block_erase = mmc_berase, + .part_type = 0, + }, +}; + +struct mmc *mmc_create(const struct mmc_config *cfg, void *priv) +{ + struct mmc *mmc = &mmc_static; + + /* First MMC device registered, fail to register a new one. + * Given users are not expecting this to fail, instead + * of failing let's just return the only MMC device + */ + if (mmc->cfg) { + debug("Warning: MMC_TINY doesn't support multiple MMC devices\n"); + return mmc; + } + + mmc->cfg = cfg; + mmc->priv = priv; + + return mmc; +} + +void mmc_destroy(struct mmc *mmc) +{ +} +#else +struct mmc *mmc_create(const struct mmc_config *cfg, void *priv) +{ + struct blk_desc *bdesc; + struct mmc *mmc; + + /* quick validation */ + if (cfg == NULL || cfg->f_min == 0 || + cfg->f_max == 0 || cfg->b_max == 0) + return NULL; + +#if !CONFIG_IS_ENABLED(DM_MMC) + if (cfg->ops == NULL || cfg->ops->send_cmd == NULL) + return NULL; +#endif + + mmc = calloc(1, sizeof(*mmc)); + if (mmc == NULL) + return NULL; + + mmc->cfg = cfg; + mmc->priv = priv; + + /* the following chunk was mmc_register() */ + + /* Setup dsr related values */ + mmc->dsr_imp = 0; + mmc->dsr = 0xffffffff; + /* Setup the universal parts of the block interface just once */ + bdesc = mmc_get_blk_desc(mmc); + bdesc->if_type = IF_TYPE_MMC; + bdesc->removable = 1; + bdesc->devnum = mmc_get_next_devnum(); + bdesc->block_read = mmc_bread; + bdesc->block_write = mmc_bwrite; + bdesc->block_erase = mmc_berase; + + /* setup initial part type */ + bdesc->part_type = mmc->cfg->part_type; + mmc_list_add(mmc); + + return mmc; +} + +void mmc_destroy(struct mmc *mmc) +{ + /* only freeing memory for now */ + free(mmc); +} +#endif + +static int mmc_select_hwpartp(struct blk_desc *desc, int hwpart) +{ + struct mmc *mmc = find_mmc_device(desc->devnum); + int ret; + + if (!mmc) + return -ENODEV; + + if (mmc->block_dev.hwpart == hwpart) + return 0; + + if (mmc->part_config == MMCPART_NOAVAILABLE) + return -EMEDIUMTYPE; + + ret = mmc_switch_part(mmc, hwpart); + if (ret) + return ret; + + return 0; +} + +static int mmc_get_dev(int dev, struct blk_desc **descp) +{ + struct mmc *mmc = find_mmc_device(dev); + int ret; + + if (!mmc) + return -ENODEV; + ret = mmc_init(mmc); + if (ret) + return ret; + + *descp = &mmc->block_dev; + + return 0; +} + +U_BOOT_LEGACY_BLK(mmc) = { + .if_typename = "mmc", + .if_type = IF_TYPE_MMC, + .max_devs = -1, + .get_dev = mmc_get_dev, + .select_hwpart = mmc_select_hwpartp, +}; diff --git a/roms/u-boot/drivers/mmc/mmc_private.h b/roms/u-boot/drivers/mmc/mmc_private.h new file mode 100644 index 000000000..a6cd250d2 --- /dev/null +++ b/roms/u-boot/drivers/mmc/mmc_private.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2008,2010 Freescale Semiconductor, Inc + * Copyright 2020 NXP + * Andy Fleming + * + * Based (loosely) on the Linux code + */ + +#ifndef _MMC_PRIVATE_H_ +#define _MMC_PRIVATE_H_ + +#include <mmc.h> + +int mmc_send_status(struct mmc *mmc, unsigned int *status); +int mmc_poll_for_busy(struct mmc *mmc, int timeout); + +int mmc_set_blocklen(struct mmc *mmc, int len); + +#if CONFIG_IS_ENABLED(BLK) +ulong mmc_bread(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, + void *dst); +#else +ulong mmc_bread(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt, + void *dst); +#endif + +#if CONFIG_IS_ENABLED(MMC_WRITE) + +#if CONFIG_IS_ENABLED(BLK) +ulong mmc_bwrite(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, + const void *src); +ulong mmc_berase(struct udevice *dev, lbaint_t start, lbaint_t blkcnt); +#else +ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt, + const void *src); +ulong mmc_berase(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt); +#endif + +#else /* CONFIG_SPL_MMC_WRITE is not defined */ + +/* declare dummies to reduce code size. */ + +#if CONFIG_IS_ENABLED(BLK) +static inline unsigned long mmc_berase(struct udevice *dev, + lbaint_t start, lbaint_t blkcnt) +{ + return 0; +} + +static inline ulong mmc_bwrite(struct udevice *dev, lbaint_t start, + lbaint_t blkcnt, const void *src) +{ + return 0; +} +#else +static inline unsigned long mmc_berase(struct blk_desc *block_dev, + lbaint_t start, lbaint_t blkcnt) +{ + return 0; +} + +static inline ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, + lbaint_t blkcnt, const void *src) +{ + return 0; +} +#endif + +#endif /* CONFIG_SPL_BUILD */ + +#ifdef CONFIG_MMC_TRACE +void mmmc_trace_before_send(struct mmc *mmc, struct mmc_cmd *cmd); +void mmmc_trace_after_send(struct mmc *mmc, struct mmc_cmd *cmd, int ret); +void mmc_trace_state(struct mmc *mmc, struct mmc_cmd *cmd); +#else +static inline void mmmc_trace_before_send(struct mmc *mmc, struct mmc_cmd *cmd) +{ +} + +static inline void mmmc_trace_after_send(struct mmc *mmc, struct mmc_cmd *cmd, + int ret) +{ +} + +static inline void mmc_trace_state(struct mmc *mmc, struct mmc_cmd *cmd) +{ +} +#endif + +/** + * mmc_get_next_devnum() - Get the next available MMC device number + * + * @return next available device number (0 = first), or -ve on error + */ +int mmc_get_next_devnum(void); + +/** + * mmc_do_preinit() - Get an MMC device ready for use + */ +void mmc_do_preinit(void); + +/** + * mmc_list_init() - Set up the list of MMC devices + */ +void mmc_list_init(void); + +/** + * mmc_list_add() - Add a new MMC device to the list of devices + * + * @mmc: Device to add + */ +void mmc_list_add(struct mmc *mmc); + +/** + * mmc_switch_part() - Switch to a new MMC hardware partition + * + * @mmc: MMC device + * @part_num: Hardware partition number + * @return 0 if OK, -ve on error + */ +int mmc_switch_part(struct mmc *mmc, unsigned int part_num); + +/** + * mmc_switch() - Issue and MMC switch mode command + * + * @mmc: MMC device + * @set: Unused + * @index: Cmdarg index + * @value: Cmdarg value + * @return 0 if OK, -ve on error + */ +int mmc_switch(struct mmc *mmc, u8 set, u8 index, u8 value); + +#endif /* _MMC_PRIVATE_H_ */ diff --git a/roms/u-boot/drivers/mmc/mmc_spi.c b/roms/u-boot/drivers/mmc/mmc_spi.c new file mode 100644 index 000000000..e2d78794f --- /dev/null +++ b/roms/u-boot/drivers/mmc/mmc_spi.c @@ -0,0 +1,510 @@ +/* + * generic mmc spi driver + * + * Copyright (C) 2010 Thomas Chou <thomas@wytron.com.tw> + * Copyright 2019 Bhargav Shah <bhargavshah1988@gmail.com> + * + * Licensed under the GPL-2 or later. + */ +#include <common.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <part.h> +#include <mmc.h> +#include <stdlib.h> +#include <linux/bitops.h> +#include <u-boot/crc.h> +#include <linux/crc7.h> +#include <asm/byteorder.h> +#include <dm.h> +#include <spi.h> + +/* MMC/SD in SPI mode reports R1 status always */ +#define R1_SPI_IDLE BIT(0) +#define R1_SPI_ERASE_RESET BIT(1) +#define R1_SPI_ILLEGAL_COMMAND BIT(2) +#define R1_SPI_COM_CRC BIT(3) +#define R1_SPI_ERASE_SEQ BIT(4) +#define R1_SPI_ADDRESS BIT(5) +#define R1_SPI_PARAMETER BIT(6) +/* R1 bit 7 is always zero, reuse this bit for error */ +#define R1_SPI_ERROR BIT(7) + +/* Response tokens used to ack each block written: */ +#define SPI_MMC_RESPONSE_CODE(x) ((x) & 0x1f) +#define SPI_RESPONSE_ACCEPTED ((2 << 1)|1) +#define SPI_RESPONSE_CRC_ERR ((5 << 1)|1) +#define SPI_RESPONSE_WRITE_ERR ((6 << 1)|1) + +/* + * Read and write blocks start with these tokens and end with crc; + * on error, read tokens act like a subset of R2_SPI_* values. + */ +/* single block write multiblock read */ +#define SPI_TOKEN_SINGLE 0xfe +/* multiblock write */ +#define SPI_TOKEN_MULTI_WRITE 0xfc +/* terminate multiblock write */ +#define SPI_TOKEN_STOP_TRAN 0xfd + +/* MMC SPI commands start with a start bit "0" and a transmit bit "1" */ +#define MMC_SPI_CMD(x) (0x40 | (x)) + +/* bus capability */ +#define MMC_SPI_VOLTAGE (MMC_VDD_32_33 | MMC_VDD_33_34) +#define MMC_SPI_MIN_CLOCK 400000 /* 400KHz to meet MMC spec */ +#define MMC_SPI_MAX_CLOCK 25000000 /* SD/MMC legacy speed */ + +/* timeout value */ +#define CMD_TIMEOUT 8 +#define READ_TIMEOUT 3000000 /* 1 sec */ +#define WRITE_TIMEOUT 3000000 /* 1 sec */ +#define R1B_TIMEOUT 3000000 /* 1 sec */ + +struct mmc_spi_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct mmc_spi_priv { + struct spi_slave *spi; +}; + +/** + * mmc_spi_sendcmd() - send a command to the SD card + * + * @dev: mmc_spi device + * @cmdidx: command index + * @cmdarg: command argument + * @resp_type: card response type + * @resp: buffer to store the card response + * @resp_size: size of the card response + * @resp_match: if true, compare each of received bytes with @resp_match_value + * @resp_match_value: a value to be compared with each of received bytes + * @r1b: if true, receive additional bytes for busy signal token + * @return 0 if OK, -ETIMEDOUT if no card response is received, -ve on error + */ +static int mmc_spi_sendcmd(struct udevice *dev, + ushort cmdidx, u32 cmdarg, u32 resp_type, + u8 *resp, u32 resp_size, + bool resp_match, u8 resp_match_value, bool r1b) +{ + int i, rpos = 0, ret = 0; + u8 cmdo[7], r; + + if (!resp || !resp_size) + return 0; + + debug("%s: cmd%d cmdarg=0x%x resp_type=0x%x " + "resp_size=%d resp_match=%d resp_match_value=0x%x\n", + __func__, cmdidx, cmdarg, resp_type, + resp_size, resp_match, resp_match_value); + + cmdo[0] = 0xff; + cmdo[1] = MMC_SPI_CMD(cmdidx); + cmdo[2] = cmdarg >> 24; + cmdo[3] = cmdarg >> 16; + cmdo[4] = cmdarg >> 8; + cmdo[5] = cmdarg; + cmdo[6] = (crc7(0, &cmdo[1], 5) << 1) | 0x01; + ret = dm_spi_xfer(dev, sizeof(cmdo) * 8, cmdo, NULL, SPI_XFER_BEGIN); + if (ret) + return ret; + + ret = dm_spi_xfer(dev, 1 * 8, NULL, &r, 0); + if (ret) + return ret; + + debug("%s: cmd%d", __func__, cmdidx); + + if (resp_match) + r = ~resp_match_value; + i = CMD_TIMEOUT; + while (i) { + ret = dm_spi_xfer(dev, 1 * 8, NULL, &r, 0); + if (ret) + return ret; + debug(" resp%d=0x%x", rpos, r); + rpos++; + i--; + + if (resp_match) { + if (r == resp_match_value) + break; + } else { + if (!(r & 0x80)) + break; + } + + if (!i) + return -ETIMEDOUT; + } + + resp[0] = r; + for (i = 1; i < resp_size; i++) { + ret = dm_spi_xfer(dev, 1 * 8, NULL, &r, 0); + if (ret) + return ret; + debug(" resp%d=0x%x", rpos, r); + rpos++; + resp[i] = r; + } + + if (r1b == true) { + i = R1B_TIMEOUT; + while (i) { + ret = dm_spi_xfer(dev, 1 * 8, NULL, &r, 0); + if (ret) + return ret; + + debug(" resp%d=0x%x", rpos, r); + rpos++; + i--; + + if (r) + break; + } + if (!i) + return -ETIMEDOUT; + } + + debug("\n"); + + return 0; +} + +/** + * mmc_spi_readdata() - read data block(s) from the SD card + * + * @dev: mmc_spi device + * @xbuf: buffer of the actual data (excluding token and crc) to read + * @bcnt: number of data blocks to transfer + * @bsize: size of the actual data (excluding token and crc) in bytes + * @return 0 if OK, -ECOMM if crc error, -ETIMEDOUT on other errors + */ +static int mmc_spi_readdata(struct udevice *dev, + void *xbuf, u32 bcnt, u32 bsize) +{ + u16 crc; + u8 *buf = xbuf, r1; + int i, ret = 0; + + while (bcnt--) { + for (i = 0; i < READ_TIMEOUT; i++) { + ret = dm_spi_xfer(dev, 1 * 8, NULL, &r1, 0); + if (ret) + return ret; + if (r1 == SPI_TOKEN_SINGLE) + break; + } + debug("%s: data tok%d 0x%x\n", __func__, i, r1); + if (r1 == SPI_TOKEN_SINGLE) { + ret = dm_spi_xfer(dev, bsize * 8, NULL, buf, 0); + if (ret) + return ret; + ret = dm_spi_xfer(dev, 2 * 8, NULL, &crc, 0); + if (ret) + return ret; +#ifdef CONFIG_MMC_SPI_CRC_ON + u16 crc_ok = be16_to_cpu(crc16_ccitt(0, buf, bsize)); + if (crc_ok != crc) { + debug("%s: data crc error, expected %04x got %04x\n", + __func__, crc_ok, crc); + r1 = R1_SPI_COM_CRC; + break; + } +#endif + r1 = 0; + } else { + r1 = R1_SPI_ERROR; + break; + } + buf += bsize; + } + + if (r1 & R1_SPI_COM_CRC) + ret = -ECOMM; + else if (r1) /* other errors */ + ret = -ETIMEDOUT; + + return ret; +} + +/** + * mmc_spi_writedata() - write data block(s) to the SD card + * + * @dev: mmc_spi device + * @xbuf: buffer of the actual data (excluding token and crc) to write + * @bcnt: number of data blocks to transfer + * @bsize: size of actual data (excluding token and crc) in bytes + * @multi: indicate a transfer by multiple block write command (CMD25) + * @return 0 if OK, -ECOMM if crc error, -ETIMEDOUT on other errors + */ +static int mmc_spi_writedata(struct udevice *dev, const void *xbuf, + u32 bcnt, u32 bsize, int multi) +{ + const u8 *buf = xbuf; + u8 r1, tok[2]; + u16 crc; + int i, ret = 0; + + tok[0] = 0xff; + tok[1] = multi ? SPI_TOKEN_MULTI_WRITE : SPI_TOKEN_SINGLE; + + while (bcnt--) { +#ifdef CONFIG_MMC_SPI_CRC_ON + crc = cpu_to_be16(crc16_ccitt(0, (u8 *)buf, bsize)); +#endif + dm_spi_xfer(dev, 2 * 8, tok, NULL, 0); + dm_spi_xfer(dev, bsize * 8, buf, NULL, 0); + dm_spi_xfer(dev, 2 * 8, &crc, NULL, 0); + for (i = 0; i < CMD_TIMEOUT; i++) { + dm_spi_xfer(dev, 1 * 8, NULL, &r1, 0); + if ((r1 & 0x10) == 0) /* response token */ + break; + } + debug("%s: data tok%d 0x%x\n", __func__, i, r1); + if (SPI_MMC_RESPONSE_CODE(r1) == SPI_RESPONSE_ACCEPTED) { + debug("%s: data accepted\n", __func__); + for (i = 0; i < WRITE_TIMEOUT; i++) { /* wait busy */ + dm_spi_xfer(dev, 1 * 8, NULL, &r1, 0); + if (i && r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WRITE_TIMEOUT) { + debug("%s: data write timeout 0x%x\n", + __func__, r1); + r1 = R1_SPI_ERROR; + break; + } + } else { + debug("%s: data error 0x%x\n", __func__, r1); + r1 = R1_SPI_COM_CRC; + break; + } + buf += bsize; + } + if (multi && bcnt == -1) { /* stop multi write */ + tok[1] = SPI_TOKEN_STOP_TRAN; + dm_spi_xfer(dev, 2 * 8, tok, NULL, 0); + for (i = 0; i < WRITE_TIMEOUT; i++) { /* wait busy */ + dm_spi_xfer(dev, 1 * 8, NULL, &r1, 0); + if (i && r1 == 0xff) { + r1 = 0; + break; + } + } + if (i == WRITE_TIMEOUT) { + debug("%s: data write timeout 0x%x\n", __func__, r1); + r1 = R1_SPI_ERROR; + } + } + + if (r1 & R1_SPI_COM_CRC) + ret = -ECOMM; + else if (r1) /* other errors */ + ret = -ETIMEDOUT; + + return ret; +} + +static int dm_mmc_spi_set_ios(struct udevice *dev) +{ + return 0; +} + +static int dm_mmc_spi_request(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + int i, multi, ret = 0; + u8 *resp = NULL; + u32 resp_size = 0; + bool resp_match = false, r1b = false; + u8 resp8 = 0, resp16[2] = { 0 }, resp40[5] = { 0 }, resp_match_value = 0; + + dm_spi_claim_bus(dev); + + for (i = 0; i < 4; i++) + cmd->response[i] = 0; + + switch (cmd->cmdidx) { + case SD_CMD_APP_SEND_OP_COND: + case MMC_CMD_SEND_OP_COND: + resp = &resp8; + resp_size = sizeof(resp8); + cmd->cmdarg = 0x40000000; + break; + case SD_CMD_SEND_IF_COND: + resp = (u8 *)&resp40[0]; + resp_size = sizeof(resp40); + resp_match = true; + resp_match_value = R1_SPI_IDLE; + break; + case MMC_CMD_SPI_READ_OCR: + resp = (u8 *)&resp40[0]; + resp_size = sizeof(resp40); + break; + case MMC_CMD_SEND_STATUS: + resp = (u8 *)&resp16[0]; + resp_size = sizeof(resp16); + break; + case MMC_CMD_SET_BLOCKLEN: + case MMC_CMD_SPI_CRC_ON_OFF: + resp = &resp8; + resp_size = sizeof(resp8); + resp_match = true; + resp_match_value = 0x0; + break; + case MMC_CMD_STOP_TRANSMISSION: + case MMC_CMD_ERASE: + resp = &resp8; + resp_size = sizeof(resp8); + r1b = true; + break; + case MMC_CMD_SEND_CSD: + case MMC_CMD_SEND_CID: + case MMC_CMD_READ_SINGLE_BLOCK: + case MMC_CMD_READ_MULTIPLE_BLOCK: + case MMC_CMD_WRITE_SINGLE_BLOCK: + case MMC_CMD_WRITE_MULTIPLE_BLOCK: + case MMC_CMD_APP_CMD: + case SD_CMD_ERASE_WR_BLK_START: + case SD_CMD_ERASE_WR_BLK_END: + resp = &resp8; + resp_size = sizeof(resp8); + break; + default: + resp = &resp8; + resp_size = sizeof(resp8); + resp_match = true; + resp_match_value = R1_SPI_IDLE; + break; + }; + + ret = mmc_spi_sendcmd(dev, cmd->cmdidx, cmd->cmdarg, cmd->resp_type, + resp, resp_size, resp_match, resp_match_value, r1b); + if (ret) + goto done; + + switch (cmd->cmdidx) { + case SD_CMD_APP_SEND_OP_COND: + case MMC_CMD_SEND_OP_COND: + cmd->response[0] = (resp8 & R1_SPI_IDLE) ? 0 : OCR_BUSY; + break; + case SD_CMD_SEND_IF_COND: + case MMC_CMD_SPI_READ_OCR: + cmd->response[0] = resp40[4]; + cmd->response[0] |= (uint)resp40[3] << 8; + cmd->response[0] |= (uint)resp40[2] << 16; + cmd->response[0] |= (uint)resp40[1] << 24; + break; + case MMC_CMD_SEND_STATUS: + if (resp16[0] || resp16[1]) + cmd->response[0] = MMC_STATUS_ERROR; + else + cmd->response[0] = MMC_STATUS_RDY_FOR_DATA; + break; + case MMC_CMD_SEND_CID: + case MMC_CMD_SEND_CSD: + ret = mmc_spi_readdata(dev, cmd->response, 1, 16); + if (ret) + return ret; + for (i = 0; i < 4; i++) + cmd->response[i] = + cpu_to_be32(cmd->response[i]); + break; + default: + cmd->response[0] = resp8; + break; + } + + debug("%s: cmd%d resp0=0x%x resp1=0x%x resp2=0x%x resp3=0x%x\n", + __func__, cmd->cmdidx, cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + + if (data) { + debug("%s: data flags=0x%x blocks=%d block_size=%d\n", + __func__, data->flags, data->blocks, data->blocksize); + multi = (cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK); + if (data->flags == MMC_DATA_READ) + ret = mmc_spi_readdata(dev, data->dest, + data->blocks, data->blocksize); + else if (data->flags == MMC_DATA_WRITE) + ret = mmc_spi_writedata(dev, data->src, + data->blocks, data->blocksize, + multi); + } + +done: + dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_END); + + dm_spi_release_bus(dev); + + return ret; +} + +static int mmc_spi_probe(struct udevice *dev) +{ + struct mmc_spi_priv *priv = dev_get_priv(dev); + struct mmc_spi_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + char *name; + + priv->spi = dev_get_parent_priv(dev); + if (!priv->spi->max_hz) + priv->spi->max_hz = MMC_SPI_MAX_CLOCK; + priv->spi->mode = SPI_MODE_0; + priv->spi->wordlen = 8; + + name = malloc(strlen(dev->parent->name) + strlen(dev->name) + 4); + if (!name) + return -ENOMEM; + sprintf(name, "%s:%s", dev->parent->name, dev->name); + + plat->cfg.name = name; + plat->cfg.host_caps = MMC_MODE_SPI; + plat->cfg.voltages = MMC_SPI_VOLTAGE; + plat->cfg.f_min = MMC_SPI_MIN_CLOCK; + plat->cfg.f_max = priv->spi->max_hz; + plat->cfg.part_type = PART_TYPE_DOS; + plat->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + plat->mmc.cfg = &plat->cfg; + plat->mmc.priv = priv; + plat->mmc.dev = dev; + + upriv->mmc = &plat->mmc; + + return 0; +} + +static int mmc_spi_bind(struct udevice *dev) +{ + struct mmc_spi_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct dm_mmc_ops mmc_spi_ops = { + .send_cmd = dm_mmc_spi_request, + .set_ios = dm_mmc_spi_set_ios, +}; + +static const struct udevice_id dm_mmc_spi_match[] = { + { .compatible = "mmc-spi-slot" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(mmc_spi) = { + .name = "mmc_spi", + .id = UCLASS_MMC, + .of_match = dm_mmc_spi_match, + .ops = &mmc_spi_ops, + .probe = mmc_spi_probe, + .bind = mmc_spi_bind, + .plat_auto = sizeof(struct mmc_spi_plat), + .priv_auto = sizeof(struct mmc_spi_priv), +}; diff --git a/roms/u-boot/drivers/mmc/mmc_write.c b/roms/u-boot/drivers/mmc/mmc_write.c new file mode 100644 index 000000000..d23b7d972 --- /dev/null +++ b/roms/u-boot/drivers/mmc/mmc_write.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2008, Freescale Semiconductor, Inc + * Andy Fleming + * + * Based vaguely on the Linux code + */ + +#include <config.h> +#include <common.h> +#include <blk.h> +#include <dm.h> +#include <part.h> +#include <div64.h> +#include <linux/math64.h> +#include "mmc_private.h" + +static ulong mmc_erase_t(struct mmc *mmc, ulong start, lbaint_t blkcnt) +{ + struct mmc_cmd cmd; + ulong end; + int err, start_cmd, end_cmd; + + if (mmc->high_capacity) { + end = start + blkcnt - 1; + } else { + end = (start + blkcnt - 1) * mmc->write_bl_len; + start *= mmc->write_bl_len; + } + + if (IS_SD(mmc)) { + start_cmd = SD_CMD_ERASE_WR_BLK_START; + end_cmd = SD_CMD_ERASE_WR_BLK_END; + } else { + start_cmd = MMC_CMD_ERASE_GROUP_START; + end_cmd = MMC_CMD_ERASE_GROUP_END; + } + + cmd.cmdidx = start_cmd; + cmd.cmdarg = start; + cmd.resp_type = MMC_RSP_R1; + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) + goto err_out; + + cmd.cmdidx = end_cmd; + cmd.cmdarg = end; + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) + goto err_out; + + cmd.cmdidx = MMC_CMD_ERASE; + cmd.cmdarg = MMC_ERASE_ARG; + cmd.resp_type = MMC_RSP_R1b; + + err = mmc_send_cmd(mmc, &cmd, NULL); + if (err) + goto err_out; + + return 0; + +err_out: + puts("mmc erase failed\n"); + return err; +} + +#if CONFIG_IS_ENABLED(BLK) +ulong mmc_berase(struct udevice *dev, lbaint_t start, lbaint_t blkcnt) +#else +ulong mmc_berase(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt) +#endif +{ +#if CONFIG_IS_ENABLED(BLK) + struct blk_desc *block_dev = dev_get_uclass_plat(dev); +#endif + int dev_num = block_dev->devnum; + int err = 0; + u32 start_rem, blkcnt_rem; + struct mmc *mmc = find_mmc_device(dev_num); + lbaint_t blk = 0, blk_r = 0; + int timeout_ms = 1000; + + if (!mmc) + return -1; + + err = blk_select_hwpart_devnum(IF_TYPE_MMC, dev_num, + block_dev->hwpart); + if (err < 0) + return -1; + + /* + * We want to see if the requested start or total block count are + * unaligned. We discard the whole numbers and only care about the + * remainder. + */ + err = div_u64_rem(start, mmc->erase_grp_size, &start_rem); + err = div_u64_rem(blkcnt, mmc->erase_grp_size, &blkcnt_rem); + if (start_rem || blkcnt_rem) + printf("\n\nCaution! Your devices Erase group is 0x%x\n" + "The erase range would be change to " + "0x" LBAF "~0x" LBAF "\n\n", + mmc->erase_grp_size, start & ~(mmc->erase_grp_size - 1), + ((start + blkcnt + mmc->erase_grp_size) + & ~(mmc->erase_grp_size - 1)) - 1); + + while (blk < blkcnt) { + if (IS_SD(mmc) && mmc->ssr.au) { + blk_r = ((blkcnt - blk) > mmc->ssr.au) ? + mmc->ssr.au : (blkcnt - blk); + } else { + blk_r = ((blkcnt - blk) > mmc->erase_grp_size) ? + mmc->erase_grp_size : (blkcnt - blk); + } + err = mmc_erase_t(mmc, start + blk, blk_r); + if (err) + break; + + blk += blk_r; + + /* Waiting for the ready status */ + if (mmc_poll_for_busy(mmc, timeout_ms)) + return 0; + } + + return blk; +} + +static ulong mmc_write_blocks(struct mmc *mmc, lbaint_t start, + lbaint_t blkcnt, const void *src) +{ + struct mmc_cmd cmd; + struct mmc_data data; + int timeout_ms = 1000; + + if ((start + blkcnt) > mmc_get_blk_desc(mmc)->lba) { + printf("MMC: block number 0x" LBAF " exceeds max(0x" LBAF ")\n", + start + blkcnt, mmc_get_blk_desc(mmc)->lba); + return 0; + } + + if (blkcnt == 0) + return 0; + else if (blkcnt == 1) + cmd.cmdidx = MMC_CMD_WRITE_SINGLE_BLOCK; + else + cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK; + + if (mmc->high_capacity) + cmd.cmdarg = start; + else + cmd.cmdarg = start * mmc->write_bl_len; + + cmd.resp_type = MMC_RSP_R1; + + data.src = src; + data.blocks = blkcnt; + data.blocksize = mmc->write_bl_len; + data.flags = MMC_DATA_WRITE; + + if (mmc_send_cmd(mmc, &cmd, &data)) { + printf("mmc write failed\n"); + return 0; + } + + /* SPI multiblock writes terminate using a special + * token, not a STOP_TRANSMISSION request. + */ + if (!mmc_host_is_spi(mmc) && blkcnt > 1) { + cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1b; + if (mmc_send_cmd(mmc, &cmd, NULL)) { + printf("mmc fail to send stop cmd\n"); + return 0; + } + } + + /* Waiting for the ready status */ + if (mmc_poll_for_busy(mmc, timeout_ms)) + return 0; + + return blkcnt; +} + +#if CONFIG_IS_ENABLED(BLK) +ulong mmc_bwrite(struct udevice *dev, lbaint_t start, lbaint_t blkcnt, + const void *src) +#else +ulong mmc_bwrite(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt, + const void *src) +#endif +{ +#if CONFIG_IS_ENABLED(BLK) + struct blk_desc *block_dev = dev_get_uclass_plat(dev); +#endif + int dev_num = block_dev->devnum; + lbaint_t cur, blocks_todo = blkcnt; + int err; + + struct mmc *mmc = find_mmc_device(dev_num); + if (!mmc) + return 0; + + err = blk_select_hwpart_devnum(IF_TYPE_MMC, dev_num, block_dev->hwpart); + if (err < 0) + return 0; + + if (mmc_set_blocklen(mmc, mmc->write_bl_len)) + return 0; + + do { + cur = (blocks_todo > mmc->cfg->b_max) ? + mmc->cfg->b_max : blocks_todo; + if (mmc_write_blocks(mmc, start, cur, src) != cur) + return 0; + blocks_todo -= cur; + start += cur; + src += cur * mmc->write_bl_len; + } while (blocks_todo > 0); + + return blkcnt; +} diff --git a/roms/u-boot/drivers/mmc/msm_sdhci.c b/roms/u-boot/drivers/mmc/msm_sdhci.c new file mode 100644 index 000000000..d63d7b3a2 --- /dev/null +++ b/roms/u-boot/drivers/mmc/msm_sdhci.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Qualcomm SDHCI driver - SD/eMMC controller + * + * (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski@gmail.com> + * + * Based on Linux driver + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <malloc.h> +#include <sdhci.h> +#include <wait_bit.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <linux/bitops.h> + +/* Non-standard registers needed for SDHCI startup */ +#define SDCC_MCI_POWER 0x0 +#define SDCC_MCI_POWER_SW_RST BIT(7) + +/* This is undocumented register */ +#define SDCC_MCI_VERSION 0x50 +#define SDCC_MCI_VERSION_MAJOR_SHIFT 28 +#define SDCC_MCI_VERSION_MAJOR_MASK (0xf << SDCC_MCI_VERSION_MAJOR_SHIFT) +#define SDCC_MCI_VERSION_MINOR_MASK 0xff + +#define SDCC_MCI_STATUS2 0x6C +#define SDCC_MCI_STATUS2_MCI_ACT 0x1 +#define SDCC_MCI_HC_MODE 0x78 + +/* Offset to SDHCI registers */ +#define SDCC_SDHCI_OFFSET 0x900 + +/* Non standard (?) SDHCI register */ +#define SDHCI_VENDOR_SPEC_CAPABILITIES0 0x11c + +struct msm_sdhc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct msm_sdhc { + struct sdhci_host host; + void *base; +}; + +DECLARE_GLOBAL_DATA_PTR; + +static int msm_sdc_clk_init(struct udevice *dev) +{ + int node = dev_of_offset(dev); + uint clk_rate = fdtdec_get_uint(gd->fdt_blob, node, "clock-frequency", + 400000); + uint clkd[2]; /* clk_id and clk_no */ + int clk_offset; + struct udevice *clk_dev; + struct clk clk; + int ret; + + ret = fdtdec_get_int_array(gd->fdt_blob, node, "clock", clkd, 2); + if (ret) + return ret; + + clk_offset = fdt_node_offset_by_phandle(gd->fdt_blob, clkd[0]); + if (clk_offset < 0) + return clk_offset; + + ret = uclass_get_device_by_of_offset(UCLASS_CLK, clk_offset, &clk_dev); + if (ret) + return ret; + + clk.id = clkd[1]; + ret = clk_request(clk_dev, &clk); + if (ret < 0) + return ret; + + ret = clk_set_rate(&clk, clk_rate); + clk_free(&clk); + if (ret < 0) + return ret; + + return 0; +} + +static int msm_sdc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct msm_sdhc_plat *plat = dev_get_plat(dev); + struct msm_sdhc *prv = dev_get_priv(dev); + struct sdhci_host *host = &prv->host; + u32 core_version, core_minor, core_major; + u32 caps; + int ret; + + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_BROKEN_R1B; + + host->max_clk = 0; + + /* Init clocks */ + ret = msm_sdc_clk_init(dev); + if (ret) + return ret; + + /* Reset the core and Enable SDHC mode */ + writel(readl(prv->base + SDCC_MCI_POWER) | SDCC_MCI_POWER_SW_RST, + prv->base + SDCC_MCI_POWER); + + + /* Wait for reset to be written to register */ + if (wait_for_bit_le32(prv->base + SDCC_MCI_STATUS2, + SDCC_MCI_STATUS2_MCI_ACT, false, 10, false)) { + printf("msm_sdhci: reset request failed\n"); + return -EIO; + } + + /* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */ + if (wait_for_bit_le32(prv->base + SDCC_MCI_POWER, + SDCC_MCI_POWER_SW_RST, false, 2, false)) { + printf("msm_sdhci: stuck in reset\n"); + return -ETIMEDOUT; + } + + /* Enable host-controller mode */ + writel(1, prv->base + SDCC_MCI_HC_MODE); + + core_version = readl(prv->base + SDCC_MCI_VERSION); + + core_major = (core_version & SDCC_MCI_VERSION_MAJOR_MASK); + core_major >>= SDCC_MCI_VERSION_MAJOR_SHIFT; + + core_minor = core_version & SDCC_MCI_VERSION_MINOR_MASK; + + /* + * Support for some capabilities is not advertised by newer + * controller versions and must be explicitly enabled. + */ + if (core_major >= 1 && core_minor != 0x11 && core_minor != 0x12) { + caps = readl(host->ioaddr + SDHCI_CAPABILITIES); + caps |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT; + writel(caps, host->ioaddr + SDHCI_VENDOR_SPEC_CAPABILITIES0); + } + + ret = mmc_of_parse(dev, &plat->cfg); + if (ret) + return ret; + + host->mmc = &plat->mmc; + host->mmc->dev = dev; + ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0); + if (ret) + return ret; + host->mmc->priv = &prv->host; + upriv->mmc = host->mmc; + + return sdhci_probe(dev); +} + +static int msm_sdc_remove(struct udevice *dev) +{ + struct msm_sdhc *priv = dev_get_priv(dev); + + /* Disable host-controller mode */ + writel(0, priv->base + SDCC_MCI_HC_MODE); + + return 0; +} + +static int msm_of_to_plat(struct udevice *dev) +{ + struct udevice *parent = dev->parent; + struct msm_sdhc *priv = dev_get_priv(dev); + struct sdhci_host *host = &priv->host; + int node = dev_of_offset(dev); + + host->name = strdup(dev->name); + host->ioaddr = dev_read_addr_ptr(dev); + host->bus_width = fdtdec_get_int(gd->fdt_blob, node, "bus-width", 4); + host->index = fdtdec_get_uint(gd->fdt_blob, node, "index", 0); + priv->base = (void *)fdtdec_get_addr_size_auto_parent(gd->fdt_blob, + dev_of_offset(parent), node, "reg", 1, NULL, false); + if (priv->base == (void *)FDT_ADDR_T_NONE || + host->ioaddr == (void *)FDT_ADDR_T_NONE) + return -EINVAL; + + return 0; +} + +static int msm_sdc_bind(struct udevice *dev) +{ + struct msm_sdhc_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id msm_mmc_ids[] = { + { .compatible = "qcom,sdhci-msm-v4" }, + { } +}; + +U_BOOT_DRIVER(msm_sdc_drv) = { + .name = "msm_sdc", + .id = UCLASS_MMC, + .of_match = msm_mmc_ids, + .of_to_plat = msm_of_to_plat, + .ops = &sdhci_ops, + .bind = msm_sdc_bind, + .probe = msm_sdc_probe, + .remove = msm_sdc_remove, + .priv_auto = sizeof(struct msm_sdhc), + .plat_auto = sizeof(struct msm_sdhc_plat), +}; diff --git a/roms/u-boot/drivers/mmc/mtk-sd.c b/roms/u-boot/drivers/mmc/mtk-sd.c new file mode 100644 index 000000000..8599f095b --- /dev/null +++ b/roms/u-boot/drivers/mmc/mtk-sd.c @@ -0,0 +1,1815 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MediaTek SD/MMC Card Interface driver + * + * Copyright (C) 2018 MediaTek Inc. + * Author: Weijie Gao <weijie.gao@mediatek.com> + */ + +#include <clk.h> +#include <common.h> +#include <dm.h> +#include <mmc.h> +#include <errno.h> +#include <malloc.h> +#include <mapmem.h> +#include <stdbool.h> +#include <asm/gpio.h> +#include <dm/device_compat.h> +#include <dm/pinctrl.h> +#include <linux/bitops.h> +#include <linux/io.h> +#include <linux/iopoll.h> + +/* MSDC_CFG */ +#define MSDC_CFG_HS400_CK_MODE_EXT BIT(22) +#define MSDC_CFG_CKMOD_EXT_M 0x300000 +#define MSDC_CFG_CKMOD_EXT_S 20 +#define MSDC_CFG_CKDIV_EXT_M 0xfff00 +#define MSDC_CFG_CKDIV_EXT_S 8 +#define MSDC_CFG_HS400_CK_MODE BIT(18) +#define MSDC_CFG_CKMOD_M 0x30000 +#define MSDC_CFG_CKMOD_S 16 +#define MSDC_CFG_CKDIV_M 0xff00 +#define MSDC_CFG_CKDIV_S 8 +#define MSDC_CFG_CKSTB BIT(7) +#define MSDC_CFG_PIO BIT(3) +#define MSDC_CFG_RST BIT(2) +#define MSDC_CFG_CKPDN BIT(1) +#define MSDC_CFG_MODE BIT(0) + +/* MSDC_IOCON */ +#define MSDC_IOCON_W_DSPL BIT(8) +#define MSDC_IOCON_DSPL BIT(2) +#define MSDC_IOCON_RSPL BIT(1) + +/* MSDC_PS */ +#define MSDC_PS_DAT0 BIT(16) +#define MSDC_PS_CDDBCE_M 0xf000 +#define MSDC_PS_CDDBCE_S 12 +#define MSDC_PS_CDSTS BIT(1) +#define MSDC_PS_CDEN BIT(0) + +/* #define MSDC_INT(EN) */ +#define MSDC_INT_ACMDRDY BIT(3) +#define MSDC_INT_ACMDTMO BIT(4) +#define MSDC_INT_ACMDCRCERR BIT(5) +#define MSDC_INT_CMDRDY BIT(8) +#define MSDC_INT_CMDTMO BIT(9) +#define MSDC_INT_RSPCRCERR BIT(10) +#define MSDC_INT_XFER_COMPL BIT(12) +#define MSDC_INT_DATTMO BIT(14) +#define MSDC_INT_DATCRCERR BIT(15) + +/* MSDC_FIFOCS */ +#define MSDC_FIFOCS_CLR BIT(31) +#define MSDC_FIFOCS_TXCNT_M 0xff0000 +#define MSDC_FIFOCS_TXCNT_S 16 +#define MSDC_FIFOCS_RXCNT_M 0xff +#define MSDC_FIFOCS_RXCNT_S 0 + +/* #define SDC_CFG */ +#define SDC_CFG_DTOC_M 0xff000000 +#define SDC_CFG_DTOC_S 24 +#define SDC_CFG_SDIOIDE BIT(20) +#define SDC_CFG_SDIO BIT(19) +#define SDC_CFG_BUSWIDTH_M 0x30000 +#define SDC_CFG_BUSWIDTH_S 16 + +/* SDC_CMD */ +#define SDC_CMD_BLK_LEN_M 0xfff0000 +#define SDC_CMD_BLK_LEN_S 16 +#define SDC_CMD_STOP BIT(14) +#define SDC_CMD_WR BIT(13) +#define SDC_CMD_DTYPE_M 0x1800 +#define SDC_CMD_DTYPE_S 11 +#define SDC_CMD_RSPTYP_M 0x380 +#define SDC_CMD_RSPTYP_S 7 +#define SDC_CMD_CMD_M 0x3f +#define SDC_CMD_CMD_S 0 + +/* SDC_STS */ +#define SDC_STS_CMDBUSY BIT(1) +#define SDC_STS_SDCBUSY BIT(0) + +/* SDC_ADV_CFG0 */ +#define SDC_RX_ENHANCE_EN BIT(20) + +/* PATCH_BIT0 */ +#define MSDC_INT_DAT_LATCH_CK_SEL_M 0x380 +#define MSDC_INT_DAT_LATCH_CK_SEL_S 7 + +/* PATCH_BIT1 */ +#define MSDC_PB1_STOP_DLY_M 0xf00 +#define MSDC_PB1_STOP_DLY_S 8 + +/* PATCH_BIT2 */ +#define MSDC_PB2_CRCSTSENSEL_M 0xe0000000 +#define MSDC_PB2_CRCSTSENSEL_S 29 +#define MSDC_PB2_CFGCRCSTS BIT(28) +#define MSDC_PB2_RESPSTSENSEL_M 0x70000 +#define MSDC_PB2_RESPSTSENSEL_S 16 +#define MSDC_PB2_CFGRESP BIT(15) +#define MSDC_PB2_RESPWAIT_M 0x0c +#define MSDC_PB2_RESPWAIT_S 2 + +/* MSDC_PAD_CTRL0 */ +#define MSDC_PAD_CTRL0_CLKRDSEL_M 0xff000000 +#define MSDC_PAD_CTRL0_CLKRDSEL_S 24 +#define MSDC_PAD_CTRL0_CLKTDSEL BIT(20) +#define MSDC_PAD_CTRL0_CLKIES BIT(19) +#define MSDC_PAD_CTRL0_CLKSMT BIT(18) +#define MSDC_PAD_CTRL0_CLKPU BIT(17) +#define MSDC_PAD_CTRL0_CLKPD BIT(16) +#define MSDC_PAD_CTRL0_CLKSR BIT(8) +#define MSDC_PAD_CTRL0_CLKDRVP_M 0x70 +#define MSDC_PAD_CTRL0_CLKDRVP_S 4 +#define MSDC_PAD_CTRL0_CLKDRVN_M 0x7 +#define MSDC_PAD_CTRL0_CLKDRVN_S 0 + +/* MSDC_PAD_CTRL1 */ +#define MSDC_PAD_CTRL1_CMDRDSEL_M 0xff000000 +#define MSDC_PAD_CTRL1_CMDRDSEL_S 24 +#define MSDC_PAD_CTRL1_CMDTDSEL BIT(20) +#define MSDC_PAD_CTRL1_CMDIES BIT(19) +#define MSDC_PAD_CTRL1_CMDSMT BIT(18) +#define MSDC_PAD_CTRL1_CMDPU BIT(17) +#define MSDC_PAD_CTRL1_CMDPD BIT(16) +#define MSDC_PAD_CTRL1_CMDSR BIT(8) +#define MSDC_PAD_CTRL1_CMDDRVP_M 0x70 +#define MSDC_PAD_CTRL1_CMDDRVP_S 4 +#define MSDC_PAD_CTRL1_CMDDRVN_M 0x7 +#define MSDC_PAD_CTRL1_CMDDRVN_S 0 + +/* MSDC_PAD_CTRL2 */ +#define MSDC_PAD_CTRL2_DATRDSEL_M 0xff000000 +#define MSDC_PAD_CTRL2_DATRDSEL_S 24 +#define MSDC_PAD_CTRL2_DATTDSEL BIT(20) +#define MSDC_PAD_CTRL2_DATIES BIT(19) +#define MSDC_PAD_CTRL2_DATSMT BIT(18) +#define MSDC_PAD_CTRL2_DATPU BIT(17) +#define MSDC_PAD_CTRL2_DATPD BIT(16) +#define MSDC_PAD_CTRL2_DATSR BIT(8) +#define MSDC_PAD_CTRL2_DATDRVP_M 0x70 +#define MSDC_PAD_CTRL2_DATDRVP_S 4 +#define MSDC_PAD_CTRL2_DATDRVN_M 0x7 +#define MSDC_PAD_CTRL2_DATDRVN_S 0 + +/* PAD_TUNE */ +#define MSDC_PAD_TUNE_CLKTDLY_M 0xf8000000 +#define MSDC_PAD_TUNE_CLKTDLY_S 27 +#define MSDC_PAD_TUNE_CMDRRDLY_M 0x7c00000 +#define MSDC_PAD_TUNE_CMDRRDLY_S 22 +#define MSDC_PAD_TUNE_CMD_SEL BIT(21) +#define MSDC_PAD_TUNE_CMDRDLY_M 0x1f0000 +#define MSDC_PAD_TUNE_CMDRDLY_S 16 +#define MSDC_PAD_TUNE_RXDLYSEL BIT(15) +#define MSDC_PAD_TUNE_RD_SEL BIT(13) +#define MSDC_PAD_TUNE_DATRRDLY_M 0x1f00 +#define MSDC_PAD_TUNE_DATRRDLY_S 8 +#define MSDC_PAD_TUNE_DATWRDLY_M 0x1f +#define MSDC_PAD_TUNE_DATWRDLY_S 0 + +#define PAD_CMD_TUNE_RX_DLY3 0x3E +#define PAD_CMD_TUNE_RX_DLY3_S 1 + +/* PAD_TUNE0 */ +#define MSDC_PAD_TUNE0_DAT0RDDLY_M 0x1f000000 +#define MSDC_PAD_TUNE0_DAT0RDDLY_S 24 +#define MSDC_PAD_TUNE0_DAT1RDDLY_M 0x1f0000 +#define MSDC_PAD_TUNE0_DAT1RDDLY_S 16 +#define MSDC_PAD_TUNE0_DAT2RDDLY_M 0x1f00 +#define MSDC_PAD_TUNE0_DAT2RDDLY_S 8 +#define MSDC_PAD_TUNE0_DAT3RDDLY_M 0x1f +#define MSDC_PAD_TUNE0_DAT3RDDLY_S 0 + +/* PAD_TUNE1 */ +#define MSDC_PAD_TUNE1_DAT4RDDLY_M 0x1f000000 +#define MSDC_PAD_TUNE1_DAT4RDDLY_S 24 +#define MSDC_PAD_TUNE1_DAT5RDDLY_M 0x1f0000 +#define MSDC_PAD_TUNE1_DAT5RDDLY_S 16 +#define MSDC_PAD_TUNE1_DAT6RDDLY_M 0x1f00 +#define MSDC_PAD_TUNE1_DAT6RDDLY_S 8 +#define MSDC_PAD_TUNE1_DAT7RDDLY_M 0x1f +#define MSDC_PAD_TUNE1_DAT7RDDLY_S 0 + +/* EMMC50_CFG0 */ +#define EMMC50_CFG_CFCSTS_SEL BIT(4) + +/* SDC_FIFO_CFG */ +#define SDC_FIFO_CFG_WRVALIDSEL BIT(24) +#define SDC_FIFO_CFG_RDVALIDSEL BIT(25) + +/* EMMC_TOP_CONTROL mask */ +#define PAD_RXDLY_SEL BIT(0) +#define DELAY_EN BIT(1) +#define PAD_DAT_RD_RXDLY2 (0x1f << 2) +#define PAD_DAT_RD_RXDLY (0x1f << 7) +#define PAD_DAT_RD_RXDLY_S 7 +#define PAD_DAT_RD_RXDLY2_SEL BIT(12) +#define PAD_DAT_RD_RXDLY_SEL BIT(13) +#define DATA_K_VALUE_SEL BIT(14) +#define SDC_RX_ENH_EN BIT(15) + +/* EMMC_TOP_CMD mask */ +#define PAD_CMD_RXDLY2 (0x1f << 0) +#define PAD_CMD_RXDLY (0x1f << 5) +#define PAD_CMD_RXDLY_S 5 +#define PAD_CMD_RD_RXDLY2_SEL BIT(10) +#define PAD_CMD_RD_RXDLY_SEL BIT(11) +#define PAD_CMD_TX_DLY (0x1f << 12) + +/* SDC_CFG_BUSWIDTH */ +#define MSDC_BUS_1BITS 0x0 +#define MSDC_BUS_4BITS 0x1 +#define MSDC_BUS_8BITS 0x2 + +#define MSDC_FIFO_SIZE 128 + +#define PAD_DELAY_MAX 32 + +#define DEFAULT_CD_DEBOUNCE 8 + +#define SCLK_CYCLES_SHIFT 20 + +#define MIN_BUS_CLK 200000 + +#define CMD_INTS_MASK \ + (MSDC_INT_CMDRDY | MSDC_INT_RSPCRCERR | MSDC_INT_CMDTMO) + +#define DATA_INTS_MASK \ + (MSDC_INT_XFER_COMPL | MSDC_INT_DATTMO | MSDC_INT_DATCRCERR) + +/* Register offset */ +struct mtk_sd_regs { + u32 msdc_cfg; + u32 msdc_iocon; + u32 msdc_ps; + u32 msdc_int; + u32 msdc_inten; + u32 msdc_fifocs; + u32 msdc_txdata; + u32 msdc_rxdata; + u32 reserved0[4]; + u32 sdc_cfg; + u32 sdc_cmd; + u32 sdc_arg; + u32 sdc_sts; + u32 sdc_resp[4]; + u32 sdc_blk_num; + u32 sdc_vol_chg; + u32 sdc_csts; + u32 sdc_csts_en; + u32 sdc_datcrc_sts; + u32 sdc_adv_cfg0; + u32 reserved1[2]; + u32 emmc_cfg0; + u32 emmc_cfg1; + u32 emmc_sts; + u32 emmc_iocon; + u32 sd_acmd_resp; + u32 sd_acmd19_trg; + u32 sd_acmd19_sts; + u32 dma_sa_high4bit; + u32 dma_sa; + u32 dma_ca; + u32 dma_ctrl; + u32 dma_cfg; + u32 sw_dbg_sel; + u32 sw_dbg_out; + u32 dma_length; + u32 reserved2; + u32 patch_bit0; + u32 patch_bit1; + u32 patch_bit2; + u32 reserved3; + u32 dat0_tune_crc; + u32 dat1_tune_crc; + u32 dat2_tune_crc; + u32 dat3_tune_crc; + u32 cmd_tune_crc; + u32 sdio_tune_wind; + u32 reserved4[2]; + u32 pad_ctrl0; + u32 pad_ctrl1; + u32 pad_ctrl2; + u32 pad_tune; + u32 pad_tune0; + u32 pad_tune1; + u32 dat_rd_dly[4]; + u32 reserved5[2]; + u32 hw_dbg_sel; + u32 main_ver; + u32 eco_ver; + u32 reserved6[27]; + u32 pad_ds_tune; + u32 pad_cmd_tune; + u32 reserved7[30]; + u32 emmc50_cfg0; + u32 reserved8[7]; + u32 sdc_fifo_cfg; +}; + +struct msdc_top_regs { + u32 emmc_top_control; + u32 emmc_top_cmd; + u32 emmc50_pad_ctl0; + u32 emmc50_pad_ds_tune; + u32 emmc50_pad_dat0_tune; + u32 emmc50_pad_dat1_tune; + u32 emmc50_pad_dat2_tune; + u32 emmc50_pad_dat3_tune; + u32 emmc50_pad_dat4_tune; + u32 emmc50_pad_dat5_tune; + u32 emmc50_pad_dat6_tune; + u32 emmc50_pad_dat7_tune; +}; + +struct msdc_compatible { + u8 clk_div_bits; + bool pad_tune0; + bool async_fifo; + bool data_tune; + bool busy_check; + bool stop_clk_fix; + bool enhance_rx; + bool builtin_pad_ctrl; + bool default_pad_dly; +}; + +struct msdc_delay_phase { + u8 maxlen; + u8 start; + u8 final_phase; +}; + +struct msdc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct msdc_tune_para { + u32 iocon; + u32 pad_tune; + u32 pad_cmd_tune; +}; + +struct msdc_host { + struct mtk_sd_regs *base; + struct msdc_top_regs *top_base; + struct mmc *mmc; + + struct msdc_compatible *dev_comp; + + struct clk src_clk; /* for SD/MMC bus clock */ + struct clk src_clk_cg; /* optional, MSDC source clock control gate */ + struct clk h_clk; /* MSDC core clock */ + + u32 src_clk_freq; /* source clock */ + u32 mclk; /* mmc framework required bus clock */ + u32 sclk; /* actual calculated bus clock */ + + /* operation timeout clocks */ + u32 timeout_ns; + u32 timeout_clks; + + /* tuning options */ + u32 hs400_ds_delay; + u32 hs200_cmd_int_delay; + u32 hs200_write_int_delay; + u32 latch_ck; + u32 r_smpl; /* sample edge */ + bool hs400_mode; + + /* whether to use gpio detection or built-in hw detection */ + bool builtin_cd; + bool cd_active_high; + + /* card detection / write protection GPIOs */ +#if CONFIG_IS_ENABLED(DM_GPIO) + struct gpio_desc gpio_wp; + struct gpio_desc gpio_cd; +#endif + + uint last_resp_type; + uint last_data_write; + + enum bus_mode timing; + + struct msdc_tune_para def_tune_para; + struct msdc_tune_para saved_tune_para; +}; + +static void msdc_reset_hw(struct msdc_host *host) +{ + u32 reg; + + setbits_le32(&host->base->msdc_cfg, MSDC_CFG_RST); + + readl_poll_timeout(&host->base->msdc_cfg, reg, + !(reg & MSDC_CFG_RST), 1000000); +} + +static void msdc_fifo_clr(struct msdc_host *host) +{ + u32 reg; + + setbits_le32(&host->base->msdc_fifocs, MSDC_FIFOCS_CLR); + + readl_poll_timeout(&host->base->msdc_fifocs, reg, + !(reg & MSDC_FIFOCS_CLR), 1000000); +} + +static u32 msdc_fifo_rx_bytes(struct msdc_host *host) +{ + return (readl(&host->base->msdc_fifocs) & + MSDC_FIFOCS_RXCNT_M) >> MSDC_FIFOCS_RXCNT_S; +} + +static u32 msdc_fifo_tx_bytes(struct msdc_host *host) +{ + return (readl(&host->base->msdc_fifocs) & + MSDC_FIFOCS_TXCNT_M) >> MSDC_FIFOCS_TXCNT_S; +} + +static u32 msdc_cmd_find_resp(struct msdc_host *host, struct mmc_cmd *cmd) +{ + u32 resp; + + switch (cmd->resp_type) { + /* Actually, R1, R5, R6, R7 are the same */ + case MMC_RSP_R1: + resp = 0x1; + break; + case MMC_RSP_R1b: + resp = 0x7; + break; + case MMC_RSP_R2: + resp = 0x2; + break; + case MMC_RSP_R3: + resp = 0x3; + break; + case MMC_RSP_NONE: + default: + resp = 0x0; + break; + } + + return resp; +} + +static u32 msdc_cmd_prepare_raw_cmd(struct msdc_host *host, + struct mmc_cmd *cmd, + struct mmc_data *data) +{ + u32 opcode = cmd->cmdidx; + u32 resp_type = msdc_cmd_find_resp(host, cmd); + uint blocksize = 0; + u32 dtype = 0; + u32 rawcmd = 0; + + switch (opcode) { + case MMC_CMD_WRITE_MULTIPLE_BLOCK: + case MMC_CMD_READ_MULTIPLE_BLOCK: + dtype = 2; + break; + case MMC_CMD_WRITE_SINGLE_BLOCK: + case MMC_CMD_READ_SINGLE_BLOCK: + case SD_CMD_APP_SEND_SCR: + case MMC_CMD_SEND_TUNING_BLOCK: + case MMC_CMD_SEND_TUNING_BLOCK_HS200: + dtype = 1; + break; + case SD_CMD_SWITCH_FUNC: /* same as MMC_CMD_SWITCH */ + case SD_CMD_SEND_IF_COND: /* same as MMC_CMD_SEND_EXT_CSD */ + case SD_CMD_APP_SD_STATUS: /* same as MMC_CMD_SEND_STATUS */ + if (data) + dtype = 1; + } + + if (data) { + if (data->flags == MMC_DATA_WRITE) + rawcmd |= SDC_CMD_WR; + + if (data->blocks > 1) + dtype = 2; + + blocksize = data->blocksize; + } + + rawcmd |= ((opcode << SDC_CMD_CMD_S) & SDC_CMD_CMD_M) | + ((resp_type << SDC_CMD_RSPTYP_S) & SDC_CMD_RSPTYP_M) | + ((blocksize << SDC_CMD_BLK_LEN_S) & SDC_CMD_BLK_LEN_M) | + ((dtype << SDC_CMD_DTYPE_S) & SDC_CMD_DTYPE_M); + + if (opcode == MMC_CMD_STOP_TRANSMISSION) + rawcmd |= SDC_CMD_STOP; + + return rawcmd; +} + +static int msdc_cmd_done(struct msdc_host *host, int events, + struct mmc_cmd *cmd) +{ + u32 *rsp = cmd->response; + int ret = 0; + + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) { + rsp[0] = readl(&host->base->sdc_resp[3]); + rsp[1] = readl(&host->base->sdc_resp[2]); + rsp[2] = readl(&host->base->sdc_resp[1]); + rsp[3] = readl(&host->base->sdc_resp[0]); + } else { + rsp[0] = readl(&host->base->sdc_resp[0]); + } + } + + if (!(events & MSDC_INT_CMDRDY)) { + if (cmd->cmdidx != MMC_CMD_SEND_TUNING_BLOCK && + cmd->cmdidx != MMC_CMD_SEND_TUNING_BLOCK_HS200) + /* + * should not clear fifo/interrupt as the tune data + * may have alreay come. + */ + msdc_reset_hw(host); + + if (events & MSDC_INT_CMDTMO) + ret = -ETIMEDOUT; + else + ret = -EIO; + } + + return ret; +} + +static bool msdc_cmd_is_ready(struct msdc_host *host) +{ + int ret; + u32 reg; + + /* The max busy time we can endure is 20ms */ + ret = readl_poll_timeout(&host->base->sdc_sts, reg, + !(reg & SDC_STS_CMDBUSY), 20000); + + if (ret) { + pr_err("CMD bus busy detected\n"); + msdc_reset_hw(host); + return false; + } + + if (host->last_resp_type == MMC_RSP_R1b && host->last_data_write) { + ret = readl_poll_timeout(&host->base->msdc_ps, reg, + reg & MSDC_PS_DAT0, 1000000); + + if (ret) { + pr_err("Card stuck in programming state!\n"); + msdc_reset_hw(host); + return false; + } + } + + return true; +} + +static int msdc_start_command(struct msdc_host *host, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + u32 rawcmd; + u32 status; + u32 blocks = 0; + int ret; + + if (!msdc_cmd_is_ready(host)) + return -EIO; + + if ((readl(&host->base->msdc_fifocs) & + MSDC_FIFOCS_TXCNT_M) >> MSDC_FIFOCS_TXCNT_S || + (readl(&host->base->msdc_fifocs) & + MSDC_FIFOCS_RXCNT_M) >> MSDC_FIFOCS_RXCNT_S) { + pr_err("TX/RX FIFO non-empty before start of IO. Reset\n"); + msdc_reset_hw(host); + } + + msdc_fifo_clr(host); + + host->last_resp_type = cmd->resp_type; + host->last_data_write = 0; + + rawcmd = msdc_cmd_prepare_raw_cmd(host, cmd, data); + + if (data) + blocks = data->blocks; + + writel(CMD_INTS_MASK, &host->base->msdc_int); + writel(DATA_INTS_MASK, &host->base->msdc_int); + writel(blocks, &host->base->sdc_blk_num); + writel(cmd->cmdarg, &host->base->sdc_arg); + writel(rawcmd, &host->base->sdc_cmd); + + ret = readl_poll_timeout(&host->base->msdc_int, status, + status & CMD_INTS_MASK, 1000000); + + if (ret) + status = MSDC_INT_CMDTMO; + + return msdc_cmd_done(host, status, cmd); +} + +static void msdc_fifo_read(struct msdc_host *host, u8 *buf, u32 size) +{ + u32 *wbuf; + + while ((size_t)buf % 4) { + *buf++ = readb(&host->base->msdc_rxdata); + size--; + } + + wbuf = (u32 *)buf; + while (size >= 4) { + *wbuf++ = readl(&host->base->msdc_rxdata); + size -= 4; + } + + buf = (u8 *)wbuf; + while (size) { + *buf++ = readb(&host->base->msdc_rxdata); + size--; + } +} + +static void msdc_fifo_write(struct msdc_host *host, const u8 *buf, u32 size) +{ + const u32 *wbuf; + + while ((size_t)buf % 4) { + writeb(*buf++, &host->base->msdc_txdata); + size--; + } + + wbuf = (const u32 *)buf; + while (size >= 4) { + writel(*wbuf++, &host->base->msdc_txdata); + size -= 4; + } + + buf = (const u8 *)wbuf; + while (size) { + writeb(*buf++, &host->base->msdc_txdata); + size--; + } +} + +static int msdc_pio_read(struct msdc_host *host, u8 *ptr, u32 size) +{ + u32 status; + u32 chksz; + int ret = 0; + + while (1) { + status = readl(&host->base->msdc_int); + writel(status, &host->base->msdc_int); + status &= DATA_INTS_MASK; + + if (status & MSDC_INT_DATCRCERR) { + ret = -EIO; + break; + } + + if (status & MSDC_INT_DATTMO) { + ret = -ETIMEDOUT; + break; + } + + chksz = min(size, (u32)MSDC_FIFO_SIZE); + + if (msdc_fifo_rx_bytes(host) >= chksz) { + msdc_fifo_read(host, ptr, chksz); + ptr += chksz; + size -= chksz; + } + + if (status & MSDC_INT_XFER_COMPL) { + if (size) { + pr_err("data not fully read\n"); + ret = -EIO; + } + + break; + } +} + + return ret; +} + +static int msdc_pio_write(struct msdc_host *host, const u8 *ptr, u32 size) +{ + u32 status; + u32 chksz; + int ret = 0; + + while (1) { + status = readl(&host->base->msdc_int); + writel(status, &host->base->msdc_int); + status &= DATA_INTS_MASK; + + if (status & MSDC_INT_DATCRCERR) { + ret = -EIO; + break; + } + + if (status & MSDC_INT_DATTMO) { + ret = -ETIMEDOUT; + break; + } + + if (status & MSDC_INT_XFER_COMPL) { + if (size) { + pr_err("data not fully written\n"); + ret = -EIO; + } + + break; + } + + chksz = min(size, (u32)MSDC_FIFO_SIZE); + + if (MSDC_FIFO_SIZE - msdc_fifo_tx_bytes(host) >= chksz) { + msdc_fifo_write(host, ptr, chksz); + ptr += chksz; + size -= chksz; + } + } + + return ret; +} + +static int msdc_start_data(struct msdc_host *host, struct mmc_data *data) +{ + u32 size; + int ret; + + if (data->flags == MMC_DATA_WRITE) + host->last_data_write = 1; + + size = data->blocks * data->blocksize; + + if (data->flags == MMC_DATA_WRITE) + ret = msdc_pio_write(host, (const u8 *)data->src, size); + else + ret = msdc_pio_read(host, (u8 *)data->dest, size); + + if (ret) { + msdc_reset_hw(host); + msdc_fifo_clr(host); + } + + return ret; +} + +static int msdc_ops_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct msdc_host *host = dev_get_priv(dev); + int cmd_ret, data_ret; + + cmd_ret = msdc_start_command(host, cmd, data); + if (cmd_ret && + !(cmd_ret == -EIO && + (cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK || + cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200))) + return cmd_ret; + + if (data) { + data_ret = msdc_start_data(host, data); + if (cmd_ret) + return cmd_ret; + else + return data_ret; + } + + return 0; +} + +static void msdc_set_timeout(struct msdc_host *host, u32 ns, u32 clks) +{ + u32 timeout, clk_ns, shift = SCLK_CYCLES_SHIFT; + u32 mode = 0; + + host->timeout_ns = ns; + host->timeout_clks = clks; + + if (host->sclk == 0) { + timeout = 0; + } else { + clk_ns = 1000000000UL / host->sclk; + timeout = (ns + clk_ns - 1) / clk_ns + clks; + /* unit is 1048576 sclk cycles */ + timeout = (timeout + (0x1 << shift) - 1) >> shift; + if (host->dev_comp->clk_div_bits == 8) + mode = (readl(&host->base->msdc_cfg) & + MSDC_CFG_CKMOD_M) >> MSDC_CFG_CKMOD_S; + else + mode = (readl(&host->base->msdc_cfg) & + MSDC_CFG_CKMOD_EXT_M) >> MSDC_CFG_CKMOD_EXT_S; + /* DDR mode will double the clk cycles for data timeout */ + timeout = mode >= 2 ? timeout * 2 : timeout; + timeout = timeout > 1 ? timeout - 1 : 0; + timeout = timeout > 255 ? 255 : timeout; + } + + clrsetbits_le32(&host->base->sdc_cfg, SDC_CFG_DTOC_M, + timeout << SDC_CFG_DTOC_S); +} + +static void msdc_set_buswidth(struct msdc_host *host, u32 width) +{ + u32 val = readl(&host->base->sdc_cfg); + + val &= ~SDC_CFG_BUSWIDTH_M; + + switch (width) { + default: + case 1: + val |= (MSDC_BUS_1BITS << SDC_CFG_BUSWIDTH_S); + break; + case 4: + val |= (MSDC_BUS_4BITS << SDC_CFG_BUSWIDTH_S); + break; + case 8: + val |= (MSDC_BUS_8BITS << SDC_CFG_BUSWIDTH_S); + break; + } + + writel(val, &host->base->sdc_cfg); +} + +static void msdc_set_mclk(struct udevice *dev, + struct msdc_host *host, enum bus_mode timing, u32 hz) +{ + u32 mode; + u32 div; + u32 sclk; + u32 reg; + + if (!hz) { + host->mclk = 0; + clrbits_le32(&host->base->msdc_cfg, MSDC_CFG_CKPDN); + return; + } + + if (host->dev_comp->clk_div_bits == 8) + clrbits_le32(&host->base->msdc_cfg, MSDC_CFG_HS400_CK_MODE); + else + clrbits_le32(&host->base->msdc_cfg, + MSDC_CFG_HS400_CK_MODE_EXT); + + if (timing == UHS_DDR50 || timing == MMC_DDR_52 || + timing == MMC_HS_400) { + if (timing == MMC_HS_400) + mode = 0x3; + else + mode = 0x2; /* ddr mode and use divisor */ + + if (hz >= (host->src_clk_freq >> 2)) { + div = 0; /* mean div = 1/4 */ + sclk = host->src_clk_freq >> 2; /* sclk = clk / 4 */ + } else { + div = (host->src_clk_freq + ((hz << 2) - 1)) / + (hz << 2); + sclk = (host->src_clk_freq >> 2) / div; + div = (div >> 1); + } + + if (timing == MMC_HS_400 && hz >= (host->src_clk_freq >> 1)) { + if (host->dev_comp->clk_div_bits == 8) + setbits_le32(&host->base->msdc_cfg, + MSDC_CFG_HS400_CK_MODE); + else + setbits_le32(&host->base->msdc_cfg, + MSDC_CFG_HS400_CK_MODE_EXT); + + sclk = host->src_clk_freq >> 1; + div = 0; /* div is ignore when bit18 is set */ + } + } else if (hz >= host->src_clk_freq) { + mode = 0x1; /* no divisor */ + div = 0; + sclk = host->src_clk_freq; + } else { + mode = 0x0; /* use divisor */ + if (hz >= (host->src_clk_freq >> 1)) { + div = 0; /* mean div = 1/2 */ + sclk = host->src_clk_freq >> 1; /* sclk = clk / 2 */ + } else { + div = (host->src_clk_freq + ((hz << 2) - 1)) / + (hz << 2); + sclk = (host->src_clk_freq >> 2) / div; + } + } + + clrbits_le32(&host->base->msdc_cfg, MSDC_CFG_CKPDN); + + if (host->dev_comp->clk_div_bits == 8) { + div = min(div, (u32)(MSDC_CFG_CKDIV_M >> MSDC_CFG_CKDIV_S)); + clrsetbits_le32(&host->base->msdc_cfg, + MSDC_CFG_CKMOD_M | MSDC_CFG_CKDIV_M, + (mode << MSDC_CFG_CKMOD_S) | + (div << MSDC_CFG_CKDIV_S)); + } else { + div = min(div, (u32)(MSDC_CFG_CKDIV_EXT_M >> + MSDC_CFG_CKDIV_EXT_S)); + clrsetbits_le32(&host->base->msdc_cfg, + MSDC_CFG_CKMOD_EXT_M | MSDC_CFG_CKDIV_EXT_M, + (mode << MSDC_CFG_CKMOD_EXT_S) | + (div << MSDC_CFG_CKDIV_EXT_S)); + } + + readl_poll_timeout(&host->base->msdc_cfg, reg, + reg & MSDC_CFG_CKSTB, 1000000); + + setbits_le32(&host->base->msdc_cfg, MSDC_CFG_CKPDN); + host->sclk = sclk; + host->mclk = hz; + host->timing = timing; + + /* needed because clk changed. */ + msdc_set_timeout(host, host->timeout_ns, host->timeout_clks); + + /* + * mmc_select_hs400() will drop to 50Mhz and High speed mode, + * tune result of hs200/200Mhz is not suitable for 50Mhz + */ + if (host->sclk <= 52000000) { + writel(host->def_tune_para.iocon, &host->base->msdc_iocon); + writel(host->def_tune_para.pad_tune, + &host->base->pad_tune); + } else { + writel(host->saved_tune_para.iocon, &host->base->msdc_iocon); + writel(host->saved_tune_para.pad_tune, + &host->base->pad_tune); + } + + dev_dbg(dev, "sclk: %d, timing: %d\n", host->sclk, timing); +} + +static int msdc_ops_set_ios(struct udevice *dev) +{ + struct msdc_plat *plat = dev_get_plat(dev); + struct msdc_host *host = dev_get_priv(dev); + struct mmc *mmc = &plat->mmc; + uint clock = mmc->clock; + + msdc_set_buswidth(host, mmc->bus_width); + + if (mmc->clk_disable) + clock = 0; + else if (clock < mmc->cfg->f_min) + clock = mmc->cfg->f_min; + + if (host->mclk != clock || host->timing != mmc->selected_mode) + msdc_set_mclk(dev, host, mmc->selected_mode, clock); + + return 0; +} + +static int msdc_ops_get_cd(struct udevice *dev) +{ + struct msdc_host *host = dev_get_priv(dev); + u32 val; + + if (host->builtin_cd) { + val = readl(&host->base->msdc_ps); + val = !!(val & MSDC_PS_CDSTS); + + return !val ^ host->cd_active_high; + } + +#if CONFIG_IS_ENABLED(DM_GPIO) + if (!host->gpio_cd.dev) + return 1; + + return dm_gpio_get_value(&host->gpio_cd); +#else + return 1; +#endif +} + +static int msdc_ops_get_wp(struct udevice *dev) +{ +#if CONFIG_IS_ENABLED(DM_GPIO) + struct msdc_host *host = dev_get_priv(dev); + + if (!host->gpio_wp.dev) + return 0; + + return !dm_gpio_get_value(&host->gpio_wp); +#else + return 0; +#endif +} + +#ifdef MMC_SUPPORTS_TUNING +static u32 test_delay_bit(u32 delay, u32 bit) +{ + bit %= PAD_DELAY_MAX; + return delay & (1 << bit); +} + +static int get_delay_len(u32 delay, u32 start_bit) +{ + int i; + + for (i = 0; i < (PAD_DELAY_MAX - start_bit); i++) { + if (test_delay_bit(delay, start_bit + i) == 0) + return i; + } + + return PAD_DELAY_MAX - start_bit; +} + +static struct msdc_delay_phase get_best_delay(struct udevice *dev, + struct msdc_host *host, u32 delay) +{ + int start = 0, len = 0; + int start_final = 0, len_final = 0; + u8 final_phase = 0xff; + struct msdc_delay_phase delay_phase = { 0, }; + + if (delay == 0) { + dev_err(dev, "phase error: [map:%x]\n", delay); + delay_phase.final_phase = final_phase; + return delay_phase; + } + + while (start < PAD_DELAY_MAX) { + len = get_delay_len(delay, start); + if (len_final < len) { + start_final = start; + len_final = len; + } + + start += len ? len : 1; + if (len >= 12 && start_final < 4) + break; + } + + /* The rule is to find the smallest delay cell */ + if (start_final == 0) + final_phase = (start_final + len_final / 3) % PAD_DELAY_MAX; + else + final_phase = (start_final + len_final / 2) % PAD_DELAY_MAX; + + dev_info(dev, "phase: [map:%x] [maxlen:%d] [final:%d]\n", + delay, len_final, final_phase); + + delay_phase.maxlen = len_final; + delay_phase.start = start_final; + delay_phase.final_phase = final_phase; + return delay_phase; +} + +static inline void msdc_set_cmd_delay(struct msdc_host *host, u32 value) +{ + void __iomem *tune_reg = &host->base->pad_tune; + + if (host->dev_comp->pad_tune0) + tune_reg = &host->base->pad_tune0; + + if (host->top_base) + clrsetbits_le32(&host->top_base->emmc_top_cmd, PAD_CMD_RXDLY, + value << PAD_CMD_RXDLY_S); + else + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_CMDRDLY_M, + value << MSDC_PAD_TUNE_CMDRDLY_S); +} + +static inline void msdc_set_data_delay(struct msdc_host *host, u32 value) +{ + void __iomem *tune_reg = &host->base->pad_tune; + + if (host->dev_comp->pad_tune0) + tune_reg = &host->base->pad_tune0; + + if (host->top_base) + clrsetbits_le32(&host->top_base->emmc_top_control, + PAD_DAT_RD_RXDLY, value << PAD_DAT_RD_RXDLY_S); + else + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_DATRRDLY_M, + value << MSDC_PAD_TUNE_DATRRDLY_S); +} + +static int hs400_tune_response(struct udevice *dev, u32 opcode) +{ + struct msdc_plat *plat = dev_get_plat(dev); + struct msdc_host *host = dev_get_priv(dev); + struct mmc *mmc = &plat->mmc; + u32 cmd_delay = 0; + struct msdc_delay_phase final_cmd_delay = { 0, }; + u8 final_delay; + void __iomem *tune_reg = &host->base->pad_cmd_tune; + int cmd_err; + int i, j; + + setbits_le32(&host->base->pad_cmd_tune, BIT(0)); + + if (mmc->selected_mode == MMC_HS_200 || + mmc->selected_mode == UHS_SDR104) + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_CMDRRDLY_M, + host->hs200_cmd_int_delay << + MSDC_PAD_TUNE_CMDRRDLY_S); + + if (host->r_smpl) + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_RSPL); + else + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_RSPL); + + for (i = 0; i < PAD_DELAY_MAX; i++) { + clrsetbits_le32(tune_reg, PAD_CMD_TUNE_RX_DLY3, + i << PAD_CMD_TUNE_RX_DLY3_S); + + for (j = 0; j < 3; j++) { + mmc_send_tuning(mmc, opcode, &cmd_err); + if (!cmd_err) { + cmd_delay |= (1 << i); + } else { + cmd_delay &= ~(1 << i); + break; + } + } + } + + final_cmd_delay = get_best_delay(dev, host, cmd_delay); + clrsetbits_le32(tune_reg, PAD_CMD_TUNE_RX_DLY3, + final_cmd_delay.final_phase << + PAD_CMD_TUNE_RX_DLY3_S); + final_delay = final_cmd_delay.final_phase; + + dev_info(dev, "Final cmd pad delay: %x\n", final_delay); + return final_delay == 0xff ? -EIO : 0; +} + +static int msdc_tune_response(struct udevice *dev, u32 opcode) +{ + struct msdc_plat *plat = dev_get_plat(dev); + struct msdc_host *host = dev_get_priv(dev); + struct mmc *mmc = &plat->mmc; + u32 rise_delay = 0, fall_delay = 0; + struct msdc_delay_phase final_rise_delay, final_fall_delay = { 0, }; + struct msdc_delay_phase internal_delay_phase; + u8 final_delay, final_maxlen; + u32 internal_delay = 0; + void __iomem *tune_reg = &host->base->pad_tune; + int cmd_err; + int i, j; + + if (host->dev_comp->pad_tune0) + tune_reg = &host->base->pad_tune0; + + if (mmc->selected_mode == MMC_HS_200 || + mmc->selected_mode == UHS_SDR104) + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_CMDRRDLY_M, + host->hs200_cmd_int_delay << + MSDC_PAD_TUNE_CMDRRDLY_S); + + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_RSPL); + + for (i = 0; i < PAD_DELAY_MAX; i++) { + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_CMDRDLY_M, + i << MSDC_PAD_TUNE_CMDRDLY_S); + + for (j = 0; j < 3; j++) { + mmc_send_tuning(mmc, opcode, &cmd_err); + if (!cmd_err) { + rise_delay |= (1 << i); + } else { + rise_delay &= ~(1 << i); + break; + } + } + } + + final_rise_delay = get_best_delay(dev, host, rise_delay); + /* if rising edge has enough margin, do not scan falling edge */ + if (final_rise_delay.maxlen >= 12 || + (final_rise_delay.start == 0 && final_rise_delay.maxlen >= 4)) + goto skip_fall; + + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_RSPL); + for (i = 0; i < PAD_DELAY_MAX; i++) { + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_CMDRDLY_M, + i << MSDC_PAD_TUNE_CMDRDLY_S); + + for (j = 0; j < 3; j++) { + mmc_send_tuning(mmc, opcode, &cmd_err); + if (!cmd_err) { + fall_delay |= (1 << i); + } else { + fall_delay &= ~(1 << i); + break; + } + } + } + + final_fall_delay = get_best_delay(dev, host, fall_delay); + +skip_fall: + final_maxlen = max(final_rise_delay.maxlen, final_fall_delay.maxlen); + if (final_maxlen == final_rise_delay.maxlen) { + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_RSPL); + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_CMDRDLY_M, + final_rise_delay.final_phase << + MSDC_PAD_TUNE_CMDRDLY_S); + final_delay = final_rise_delay.final_phase; + } else { + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_RSPL); + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_CMDRDLY_M, + final_fall_delay.final_phase << + MSDC_PAD_TUNE_CMDRDLY_S); + final_delay = final_fall_delay.final_phase; + } + + if (host->dev_comp->async_fifo || host->hs200_cmd_int_delay) + goto skip_internal; + + for (i = 0; i < PAD_DELAY_MAX; i++) { + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_CMDRRDLY_M, + i << MSDC_PAD_TUNE_CMDRRDLY_S); + + mmc_send_tuning(mmc, opcode, &cmd_err); + if (!cmd_err) + internal_delay |= (1 << i); + } + + dev_dbg(dev, "Final internal delay: 0x%x\n", internal_delay); + + internal_delay_phase = get_best_delay(dev, host, internal_delay); + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_CMDRRDLY_M, + internal_delay_phase.final_phase << + MSDC_PAD_TUNE_CMDRRDLY_S); + +skip_internal: + dev_dbg(dev, "Final cmd pad delay: %x\n", final_delay); + return final_delay == 0xff ? -EIO : 0; +} + +static int msdc_tune_data(struct udevice *dev, u32 opcode) +{ + struct msdc_plat *plat = dev_get_plat(dev); + struct msdc_host *host = dev_get_priv(dev); + struct mmc *mmc = &plat->mmc; + u32 rise_delay = 0, fall_delay = 0; + struct msdc_delay_phase final_rise_delay, final_fall_delay = { 0, }; + u8 final_delay, final_maxlen; + void __iomem *tune_reg = &host->base->pad_tune; + int cmd_err; + int i, ret; + + if (host->dev_comp->pad_tune0) + tune_reg = &host->base->pad_tune0; + + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_DSPL); + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_W_DSPL); + + for (i = 0; i < PAD_DELAY_MAX; i++) { + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_DATRRDLY_M, + i << MSDC_PAD_TUNE_DATRRDLY_S); + + ret = mmc_send_tuning(mmc, opcode, &cmd_err); + if (!ret) { + rise_delay |= (1 << i); + } else if (cmd_err) { + /* in this case, retune response is needed */ + ret = msdc_tune_response(dev, opcode); + if (ret) + break; + } + } + + final_rise_delay = get_best_delay(dev, host, rise_delay); + if (final_rise_delay.maxlen >= 12 || + (final_rise_delay.start == 0 && final_rise_delay.maxlen >= 4)) + goto skip_fall; + + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_DSPL); + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_W_DSPL); + + for (i = 0; i < PAD_DELAY_MAX; i++) { + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_DATRRDLY_M, + i << MSDC_PAD_TUNE_DATRRDLY_S); + + ret = mmc_send_tuning(mmc, opcode, &cmd_err); + if (!ret) { + fall_delay |= (1 << i); + } else if (cmd_err) { + /* in this case, retune response is needed */ + ret = msdc_tune_response(dev, opcode); + if (ret) + break; + } + } + + final_fall_delay = get_best_delay(dev, host, fall_delay); + +skip_fall: + final_maxlen = max(final_rise_delay.maxlen, final_fall_delay.maxlen); + if (final_maxlen == final_rise_delay.maxlen) { + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_DSPL); + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_W_DSPL); + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_DATRRDLY_M, + final_rise_delay.final_phase << + MSDC_PAD_TUNE_DATRRDLY_S); + final_delay = final_rise_delay.final_phase; + } else { + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_DSPL); + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_W_DSPL); + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_DATRRDLY_M, + final_fall_delay.final_phase << + MSDC_PAD_TUNE_DATRRDLY_S); + final_delay = final_fall_delay.final_phase; + } + + if (mmc->selected_mode == MMC_HS_200 || + mmc->selected_mode == UHS_SDR104) + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_DATWRDLY_M, + host->hs200_write_int_delay << + MSDC_PAD_TUNE_DATWRDLY_S); + + dev_dbg(dev, "Final data pad delay: %x\n", final_delay); + + return final_delay == 0xff ? -EIO : 0; +} + +/* + * MSDC IP which supports data tune + async fifo can do CMD/DAT tune + * together, which can save the tuning time. + */ +static int msdc_tune_together(struct udevice *dev, u32 opcode) +{ + struct msdc_plat *plat = dev_get_plat(dev); + struct msdc_host *host = dev_get_priv(dev); + struct mmc *mmc = &plat->mmc; + u32 rise_delay = 0, fall_delay = 0; + struct msdc_delay_phase final_rise_delay, final_fall_delay = { 0, }; + u8 final_delay, final_maxlen; + int i, ret; + + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_DSPL); + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_W_DSPL); + + for (i = 0; i < PAD_DELAY_MAX; i++) { + msdc_set_cmd_delay(host, i); + msdc_set_data_delay(host, i); + ret = mmc_send_tuning(mmc, opcode, NULL); + if (!ret) + rise_delay |= (1 << i); + } + + final_rise_delay = get_best_delay(dev, host, rise_delay); + if (final_rise_delay.maxlen >= 12 || + (final_rise_delay.start == 0 && final_rise_delay.maxlen >= 4)) + goto skip_fall; + + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_DSPL); + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_W_DSPL); + + for (i = 0; i < PAD_DELAY_MAX; i++) { + msdc_set_cmd_delay(host, i); + msdc_set_data_delay(host, i); + ret = mmc_send_tuning(mmc, opcode, NULL); + if (!ret) + fall_delay |= (1 << i); + } + + final_fall_delay = get_best_delay(dev, host, fall_delay); + +skip_fall: + final_maxlen = max(final_rise_delay.maxlen, final_fall_delay.maxlen); + if (final_maxlen == final_rise_delay.maxlen) { + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_DSPL); + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_W_DSPL); + final_delay = final_rise_delay.final_phase; + } else { + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_DSPL); + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_W_DSPL); + final_delay = final_fall_delay.final_phase; + } + + msdc_set_cmd_delay(host, final_delay); + msdc_set_data_delay(host, final_delay); + + dev_info(dev, "Final pad delay: %x\n", final_delay); + return final_delay == 0xff ? -EIO : 0; +} + +static int msdc_execute_tuning(struct udevice *dev, uint opcode) +{ + struct msdc_plat *plat = dev_get_plat(dev); + struct msdc_host *host = dev_get_priv(dev); + struct mmc *mmc = &plat->mmc; + int ret = 0; + + if (host->dev_comp->data_tune && host->dev_comp->async_fifo) { + ret = msdc_tune_together(dev, opcode); + if (ret == -EIO) { + dev_err(dev, "Tune fail!\n"); + return ret; + } + + if (mmc->selected_mode == MMC_HS_400) { + clrbits_le32(&host->base->msdc_iocon, + MSDC_IOCON_DSPL | MSDC_IOCON_W_DSPL); + clrsetbits_le32(&host->base->pad_tune, + MSDC_PAD_TUNE_DATRRDLY_M, 0); + + writel(host->hs400_ds_delay, &host->base->pad_ds_tune); + /* for hs400 mode it must be set to 0 */ + clrbits_le32(&host->base->patch_bit2, + MSDC_PB2_CFGCRCSTS); + host->hs400_mode = true; + } + goto tune_done; + } + + if (mmc->selected_mode == MMC_HS_400) + ret = hs400_tune_response(dev, opcode); + else + ret = msdc_tune_response(dev, opcode); + if (ret == -EIO) { + dev_err(dev, "Tune response fail!\n"); + return ret; + } + + if (mmc->selected_mode != MMC_HS_400) { + ret = msdc_tune_data(dev, opcode); + if (ret == -EIO) { + dev_err(dev, "Tune data fail!\n"); + return ret; + } + } + +tune_done: + host->saved_tune_para.iocon = readl(&host->base->msdc_iocon); + host->saved_tune_para.pad_tune = readl(&host->base->pad_tune); + host->saved_tune_para.pad_cmd_tune = readl(&host->base->pad_cmd_tune); + + return ret; +} +#endif + +static void msdc_init_hw(struct msdc_host *host) +{ + u32 val; + void __iomem *tune_reg = &host->base->pad_tune; + void __iomem *rd_dly0_reg = &host->base->pad_tune0; + void __iomem *rd_dly1_reg = &host->base->pad_tune1; + + if (host->dev_comp->pad_tune0) { + tune_reg = &host->base->pad_tune0; + rd_dly0_reg = &host->base->dat_rd_dly[0]; + rd_dly1_reg = &host->base->dat_rd_dly[1]; + } + + /* Configure to MMC/SD mode, clock free running */ + setbits_le32(&host->base->msdc_cfg, MSDC_CFG_MODE); + + /* Use PIO mode */ + setbits_le32(&host->base->msdc_cfg, MSDC_CFG_PIO); + + /* Reset */ + msdc_reset_hw(host); + + /* Enable/disable hw card detection according to fdt option */ + if (host->builtin_cd) + clrsetbits_le32(&host->base->msdc_ps, + MSDC_PS_CDDBCE_M, + (DEFAULT_CD_DEBOUNCE << MSDC_PS_CDDBCE_S) | + MSDC_PS_CDEN); + else + clrbits_le32(&host->base->msdc_ps, MSDC_PS_CDEN); + + /* Clear all interrupts */ + val = readl(&host->base->msdc_int); + writel(val, &host->base->msdc_int); + + /* Enable data & cmd interrupts */ + writel(DATA_INTS_MASK | CMD_INTS_MASK, &host->base->msdc_inten); + + writel(0, tune_reg); + writel(0, &host->base->msdc_iocon); + + if (host->r_smpl) + setbits_le32(&host->base->msdc_iocon, MSDC_IOCON_RSPL); + else + clrbits_le32(&host->base->msdc_iocon, MSDC_IOCON_RSPL); + + writel(0x403c0046, &host->base->patch_bit0); + writel(0xffff4089, &host->base->patch_bit1); + + if (host->dev_comp->stop_clk_fix) + clrsetbits_le32(&host->base->patch_bit1, MSDC_PB1_STOP_DLY_M, + 3 << MSDC_PB1_STOP_DLY_S); + + if (host->dev_comp->busy_check) + clrbits_le32(&host->base->patch_bit1, (1 << 7)); + + setbits_le32(&host->base->emmc50_cfg0, EMMC50_CFG_CFCSTS_SEL); + + if (host->dev_comp->async_fifo) { + clrsetbits_le32(&host->base->patch_bit2, MSDC_PB2_RESPWAIT_M, + 3 << MSDC_PB2_RESPWAIT_S); + + if (host->dev_comp->enhance_rx) { + if (host->top_base) + setbits_le32(&host->top_base->emmc_top_control, + SDC_RX_ENH_EN); + else + setbits_le32(&host->base->sdc_adv_cfg0, + SDC_RX_ENHANCE_EN); + } else { + clrsetbits_le32(&host->base->patch_bit2, + MSDC_PB2_RESPSTSENSEL_M, + 2 << MSDC_PB2_RESPSTSENSEL_S); + clrsetbits_le32(&host->base->patch_bit2, + MSDC_PB2_CRCSTSENSEL_M, + 2 << MSDC_PB2_CRCSTSENSEL_S); + } + + /* use async fifo to avoid tune internal delay */ + clrbits_le32(&host->base->patch_bit2, + MSDC_PB2_CFGRESP); + clrbits_le32(&host->base->patch_bit2, + MSDC_PB2_CFGCRCSTS); + } + + if (host->dev_comp->data_tune) { + setbits_le32(tune_reg, + MSDC_PAD_TUNE_RD_SEL | MSDC_PAD_TUNE_CMD_SEL); + clrsetbits_le32(&host->base->patch_bit0, + MSDC_INT_DAT_LATCH_CK_SEL_M, + host->latch_ck << + MSDC_INT_DAT_LATCH_CK_SEL_S); + } else { + /* choose clock tune */ + setbits_le32(tune_reg, MSDC_PAD_TUNE_RXDLYSEL); + } + + if (host->dev_comp->builtin_pad_ctrl) { + /* Set pins driving strength */ + writel(MSDC_PAD_CTRL0_CLKPD | MSDC_PAD_CTRL0_CLKSMT | + MSDC_PAD_CTRL0_CLKIES | (4 << MSDC_PAD_CTRL0_CLKDRVN_S) | + (4 << MSDC_PAD_CTRL0_CLKDRVP_S), &host->base->pad_ctrl0); + writel(MSDC_PAD_CTRL1_CMDPU | MSDC_PAD_CTRL1_CMDSMT | + MSDC_PAD_CTRL1_CMDIES | (4 << MSDC_PAD_CTRL1_CMDDRVN_S) | + (4 << MSDC_PAD_CTRL1_CMDDRVP_S), &host->base->pad_ctrl1); + writel(MSDC_PAD_CTRL2_DATPU | MSDC_PAD_CTRL2_DATSMT | + MSDC_PAD_CTRL2_DATIES | (4 << MSDC_PAD_CTRL2_DATDRVN_S) | + (4 << MSDC_PAD_CTRL2_DATDRVP_S), &host->base->pad_ctrl2); + } + + if (host->dev_comp->default_pad_dly) { + /* Default pad delay may be needed if tuning not enabled */ + clrsetbits_le32(tune_reg, MSDC_PAD_TUNE_CLKTDLY_M | + MSDC_PAD_TUNE_CMDRRDLY_M | + MSDC_PAD_TUNE_CMDRDLY_M | + MSDC_PAD_TUNE_DATRRDLY_M | + MSDC_PAD_TUNE_DATWRDLY_M, + (0x10 << MSDC_PAD_TUNE_CLKTDLY_S) | + (0x10 << MSDC_PAD_TUNE_CMDRRDLY_S) | + (0x10 << MSDC_PAD_TUNE_CMDRDLY_S) | + (0x10 << MSDC_PAD_TUNE_DATRRDLY_S) | + (0x10 << MSDC_PAD_TUNE_DATWRDLY_S)); + + writel((0x10 << MSDC_PAD_TUNE0_DAT0RDDLY_S) | + (0x10 << MSDC_PAD_TUNE0_DAT1RDDLY_S) | + (0x10 << MSDC_PAD_TUNE0_DAT2RDDLY_S) | + (0x10 << MSDC_PAD_TUNE0_DAT3RDDLY_S), + rd_dly0_reg); + + writel((0x10 << MSDC_PAD_TUNE1_DAT4RDDLY_S) | + (0x10 << MSDC_PAD_TUNE1_DAT5RDDLY_S) | + (0x10 << MSDC_PAD_TUNE1_DAT6RDDLY_S) | + (0x10 << MSDC_PAD_TUNE1_DAT7RDDLY_S), + rd_dly1_reg); + } + + /* Configure to enable SDIO mode otherwise sdio cmd5 won't work */ + setbits_le32(&host->base->sdc_cfg, SDC_CFG_SDIO); + + /* disable detecting SDIO device interrupt function */ + clrbits_le32(&host->base->sdc_cfg, SDC_CFG_SDIOIDE); + + /* Configure to default data timeout */ + clrsetbits_le32(&host->base->sdc_cfg, SDC_CFG_DTOC_M, + 3 << SDC_CFG_DTOC_S); + + if (host->dev_comp->stop_clk_fix) { + clrbits_le32(&host->base->sdc_fifo_cfg, + SDC_FIFO_CFG_WRVALIDSEL); + clrbits_le32(&host->base->sdc_fifo_cfg, + SDC_FIFO_CFG_RDVALIDSEL); + } + + host->def_tune_para.iocon = readl(&host->base->msdc_iocon); + host->def_tune_para.pad_tune = readl(&host->base->pad_tune); +} + +static void msdc_ungate_clock(struct msdc_host *host) +{ + clk_enable(&host->src_clk); + clk_enable(&host->h_clk); + if (host->src_clk_cg.dev) + clk_enable(&host->src_clk_cg); +} + +static int msdc_drv_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct msdc_plat *plat = dev_get_plat(dev); + struct msdc_host *host = dev_get_priv(dev); + struct mmc_config *cfg = &plat->cfg; + + cfg->name = dev->name; + + host->dev_comp = (struct msdc_compatible *)dev_get_driver_data(dev); + + host->src_clk_freq = clk_get_rate(&host->src_clk); + + if (host->dev_comp->clk_div_bits == 8) + cfg->f_min = host->src_clk_freq / (4 * 255); + else + cfg->f_min = host->src_clk_freq / (4 * 4095); + + if (cfg->f_min < MIN_BUS_CLK) + cfg->f_min = MIN_BUS_CLK; + + if (cfg->f_max < cfg->f_min || cfg->f_max > host->src_clk_freq) + cfg->f_max = host->src_clk_freq; + + cfg->b_max = 1024; + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + + host->mmc = &plat->mmc; + host->timeout_ns = 100000000; + host->timeout_clks = 3 * (1 << SCLK_CYCLES_SHIFT); + +#ifdef CONFIG_PINCTRL + pinctrl_select_state(dev, "default"); +#endif + + msdc_ungate_clock(host); + msdc_init_hw(host); + + upriv->mmc = &plat->mmc; + + return 0; +} + +static int msdc_of_to_plat(struct udevice *dev) +{ + struct msdc_plat *plat = dev_get_plat(dev); + struct msdc_host *host = dev_get_priv(dev); + struct mmc_config *cfg = &plat->cfg; + fdt_addr_t base, top_base; + int ret; + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) + return -EINVAL; + host->base = map_sysmem(base, 0); + + top_base = dev_read_addr_index(dev, 1); + if (top_base == FDT_ADDR_T_NONE) + host->top_base = NULL; + else + host->top_base = map_sysmem(top_base, 0); + + ret = mmc_of_parse(dev, cfg); + if (ret) + return ret; + + ret = clk_get_by_name(dev, "source", &host->src_clk); + if (ret < 0) + return ret; + + ret = clk_get_by_name(dev, "hclk", &host->h_clk); + if (ret < 0) + return ret; + + clk_get_by_name(dev, "source_cg", &host->src_clk_cg); /* optional */ + +#if CONFIG_IS_ENABLED(DM_GPIO) + gpio_request_by_name(dev, "wp-gpios", 0, &host->gpio_wp, GPIOD_IS_IN); + gpio_request_by_name(dev, "cd-gpios", 0, &host->gpio_cd, GPIOD_IS_IN); +#endif + + host->hs400_ds_delay = dev_read_u32_default(dev, "hs400-ds-delay", 0); + host->hs200_cmd_int_delay = + dev_read_u32_default(dev, "cmd_int_delay", 0); + host->hs200_write_int_delay = + dev_read_u32_default(dev, "write_int_delay", 0); + host->latch_ck = dev_read_u32_default(dev, "latch-ck", 0); + host->r_smpl = dev_read_u32_default(dev, "r_smpl", 0); + host->builtin_cd = dev_read_u32_default(dev, "builtin-cd", 0); + host->cd_active_high = dev_read_bool(dev, "cd-active-high"); + + return 0; +} + +static int msdc_drv_bind(struct udevice *dev) +{ + struct msdc_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct dm_mmc_ops msdc_ops = { + .send_cmd = msdc_ops_send_cmd, + .set_ios = msdc_ops_set_ios, + .get_cd = msdc_ops_get_cd, + .get_wp = msdc_ops_get_wp, +#ifdef MMC_SUPPORTS_TUNING + .execute_tuning = msdc_execute_tuning, +#endif +}; + +static const struct msdc_compatible mt7620_compat = { + .clk_div_bits = 8, + .pad_tune0 = false, + .async_fifo = false, + .data_tune = false, + .busy_check = false, + .stop_clk_fix = false, + .enhance_rx = false, + .builtin_pad_ctrl = true, + .default_pad_dly = true, +}; + +static const struct msdc_compatible mt7622_compat = { + .clk_div_bits = 12, + .pad_tune0 = true, + .async_fifo = true, + .data_tune = true, + .busy_check = true, + .stop_clk_fix = true, +}; + +static const struct msdc_compatible mt7623_compat = { + .clk_div_bits = 12, + .pad_tune0 = true, + .async_fifo = true, + .data_tune = true, + .busy_check = false, + .stop_clk_fix = false, + .enhance_rx = false +}; + +static const struct msdc_compatible mt8512_compat = { + .clk_div_bits = 12, + .pad_tune0 = true, + .async_fifo = true, + .data_tune = true, + .busy_check = true, + .stop_clk_fix = true, +}; + +static const struct msdc_compatible mt8516_compat = { + .clk_div_bits = 12, + .pad_tune0 = true, + .async_fifo = true, + .data_tune = true, + .busy_check = true, + .stop_clk_fix = true, +}; + +static const struct msdc_compatible mt8183_compat = { + .clk_div_bits = 12, + .pad_tune0 = true, + .async_fifo = true, + .data_tune = true, + .busy_check = true, + .stop_clk_fix = true, +}; + +static const struct udevice_id msdc_ids[] = { + { .compatible = "mediatek,mt7620-mmc", .data = (ulong)&mt7620_compat }, + { .compatible = "mediatek,mt7622-mmc", .data = (ulong)&mt7622_compat }, + { .compatible = "mediatek,mt7623-mmc", .data = (ulong)&mt7623_compat }, + { .compatible = "mediatek,mt8512-mmc", .data = (ulong)&mt8512_compat }, + { .compatible = "mediatek,mt8516-mmc", .data = (ulong)&mt8516_compat }, + { .compatible = "mediatek,mt8183-mmc", .data = (ulong)&mt8183_compat }, + {} +}; + +U_BOOT_DRIVER(mtk_sd_drv) = { + .name = "mtk_sd", + .id = UCLASS_MMC, + .of_match = msdc_ids, + .of_to_plat = msdc_of_to_plat, + .bind = msdc_drv_bind, + .probe = msdc_drv_probe, + .ops = &msdc_ops, + .plat_auto = sizeof(struct msdc_plat), + .priv_auto = sizeof(struct msdc_host), +}; diff --git a/roms/u-boot/drivers/mmc/mv_sdhci.c b/roms/u-boot/drivers/mmc/mv_sdhci.c new file mode 100644 index 000000000..591137f50 --- /dev/null +++ b/roms/u-boot/drivers/mmc/mv_sdhci.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Marvell SD Host Controller Interface + */ + +#include <common.h> +#include <dm.h> +#include <malloc.h> +#include <sdhci.h> +#include <asm/global_data.h> +#include <linux/mbus.h> + +#define MVSDH_NAME "mv_sdh" + +#define SDHCI_WINDOW_CTRL(win) (0x4080 + ((win) << 4)) +#define SDHCI_WINDOW_BASE(win) (0x4084 + ((win) << 4)) + +static void sdhci_mvebu_mbus_config(void __iomem *base) +{ + const struct mbus_dram_target_info *dram; + int i; + + dram = mvebu_mbus_dram_info(); + + for (i = 0; i < 4; i++) { + writel(0, base + SDHCI_WINDOW_CTRL(i)); + writel(0, base + SDHCI_WINDOW_BASE(i)); + } + + for (i = 0; i < dram->num_cs; i++) { + const struct mbus_dram_window *cs = dram->cs + i; + + /* Write size, attributes and target id to control register */ + writel(((cs->size - 1) & 0xffff0000) | (cs->mbus_attr << 8) | + (dram->mbus_dram_target_id << 4) | 1, + base + SDHCI_WINDOW_CTRL(i)); + + /* Write base address to base register */ + writel(cs->base, base + SDHCI_WINDOW_BASE(i)); + } +} + +#ifndef CONFIG_DM_MMC + +#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS +static struct sdhci_ops mv_ops; + +#if defined(CONFIG_SHEEVA_88SV331xV5) +#define SD_CE_ATA_2 0xEA +#define MMC_CARD 0x1000 +#define MMC_WIDTH 0x0100 +static inline void mv_sdhci_writeb(struct sdhci_host *host, u8 val, int reg) +{ + struct mmc *mmc = host->mmc; + u32 ata = (unsigned long)host->ioaddr + SD_CE_ATA_2; + + if (!IS_SD(mmc) && reg == SDHCI_HOST_CONTROL) { + if (mmc->bus_width == 8) + writew(readw(ata) | (MMC_CARD | MMC_WIDTH), ata); + else + writew(readw(ata) & ~(MMC_CARD | MMC_WIDTH), ata); + } + + writeb(val, host->ioaddr + reg); +} + +#else +#define mv_sdhci_writeb NULL +#endif /* CONFIG_SHEEVA_88SV331xV5 */ +#endif /* CONFIG_MMC_SDHCI_IO_ACCESSORS */ + +int mv_sdh_init(unsigned long regbase, u32 max_clk, u32 min_clk, u32 quirks) +{ + struct sdhci_host *host = NULL; + host = calloc(1, sizeof(*host)); + if (!host) { + printf("sdh_host malloc fail!\n"); + return -ENOMEM; + } + + host->name = MVSDH_NAME; + host->ioaddr = (void *)regbase; + host->quirks = quirks; + host->max_clk = max_clk; +#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS + memset(&mv_ops, 0, sizeof(struct sdhci_ops)); + mv_ops.write_b = mv_sdhci_writeb; + host->ops = &mv_ops; +#endif + + if (CONFIG_IS_ENABLED(ARCH_MVEBU)) { + /* Configure SDHCI MBUS mbus bridge windows */ + sdhci_mvebu_mbus_config((void __iomem *)regbase); + } + + return add_sdhci(host, 0, min_clk); +} + +#else + +DECLARE_GLOBAL_DATA_PTR; + +struct mv_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +static int mv_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct mv_sdhci_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + int ret; + + host->name = MVSDH_NAME; + host->ioaddr = dev_read_addr_ptr(dev); + host->quirks = SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_WAIT_SEND_CMD; + host->mmc = &plat->mmc; + host->mmc->dev = dev; + host->mmc->priv = host; + + ret = mmc_of_parse(dev, &plat->cfg); + if (ret) + return ret; + + ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0); + if (ret) + return ret; + + if (CONFIG_IS_ENABLED(ARCH_MVEBU)) { + /* Configure SDHCI MBUS mbus bridge windows */ + sdhci_mvebu_mbus_config(host->ioaddr); + } + + upriv->mmc = host->mmc; + + return sdhci_probe(dev); +} + +static int mv_sdhci_bind(struct udevice *dev) +{ + struct mv_sdhci_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id mv_sdhci_ids[] = { + { .compatible = "marvell,armada-380-sdhci" }, + { } +}; + +U_BOOT_DRIVER(mv_sdhci_drv) = { + .name = MVSDH_NAME, + .id = UCLASS_MMC, + .of_match = mv_sdhci_ids, + .bind = mv_sdhci_bind, + .probe = mv_sdhci_probe, + .ops = &sdhci_ops, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct mv_sdhci_plat), +}; +#endif /* CONFIG_DM_MMC */ diff --git a/roms/u-boot/drivers/mmc/mvebu_mmc.c b/roms/u-boot/drivers/mmc/mvebu_mmc.c new file mode 100644 index 000000000..fea55c61e --- /dev/null +++ b/roms/u-boot/drivers/mmc/mvebu_mmc.c @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Marvell MMC/SD/SDIO driver + * + * (C) Copyright 2012-2014 + * Marvell Semiconductor <www.marvell.com> + * Written-by: Maen Suleiman, Gerald Kerma + */ + +#include <common.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <dm.h> +#include <fdtdec.h> +#include <part.h> +#include <mmc.h> +#include <asm/io.h> +#include <asm/arch/cpu.h> +#include <asm/arch/soc.h> +#include <mvebu_mmc.h> +#include <dm/device_compat.h> + +#define MVEBU_TARGET_DRAM 0 + +#define TIMEOUT_DELAY 5*CONFIG_SYS_HZ /* wait 5 seconds */ + +static inline void *get_regbase(const struct mmc *mmc) +{ + struct mvebu_mmc_plat *pdata = mmc->priv; + + return pdata->iobase; +} + +static void mvebu_mmc_write(const struct mmc *mmc, u32 offs, u32 val) +{ + writel(val, get_regbase(mmc) + (offs)); +} + +static u32 mvebu_mmc_read(const struct mmc *mmc, u32 offs) +{ + return readl(get_regbase(mmc) + (offs)); +} + +static int mvebu_mmc_setup_data(struct udevice *dev, struct mmc_data *data) +{ + struct mvebu_mmc_plat *pdata = dev_get_plat(dev); + struct mmc *mmc = &pdata->mmc; + u32 ctrl_reg; + + dev_dbg(dev, "data %s : blocks=%d blksz=%d\n", + (data->flags & MMC_DATA_READ) ? "read" : "write", + data->blocks, data->blocksize); + + /* default to maximum timeout */ + ctrl_reg = mvebu_mmc_read(mmc, SDIO_HOST_CTRL); + ctrl_reg |= SDIO_HOST_CTRL_TMOUT(SDIO_HOST_CTRL_TMOUT_MAX); + mvebu_mmc_write(mmc, SDIO_HOST_CTRL, ctrl_reg); + + if (data->flags & MMC_DATA_READ) { + mvebu_mmc_write(mmc, SDIO_SYS_ADDR_LOW, (u32)data->dest & 0xffff); + mvebu_mmc_write(mmc, SDIO_SYS_ADDR_HI, (u32)data->dest >> 16); + } else { + mvebu_mmc_write(mmc, SDIO_SYS_ADDR_LOW, (u32)data->src & 0xffff); + mvebu_mmc_write(mmc, SDIO_SYS_ADDR_HI, (u32)data->src >> 16); + } + + mvebu_mmc_write(mmc, SDIO_BLK_COUNT, data->blocks); + mvebu_mmc_write(mmc, SDIO_BLK_SIZE, data->blocksize); + + return 0; +} + +static int mvebu_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + ulong start; + ushort waittype = 0; + ushort resptype = 0; + ushort xfertype = 0; + ushort resp_indx = 0; + struct mvebu_mmc_plat *pdata = dev_get_plat(dev); + struct mmc *mmc = &pdata->mmc; + + dev_dbg(dev, "cmdidx [0x%x] resp_type[0x%x] cmdarg[0x%x]\n", + cmd->cmdidx, cmd->resp_type, cmd->cmdarg); + + dev_dbg(dev, "cmd %d (hw state 0x%04x)\n", + cmd->cmdidx, mvebu_mmc_read(mmc, SDIO_HW_STATE)); + + /* + * Hardware weirdness. The FIFO_EMPTY bit of the HW_STATE + * register is sometimes not set before a while when some + * "unusual" data block sizes are used (such as with the SWITCH + * command), even despite the fact that the XFER_DONE interrupt + * was raised. And if another data transfer starts before + * this bit comes to good sense (which eventually happens by + * itself) then the new transfer simply fails with a timeout. + */ + if (!(mvebu_mmc_read(mmc, SDIO_HW_STATE) & CMD_FIFO_EMPTY)) { + ushort hw_state, count = 0; + + start = get_timer(0); + do { + hw_state = mvebu_mmc_read(mmc, SDIO_HW_STATE); + if ((get_timer(0) - start) > TIMEOUT_DELAY) { + printf("%s : FIFO_EMPTY bit missing\n", + dev->name); + break; + } + count++; + } while (!(hw_state & CMD_FIFO_EMPTY)); + dev_dbg(dev, "*** wait for FIFO_EMPTY bit (hw=0x%04x, count=%d, jiffies=%ld)\n", + hw_state, count, (get_timer(0) - (start))); + } + + /* Clear status */ + mvebu_mmc_write(mmc, SDIO_NOR_INTR_STATUS, SDIO_POLL_MASK); + mvebu_mmc_write(mmc, SDIO_ERR_INTR_STATUS, SDIO_POLL_MASK); + + resptype = SDIO_CMD_INDEX(cmd->cmdidx); + + /* Analyzing resptype/xfertype/waittype for the command */ + if (cmd->resp_type & MMC_RSP_BUSY) + resptype |= SDIO_CMD_RSP_48BUSY; + else if (cmd->resp_type & MMC_RSP_136) + resptype |= SDIO_CMD_RSP_136; + else if (cmd->resp_type & MMC_RSP_PRESENT) + resptype |= SDIO_CMD_RSP_48; + else + resptype |= SDIO_CMD_RSP_NONE; + + if (cmd->resp_type & MMC_RSP_CRC) + resptype |= SDIO_CMD_CHECK_CMDCRC; + + if (cmd->resp_type & MMC_RSP_OPCODE) + resptype |= SDIO_CMD_INDX_CHECK; + + if (cmd->resp_type & MMC_RSP_PRESENT) { + resptype |= SDIO_UNEXPECTED_RESP; + waittype |= SDIO_NOR_UNEXP_RSP; + } + + if (data) { + int err = mvebu_mmc_setup_data(dev, data); + + if (err) { + dev_dbg(dev, "command DATA error :%x\n", err); + return err; + } + + resptype |= SDIO_CMD_DATA_PRESENT | SDIO_CMD_CHECK_DATACRC16; + xfertype |= SDIO_XFER_MODE_HW_WR_DATA_EN; + if (data->flags & MMC_DATA_READ) { + xfertype |= SDIO_XFER_MODE_TO_HOST; + waittype = SDIO_NOR_DMA_INI; + } else { + waittype |= SDIO_NOR_XFER_DONE; + } + } else { + waittype |= SDIO_NOR_CMD_DONE; + } + + /* Setting cmd arguments */ + mvebu_mmc_write(mmc, SDIO_ARG_LOW, cmd->cmdarg & 0xffff); + mvebu_mmc_write(mmc, SDIO_ARG_HI, cmd->cmdarg >> 16); + + /* Setting Xfer mode */ + mvebu_mmc_write(mmc, SDIO_XFER_MODE, xfertype); + + /* Sending command */ + mvebu_mmc_write(mmc, SDIO_CMD, resptype); + + start = get_timer(0); + + while (!((mvebu_mmc_read(mmc, SDIO_NOR_INTR_STATUS)) & waittype)) { + if (mvebu_mmc_read(mmc, SDIO_NOR_INTR_STATUS) & SDIO_NOR_ERROR) { + dev_dbg(dev, "error! cmdidx : %d, err reg: %04x\n", + cmd->cmdidx, + mvebu_mmc_read(mmc, SDIO_ERR_INTR_STATUS)); + if (mvebu_mmc_read(mmc, SDIO_ERR_INTR_STATUS) & + (SDIO_ERR_CMD_TIMEOUT | SDIO_ERR_DATA_TIMEOUT)) { + dev_dbg(dev, "command READ timed out\n"); + return -ETIMEDOUT; + } + dev_dbg(dev, "command READ error\n"); + return -ECOMM; + } + + if ((get_timer(0) - start) > TIMEOUT_DELAY) { + dev_dbg(dev, "command timed out\n"); + return -ETIMEDOUT; + } + } + + /* Handling response */ + if (cmd->resp_type & MMC_RSP_136) { + uint response[8]; + + for (resp_indx = 0; resp_indx < 8; resp_indx++) + response[resp_indx] = mvebu_mmc_read(mmc, SDIO_RSP(resp_indx)); + + cmd->response[0] = ((response[0] & 0x03ff) << 22) | + ((response[1] & 0xffff) << 6) | + ((response[2] & 0xfc00) >> 10); + cmd->response[1] = ((response[2] & 0x03ff) << 22) | + ((response[3] & 0xffff) << 6) | + ((response[4] & 0xfc00) >> 10); + cmd->response[2] = ((response[4] & 0x03ff) << 22) | + ((response[5] & 0xffff) << 6) | + ((response[6] & 0xfc00) >> 10); + cmd->response[3] = ((response[6] & 0x03ff) << 22) | + ((response[7] & 0x3fff) << 8); + } else if (cmd->resp_type & MMC_RSP_PRESENT) { + uint response[3]; + + for (resp_indx = 0; resp_indx < 3; resp_indx++) + response[resp_indx] = mvebu_mmc_read(mmc, SDIO_RSP(resp_indx)); + + cmd->response[0] = ((response[2] & 0x003f) << (8 - 8)) | + ((response[1] & 0xffff) << (14 - 8)) | + ((response[0] & 0x03ff) << (30 - 8)); + cmd->response[1] = ((response[0] & 0xfc00) >> 10); + cmd->response[2] = 0; + cmd->response[3] = 0; + } else { + cmd->response[0] = 0; + cmd->response[1] = 0; + cmd->response[2] = 0; + cmd->response[3] = 0; + } + + dev_dbg(dev, "resp[0x%x] ", cmd->resp_type); + debug("[0x%x] ", cmd->response[0]); + debug("[0x%x] ", cmd->response[1]); + debug("[0x%x] ", cmd->response[2]); + debug("[0x%x] ", cmd->response[3]); + debug("\n"); + + if (mvebu_mmc_read(mmc, SDIO_ERR_INTR_STATUS) & + (SDIO_ERR_CMD_TIMEOUT | SDIO_ERR_DATA_TIMEOUT)) + return -ETIMEDOUT; + + return 0; +} + +static void mvebu_mmc_power_up(struct udevice *dev) +{ + struct mvebu_mmc_plat *pdata = dev_get_plat(dev); + struct mmc *mmc = &pdata->mmc; + + dev_dbg(dev, "power up\n"); + + /* disable interrupts */ + mvebu_mmc_write(mmc, SDIO_NOR_INTR_EN, 0); + mvebu_mmc_write(mmc, SDIO_ERR_INTR_EN, 0); + + /* SW reset */ + mvebu_mmc_write(mmc, SDIO_SW_RESET, SDIO_SW_RESET_NOW); + + mvebu_mmc_write(mmc, SDIO_XFER_MODE, 0); + + /* enable status */ + mvebu_mmc_write(mmc, SDIO_NOR_STATUS_EN, SDIO_POLL_MASK); + mvebu_mmc_write(mmc, SDIO_ERR_STATUS_EN, SDIO_POLL_MASK); + + /* enable interrupts status */ + mvebu_mmc_write(mmc, SDIO_NOR_INTR_STATUS, SDIO_POLL_MASK); + mvebu_mmc_write(mmc, SDIO_ERR_INTR_STATUS, SDIO_POLL_MASK); +} + +static void mvebu_mmc_set_clk(struct udevice *dev, unsigned int clock) +{ + unsigned int m; + struct mvebu_mmc_plat *pdata = dev_get_plat(dev); + struct mmc *mmc = &pdata->mmc; + + if (clock == 0) { + dev_dbg(dev, "clock off\n"); + mvebu_mmc_write(mmc, SDIO_XFER_MODE, SDIO_XFER_MODE_STOP_CLK); + mvebu_mmc_write(mmc, SDIO_CLK_DIV, MVEBU_MMC_BASE_DIV_MAX); + } else { + m = MVEBU_MMC_BASE_FAST_CLOCK/(2*clock) - 1; + if (m > MVEBU_MMC_BASE_DIV_MAX) + m = MVEBU_MMC_BASE_DIV_MAX; + mvebu_mmc_write(mmc, SDIO_CLK_DIV, m & MVEBU_MMC_BASE_DIV_MAX); + dev_dbg(dev, "clock (%d) div : %d\n", clock, m); + } +} + +static void mvebu_mmc_set_bus(struct udevice *dev, unsigned int bus) +{ + struct mvebu_mmc_plat *pdata = dev_get_plat(dev); + struct mmc *mmc = &pdata->mmc; + u32 ctrl_reg = 0; + + ctrl_reg = mvebu_mmc_read(mmc, SDIO_HOST_CTRL); + ctrl_reg &= ~SDIO_HOST_CTRL_DATA_WIDTH_4_BITS; + + switch (bus) { + case 4: + ctrl_reg |= SDIO_HOST_CTRL_DATA_WIDTH_4_BITS; + break; + case 1: + default: + ctrl_reg |= SDIO_HOST_CTRL_DATA_WIDTH_1_BIT; + } + + /* default transfer mode */ + ctrl_reg |= SDIO_HOST_CTRL_BIG_ENDIAN; + ctrl_reg &= ~SDIO_HOST_CTRL_LSB_FIRST; + + /* default to maximum timeout */ + ctrl_reg |= SDIO_HOST_CTRL_TMOUT(SDIO_HOST_CTRL_TMOUT_MAX); + ctrl_reg |= SDIO_HOST_CTRL_TMOUT_EN; + + ctrl_reg |= SDIO_HOST_CTRL_PUSH_PULL_EN; + + ctrl_reg |= SDIO_HOST_CTRL_CARD_TYPE_MEM_ONLY; + + dev_dbg(dev, "ctrl 0x%04x: %s %s %s\n", ctrl_reg, + (ctrl_reg & SDIO_HOST_CTRL_PUSH_PULL_EN) ? + "push-pull" : "open-drain", + (ctrl_reg & SDIO_HOST_CTRL_DATA_WIDTH_4_BITS) ? + "4bit-width" : "1bit-width", + (ctrl_reg & SDIO_HOST_CTRL_HI_SPEED_EN) ? + "high-speed" : ""); + + mvebu_mmc_write(mmc, SDIO_HOST_CTRL, ctrl_reg); +} + +static int mvebu_mmc_set_ios(struct udevice *dev) +{ + struct mvebu_mmc_plat *pdata = dev_get_plat(dev); + struct mmc *mmc = &pdata->mmc; + + dev_dbg(dev, "bus[%d] clock[%d]\n", + mmc->bus_width, mmc->clock); + mvebu_mmc_set_bus(dev, mmc->bus_width); + mvebu_mmc_set_clk(dev, mmc->clock); + + return 0; +} + +/* + * Set window register. + */ +static void mvebu_window_setup(const struct mmc *mmc) +{ + int i; + + for (i = 0; i < 4; i++) { + mvebu_mmc_write(mmc, WINDOW_CTRL(i), 0); + mvebu_mmc_write(mmc, WINDOW_BASE(i), 0); + } + for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { + u32 size, base, attrib; + + /* Enable DRAM bank */ + switch (i) { + case 0: + attrib = KWCPU_ATTR_DRAM_CS0; + break; + case 1: + attrib = KWCPU_ATTR_DRAM_CS1; + break; + case 2: + attrib = KWCPU_ATTR_DRAM_CS2; + break; + case 3: + attrib = KWCPU_ATTR_DRAM_CS3; + break; + default: + /* invalide bank, disable access */ + attrib = 0; + break; + } + + size = gd->bd->bi_dram[i].size; + base = gd->bd->bi_dram[i].start; + if (size && attrib) { + mvebu_mmc_write(mmc, WINDOW_CTRL(i), + MVCPU_WIN_CTRL_DATA(size, + MVEBU_TARGET_DRAM, + attrib, + MVCPU_WIN_ENABLE)); + } else { + mvebu_mmc_write(mmc, WINDOW_CTRL(i), MVCPU_WIN_DISABLE); + } + mvebu_mmc_write(mmc, WINDOW_BASE(i), base); + } +} + +static int mvebu_mmc_initialize(struct udevice *dev) +{ + struct mvebu_mmc_plat *pdata = dev_get_plat(dev); + struct mmc *mmc = &pdata->mmc; + + dev_dbg(dev, "%s\n", __func__); + + /* + * Setting host parameters + * Initial Host Ctrl : Timeout : max , Normal Speed mode, + * 4-bit data mode, Big Endian, SD memory Card, Push_pull CMD Line + */ + mvebu_mmc_write(mmc, SDIO_HOST_CTRL, + SDIO_HOST_CTRL_TMOUT(SDIO_HOST_CTRL_TMOUT_MAX) | + SDIO_HOST_CTRL_DATA_WIDTH_4_BITS | + SDIO_HOST_CTRL_BIG_ENDIAN | + SDIO_HOST_CTRL_PUSH_PULL_EN | + SDIO_HOST_CTRL_CARD_TYPE_MEM_ONLY); + + mvebu_mmc_write(mmc, SDIO_CLK_CTRL, 0); + + /* enable status */ + mvebu_mmc_write(mmc, SDIO_NOR_STATUS_EN, SDIO_POLL_MASK); + mvebu_mmc_write(mmc, SDIO_ERR_STATUS_EN, SDIO_POLL_MASK); + + /* disable interrupts */ + mvebu_mmc_write(mmc, SDIO_NOR_INTR_EN, 0); + mvebu_mmc_write(mmc, SDIO_ERR_INTR_EN, 0); + + mvebu_window_setup(mmc); + + /* SW reset */ + mvebu_mmc_write(mmc, SDIO_SW_RESET, SDIO_SW_RESET_NOW); + + return 0; +} + +static int mvebu_mmc_of_to_plat(struct udevice *dev) +{ + struct mvebu_mmc_plat *pdata = dev_get_plat(dev); + fdt_addr_t addr; + + addr = dev_read_addr(dev); + if (addr == FDT_ADDR_T_NONE) + return -EINVAL; + + pdata->iobase = (void *)addr; + + return 0; +} + +static int mvebu_mmc_probe(struct udevice *dev) +{ + struct mvebu_mmc_plat *pdata = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct mmc *mmc = &pdata->mmc; + struct mmc_config *cfg = &pdata->cfg; + + cfg->name = dev->name; + cfg->f_min = MVEBU_MMC_BASE_FAST_CLOCK / MVEBU_MMC_BASE_DIV_MAX; + cfg->f_max = MVEBU_MMC_CLOCKRATE_MAX; + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + cfg->host_caps = MMC_MODE_4BIT | MMC_MODE_HS | MMC_MODE_HS_52MHz; + cfg->part_type = PART_TYPE_DOS; + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + mmc->cfg = cfg; + mmc->priv = pdata; + mmc->dev = dev; + upriv->mmc = mmc; + + mvebu_mmc_power_up(dev); + mvebu_mmc_initialize(dev); + + return 0; +} + +static const struct dm_mmc_ops mvebu_dm_mmc_ops = { + .send_cmd = mvebu_mmc_send_cmd, + .set_ios = mvebu_mmc_set_ios, +}; + +static int mvebu_mmc_bind(struct udevice *dev) +{ + struct mvebu_mmc_plat *pdata = dev_get_plat(dev); + + return mmc_bind(dev, &pdata->mmc, &pdata->cfg); +} + +static const struct udevice_id mvebu_mmc_match[] = { + { .compatible = "marvell,orion-sdio" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(mvebu_mmc) = { + .name = "mvebu_mmc", + .id = UCLASS_MMC, + .of_match = mvebu_mmc_match, + .ops = &mvebu_dm_mmc_ops, + .probe = mvebu_mmc_probe, + .bind = mvebu_mmc_bind, + .of_to_plat = mvebu_mmc_of_to_plat, + .plat_auto = sizeof(struct mvebu_mmc_plat), +}; diff --git a/roms/u-boot/drivers/mmc/mxcmmc.c b/roms/u-boot/drivers/mmc/mxcmmc.c new file mode 100644 index 000000000..0057273a2 --- /dev/null +++ b/roms/u-boot/drivers/mmc/mxcmmc.c @@ -0,0 +1,524 @@ +/* + * This is a driver for the SDHC controller found in Freescale MX2/MX3 + * SoCs. It is basically the same hardware as found on MX1 (imxmmc.c). + * Unlike the hardware found on MX1, this hardware just works and does + * not need all the quirks found in imxmmc.c, hence the seperate driver. + * + * Copyright (C) 2009 Ilya Yanok, <yanok@emcraft.com> + * Copyright (C) 2008 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> + * Copyright (C) 2006 Pavel Pisa, PiKRON <ppisa@pikron.com> + * + * derived from pxamci.c by Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <config.h> +#include <common.h> +#include <command.h> +#include <mmc.h> +#include <part.h> +#include <malloc.h> +#include <mmc.h> +#include <time.h> +#include <linux/errno.h> +#include <asm/io.h> +#include <asm/arch/clock.h> + +#define DRIVER_NAME "mxc-mmc" + +struct mxcmci_regs { + u32 str_stp_clk; + u32 status; + u32 clk_rate; + u32 cmd_dat_cont; + u32 res_to; + u32 read_to; + u32 blk_len; + u32 nob; + u32 rev_no; + u32 int_cntr; + u32 cmd; + u32 arg; + u32 pad; + u32 res_fifo; + u32 buffer_access; +}; + +#define STR_STP_CLK_RESET (1 << 3) +#define STR_STP_CLK_START_CLK (1 << 1) +#define STR_STP_CLK_STOP_CLK (1 << 0) + +#define STATUS_CARD_INSERTION (1 << 31) +#define STATUS_CARD_REMOVAL (1 << 30) +#define STATUS_YBUF_EMPTY (1 << 29) +#define STATUS_XBUF_EMPTY (1 << 28) +#define STATUS_YBUF_FULL (1 << 27) +#define STATUS_XBUF_FULL (1 << 26) +#define STATUS_BUF_UND_RUN (1 << 25) +#define STATUS_BUF_OVFL (1 << 24) +#define STATUS_SDIO_INT_ACTIVE (1 << 14) +#define STATUS_END_CMD_RESP (1 << 13) +#define STATUS_WRITE_OP_DONE (1 << 12) +#define STATUS_DATA_TRANS_DONE (1 << 11) +#define STATUS_READ_OP_DONE (1 << 11) +#define STATUS_WR_CRC_ERROR_CODE_MASK (3 << 10) +#define STATUS_CARD_BUS_CLK_RUN (1 << 8) +#define STATUS_BUF_READ_RDY (1 << 7) +#define STATUS_BUF_WRITE_RDY (1 << 6) +#define STATUS_RESP_CRC_ERR (1 << 5) +#define STATUS_CRC_READ_ERR (1 << 3) +#define STATUS_CRC_WRITE_ERR (1 << 2) +#define STATUS_TIME_OUT_RESP (1 << 1) +#define STATUS_TIME_OUT_READ (1 << 0) +#define STATUS_ERR_MASK 0x2f + +#define CMD_DAT_CONT_CMD_RESP_LONG_OFF (1 << 12) +#define CMD_DAT_CONT_STOP_READWAIT (1 << 11) +#define CMD_DAT_CONT_START_READWAIT (1 << 10) +#define CMD_DAT_CONT_BUS_WIDTH_4 (2 << 8) +#define CMD_DAT_CONT_INIT (1 << 7) +#define CMD_DAT_CONT_WRITE (1 << 4) +#define CMD_DAT_CONT_DATA_ENABLE (1 << 3) +#define CMD_DAT_CONT_RESPONSE_48BIT_CRC (1 << 0) +#define CMD_DAT_CONT_RESPONSE_136BIT (2 << 0) +#define CMD_DAT_CONT_RESPONSE_48BIT (3 << 0) + +#define INT_SDIO_INT_WKP_EN (1 << 18) +#define INT_CARD_INSERTION_WKP_EN (1 << 17) +#define INT_CARD_REMOVAL_WKP_EN (1 << 16) +#define INT_CARD_INSERTION_EN (1 << 15) +#define INT_CARD_REMOVAL_EN (1 << 14) +#define INT_SDIO_IRQ_EN (1 << 13) +#define INT_DAT0_EN (1 << 12) +#define INT_BUF_READ_EN (1 << 4) +#define INT_BUF_WRITE_EN (1 << 3) +#define INT_END_CMD_RES_EN (1 << 2) +#define INT_WRITE_OP_DONE_EN (1 << 1) +#define INT_READ_OP_EN (1 << 0) + +struct mxcmci_host { + struct mmc *mmc; + struct mxcmci_regs *base; + int irq; + int detect_irq; + int dma; + int do_dma; + unsigned int power_mode; + + struct mmc_cmd *cmd; + struct mmc_data *data; + + unsigned int dma_nents; + unsigned int datasize; + unsigned int dma_dir; + + u16 rev_no; + unsigned int cmdat; + + int clock; +}; + +static struct mxcmci_host mxcmci_host; + +/* maintainer note: do we really want to have a global host pointer? */ +static struct mxcmci_host *host = &mxcmci_host; + +static inline int mxcmci_use_dma(struct mxcmci_host *host) +{ + return host->do_dma; +} + +static void mxcmci_softreset(struct mxcmci_host *host) +{ + int i; + + /* reset sequence */ + writel(STR_STP_CLK_RESET, &host->base->str_stp_clk); + writel(STR_STP_CLK_RESET | STR_STP_CLK_START_CLK, + &host->base->str_stp_clk); + + for (i = 0; i < 8; i++) + writel(STR_STP_CLK_START_CLK, &host->base->str_stp_clk); + + writel(0xff, &host->base->res_to); +} + +static void mxcmci_setup_data(struct mxcmci_host *host, struct mmc_data *data) +{ + unsigned int nob = data->blocks; + unsigned int blksz = data->blocksize; + unsigned int datasize = nob * blksz; + + host->data = data; + + writel(nob, &host->base->nob); + writel(blksz, &host->base->blk_len); + host->datasize = datasize; +} + +static int mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_cmd *cmd, + unsigned int cmdat) +{ + if (host->cmd != NULL) + printf("mxcmci: error!\n"); + host->cmd = cmd; + + switch (cmd->resp_type) { + case MMC_RSP_R1: /* short CRC, OPCODE */ + case MMC_RSP_R1b:/* short CRC, OPCODE, BUSY */ + cmdat |= CMD_DAT_CONT_RESPONSE_48BIT_CRC; + break; + case MMC_RSP_R2: /* long 136 bit + CRC */ + cmdat |= CMD_DAT_CONT_RESPONSE_136BIT; + break; + case MMC_RSP_R3: /* short */ + cmdat |= CMD_DAT_CONT_RESPONSE_48BIT; + break; + case MMC_RSP_NONE: + break; + default: + printf("mxcmci: unhandled response type 0x%x\n", + cmd->resp_type); + return -EINVAL; + } + + writel(cmd->cmdidx, &host->base->cmd); + writel(cmd->cmdarg, &host->base->arg); + writel(cmdat, &host->base->cmd_dat_cont); + + return 0; +} + +static void mxcmci_finish_request(struct mxcmci_host *host, + struct mmc_cmd *cmd, struct mmc_data *data) +{ + host->cmd = NULL; + host->data = NULL; +} + +static int mxcmci_finish_data(struct mxcmci_host *host, unsigned int stat) +{ + int data_error = 0; + + if (stat & STATUS_ERR_MASK) { + printf("request failed. status: 0x%08x\n", + stat); + if (stat & STATUS_CRC_READ_ERR) { + data_error = -EILSEQ; + } else if (stat & STATUS_CRC_WRITE_ERR) { + u32 err_code = (stat >> 9) & 0x3; + if (err_code == 2) /* No CRC response */ + data_error = -ETIMEDOUT; + else + data_error = -EILSEQ; + } else if (stat & STATUS_TIME_OUT_READ) { + data_error = -ETIMEDOUT; + } else { + data_error = -EIO; + } + } + + host->data = NULL; + + return data_error; +} + +static int mxcmci_read_response(struct mxcmci_host *host, unsigned int stat) +{ + struct mmc_cmd *cmd = host->cmd; + int i; + u32 a, b, c; + u32 *resp = (u32 *)cmd->response; + + if (!cmd) + return 0; + + if (stat & STATUS_TIME_OUT_RESP) { + printf("CMD TIMEOUT\n"); + return -ETIMEDOUT; + } else if (stat & STATUS_RESP_CRC_ERR && cmd->resp_type & MMC_RSP_CRC) { + printf("cmd crc error\n"); + return -EILSEQ; + } + + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) { + for (i = 0; i < 4; i++) { + a = readl(&host->base->res_fifo) & 0xFFFF; + b = readl(&host->base->res_fifo) & 0xFFFF; + resp[i] = a << 16 | b; + } + } else { + a = readl(&host->base->res_fifo) & 0xFFFF; + b = readl(&host->base->res_fifo) & 0xFFFF; + c = readl(&host->base->res_fifo) & 0xFFFF; + resp[0] = a << 24 | b << 8 | c >> 8; + } + } + return 0; +} + +static int mxcmci_poll_status(struct mxcmci_host *host, u32 mask) +{ + u32 stat; + unsigned long timeout = get_ticks() + CONFIG_SYS_HZ; + + do { + stat = readl(&host->base->status); + if (stat & STATUS_ERR_MASK) + return stat; + if (timeout < get_ticks()) + return STATUS_TIME_OUT_READ; + if (stat & mask) + return 0; + } while (1); +} + +static int mxcmci_pull(struct mxcmci_host *host, void *_buf, int bytes) +{ + unsigned int stat; + u32 *buf = _buf; + + while (bytes > 3) { + stat = mxcmci_poll_status(host, + STATUS_BUF_READ_RDY | STATUS_READ_OP_DONE); + if (stat) + return stat; + *buf++ = readl(&host->base->buffer_access); + bytes -= 4; + } + + if (bytes) { + u8 *b = (u8 *)buf; + u32 tmp; + + stat = mxcmci_poll_status(host, + STATUS_BUF_READ_RDY | STATUS_READ_OP_DONE); + if (stat) + return stat; + tmp = readl(&host->base->buffer_access); + memcpy(b, &tmp, bytes); + } + + return 0; +} + +static int mxcmci_push(struct mxcmci_host *host, const void *_buf, int bytes) +{ + unsigned int stat; + const u32 *buf = _buf; + + while (bytes > 3) { + stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY); + if (stat) + return stat; + writel(*buf++, &host->base->buffer_access); + bytes -= 4; + } + + if (bytes) { + const u8 *b = (u8 *)buf; + u32 tmp; + + stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY); + if (stat) + return stat; + + memcpy(&tmp, b, bytes); + writel(tmp, &host->base->buffer_access); + } + + stat = mxcmci_poll_status(host, STATUS_BUF_WRITE_RDY); + if (stat) + return stat; + + return 0; +} + +static int mxcmci_transfer_data(struct mxcmci_host *host) +{ + struct mmc_data *data = host->data; + int stat; + unsigned long length; + + length = data->blocks * data->blocksize; + host->datasize = 0; + + if (data->flags & MMC_DATA_READ) { + stat = mxcmci_pull(host, data->dest, length); + if (stat) + return stat; + host->datasize += length; + } else { + stat = mxcmci_push(host, (const void *)(data->src), length); + if (stat) + return stat; + host->datasize += length; + stat = mxcmci_poll_status(host, STATUS_WRITE_OP_DONE); + if (stat) + return stat; + } + return 0; +} + +static int mxcmci_cmd_done(struct mxcmci_host *host, unsigned int stat) +{ + int datastat; + int ret; + + ret = mxcmci_read_response(host, stat); + + if (ret) { + mxcmci_finish_request(host, host->cmd, host->data); + return ret; + } + + if (!host->data) { + mxcmci_finish_request(host, host->cmd, host->data); + return 0; + } + + datastat = mxcmci_transfer_data(host); + ret = mxcmci_finish_data(host, datastat); + mxcmci_finish_request(host, host->cmd, host->data); + return ret; +} + +static int mxcmci_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mxcmci_host *host = mmc->priv; + unsigned int cmdat = host->cmdat; + u32 stat; + int ret; + + host->cmdat &= ~CMD_DAT_CONT_INIT; + if (data) { + mxcmci_setup_data(host, data); + + cmdat |= CMD_DAT_CONT_DATA_ENABLE; + + if (data->flags & MMC_DATA_WRITE) + cmdat |= CMD_DAT_CONT_WRITE; + } + + if ((ret = mxcmci_start_cmd(host, cmd, cmdat))) { + mxcmci_finish_request(host, cmd, data); + return ret; + } + + do { + stat = readl(&host->base->status); + writel(stat, &host->base->status); + } while (!(stat & STATUS_END_CMD_RESP)); + + return mxcmci_cmd_done(host, stat); +} + +static void mxcmci_set_clk_rate(struct mxcmci_host *host, unsigned int clk_ios) +{ + unsigned int divider; + int prescaler = 0; + unsigned long clk_in = mxc_get_clock(MXC_ESDHC_CLK); + + while (prescaler <= 0x800) { + for (divider = 1; divider <= 0xF; divider++) { + int x; + + x = (clk_in / (divider + 1)); + + if (prescaler) + x /= (prescaler * 2); + + if (x <= clk_ios) + break; + } + if (divider < 0x10) + break; + + if (prescaler == 0) + prescaler = 1; + else + prescaler <<= 1; + } + + writel((prescaler << 4) | divider, &host->base->clk_rate); +} + +static int mxcmci_set_ios(struct mmc *mmc) +{ + struct mxcmci_host *host = mmc->priv; + if (mmc->bus_width == 4) + host->cmdat |= CMD_DAT_CONT_BUS_WIDTH_4; + else + host->cmdat &= ~CMD_DAT_CONT_BUS_WIDTH_4; + + if (mmc->clock) { + mxcmci_set_clk_rate(host, mmc->clock); + writel(STR_STP_CLK_START_CLK, &host->base->str_stp_clk); + } else { + writel(STR_STP_CLK_STOP_CLK, &host->base->str_stp_clk); + } + + host->clock = mmc->clock; + + return 0; +} + +static int mxcmci_init(struct mmc *mmc) +{ + struct mxcmci_host *host = mmc->priv; + + mxcmci_softreset(host); + + host->rev_no = readl(&host->base->rev_no); + if (host->rev_no != 0x400) { + printf("wrong rev.no. 0x%08x. aborting.\n", + host->rev_no); + return -ENODEV; + } + + /* recommended in data sheet */ + writel(0x2db4, &host->base->read_to); + + writel(0, &host->base->int_cntr); + + return 0; +} + +static const struct mmc_ops mxcmci_ops = { + .send_cmd = mxcmci_request, + .set_ios = mxcmci_set_ios, + .init = mxcmci_init, +}; + +static struct mmc_config mxcmci_cfg = { + .name = "MXC MCI", + .ops = &mxcmci_ops, + .host_caps = MMC_MODE_4BIT, + .voltages = MMC_VDD_32_33 | MMC_VDD_33_34, + .b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT, +}; + +static int mxcmci_initialize(struct bd_info *bis) +{ + host->base = (struct mxcmci_regs *)CONFIG_MXC_MCI_REGS_BASE; + + mxcmci_cfg.f_min = mxc_get_clock(MXC_ESDHC_CLK) >> 7; + mxcmci_cfg.f_max = mxc_get_clock(MXC_ESDHC_CLK) >> 1; + + host->mmc = mmc_create(&mxcmci_cfg, host); + if (host->mmc == NULL) + return -1; + + return 0; +} + +int mxc_mmc_init(struct bd_info *bis) +{ + return mxcmci_initialize(bis); +} diff --git a/roms/u-boot/drivers/mmc/mxsmmc.c b/roms/u-boot/drivers/mmc/mxsmmc.c new file mode 100644 index 000000000..8fd417641 --- /dev/null +++ b/roms/u-boot/drivers/mmc/mxsmmc.c @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Freescale i.MX28 SSP MMC driver + * + * Copyright (C) 2019 DENX Software Engineering + * Lukasz Majewski, DENX Software Engineering, lukma@denx.de + * + * Copyright (C) 2011 Marek Vasut <marek.vasut@gmail.com> + * on behalf of DENX Software Engineering GmbH + * + * Based on code from LTIB: + * (C) Copyright 2008-2010 Freescale Semiconductor, Inc. + * Terry Lv + * + * Copyright 2007, Freescale Semiconductor, Inc + * Andy Fleming + * + * Based vaguely on the pxa mmc code: + * (C) Copyright 2003 + * Kyle Harris, Nexus Technologies, Inc. kharris@nexus-tech.net + */ + +#include <common.h> +#include <log.h> +#include <malloc.h> +#include <mmc.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/imx-regs.h> +#include <asm/arch/sys_proto.h> +#include <asm/mach-imx/dma.h> +#include <bouncebuf.h> + +#define MXSMMC_MAX_TIMEOUT 10000 +#define MXSMMC_SMALL_TRANSFER 512 + +#if !CONFIG_IS_ENABLED(DM_MMC) +struct mxsmmc_priv { + int id; + int (*mmc_is_wp)(int); + int (*mmc_cd)(int); + struct mmc_config cfg; /* mmc configuration */ + struct mxs_dma_desc *desc; + uint32_t buswidth; + struct mxs_ssp_regs *regs; +}; +#else /* CONFIG_IS_ENABLED(DM_MMC) */ +#include <dm/device.h> +#include <dm/read.h> +#include <dt-structs.h> + +struct mxsmmc_plat { +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct dtd_fsl_imx23_mmc dtplat; +#endif + struct mmc_config cfg; + struct mmc mmc; + fdt_addr_t base; + int non_removable; + int buswidth; + int dma_id; + int clk_id; +}; + +struct mxsmmc_priv { + int clkid; + struct mxs_dma_desc *desc; + u32 buswidth; + struct mxs_ssp_regs *regs; + unsigned int dma_channel; +}; +#endif + +#if !CONFIG_IS_ENABLED(DM_MMC) +static int mxsmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data); + +static int mxsmmc_cd(struct mxsmmc_priv *priv) +{ + struct mxs_ssp_regs *ssp_regs = priv->regs; + + if (priv->mmc_cd) + return priv->mmc_cd(priv->id); + + return !(readl(&ssp_regs->hw_ssp_status) & SSP_STATUS_CARD_DETECT); +} + +static int mxsmmc_set_ios(struct mmc *mmc) +{ + struct mxsmmc_priv *priv = mmc->priv; + struct mxs_ssp_regs *ssp_regs = priv->regs; + + /* Set the clock speed */ + if (mmc->clock) + mxs_set_ssp_busclock(priv->id, mmc->clock / 1000); + + switch (mmc->bus_width) { + case 1: + priv->buswidth = SSP_CTRL0_BUS_WIDTH_ONE_BIT; + break; + case 4: + priv->buswidth = SSP_CTRL0_BUS_WIDTH_FOUR_BIT; + break; + case 8: + priv->buswidth = SSP_CTRL0_BUS_WIDTH_EIGHT_BIT; + break; + } + + /* Set the bus width */ + clrsetbits_le32(&ssp_regs->hw_ssp_ctrl0, + SSP_CTRL0_BUS_WIDTH_MASK, priv->buswidth); + + debug("MMC%d: Set %d bits bus width\n", + mmc->block_dev.devnum, mmc->bus_width); + + return 0; +} + +static int mxsmmc_init(struct mmc *mmc) +{ + struct mxsmmc_priv *priv = mmc->priv; + struct mxs_ssp_regs *ssp_regs = priv->regs; + + /* Reset SSP */ + mxs_reset_block(&ssp_regs->hw_ssp_ctrl0_reg); + + /* Reconfigure the SSP block for MMC operation */ + writel(SSP_CTRL1_SSP_MODE_SD_MMC | + SSP_CTRL1_WORD_LENGTH_EIGHT_BITS | + SSP_CTRL1_DMA_ENABLE | + SSP_CTRL1_POLARITY | + SSP_CTRL1_RECV_TIMEOUT_IRQ_EN | + SSP_CTRL1_DATA_CRC_IRQ_EN | + SSP_CTRL1_DATA_TIMEOUT_IRQ_EN | + SSP_CTRL1_RESP_TIMEOUT_IRQ_EN | + SSP_CTRL1_RESP_ERR_IRQ_EN, + &ssp_regs->hw_ssp_ctrl1_set); + + /* Set initial bit clock 400 KHz */ + mxs_set_ssp_busclock(priv->id, 400); + + /* Send initial 74 clock cycles (185 us @ 400 KHz)*/ + writel(SSP_CMD0_CONT_CLKING_EN, &ssp_regs->hw_ssp_cmd0_set); + udelay(200); + writel(SSP_CMD0_CONT_CLKING_EN, &ssp_regs->hw_ssp_cmd0_clr); + + return 0; +} + +static const struct mmc_ops mxsmmc_ops = { + .send_cmd = mxsmmc_send_cmd, + .set_ios = mxsmmc_set_ios, + .init = mxsmmc_init, +}; + +int mxsmmc_initialize(struct bd_info *bis, int id, int (*wp)(int), + int (*cd)(int)) +{ + struct mmc *mmc = NULL; + struct mxsmmc_priv *priv = NULL; + int ret; + const unsigned int mxsmmc_clk_id = mxs_ssp_clock_by_bus(id); + + if (!mxs_ssp_bus_id_valid(id)) + return -ENODEV; + + priv = malloc(sizeof(struct mxsmmc_priv)); + if (!priv) + return -ENOMEM; + + priv->desc = mxs_dma_desc_alloc(); + if (!priv->desc) { + free(priv); + return -ENOMEM; + } + + ret = mxs_dma_init_channel(MXS_DMA_CHANNEL_AHB_APBH_SSP0 + id); + if (ret) + return ret; + + priv->mmc_is_wp = wp; + priv->mmc_cd = cd; + priv->id = id; + priv->regs = mxs_ssp_regs_by_bus(id); + + priv->cfg.name = "MXS MMC"; + priv->cfg.ops = &mxsmmc_ops; + + priv->cfg.voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + + priv->cfg.host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT | + MMC_MODE_HS_52MHz | MMC_MODE_HS; + + /* + * SSPCLK = 480 * 18 / 29 / 1 = 297.731 MHz + * SSP bit rate = SSPCLK / (CLOCK_DIVIDE * (1 + CLOCK_RATE)), + * CLOCK_DIVIDE has to be an even value from 2 to 254, and + * CLOCK_RATE could be any integer from 0 to 255. + */ + priv->cfg.f_min = 400000; + priv->cfg.f_max = mxc_get_clock(MXC_SSP0_CLK + mxsmmc_clk_id) + * 1000 / 2; + priv->cfg.b_max = 0x20; + + mmc = mmc_create(&priv->cfg, priv); + if (!mmc) { + mxs_dma_desc_free(priv->desc); + free(priv); + return -ENOMEM; + } + return 0; +} +#endif /* CONFIG_IS_ENABLED(DM_MMC) */ + +static int mxsmmc_send_cmd_pio(struct mxsmmc_priv *priv, struct mmc_data *data) +{ + struct mxs_ssp_regs *ssp_regs = priv->regs; + uint32_t *data_ptr; + int timeout = MXSMMC_MAX_TIMEOUT; + uint32_t reg; + uint32_t data_count = data->blocksize * data->blocks; + + if (data->flags & MMC_DATA_READ) { + data_ptr = (uint32_t *)data->dest; + while (data_count && --timeout) { + reg = readl(&ssp_regs->hw_ssp_status); + if (!(reg & SSP_STATUS_FIFO_EMPTY)) { + *data_ptr++ = readl(&ssp_regs->hw_ssp_data); + data_count -= 4; + timeout = MXSMMC_MAX_TIMEOUT; + } else + udelay(1000); + } + } else { + data_ptr = (uint32_t *)data->src; + timeout *= 100; + while (data_count && --timeout) { + reg = readl(&ssp_regs->hw_ssp_status); + if (!(reg & SSP_STATUS_FIFO_FULL)) { + writel(*data_ptr++, &ssp_regs->hw_ssp_data); + data_count -= 4; + timeout = MXSMMC_MAX_TIMEOUT; + } else + udelay(1000); + } + } + + return timeout ? 0 : -ECOMM; +} + +static int mxsmmc_send_cmd_dma(struct mxsmmc_priv *priv, struct mmc_data *data) +{ + uint32_t data_count = data->blocksize * data->blocks; + int dmach; + struct mxs_dma_desc *desc = priv->desc; + void *addr; + unsigned int flags; + struct bounce_buffer bbstate; + + memset(desc, 0, sizeof(struct mxs_dma_desc)); + desc->address = (dma_addr_t)desc; + + if (data->flags & MMC_DATA_READ) { + priv->desc->cmd.data = MXS_DMA_DESC_COMMAND_DMA_WRITE; + addr = data->dest; + flags = GEN_BB_WRITE; + } else { + priv->desc->cmd.data = MXS_DMA_DESC_COMMAND_DMA_READ; + addr = (void *)data->src; + flags = GEN_BB_READ; + } + + bounce_buffer_start(&bbstate, addr, data_count, flags); + + priv->desc->cmd.address = (dma_addr_t)bbstate.bounce_buffer; + + priv->desc->cmd.data |= MXS_DMA_DESC_IRQ | MXS_DMA_DESC_DEC_SEM | + (data_count << MXS_DMA_DESC_BYTES_OFFSET); + +#if !CONFIG_IS_ENABLED(DM_MMC) + dmach = MXS_DMA_CHANNEL_AHB_APBH_SSP0 + priv->id; +#else + dmach = priv->dma_channel; +#endif + mxs_dma_desc_append(dmach, priv->desc); + if (mxs_dma_go(dmach)) { + bounce_buffer_stop(&bbstate); + return -ECOMM; + } + + bounce_buffer_stop(&bbstate); + + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +/* + * Sends a command out on the bus. Takes the mmc pointer, + * a command pointer, and an optional data pointer. + */ +static int +mxsmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, struct mmc_data *data) +{ + struct mxsmmc_priv *priv = mmc->priv; + struct mxs_ssp_regs *ssp_regs = priv->regs; +#else +static int +mxsmmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, struct mmc_data *data) +{ + struct mxsmmc_plat *plat = dev_get_plat(dev); + struct mxsmmc_priv *priv = dev_get_priv(dev); + struct mxs_ssp_regs *ssp_regs = priv->regs; + struct mmc *mmc = &plat->mmc; +#endif + uint32_t reg; + int timeout; + uint32_t ctrl0; + int ret; +#if !CONFIG_IS_ENABLED(DM_MMC) + int devnum = mmc->block_dev.devnum; +#else + int devnum = mmc_get_blk_desc(mmc)->devnum; +#endif + debug("MMC%d: CMD%d\n", devnum, cmd->cmdidx); + + /* Check bus busy */ + timeout = MXSMMC_MAX_TIMEOUT; + while (--timeout) { + udelay(1000); + reg = readl(&ssp_regs->hw_ssp_status); + if (!(reg & + (SSP_STATUS_BUSY | SSP_STATUS_DATA_BUSY | + SSP_STATUS_CMD_BUSY))) { + break; + } + } + + if (!timeout) { + printf("MMC%d: Bus busy timeout!\n", devnum); + return -ETIMEDOUT; + } +#if !CONFIG_IS_ENABLED(DM_MMC) + /* See if card is present */ + if (!mxsmmc_cd(priv)) { + printf("MMC%d: No card detected!\n", devnum); + return -ENOMEDIUM; + } +#endif + /* Start building CTRL0 contents */ + ctrl0 = priv->buswidth; + + /* Set up command */ + if (!(cmd->resp_type & MMC_RSP_CRC)) + ctrl0 |= SSP_CTRL0_IGNORE_CRC; + if (cmd->resp_type & MMC_RSP_PRESENT) /* Need to get response */ + ctrl0 |= SSP_CTRL0_GET_RESP; + if (cmd->resp_type & MMC_RSP_136) /* It's a 136 bits response */ + ctrl0 |= SSP_CTRL0_LONG_RESP; + + if (data && (data->blocksize * data->blocks < MXSMMC_SMALL_TRANSFER)) + writel(SSP_CTRL1_DMA_ENABLE, &ssp_regs->hw_ssp_ctrl1_clr); + else + writel(SSP_CTRL1_DMA_ENABLE, &ssp_regs->hw_ssp_ctrl1_set); + + /* Command index */ + reg = readl(&ssp_regs->hw_ssp_cmd0); + reg &= ~(SSP_CMD0_CMD_MASK | SSP_CMD0_APPEND_8CYC); + reg |= cmd->cmdidx << SSP_CMD0_CMD_OFFSET; + if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) + reg |= SSP_CMD0_APPEND_8CYC; + writel(reg, &ssp_regs->hw_ssp_cmd0); + + /* Command argument */ + writel(cmd->cmdarg, &ssp_regs->hw_ssp_cmd1); + + /* Set up data */ + if (data) { + /* READ or WRITE */ + if (data->flags & MMC_DATA_READ) { + ctrl0 |= SSP_CTRL0_READ; +#if !CONFIG_IS_ENABLED(DM_MMC) + } else if (priv->mmc_is_wp && + priv->mmc_is_wp(devnum)) { + printf("MMC%d: Can not write a locked card!\n", devnum); + return -EOPNOTSUPP; +#endif + } + ctrl0 |= SSP_CTRL0_DATA_XFER; + + reg = data->blocksize * data->blocks; +#if defined(CONFIG_MX23) + ctrl0 |= reg & SSP_CTRL0_XFER_COUNT_MASK; + + clrsetbits_le32(&ssp_regs->hw_ssp_cmd0, + SSP_CMD0_BLOCK_SIZE_MASK | SSP_CMD0_BLOCK_COUNT_MASK, + ((data->blocks - 1) << SSP_CMD0_BLOCK_COUNT_OFFSET) | + ((ffs(data->blocksize) - 1) << + SSP_CMD0_BLOCK_SIZE_OFFSET)); +#elif defined(CONFIG_MX28) + writel(reg, &ssp_regs->hw_ssp_xfer_size); + + reg = ((data->blocks - 1) << + SSP_BLOCK_SIZE_BLOCK_COUNT_OFFSET) | + ((ffs(data->blocksize) - 1) << + SSP_BLOCK_SIZE_BLOCK_SIZE_OFFSET); + writel(reg, &ssp_regs->hw_ssp_block_size); +#endif + } + + /* Kick off the command */ + ctrl0 |= SSP_CTRL0_WAIT_FOR_IRQ | SSP_CTRL0_ENABLE | SSP_CTRL0_RUN; + writel(ctrl0, &ssp_regs->hw_ssp_ctrl0); + + /* Wait for the command to complete */ + timeout = MXSMMC_MAX_TIMEOUT; + while (--timeout) { + udelay(1000); + reg = readl(&ssp_regs->hw_ssp_status); + if (!(reg & SSP_STATUS_CMD_BUSY)) + break; + } + + if (!timeout) { + printf("MMC%d: Command %d busy\n", devnum, cmd->cmdidx); + return -ETIMEDOUT; + } + + /* Check command timeout */ + if (reg & SSP_STATUS_RESP_TIMEOUT) { + debug("MMC%d: Command %d timeout (status 0x%08x)\n", + devnum, cmd->cmdidx, reg); + return -ETIMEDOUT; + } + + /* Check command errors */ + if (reg & (SSP_STATUS_RESP_CRC_ERR | SSP_STATUS_RESP_ERR)) { + printf("MMC%d: Command %d error (status 0x%08x)!\n", + devnum, cmd->cmdidx, reg); + return -ECOMM; + } + + /* Copy response to response buffer */ + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[3] = readl(&ssp_regs->hw_ssp_sdresp0); + cmd->response[2] = readl(&ssp_regs->hw_ssp_sdresp1); + cmd->response[1] = readl(&ssp_regs->hw_ssp_sdresp2); + cmd->response[0] = readl(&ssp_regs->hw_ssp_sdresp3); + } else + cmd->response[0] = readl(&ssp_regs->hw_ssp_sdresp0); + + /* Return if no data to process */ + if (!data) + return 0; + + if (data->blocksize * data->blocks < MXSMMC_SMALL_TRANSFER) { + ret = mxsmmc_send_cmd_pio(priv, data); + if (ret) { + printf("MMC%d: Data timeout with command %d " + "(status 0x%08x)!\n", devnum, cmd->cmdidx, reg); + return ret; + } + } else { + ret = mxsmmc_send_cmd_dma(priv, data); + if (ret) { + printf("MMC%d: DMA transfer failed\n", devnum); + return ret; + } + } + + /* Check data errors */ + reg = readl(&ssp_regs->hw_ssp_status); + if (reg & + (SSP_STATUS_TIMEOUT | SSP_STATUS_DATA_CRC_ERR | + SSP_STATUS_FIFO_OVRFLW | SSP_STATUS_FIFO_UNDRFLW)) { + printf("MMC%d: Data error with command %d (status 0x%08x)!\n", + devnum, cmd->cmdidx, reg); + return -ECOMM; + } + + return 0; +} + +#if CONFIG_IS_ENABLED(DM_MMC) +/* Base numbers of i.MX2[38] clk for ssp0 IP block */ +#define MXS_SSP_IMX23_CLKID_SSP0 33 +#define MXS_SSP_IMX28_CLKID_SSP0 46 + +static int mxsmmc_get_cd(struct udevice *dev) +{ + struct mxsmmc_plat *plat = dev_get_plat(dev); + struct mxsmmc_priv *priv = dev_get_priv(dev); + struct mxs_ssp_regs *ssp_regs = priv->regs; + + if (plat->non_removable) + return 1; + + return !(readl(&ssp_regs->hw_ssp_status) & SSP_STATUS_CARD_DETECT); +} + +static int mxsmmc_set_ios(struct udevice *dev) +{ + struct mxsmmc_plat *plat = dev_get_plat(dev); + struct mxsmmc_priv *priv = dev_get_priv(dev); + struct mxs_ssp_regs *ssp_regs = priv->regs; + struct mmc *mmc = &plat->mmc; + + /* Set the clock speed */ + if (mmc->clock) + mxs_set_ssp_busclock(priv->clkid, mmc->clock / 1000); + + switch (mmc->bus_width) { + case 1: + priv->buswidth = SSP_CTRL0_BUS_WIDTH_ONE_BIT; + break; + case 4: + priv->buswidth = SSP_CTRL0_BUS_WIDTH_FOUR_BIT; + break; + case 8: + priv->buswidth = SSP_CTRL0_BUS_WIDTH_EIGHT_BIT; + break; + } + + /* Set the bus width */ + clrsetbits_le32(&ssp_regs->hw_ssp_ctrl0, + SSP_CTRL0_BUS_WIDTH_MASK, priv->buswidth); + + debug("MMC%d: Set %d bits bus width\n", mmc_get_blk_desc(mmc)->devnum, + mmc->bus_width); + + return 0; +} + +static int mxsmmc_init(struct udevice *dev) +{ + struct mxsmmc_priv *priv = dev_get_priv(dev); + struct mxs_ssp_regs *ssp_regs = priv->regs; + + /* Reset SSP */ + mxs_reset_block(&ssp_regs->hw_ssp_ctrl0_reg); + + /* Reconfigure the SSP block for MMC operation */ + writel(SSP_CTRL1_SSP_MODE_SD_MMC | + SSP_CTRL1_WORD_LENGTH_EIGHT_BITS | + SSP_CTRL1_DMA_ENABLE | + SSP_CTRL1_POLARITY | + SSP_CTRL1_RECV_TIMEOUT_IRQ_EN | + SSP_CTRL1_DATA_CRC_IRQ_EN | + SSP_CTRL1_DATA_TIMEOUT_IRQ_EN | + SSP_CTRL1_RESP_TIMEOUT_IRQ_EN | + SSP_CTRL1_RESP_ERR_IRQ_EN, + &ssp_regs->hw_ssp_ctrl1_set); + + /* Set initial bit clock 400 KHz */ + mxs_set_ssp_busclock(priv->clkid, 400); + + /* Send initial 74 clock cycles (185 us @ 400 KHz)*/ + writel(SSP_CMD0_CONT_CLKING_EN, &ssp_regs->hw_ssp_cmd0_set); + udelay(200); + writel(SSP_CMD0_CONT_CLKING_EN, &ssp_regs->hw_ssp_cmd0_clr); + + return 0; +} + +static int mxsmmc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct mxsmmc_plat *plat = dev_get_plat(dev); + struct mxsmmc_priv *priv = dev_get_priv(dev); + struct blk_desc *bdesc; + struct mmc *mmc; + int ret, clkid; + + debug("%s: probe\n", __func__); + +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct dtd_fsl_imx23_mmc *dtplat = &plat->dtplat; + struct phandle_1_arg *p1a = &dtplat->clocks[0]; + + priv->buswidth = dtplat->bus_width; + priv->regs = (struct mxs_ssp_regs *)dtplat->reg[0]; + priv->dma_channel = dtplat->dmas[1]; + clkid = p1a->arg[0]; + plat->non_removable = dtplat->non_removable; + + debug("OF_PLATDATA: regs: 0x%p bw: %d clkid: %d non_removable: %d\n", + priv->regs, priv->buswidth, clkid, plat->non_removable); +#else + priv->regs = (struct mxs_ssp_regs *)plat->base; + priv->dma_channel = plat->dma_id; + clkid = plat->clk_id; +#endif + +#ifdef CONFIG_MX28 + priv->clkid = clkid - MXS_SSP_IMX28_CLKID_SSP0; +#else /* CONFIG_MX23 */ + priv->clkid = clkid - MXS_SSP_IMX23_CLKID_SSP0; +#endif + mmc = &plat->mmc; + mmc->cfg = &plat->cfg; + mmc->dev = dev; + + priv->desc = mxs_dma_desc_alloc(); + if (!priv->desc) { + printf("%s: Cannot allocate DMA descriptor\n", __func__); + return -ENOMEM; + } + + ret = mxs_dma_init_channel(priv->dma_channel); + if (ret) + return ret; + + plat->cfg.name = "MXS MMC"; + plat->cfg.voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + + plat->cfg.host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT | + MMC_MODE_HS_52MHz | MMC_MODE_HS; + + /* + * SSPCLK = 480 * 18 / 29 / 1 = 297.731 MHz + * SSP bit rate = SSPCLK / (CLOCK_DIVIDE * (1 + CLOCK_RATE)), + * CLOCK_DIVIDE has to be an even value from 2 to 254, and + * CLOCK_RATE could be any integer from 0 to 255. + */ + plat->cfg.f_min = 400000; + plat->cfg.f_max = mxc_get_clock(MXC_SSP0_CLK + priv->clkid) * 1000 / 2; + plat->cfg.b_max = 0x20; + + bdesc = mmc_get_blk_desc(mmc); + if (!bdesc) { + printf("%s: No block device descriptor!\n", __func__); + return -ENODEV; + } + + if (plat->non_removable) + bdesc->removable = 0; + + ret = mxsmmc_init(dev); + if (ret) + printf("%s: MMC%d init error %d\n", __func__, + bdesc->devnum, ret); + + /* Set the initial clock speed */ + mmc_set_clock(mmc, 400000, MMC_CLK_ENABLE); + + upriv->mmc = mmc; + + return 0; +}; + +#if CONFIG_IS_ENABLED(BLK) +static int mxsmmc_bind(struct udevice *dev) +{ + struct mxsmmc_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} +#endif + +static const struct dm_mmc_ops mxsmmc_ops = { + .get_cd = mxsmmc_get_cd, + .send_cmd = mxsmmc_send_cmd, + .set_ios = mxsmmc_set_ios, +}; + +#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA) +static int mxsmmc_of_to_plat(struct udevice *bus) +{ + struct mxsmmc_plat *plat = dev_get_plat(bus); + u32 prop[2]; + int ret; + + plat->base = dev_read_addr(bus); + plat->buswidth = + dev_read_u32_default(bus, "bus-width", 1); + plat->non_removable = dev_read_bool(bus, "non-removable"); + + ret = dev_read_u32_array(bus, "dmas", prop, ARRAY_SIZE(prop)); + if (ret) { + printf("%s: Reading 'dmas' property failed!\n", __func__); + return ret; + } + plat->dma_id = prop[1]; + + ret = dev_read_u32_array(bus, "clocks", prop, ARRAY_SIZE(prop)); + if (ret) { + printf("%s: Reading 'clocks' property failed!\n", __func__); + return ret; + } + plat->clk_id = prop[1]; + + debug("%s: base=0x%x, bus_width=%d %s dma_id=%d clk_id=%d\n", + __func__, (uint)plat->base, plat->buswidth, + plat->non_removable ? "non-removable" : NULL, + plat->dma_id, plat->clk_id); + + return 0; +} + +static const struct udevice_id mxsmmc_ids[] = { + { .compatible = "fsl,imx23-mmc", }, + { .compatible = "fsl,imx28-mmc", }, + { /* sentinel */ } +}; +#endif + +U_BOOT_DRIVER(fsl_imx23_mmc) = { + .name = "fsl_imx23_mmc", + .id = UCLASS_MMC, +#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA) + .of_match = mxsmmc_ids, + .of_to_plat = mxsmmc_of_to_plat, +#endif + .ops = &mxsmmc_ops, +#if CONFIG_IS_ENABLED(BLK) + .bind = mxsmmc_bind, +#endif + .probe = mxsmmc_probe, + .priv_auto = sizeof(struct mxsmmc_priv), + .plat_auto = sizeof(struct mxsmmc_plat), +}; + +DM_DRIVER_ALIAS(fsl_imx23_mmc, fsl_imx28_mmc) +#endif /* CONFIG_DM_MMC */ diff --git a/roms/u-boot/drivers/mmc/nexell_dw_mmc.c b/roms/u-boot/drivers/mmc/nexell_dw_mmc.c new file mode 100644 index 000000000..2723e4887 --- /dev/null +++ b/roms/u-boot/drivers/mmc/nexell_dw_mmc.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2016 Nexell + * Youngbok, Park <park@nexell.co.kr> + * + * (C) Copyright 2019 Stefan Bosch <stefan_b@posteo.net> + */ + +#include <common.h> +#include <dm.h> +#include <dt-structs.h> +#include <dwmmc.h> +#include <log.h> +#include <syscon.h> +#include <asm/arch/reset.h> +#include <asm/arch/clk.h> + +#define DWMCI_CLKSEL 0x09C +#define DWMCI_SHIFT_0 0x0 +#define DWMCI_SHIFT_1 0x1 +#define DWMCI_SHIFT_2 0x2 +#define DWMCI_SHIFT_3 0x3 +#define DWMCI_SET_SAMPLE_CLK(x) (x) +#define DWMCI_SET_DRV_CLK(x) ((x) << 16) +#define DWMCI_SET_DIV_RATIO(x) ((x) << 24) +#define DWMCI_CLKCTRL 0x114 +#define NX_MMC_CLK_DELAY(x, y, a, b) ((((x) & 0xFF) << 0) |\ + (((y) & 0x03) << 16) |\ + (((a) & 0xFF) << 8) |\ + (((b) & 0x03) << 24)) + +struct nexell_mmc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct nexell_dwmmc_priv { + struct clk *clk; + struct dwmci_host host; + int fifo_size; + bool fifo_mode; + int frequency; + u32 min_freq; + u32 max_freq; + int d_delay; + int d_shift; + int s_delay; + int s_shift; + bool mmcboost; +}; + +struct clk *clk_get(const char *id); + +static int nx_dw_mmc_clksel(struct dwmci_host *host) +{ + /* host->priv is pointer to "struct udevice" */ + struct nexell_dwmmc_priv *priv = dev_get_priv(host->priv); + u32 val; + + if (priv->mmcboost) + val = DWMCI_SET_SAMPLE_CLK(DWMCI_SHIFT_0) | + DWMCI_SET_DRV_CLK(DWMCI_SHIFT_0) | DWMCI_SET_DIV_RATIO(1); + else + val = DWMCI_SET_SAMPLE_CLK(DWMCI_SHIFT_0) | + DWMCI_SET_DRV_CLK(DWMCI_SHIFT_0) | DWMCI_SET_DIV_RATIO(3); + + dwmci_writel(host, DWMCI_CLKSEL, val); + + return 0; +} + +static void nx_dw_mmc_reset(int ch) +{ + int rst_id = RESET_ID_SDMMC0 + ch; + + nx_rstcon_setrst(rst_id, 0); + nx_rstcon_setrst(rst_id, 1); +} + +static void nx_dw_mmc_clk_delay(struct udevice *dev) +{ + unsigned int delay; + struct nexell_dwmmc_priv *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + + delay = NX_MMC_CLK_DELAY(priv->d_delay, + priv->d_shift, priv->s_delay, priv->s_shift); + + writel(delay, (host->ioaddr + DWMCI_CLKCTRL)); + debug("%s: Values set: d_delay==%d, d_shift==%d, s_delay==%d, " + "s_shift==%d\n", __func__, priv->d_delay, priv->d_shift, + priv->s_delay, priv->s_shift); +} + +static unsigned int nx_dw_mmc_get_clk(struct dwmci_host *host, uint freq) +{ + struct clk *clk; + struct udevice *dev = host->priv; + struct nexell_dwmmc_priv *priv = dev_get_priv(dev); + + int index = host->dev_index; + char name[50] = { 0, }; + + clk = priv->clk; + if (!clk) { + sprintf(name, "%s.%d", DEV_NAME_SDHC, index); + clk = clk_get((const char *)name); + if (!clk) + return 0; + priv->clk = clk; + } + + return clk_get_rate(clk) / 2; +} + +static unsigned long nx_dw_mmc_set_clk(struct dwmci_host *host, + unsigned int rate) +{ + struct clk *clk; + char name[50] = { 0, }; + struct udevice *dev = host->priv; + struct nexell_dwmmc_priv *priv = dev_get_priv(dev); + + int index = host->dev_index; + + clk = priv->clk; + if (!clk) { + sprintf(name, "%s.%d", DEV_NAME_SDHC, index); + clk = clk_get((const char *)name); + if (!clk) { + debug("%s: clk_get(\"%s\") failed!\n", __func__, name); + return 0; + } + priv->clk = clk; + } + + clk_disable(clk); + rate = clk_set_rate(clk, rate); + clk_enable(clk); + + return rate; +} + +static int nexell_dwmmc_of_to_plat(struct udevice *dev) +{ + struct nexell_dwmmc_priv *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + int val = -1; + + debug("%s\n", __func__); + + host->name = dev->name; + host->ioaddr = dev_read_addr_ptr(dev); + host->buswidth = dev_read_u32_default(dev, "bus-width", 4); + host->get_mmc_clk = nx_dw_mmc_get_clk; + host->clksel = nx_dw_mmc_clksel; + host->priv = dev; + + val = dev_read_u32_default(dev, "index", -1); + if (val < 0 || val > 2) { + debug(" 'index' missing/invalid!\n"); + return -EINVAL; + } + host->dev_index = val; + + priv->fifo_size = dev_read_u32_default(dev, "fifo-size", 0x20); + priv->fifo_mode = dev_read_bool(dev, "fifo-mode"); + priv->frequency = dev_read_u32_default(dev, "frequency", 50000000); + priv->max_freq = dev_read_u32_default(dev, "max-frequency", 50000000); + priv->min_freq = 400000; /* 400 kHz */ + priv->d_delay = dev_read_u32_default(dev, "drive_dly", 0); + priv->d_shift = dev_read_u32_default(dev, "drive_shift", 3); + priv->s_delay = dev_read_u32_default(dev, "sample_dly", 0); + priv->s_shift = dev_read_u32_default(dev, "sample_shift", 2); + priv->mmcboost = dev_read_u32_default(dev, "mmcboost", 0); + + debug(" index==%d, name==%s, ioaddr==0x%08x\n", + host->dev_index, host->name, (u32)host->ioaddr); + return 0; +} + +static int nexell_dwmmc_probe(struct udevice *dev) +{ + struct nexell_mmc_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct nexell_dwmmc_priv *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + struct udevice *pwr_dev __maybe_unused; + + host->fifoth_val = MSIZE(0x2) | + RX_WMARK(priv->fifo_size / 2 - 1) | + TX_WMARK(priv->fifo_size / 2); + + host->fifo_mode = priv->fifo_mode; + + dwmci_setup_cfg(&plat->cfg, host, priv->max_freq, priv->min_freq); + host->mmc = &plat->mmc; + host->mmc->priv = &priv->host; + host->mmc->dev = dev; + upriv->mmc = host->mmc; + + if (nx_dw_mmc_set_clk(host, priv->frequency * 4) != + priv->frequency * 4) { + debug("%s: nx_dw_mmc_set_clk(host, %d) failed!\n", + __func__, priv->frequency * 4); + return -EIO; + } + debug("%s: nx_dw_mmc_set_clk(host, %d) OK\n", + __func__, priv->frequency * 4); + + nx_dw_mmc_reset(host->dev_index); + nx_dw_mmc_clk_delay(dev); + + return dwmci_probe(dev); +} + +static int nexell_dwmmc_bind(struct udevice *dev) +{ + struct nexell_mmc_plat *plat = dev_get_plat(dev); + + return dwmci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id nexell_dwmmc_ids[] = { + { .compatible = "nexell,nexell-dwmmc" }, + { } +}; + +U_BOOT_DRIVER(nexell_dwmmc_drv) = { + .name = "nexell_dwmmc", + .id = UCLASS_MMC, + .of_match = nexell_dwmmc_ids, + .of_to_plat = nexell_dwmmc_of_to_plat, + .ops = &dm_dwmci_ops, + .bind = nexell_dwmmc_bind, + .probe = nexell_dwmmc_probe, + .priv_auto = sizeof(struct nexell_dwmmc_priv), + .plat_auto = sizeof(struct nexell_mmc_plat), +}; diff --git a/roms/u-boot/drivers/mmc/octeontx_hsmmc.c b/roms/u-boot/drivers/mmc/octeontx_hsmmc.c new file mode 100644 index 000000000..2e569a9e0 --- /dev/null +++ b/roms/u-boot/drivers/mmc/octeontx_hsmmc.c @@ -0,0 +1,3997 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Marvell International Ltd. + */ + +#include <clk.h> +#include <cpu_func.h> +#include <dm.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <env.h> +#include <errno.h> +#include <fdtdec.h> +#include <log.h> +#include <malloc.h> +#include <memalign.h> +#include <mmc.h> +#include <part.h> +#include <pci.h> +#include <pci_ids.h> +#include <power/regulator.h> +#include <time.h> +#include <watchdog.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/libfdt.h> + +#if defined(CONFIG_ARCH_OCTEON) +#include <mach/octeon-model.h> +#include <mach/cvmx-regs.h> +#include <mach/cvmx-mio-emm-defs.h> +#else +#include <asm/arch/board.h> +#include <asm/arch/clock.h> +#include <asm/arch/csrs/csrs-mio_emm.h> +#endif + +#include "octeontx_hsmmc.h" + +/* Use dummy implementation for MIPS Octeon to always return false */ +#if defined(CONFIG_ARCH_OCTEON) +#define otx_is_soc(ver) 0 +#endif + +#define MMC_TIMEOUT_SHORT 20 /* in ms */ +#define MMC_TIMEOUT_LONG 1000 +#define MMC_TIMEOUT_ERASE 10000 + +#define MMC_DEFAULT_DATA_IN_TAP 10 +#define MMC_DEFAULT_CMD_IN_TAP 10 +#define MMC_DEFAULT_CMD_OUT_TAP 39 +#define MMC_DEFAULT_DATA_OUT_TAP 39 +#define MMC_DEFAULT_HS200_CMD_IN_TAP 24 +#define MMC_DEFAULT_HS200_DATA_IN_TAP 24 +#define MMC_DEFAULT_HS200_CMD_OUT_TAP (otx_is_soc(CN95XX) ? 10 : 5) +#define MMC_DEFAULT_HS200_DATA_OUT_TAP (otx_is_soc(CN95XX) ? 10 : 5) +#define MMC_DEFAULT_HS400_CMD_OUT_TAP (otx_is_soc(CN95XX) ? 10 : 5) +#define MMC_DEFAULT_HS400_DATA_OUT_TAP (otx_is_soc(CN95XX) ? 5 : 3) +#define MMC_DEFAULT_HS200_CMD_OUT_DLY 800 /* Delay in ps */ +#define MMC_DEFAULT_HS200_DATA_OUT_DLY 800 /* Delay in ps */ +#define MMC_DEFAULT_HS400_CMD_OUT_DLY 800 /* Delay in ps */ +#define MMC_DEFAULT_HS400_DATA_OUT_DLY 400 /* Delay in ps */ +#define MMC_DEFAULT_SD_UHS_SDR104_CMD_OUT_TAP MMC_DEFAULT_HS200_CMD_OUT_TAP +#define MMC_DEFAULT_SD_UHS_SDR104_DATA_OUT_TAP MMC_DEFAULT_HS200_DATA_OUT_TAP +#define MMC_LEGACY_DEFAULT_CMD_OUT_TAP 39 +#define MMC_LEGACY_DEFAULT_DATA_OUT_TAP 39 +#define MMC_SD_LEGACY_DEFAULT_CMD_OUT_TAP 63 +#define MMC_SD_LEGACY_DEFAULT_DATA_OUT_TAP 63 +#define MMC_HS_CMD_OUT_TAP 32 +#define MMC_HS_DATA_OUT_TAP 32 +#define MMC_SD_HS_CMD_OUT_TAP 26 +#define MMC_SD_HS_DATA_OUT_TAP 26 +#define MMC_SD_UHS_SDR25_CMD_OUT_TAP 26 +#define MMC_SD_UHS_SDR25_DATA_OUT_TAP 26 +#define MMC_SD_UHS_SDR50_CMD_OUT_TAP 26 +#define MMC_SD_UHS_SDR50_DATA_OUT_TAP 26 +#define MMC_DEFAULT_TAP_DELAY 4 +#define TOTAL_NO_OF_TAPS 512 +static void octeontx_mmc_switch_to(struct mmc *mmc); +static void set_wdog(struct mmc *mmc, u64 us); +static void do_switch(struct mmc *mmc, union mio_emm_switch emm_switch); +static int octeontx_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data); +static int octeontx_mmc_configure_delay(struct mmc *mmc); +static int octeontx_mmc_calibrate_delay(struct mmc *mmc); +#if !defined(CONFIG_ARCH_OCTEON) +static int octeontx2_mmc_calc_delay(struct mmc *mmc, int delay); +static void octeontx_mmc_set_timing(struct mmc *mmc); +static int octeontx_mmc_set_input_bus_timing(struct mmc *mmc); +static int octeontx_mmc_set_output_bus_timing(struct mmc *mmc); +#endif + +static bool host_probed; + +/** + * Get the slot data structure from a MMC data structure + */ +static inline struct octeontx_mmc_slot *mmc_to_slot(struct mmc *mmc) +{ + return container_of(mmc, struct octeontx_mmc_slot, mmc); +} + +static inline struct octeontx_mmc_host *mmc_to_host(struct mmc *mmc) +{ + return mmc_to_slot(mmc)->host; +} + +static inline struct octeontx_mmc_slot *dev_to_mmc_slot(struct udevice *dev) +{ + return dev_get_priv(dev); +} + +static inline struct mmc *dev_to_mmc(struct udevice *dev) +{ + return &((struct octeontx_mmc_slot *)dev_get_priv(dev))->mmc; +} + +#ifdef DEBUG +const char *mmc_reg_str(u64 reg) +{ + if (reg == MIO_EMM_DMA_CFG()) + return "MIO_EMM_DMA_CFG"; + if (reg == MIO_EMM_DMA_ADR()) + return "MIO_EMM_DMA_ADR"; + if (reg == MIO_EMM_DMA_INT()) + return "MIO_EMM_DMA_INT"; + if (reg == MIO_EMM_CFG()) + return "MIO_EMM_CFG"; + if (reg == MIO_EMM_MODEX(0)) + return "MIO_EMM_MODE0"; + if (reg == MIO_EMM_MODEX(1)) + return "MIO_EMM_MODE1"; + if (reg == MIO_EMM_MODEX(2)) + return "MIO_EMM_MODE2"; + if (reg == MIO_EMM_MODEX(3)) + return "MIO_EMM_MODE3"; + if (reg == MIO_EMM_IO_CTL()) + return "MIO_EMM_IO_CTL"; + if (reg == MIO_EMM_SWITCH()) + return "MIO_EMM_SWITCH"; + if (reg == MIO_EMM_DMA()) + return "MIO_EMM_DMA"; + if (reg == MIO_EMM_CMD()) + return "MIO_EMM_CMD"; + if (reg == MIO_EMM_RSP_STS()) + return "MIO_EMM_RSP_STS"; + if (reg == MIO_EMM_RSP_LO()) + return "MIO_EMM_RSP_LO"; + if (reg == MIO_EMM_RSP_HI()) + return "MIO_EMM_RSP_HI"; + if (reg == MIO_EMM_INT()) + return "MIO_EMM_INT"; + if (reg == MIO_EMM_WDOG()) + return "MIO_EMM_WDOG"; + if (reg == MIO_EMM_DMA_ARG()) + return "MIO_EMM_DMA_ARG"; + if (IS_ENABLED(CONFIG_ARCH_OCTEONTX)) { + if (reg == MIO_EMM_SAMPLE()) + return "MIO_EMM_SAMPLE"; + } + if (reg == MIO_EMM_STS_MASK()) + return "MIO_EMM_STS_MASK"; + if (reg == MIO_EMM_RCA()) + return "MIO_EMM_RCA"; + if (reg == MIO_EMM_BUF_IDX()) + return "MIO_EMM_BUF_IDX"; + if (reg == MIO_EMM_BUF_DAT()) + return "MIO_EMM_BUF_DAT"; + if (!IS_ENABLED(CONFIG_ARCH_OCTEONTX)) { + if (reg == MIO_EMM_CALB()) + return "MIO_EMM_CALB"; + if (reg == MIO_EMM_TAP()) + return "MIO_EMM_TAP"; + if (reg == MIO_EMM_TIMING()) + return "MIO_EMM_TIMING"; + if (reg == MIO_EMM_DEBUG()) + return "MIO_EMM_DEBUG"; + } + + return "UNKNOWN"; +} +#endif + +static void octeontx_print_rsp_sts(struct mmc *mmc) +{ +#ifdef DEBUG + union mio_emm_rsp_sts emm_rsp_sts; + const struct octeontx_mmc_host *host = mmc_to_host(mmc); + static const char * const ctype_xor_str[] = { + "No data", + "Read data into Dbuf", + "Write data from Dbuf", + "Reserved" + }; + + static const char * const rtype_xor_str[] = { + "No response", + "R1, 48 bits", + "R2, 136 bits", + "R3, 48 bits", + "R4, 48 bits", + "R5, 48 bits", + "Reserved 6", + "Reserved 7" + }; + + emm_rsp_sts.u = readq(host->base_addr + MIO_EMM_RSP_STS()); + printf("\nMIO_EMM_RSP_STS: 0x%016llx\n", emm_rsp_sts.u); + printf(" 60-61: bus_id: %u\n", emm_rsp_sts.s.bus_id); + printf(" 59: cmd_val: %s\n", + emm_rsp_sts.s.cmd_val ? "yes" : "no"); + printf(" 58: switch_val: %s\n", + emm_rsp_sts.s.switch_val ? "yes" : "no"); + printf(" 57: dma_val: %s\n", + emm_rsp_sts.s.dma_val ? "yes" : "no"); + printf(" 56: dma_pend: %s\n", + emm_rsp_sts.s.dma_pend ? "yes" : "no"); + printf(" 28: dbuf_err: %s\n", + emm_rsp_sts.s.dbuf_err ? "yes" : "no"); + printf(" 23: dbuf: %u\n", emm_rsp_sts.s.dbuf); + printf(" 22: blk_timeout: %s\n", + emm_rsp_sts.s.blk_timeout ? "yes" : "no"); + printf(" 21: blk_crc_err: %s\n", + emm_rsp_sts.s.blk_crc_err ? "yes" : "no"); + printf(" 20: rsp_busybit: %s\n", + emm_rsp_sts.s.rsp_busybit ? "yes" : "no"); + printf(" 19: stp_timeout: %s\n", + emm_rsp_sts.s.stp_timeout ? "yes" : "no"); + printf(" 18: stp_crc_err: %s\n", + emm_rsp_sts.s.stp_crc_err ? "yes" : "no"); + printf(" 17: stp_bad_sts: %s\n", + emm_rsp_sts.s.stp_bad_sts ? "yes" : "no"); + printf(" 16: stp_val: %s\n", + emm_rsp_sts.s.stp_val ? "yes" : "no"); + printf(" 15: rsp_timeout: %s\n", + emm_rsp_sts.s.rsp_timeout ? "yes" : "no"); + printf(" 14: rsp_crc_err: %s\n", + emm_rsp_sts.s.rsp_crc_err ? "yes" : "no"); + printf(" 13: rsp_bad_sts: %s\n", + emm_rsp_sts.s.rsp_bad_sts ? "yes" : "no"); + printf(" 12: rsp_val: %s\n", + emm_rsp_sts.s.rsp_val ? "yes" : "no"); + printf(" 9-11: rsp_type: %s\n", + rtype_xor_str[emm_rsp_sts.s.rsp_type]); + printf(" 7-8: cmd_type: %s\n", + ctype_xor_str[emm_rsp_sts.s.cmd_type]); + printf(" 1-6: cmd_idx: %u\n", + emm_rsp_sts.s.cmd_idx); + printf(" 0: cmd_done: %s\n", + emm_rsp_sts.s.cmd_done ? "yes" : "no"); +#endif +} + +static inline u64 read_csr(struct mmc *mmc, u64 reg) +{ + const struct octeontx_mmc_host *host = mmc_to_host(mmc); + u64 value = readq(host->base_addr + reg); +#ifdef DEBUG_CSR + printf(" %s: %s(0x%p) => 0x%llx\n", __func__, + mmc_reg_str(reg), host->base_addr + reg, + value); +#endif + return value; +} + +/** + * Writes to a CSR register + * + * @param[in] mmc pointer to mmc data structure + * @param reg register offset + * @param value value to write to register + */ +static inline void write_csr(struct mmc *mmc, u64 reg, u64 value) +{ + const struct octeontx_mmc_host *host = mmc_to_host(mmc); + void *addr = host->base_addr + reg; + +#ifdef DEBUG_CSR + printf(" %s: %s(0x%p) <= 0x%llx\n", __func__, mmc_reg_str(reg), + addr, value); +#endif + writeq(value, addr); +} + +#ifdef DEBUG +static void mmc_print_status(u32 status) +{ +#ifdef DEBUG_STATUS + static const char * const state[] = { + "Idle", /* 0 */ + "Ready", /* 1 */ + "Ident", /* 2 */ + "Standby", /* 3 */ + "Tran", /* 4 */ + "Data", /* 5 */ + "Receive", /* 6 */ + "Program", /* 7 */ + "Dis", /* 8 */ + "Btst", /* 9 */ + "Sleep", /* 10 */ + "reserved", /* 11 */ + "reserved", /* 12 */ + "reserved", /* 13 */ + "reserved", /* 14 */ + "reserved" /* 15 */ }; + if (status & R1_APP_CMD) + puts("MMC ACMD\n"); + if (status & R1_SWITCH_ERROR) + puts("MMC switch error\n"); + if (status & R1_READY_FOR_DATA) + puts("MMC ready for data\n"); + printf("MMC %s state\n", state[R1_CURRENT_STATE(status)]); + if (status & R1_ERASE_RESET) + puts("MMC erase reset\n"); + if (status & R1_WP_ERASE_SKIP) + puts("MMC partial erase due to write protected blocks\n"); + if (status & R1_CID_CSD_OVERWRITE) + puts("MMC CID/CSD overwrite error\n"); + if (status & R1_ERROR) + puts("MMC undefined device error\n"); + if (status & R1_CC_ERROR) + puts("MMC device error\n"); + if (status & R1_CARD_ECC_FAILED) + puts("MMC internal ECC failed to correct data\n"); + if (status & R1_ILLEGAL_COMMAND) + puts("MMC illegal command\n"); + if (status & R1_COM_CRC_ERROR) + puts("MMC CRC of previous command failed\n"); + if (status & R1_LOCK_UNLOCK_FAILED) + puts("MMC sequence or password error in lock/unlock device command\n"); + if (status & R1_CARD_IS_LOCKED) + puts("MMC device locked by host\n"); + if (status & R1_WP_VIOLATION) + puts("MMC attempt to program write protected block\n"); + if (status & R1_ERASE_PARAM) + puts("MMC invalid selection of erase groups for erase\n"); + if (status & R1_ERASE_SEQ_ERROR) + puts("MMC error in sequence of erase commands\n"); + if (status & R1_BLOCK_LEN_ERROR) + puts("MMC block length error\n"); + if (status & R1_ADDRESS_ERROR) + puts("MMC address misalign error\n"); + if (status & R1_OUT_OF_RANGE) + puts("MMC address out of range\n"); +#endif +} +#endif + +#if !defined(CONFIG_ARCH_OCTEON) +/** + * Print out all of the register values where mmc is optional + * + * @param mmc MMC device (can be NULL) + * @param host Pointer to host data structure (can be NULL if mmc is !NULL) + */ +static void octeontx_mmc_print_registers2(struct mmc *mmc, + struct octeontx_mmc_host *host) +{ + struct octeontx_mmc_slot *slot = mmc ? mmc->priv : NULL; + union mio_emm_dma_cfg emm_dma_cfg; + union mio_emm_dma_adr emm_dma_adr; + union mio_emm_dma_int emm_dma_int; + union mio_emm_cfg emm_cfg; + union mio_emm_modex emm_mode; + union mio_emm_switch emm_switch; + union mio_emm_dma emm_dma; + union mio_emm_cmd emm_cmd; + union mio_emm_rsp_sts emm_rsp_sts; + union mio_emm_rsp_lo emm_rsp_lo; + union mio_emm_rsp_hi emm_rsp_hi; + union mio_emm_int emm_int; + union mio_emm_wdog emm_wdog; + union mio_emm_sample emm_sample; + union mio_emm_calb emm_calb; + union mio_emm_tap emm_tap; + union mio_emm_timing emm_timing; + union mio_emm_io_ctl io_ctl; + union mio_emm_debug emm_debug; + union mio_emm_sts_mask emm_sts_mask; + union mio_emm_rca emm_rca; + int bus; + + static const char * const bus_width_str[] = { + "1-bit data bus (power on)", + "4-bit data bus", + "8-bit data bus", + "reserved (3)", + "reserved (4)", + "4-bit data bus (dual data rate)", + "8-bit data bus (dual data rate)", + "reserved (7)", + "reserved (8)", + "invalid (9)", + "invalid (10)", + "invalid (11)", + "invalid (12)", + "invalid (13)", + "invalid (14)", + "invalid (15)", + }; + static const char * const ctype_xor_str[] = { + "No data", + "Read data into Dbuf", + "Write data from Dbuf", + "Reserved" + }; + + static const char * const rtype_xor_str[] = { + "No response", + "R1, 48 bits", + "R2, 136 bits", + "R3, 48 bits", + "R4, 48 bits", + "R5, 48 bits", + "Reserved 6", + "Reserved 7" + }; + + if (!host && mmc) + host = mmc_to_host(mmc); + + if (mmc) + printf("%s: bus id: %u\n", __func__, slot->bus_id); + emm_dma_cfg.u = readq(host->base_addr + MIO_EMM_DMA_CFG()); + printf("MIO_EMM_DMA_CFG: 0x%016llx\n", + emm_dma_cfg.u); + printf(" 63: en: %s\n", + emm_dma_cfg.s.en ? "enabled" : "disabled"); + printf(" 62: rw: %s\n", + emm_dma_cfg.s.rw ? "write" : "read"); + printf(" 61: clr: %s\n", + emm_dma_cfg.s.clr ? "clear" : "not clear"); + printf(" 59: swap32: %s\n", + emm_dma_cfg.s.swap32 ? "yes" : "no"); + printf(" 58: swap16: %s\n", + emm_dma_cfg.s.swap16 ? "yes" : "no"); + printf(" 57: swap8: %s\n", + emm_dma_cfg.s.swap8 ? "yes" : "no"); + printf(" 56: endian: %s\n", + emm_dma_cfg.s.endian ? "little" : "big"); + printf(" 36-55: size: %u\n", + emm_dma_cfg.s.size); + + emm_dma_adr.u = readq(host->base_addr + MIO_EMM_DMA_ADR()); + printf("MIO_EMM_DMA_ADR: 0x%016llx\n", emm_dma_adr.u); + printf(" 0-49: adr: 0x%llx\n", + (u64)emm_dma_adr.s.adr); + + emm_dma_int.u = readq(host->base_addr + MIO_EMM_DMA_INT()); + printf("\nMIO_EMM_DMA_INT: 0x%016llx\n", + emm_dma_int.u); + printf(" 1: FIFO: %s\n", + emm_dma_int.s.fifo ? "yes" : "no"); + printf(" 0: Done: %s\n", + emm_dma_int.s.done ? "yes" : "no"); + emm_cfg.u = readq(host->base_addr + MIO_EMM_CFG()); + + printf("\nMIO_EMM_CFG: 0x%016llx\n", + emm_cfg.u); + printf(" 3: bus_ena3: %s\n", + emm_cfg.s.bus_ena & 0x08 ? "yes" : "no"); + printf(" 2: bus_ena2: %s\n", + emm_cfg.s.bus_ena & 0x04 ? "yes" : "no"); + printf(" 1: bus_ena1: %s\n", + emm_cfg.s.bus_ena & 0x02 ? "yes" : "no"); + printf(" 0: bus_ena0: %s\n", + emm_cfg.s.bus_ena & 0x01 ? "yes" : "no"); + for (bus = 0; bus < 4; bus++) { + emm_mode.u = readq(host->base_addr + MIO_EMM_MODEX(bus)); + printf("\nMIO_EMM_MODE%u: 0x%016llx\n", + bus, emm_mode.u); + if (!IS_ENABLED(CONFIG_ARCH_OCTEONTX)) { + printf(" 50: hs400_timing: %s\n", + emm_mode.s.hs400_timing ? "yes" : "no"); + printf(" 49: hs200_timing: %s\n", + emm_mode.s.hs200_timing ? "yes" : "no"); + } + printf(" 48: hs_timing: %s\n", + emm_mode.s.hs_timing ? "yes" : "no"); + printf(" 40-42: bus_width: %s\n", + bus_width_str[emm_mode.s.bus_width]); + printf(" 32-35: power_class %u\n", + emm_mode.s.power_class); + printf(" 16-31: clk_hi: %u\n", + emm_mode.s.clk_hi); + printf(" 0-15: clk_lo: %u\n", + emm_mode.s.clk_lo); + } + + emm_switch.u = readq(host->base_addr + MIO_EMM_SWITCH()); + printf("\nMIO_EMM_SWITCH: 0x%016llx\n", emm_switch.u); + printf(" 60-61: bus_id: %u\n", emm_switch.s.bus_id); + printf(" 59: switch_exe: %s\n", + emm_switch.s.switch_exe ? "yes" : "no"); + printf(" 58: switch_err0: %s\n", + emm_switch.s.switch_err0 ? "yes" : "no"); + printf(" 57: switch_err1: %s\n", + emm_switch.s.switch_err1 ? "yes" : "no"); + printf(" 56: switch_err2: %s\n", + emm_switch.s.switch_err2 ? "yes" : "no"); + printf(" 48: hs_timing: %s\n", + emm_switch.s.hs_timing ? "yes" : "no"); + printf(" 42-40: bus_width: %s\n", + bus_width_str[emm_switch.s.bus_width]); + printf(" 32-35: power_class: %u\n", + emm_switch.s.power_class); + printf(" 16-31: clk_hi: %u\n", + emm_switch.s.clk_hi); + printf(" 0-15: clk_lo: %u\n", emm_switch.s.clk_lo); + + emm_dma.u = readq(host->base_addr + MIO_EMM_DMA()); + printf("\nMIO_EMM_DMA: 0x%016llx\n", emm_dma.u); + printf(" 60-61: bus_id: %u\n", emm_dma.s.bus_id); + printf(" 59: dma_val: %s\n", + emm_dma.s.dma_val ? "yes" : "no"); + printf(" 58: sector: %s mode\n", + emm_dma.s.sector ? "sector" : "byte"); + printf(" 57: dat_null: %s\n", + emm_dma.s.dat_null ? "yes" : "no"); + printf(" 51-56: thres: %u\n", emm_dma.s.thres); + printf(" 50: rel_wr: %s\n", + emm_dma.s.rel_wr ? "yes" : "no"); + printf(" 49: rw: %s\n", + emm_dma.s.rw ? "write" : "read"); + printf(" 48: multi: %s\n", + emm_dma.s.multi ? "yes" : "no"); + printf(" 32-47: block_cnt: %u\n", + emm_dma.s.block_cnt); + printf(" 0-31: card_addr: 0x%x\n", + emm_dma.s.card_addr); + + emm_cmd.u = readq(host->base_addr + MIO_EMM_CMD()); + printf("\nMIO_EMM_CMD: 0x%016llx\n", emm_cmd.u); + printf("\n 62: skip_busy: %s\n", + emm_cmd.s.skip_busy ? "yes" : "no"); + printf(" 60-61: bus_id: %u\n", emm_cmd.s.bus_id); + printf(" 59: cmd_val: %s\n", + emm_cmd.s.cmd_val ? "yes" : "no"); + printf(" 55: dbuf: %u\n", emm_cmd.s.dbuf); + printf(" 49-54: offset: %u\n", emm_cmd.s.offset); + printf(" 41-42: ctype_xor: %s\n", + ctype_xor_str[emm_cmd.s.ctype_xor]); + printf(" 38-40: rtype_xor: %s\n", + rtype_xor_str[emm_cmd.s.rtype_xor]); + printf(" 32-37: cmd_idx: %u\n", emm_cmd.s.cmd_idx); + printf(" 0-31: arg: 0x%x\n", emm_cmd.s.arg); + + emm_rsp_sts.u = readq(host->base_addr + MIO_EMM_RSP_STS()); + printf("\nMIO_EMM_RSP_STS: 0x%016llx\n", emm_rsp_sts.u); + printf(" 60-61: bus_id: %u\n", emm_rsp_sts.s.bus_id); + printf(" 59: cmd_val: %s\n", + emm_rsp_sts.s.cmd_val ? "yes" : "no"); + printf(" 58: switch_val: %s\n", + emm_rsp_sts.s.switch_val ? "yes" : "no"); + printf(" 57: dma_val: %s\n", + emm_rsp_sts.s.dma_val ? "yes" : "no"); + printf(" 56: dma_pend: %s\n", + emm_rsp_sts.s.dma_pend ? "yes" : "no"); + printf(" 28: dbuf_err: %s\n", + emm_rsp_sts.s.dbuf_err ? "yes" : "no"); + printf(" 23: dbuf: %u\n", emm_rsp_sts.s.dbuf); + printf(" 22: blk_timeout: %s\n", + emm_rsp_sts.s.blk_timeout ? "yes" : "no"); + printf(" 21: blk_crc_err: %s\n", + emm_rsp_sts.s.blk_crc_err ? "yes" : "no"); + printf(" 20: rsp_busybit: %s\n", + emm_rsp_sts.s.rsp_busybit ? "yes" : "no"); + printf(" 19: stp_timeout: %s\n", + emm_rsp_sts.s.stp_timeout ? "yes" : "no"); + printf(" 18: stp_crc_err: %s\n", + emm_rsp_sts.s.stp_crc_err ? "yes" : "no"); + printf(" 17: stp_bad_sts: %s\n", + emm_rsp_sts.s.stp_bad_sts ? "yes" : "no"); + printf(" 16: stp_val: %s\n", + emm_rsp_sts.s.stp_val ? "yes" : "no"); + printf(" 15: rsp_timeout: %s\n", + emm_rsp_sts.s.rsp_timeout ? "yes" : "no"); + printf(" 14: rsp_crc_err: %s\n", + emm_rsp_sts.s.rsp_crc_err ? "yes" : "no"); + printf(" 13: rsp_bad_sts: %s\n", + emm_rsp_sts.s.rsp_bad_sts ? "yes" : "no"); + printf(" 12: rsp_val: %s\n", + emm_rsp_sts.s.rsp_val ? "yes" : "no"); + printf(" 9-11: rsp_type: %s\n", + rtype_xor_str[emm_rsp_sts.s.rsp_type]); + printf(" 7-8: cmd_type: %s\n", + ctype_xor_str[emm_rsp_sts.s.cmd_type]); + printf(" 1-6: cmd_idx: %u\n", + emm_rsp_sts.s.cmd_idx); + printf(" 0: cmd_done: %s\n", + emm_rsp_sts.s.cmd_done ? "yes" : "no"); + + emm_rsp_lo.u = readq(host->base_addr + MIO_EMM_RSP_LO()); + printf("\nMIO_EMM_RSP_STS_LO: 0x%016llx\n", emm_rsp_lo.u); + + emm_rsp_hi.u = readq(host->base_addr + MIO_EMM_RSP_HI()); + printf("\nMIO_EMM_RSP_STS_HI: 0x%016llx\n", emm_rsp_hi.u); + + emm_int.u = readq(host->base_addr + MIO_EMM_INT()); + printf("\nMIO_EMM_INT: 0x%016llx\n", emm_int.u); + printf(" 6: switch_err: %s\n", + emm_int.s.switch_err ? "yes" : "no"); + printf(" 5: switch_done: %s\n", + emm_int.s.switch_done ? "yes" : "no"); + printf(" 4: dma_err: %s\n", + emm_int.s.dma_err ? "yes" : "no"); + printf(" 3: cmd_err: %s\n", + emm_int.s.cmd_err ? "yes" : "no"); + printf(" 2: dma_done: %s\n", + emm_int.s.dma_done ? "yes" : "no"); + printf(" 1: cmd_done: %s\n", + emm_int.s.cmd_done ? "yes" : "no"); + printf(" 0: buf_done: %s\n", + emm_int.s.buf_done ? "yes" : "no"); + + emm_wdog.u = readq(host->base_addr + MIO_EMM_WDOG()); + printf("\nMIO_EMM_WDOG: 0x%016llx (%u)\n", + emm_wdog.u, emm_wdog.s.clk_cnt); + + if (IS_ENABLED(CONFIG_ARCH_OCTEONTX)) { + emm_sample.u = readq(host->base_addr + MIO_EMM_SAMPLE()); + printf("\nMIO_EMM_SAMPLE: 0x%016llx\n", + emm_sample.u); + printf(" 16-25: cmd_cnt: %u\n", + emm_sample.s.cmd_cnt); + printf(" 0-9: dat_cnt: %u\n", + emm_sample.s.dat_cnt); + } + + emm_sts_mask.u = readq(host->base_addr + MIO_EMM_STS_MASK()); + printf("\nMIO_EMM_STS_MASK: 0x%016llx\n", emm_sts_mask.u); + + emm_rca.u = readq(host->base_addr + MIO_EMM_RCA()); + printf("\nMIO_EMM_RCA: 0x%016llx\n", emm_rca.u); + printf(" 0-15: card_rca: 0x%04x\n", + emm_rca.s.card_rca); + if (!IS_ENABLED(CONFIG_ARCH_OCTEONTX)) { + emm_calb.u = readq(host->base_addr + MIO_EMM_CALB()); + printf("\nMIO_EMM_CALB: 0x%016llx\n", + emm_calb.u); + printf(" 0: start: %u\n", + emm_calb.s.start); + emm_tap.u = readq(host->base_addr + MIO_EMM_TAP()); + printf("\nMIO_EMM_TAP: 0x%016llx\n", + emm_tap.u); + printf(" 7-0: delay: %u\n", emm_tap.s.delay); + emm_timing.u = readq(host->base_addr + MIO_EMM_TIMING()); + printf("\nMIO_EMM_TIMING: 0x%016llx\n", + emm_timing.u); + printf(" 53-48: cmd_in_tap: %u\n", + emm_timing.s.cmd_in_tap); + printf(" 37-32: cmd_out_tap: %u\n", + emm_timing.s.cmd_out_tap); + printf(" 21-16: data_in_tap: %u\n", + emm_timing.s.data_in_tap); + printf(" 5-0: data_out_tap: %u\n", + emm_timing.s.data_out_tap); + io_ctl.u = readq(host->base_addr + MIO_EMM_IO_CTL()); + printf("\nMIO_IO_CTL: 0x%016llx\n", io_ctl.u); + printf(" 3-2: drive: %u (%u mA)\n", + io_ctl.s.drive, 2 << io_ctl.s.drive); + printf(" 0: slew: %u %s\n", io_ctl.s.slew, + io_ctl.s.slew ? "high" : "low"); + emm_debug.u = readq(host->base_addr + MIO_EMM_DEBUG()); + printf("\nMIO_EMM_DEBUG: 0x%016llx\n", + emm_debug.u); + printf(" 21: rdsync_rst 0x%x\n", + emm_debug.s.rdsync_rst); + printf(" 20: emmc_clk_disable 0x%x\n", + emm_debug.s.emmc_clk_disable); + printf(" 19-16: dma_sm: 0x%x\n", + emm_debug.s.dma_sm); + printf(" 15-12: data_sm: 0x%x\n", + emm_debug.s.data_sm); + printf(" 11-8: cmd_sm: 0x%x\n", + emm_debug.s.cmd_sm); + printf(" 0: clk_on: 0x%x\n", + emm_debug.s.clk_on); + } + + puts("\n"); +} + +/** + * Print out all of the register values + * + * @param mmc MMC device + */ +static void octeontx_mmc_print_registers(struct mmc *mmc) +{ +#ifdef DEBUG_REGISTERS + const int print = 1; +#else + const int print = 0; +#endif + if (print) + octeontx_mmc_print_registers2(mmc, mmc_to_host(mmc)); +} +#else +static void octeontx_mmc_print_registers(struct mmc *mmc) +{ + return; +} +#endif + +static const struct octeontx_sd_mods octeontx_cr_types[] = { +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD0 */ +{ {0, 3}, {0, 3}, {0, 0} }, /* CMD1 */ +{ {0, 2}, {0, 2}, {0, 0} }, /* CMD2 */ +{ {0, 1}, {0, 3}, {0, 0} }, /* CMD3 SD_CMD_SEND_RELATIVE_ADDR 0, 2 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD4 */ +{ {0, 1}, {0, 1}, {0, 0} }, /* CMD5 */ +{ {0, 1}, {1, 1}, {0, 1} }, /* + * CMD6 SD_CMD_SWITCH_FUNC 1,0 + * (ACMD) SD_APP_SET_BUS_WIDTH + */ +{ {0, 1}, {0, 1}, {0, 0} }, /* CMD7 */ +{ {1, 1}, {0, 3}, {0, 0} }, /* CMD8 SD_CMD_SEND_IF_COND 1,2 */ +{ {0, 2}, {0, 2}, {0, 0} }, /* CMD9 */ +{ {0, 2}, {0, 2}, {0, 0} }, /* CMD10 */ +{ {1, 1}, {0, 1}, {1, 1} }, /* CMD11 SD_CMD_SWITCH_UHS18V 1,0 */ +{ {0, 1}, {0, 1}, {0, 0} }, /* CMD12 */ +{ {0, 1}, {0, 1}, {1, 3} }, /* CMD13 (ACMD)) SD_CMD_APP_SD_STATUS 1,2 */ +{ {1, 1}, {1, 1}, {0, 0} }, /* CMD14 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD15 */ +{ {0, 1}, {0, 1}, {0, 0} }, /* CMD16 */ +{ {1, 1}, {1, 1}, {0, 0} }, /* CMD17 */ +{ {1, 1}, {1, 1}, {0, 0} }, /* CMD18 */ +{ {3, 1}, {3, 1}, {0, 0} }, /* CMD19 */ +{ {2, 1}, {0, 0}, {0, 0} }, /* CMD20 */ /* SD 2,0 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD21 */ +{ {0, 0}, {0, 0}, {1, 1} }, /* CMD22 (ACMD) SD_APP_SEND_NUM_WR_BLKS 1,0 */ +{ {0, 1}, {0, 1}, {0, 1} }, /* CMD23 */ /* SD ACMD 1,0 */ +{ {2, 1}, {2, 1}, {2, 1} }, /* CMD24 */ +{ {2, 1}, {2, 1}, {2, 1} }, /* CMD25 */ +{ {2, 1}, {2, 1}, {2, 1} }, /* CMD26 */ +{ {2, 1}, {2, 1}, {2, 1} }, /* CMD27 */ +{ {0, 1}, {0, 1}, {0, 1} }, /* CMD28 */ +{ {0, 1}, {0, 1}, {0, 1} }, /* CMD29 */ +{ {1, 1}, {1, 1}, {1, 1} }, /* CMD30 */ +{ {1, 1}, {1, 1}, {1, 1} }, /* CMD31 */ +{ {0, 0}, {0, 1}, {0, 0} }, /* CMD32 SD_CMD_ERASE_WR_BLK_START 0,1 */ +{ {0, 0}, {0, 1}, {0, 0} }, /* CMD33 SD_CMD_ERASE_WR_BLK_END 0,1 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD34 */ +{ {0, 1}, {0, 1}, {0, 1} }, /* CMD35 */ +{ {0, 1}, {0, 1}, {0, 1} }, /* CMD36 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD37 */ +{ {0, 1}, {0, 1}, {0, 1} }, /* CMD38 */ +{ {0, 4}, {0, 4}, {0, 4} }, /* CMD39 */ +{ {0, 5}, {0, 5}, {0, 5} }, /* CMD40 */ +{ {0, 0}, {0, 0}, {0, 3} }, /* CMD41 (ACMD) SD_CMD_APP_SEND_OP_COND 0,3 */ +{ {2, 1}, {2, 1}, {2, 1} }, /* CMD42 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD43 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD44 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD45 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD46 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD47 */ +{ {0, 0}, {1, 0}, {0, 0} }, /* CMD48 SD_CMD_READ_EXTR_SINGLE */ +{ {0, 0}, {2, 0}, {0, 0} }, /* CMD49 SD_CMD_WRITE_EXTR_SINGLE */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD50 */ +{ {0, 0}, {0, 0}, {1, 1} }, /* CMD51 (ACMD) SD_CMD_APP_SEND_SCR 1,1 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD52 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD53 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD54 */ +{ {0, 1}, {0, 1}, {0, 1} }, /* CMD55 */ +{ {0xff, 0xff}, {0xff, 0xff}, {0xff, 0xff} }, /* CMD56 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD57 */ +{ {0, 0}, {0, 3}, {0, 3} }, /* CMD58 SD_CMD_SPI_READ_OCR 0,3 */ +{ {0, 0}, {0, 1}, {0, 0} }, /* CMD59 SD_CMD_SPI_CRC_ON_OFF 0,1 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD60 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD61 */ +{ {0, 0}, {0, 0}, {0, 0} }, /* CMD62 */ +{ {0, 0}, {0, 0}, {0, 0} } /* CMD63 */ +}; + +/** + * Returns XOR values needed for SD commands and other quirks + * + * @param mmc mmc device + * @param cmd command information + * + * @return octeontx_mmc_cr_mods data structure with various quirks and flags + */ +static struct octeontx_mmc_cr_mods +octeontx_mmc_get_cr_mods(struct mmc *mmc, const struct mmc_cmd *cmd, + const struct mmc_data *data) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + struct octeontx_mmc_cr_mods cr = {0, 0}; + const struct octeontx_sd_mods *sdm = + &octeontx_cr_types[cmd->cmdidx & 0x3f]; + u8 c = sdm->mmc.c, r = sdm->mmc.r; + u8 desired_ctype = 0; + + if (IS_MMC(mmc)) { +#ifdef MMC_SUPPORTS_TUNING + if (cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200) { + if (cmd->resp_type == MMC_RSP_R1) + cr.rtype_xor = 1; + if (data && data->flags & MMC_DATA_READ) + cr.ctype_xor = 1; + } +#endif + return cr; + } + + if (cmd->cmdidx == 56) + c = (cmd->cmdarg & 1) ? 1 : 2; + + if (data) { + if (data->flags & MMC_DATA_READ) + desired_ctype = 1; + else if (data->flags & MMC_DATA_WRITE) + desired_ctype = 2; + } + + cr.ctype_xor = c ^ desired_ctype; + if (slot->is_acmd) + cr.rtype_xor = r ^ sdm->sdacmd.r; + else + cr.rtype_xor = r ^ sdm->sd.r; + + debug("%s(%s): mmc c: %d, mmc r: %d, desired c: %d, xor c: %d, xor r: %d\n", + __func__, mmc->dev->name, c, r, desired_ctype, + cr.ctype_xor, cr.rtype_xor); + return cr; +} + +/** + * Keep track of switch commands internally + */ +static void octeontx_mmc_track_switch(struct mmc *mmc, u32 cmd_arg) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + u8 how = (cmd_arg >> 24) & 3; + u8 where = (u8)(cmd_arg >> 16); + u8 val = (u8)(cmd_arg >> 8); + + slot->want_switch = slot->cached_switch; + + if (slot->is_acmd) + return; + + if (how != 3) + return; + + switch (where) { + case EXT_CSD_BUS_WIDTH: + slot->want_switch.s.bus_width = val; + break; + case EXT_CSD_POWER_CLASS: + slot->want_switch.s.power_class = val; + break; + case EXT_CSD_HS_TIMING: + slot->want_switch.s.hs_timing = 0; +#if !defined(CONFIG_ARCH_OCTEON) + slot->want_switch.s.hs200_timing = 0; + slot->want_switch.s.hs400_timing = 0; +#endif + switch (val & 0xf) { + case 0: + break; + case 1: + slot->want_switch.s.hs_timing = 1; + break; +#if !defined(CONFIG_ARCH_OCTEON) + case 2: + if (!slot->is_asim && !slot->is_emul) + slot->want_switch.s.hs200_timing = 1; + break; + case 3: + if (!slot->is_asim && !slot->is_emul) + slot->want_switch.s.hs400_timing = 1; + break; +#endif + default: + pr_err("%s(%s): Unsupported timing mode 0x%x\n", + __func__, mmc->dev->name, val & 0xf); + break; + } + break; + default: + break; + } +} + +static int octeontx_mmc_print_rsp_errors(struct mmc *mmc, + union mio_emm_rsp_sts rsp_sts) +{ + bool err = false; + const char *name = mmc->dev->name; + + if (rsp_sts.s.acc_timeout) { + pr_warn("%s(%s): acc_timeout\n", __func__, name); + err = true; + } + if (rsp_sts.s.dbuf_err) { + pr_warn("%s(%s): dbuf_err\n", __func__, name); + err = true; + } + if (rsp_sts.s.blk_timeout) { + pr_warn("%s(%s): blk_timeout\n", __func__, name); + err = true; + } + if (rsp_sts.s.blk_crc_err) { + pr_warn("%s(%s): blk_crc_err\n", __func__, name); + err = true; + } + if (rsp_sts.s.stp_timeout) { + pr_warn("%s(%s): stp_timeout\n", __func__, name); + err = true; + } + if (rsp_sts.s.stp_crc_err) { + pr_warn("%s(%s): stp_crc_err\n", __func__, name); + err = true; + } + if (rsp_sts.s.stp_bad_sts) { + pr_warn("%s(%s): stp_bad_sts\n", __func__, name); + err = true; + } + if (err) + pr_warn(" rsp_sts: 0x%llx\n", rsp_sts.u); + + return err ? -1 : 0; +} + +/** + * Starts a DMA operation for block read/write + * + * @param mmc mmc device + * @param write true if write operation + * @param clear true to clear DMA operation + * @param adr source or destination DMA address + * @param size size in blocks + * @param timeout timeout in ms + */ +static void octeontx_mmc_start_dma(struct mmc *mmc, bool write, + bool clear, u32 block, dma_addr_t adr, + u32 size, int timeout) +{ + const struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + union mio_emm_dma_cfg emm_dma_cfg; + union mio_emm_dma_adr emm_dma_adr; + union mio_emm_dma emm_dma; + + /* Clear any interrupts */ + write_csr(mmc, MIO_EMM_DMA_INT(), + read_csr(mmc, MIO_EMM_DMA_INT())); + + emm_dma_cfg.u = 0; + emm_dma_cfg.s.en = 1; + emm_dma_cfg.s.rw = !!write; + emm_dma_cfg.s.clr = !!clear; + emm_dma_cfg.s.size = ((u64)(size * mmc->read_bl_len) / 8) - 1; +#if __BYTE_ORDER != __BIG_ENDIAN + emm_dma_cfg.s.endian = 1; +#endif + emm_dma_adr.u = 0; + emm_dma_adr.s.adr = adr; + write_csr(mmc, MIO_EMM_DMA_ADR(), emm_dma_adr.u); + write_csr(mmc, MIO_EMM_DMA_CFG(), emm_dma_cfg.u); + + emm_dma.u = 0; + emm_dma.s.bus_id = slot->bus_id; + emm_dma.s.dma_val = 1; + emm_dma.s.rw = !!write; + emm_dma.s.sector = mmc->high_capacity ? 1 : 0; + + if (size > 1 && ((IS_SD(mmc) && (mmc->scr[0] & 2)) || !IS_SD(mmc))) + emm_dma.s.multi = 1; + else + emm_dma.s.multi = 0; + + emm_dma.s.block_cnt = size; + if (!mmc->high_capacity) + block *= mmc->read_bl_len; + emm_dma.s.card_addr = block; + debug("%s(%s): card address: 0x%x, size: %d, multi: %d\n", + __func__, mmc->dev->name, block, size, emm_dma.s.multi); + + if (timeout > 0) + timeout = (timeout * 1000) - 1000; + set_wdog(mmc, timeout); + + debug(" Writing 0x%llx to mio_emm_dma\n", emm_dma.u); + write_csr(mmc, MIO_EMM_DMA(), emm_dma.u); +} + +/** + * Waits for a DMA operation to complete + * + * @param mmc mmc device + * @param timeout timeout in ms + * + * @return 0 for success (could be DMA errors), -ETIMEDOUT on timeout + */ + +/** + * Cleanup DMA engine after a failure + * + * @param mmc mmc device + * @param rsp_sts rsp status + */ +static void octeontx_mmc_cleanup_dma(struct mmc *mmc, + union mio_emm_rsp_sts rsp_sts) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + union mio_emm_dma emm_dma; + ulong start; + int retries = 3; + + do { + debug("%s(%s): rsp_sts: 0x%llx, rsp_lo: 0x%llx, dma_int: 0x%llx\n", + __func__, mmc->dev->name, rsp_sts.u, + read_csr(mmc, MIO_EMM_RSP_LO()), + read_csr(mmc, MIO_EMM_DMA_INT())); + emm_dma.u = read_csr(mmc, MIO_EMM_DMA()); + emm_dma.s.dma_val = 1; + emm_dma.s.dat_null = 1; + emm_dma.s.bus_id = slot->bus_id; + write_csr(mmc, MIO_EMM_DMA(), emm_dma.u); + start = get_timer(0); + do { + rsp_sts.u = read_csr(mmc, MIO_EMM_RSP_STS()); + WATCHDOG_RESET(); + } while (get_timer(start) < 100 && + (rsp_sts.s.dma_val || rsp_sts.s.dma_pend)); + } while (retries-- >= 0 && rsp_sts.s.dma_pend); + if (rsp_sts.s.dma_val) + pr_err("%s(%s): Error: could not clean up DMA. RSP_STS: 0x%llx, RSP_LO: 0x%llx\n", + __func__, mmc->dev->name, rsp_sts.u, + read_csr(mmc, MIO_EMM_RSP_LO())); + debug(" rsp_sts after clearing up DMA: 0x%llx\n", + read_csr(mmc, MIO_EMM_RSP_STS())); +} + +/** + * Waits for a DMA operation to complete + * + * @param mmc mmc device + * @param timeout timeout in ms + * @param verbose true to print out error information + * + * @return 0 for success (could be DMA errors), -ETIMEDOUT on timeout + * or -EIO if IO error. + */ +static int octeontx_mmc_wait_dma(struct mmc *mmc, bool write, ulong timeout, + bool verbose) +{ + struct octeontx_mmc_host *host = mmc_to_host(mmc); + ulong start_time = get_timer(0); + union mio_emm_dma_int emm_dma_int; + union mio_emm_rsp_sts rsp_sts; + union mio_emm_dma emm_dma; + bool timed_out = false; + bool err = false; + + debug("%s(%s, %lu, %d), delay: %uus\n", __func__, mmc->dev->name, + timeout, verbose, host->dma_wait_delay); + + udelay(host->dma_wait_delay); + do { + emm_dma_int.u = read_csr(mmc, MIO_EMM_DMA_INT()); + rsp_sts.u = read_csr(mmc, MIO_EMM_RSP_STS()); + if (write) { + if ((rsp_sts.s.dma_pend && !rsp_sts.s.dma_val) || + rsp_sts.s.blk_timeout || + rsp_sts.s.stp_timeout || + rsp_sts.s.rsp_timeout) { + err = true; +#ifdef DEBUG + debug("%s: f1\n", __func__); + octeontx_mmc_print_rsp_errors(mmc, rsp_sts); +#endif + break; + } + } else { + if (rsp_sts.s.blk_crc_err || + (rsp_sts.s.dma_pend && !rsp_sts.s.dma_val)) { + err = true; +#if defined(DEBUG) + octeontx_mmc_print_rsp_errors(mmc, rsp_sts); +#endif + break; + } + } + if (rsp_sts.s.dma_pend) { + /* + * If this is set then an error has occurred. + * Try and restart the DMA operation. + */ + emm_dma.u = read_csr(mmc, MIO_EMM_DMA()); + if (verbose) { + pr_err("%s(%s): DMA pending error: rsp_sts: 0x%llx, dma_int: 0x%llx, emm_dma: 0x%llx\n", + __func__, mmc->dev->name, rsp_sts.u, + emm_dma_int.u, emm_dma.u); + octeontx_print_rsp_sts(mmc); + debug(" MIO_EMM_DEBUG: 0x%llx\n", + read_csr(mmc, MIO_EMM_DEBUG())); + pr_err("%s: Trying DMA resume...\n", __func__); + } + emm_dma.s.dma_val = 1; + emm_dma.s.dat_null = 1; + write_csr(mmc, MIO_EMM_DMA(), emm_dma.u); + udelay(10); + } else if (!rsp_sts.s.dma_val && emm_dma_int.s.done) { + break; + } + WATCHDOG_RESET(); + timed_out = (get_timer(start_time) > timeout); + } while (!timed_out); + + if (timed_out || err) { + if (verbose) { + pr_err("%s(%s): MMC DMA %s after %lu ms, rsp_sts: 0x%llx, dma_int: 0x%llx, rsp_sts_lo: 0x%llx, emm_dma: 0x%llx\n", + __func__, mmc->dev->name, + timed_out ? "timed out" : "error", + get_timer(start_time), rsp_sts.u, + emm_dma_int.u, + read_csr(mmc, MIO_EMM_RSP_LO()), + read_csr(mmc, MIO_EMM_DMA())); + octeontx_print_rsp_sts(mmc); + } + if (rsp_sts.s.dma_pend) + octeontx_mmc_cleanup_dma(mmc, rsp_sts); + } else { + write_csr(mmc, MIO_EMM_DMA_INT(), + read_csr(mmc, MIO_EMM_DMA_INT())); + } + + return timed_out ? -ETIMEDOUT : (err ? -EIO : 0); +} + +/** + * Read blocks from the MMC/SD device + * + * @param mmc mmc device + * @param cmd command + * @param data data for read + * @param verbose true to print out error information + * + * @return number of blocks read or 0 if error + */ +static int octeontx_mmc_read_blocks(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data, bool verbose) +{ + struct octeontx_mmc_host *host = mmc_to_host(mmc); + union mio_emm_rsp_sts rsp_sts; + dma_addr_t dma_addr = (dma_addr_t)dm_pci_virt_to_mem(host->dev, + data->dest); + ulong count; + ulong blkcnt = data->blocks; + ulong start = cmd->cmdarg; + int timeout = 1000 + blkcnt * 20; + bool timed_out = false; + bool multi_xfer = cmd->cmdidx == MMC_CMD_READ_MULTIPLE_BLOCK; + + debug("%s(%s): dest: %p, dma address: 0x%llx, blkcnt: %lu, start: %lu\n", + __func__, mmc->dev->name, data->dest, dma_addr, blkcnt, start); + debug("%s: rsp_sts: 0x%llx\n", __func__, + read_csr(mmc, MIO_EMM_RSP_STS())); + /* use max timeout for multi-block transfers */ + /* timeout = 0; */ + + /* + * If we have a valid SD card in the slot, we set the response bit + * mask to check for CRC errors and timeouts only. + * Otherwise, use the default power on reset value. + */ + write_csr(mmc, MIO_EMM_STS_MASK(), + IS_SD(mmc) ? 0x00b00000ull : 0xe4390080ull); + invalidate_dcache_range((u64)data->dest, + (u64)data->dest + blkcnt * data->blocksize); + + if (multi_xfer) { + octeontx_mmc_start_dma(mmc, false, false, start, dma_addr, + blkcnt, timeout); + timed_out = !!octeontx_mmc_wait_dma(mmc, false, timeout, + verbose); + rsp_sts.u = read_csr(mmc, MIO_EMM_RSP_STS()); + if (timed_out || rsp_sts.s.dma_val || rsp_sts.s.dma_pend) { + if (!verbose) + return 0; + + pr_err("%s(%s): Error: DMA timed out. rsp_sts: 0x%llx, emm_int: 0x%llx, dma_int: 0x%llx, rsp_lo: 0x%llx\n", + __func__, mmc->dev->name, rsp_sts.u, + read_csr(mmc, MIO_EMM_INT()), + read_csr(mmc, MIO_EMM_DMA_INT()), + read_csr(mmc, MIO_EMM_RSP_LO())); + pr_err("%s: block count: %lu, start: 0x%lx\n", + __func__, blkcnt, start); + octeontx_mmc_print_registers(mmc); + return 0; + } + } else { + count = blkcnt; + timeout = 1000; + do { + octeontx_mmc_start_dma(mmc, false, false, start, + dma_addr, 1, timeout); + dma_addr += mmc->read_bl_len; + start++; + + timed_out = !!octeontx_mmc_wait_dma(mmc, false, + timeout, verbose); + rsp_sts.u = read_csr(mmc, MIO_EMM_RSP_STS()); + if (timed_out || rsp_sts.s.dma_val || + rsp_sts.s.dma_pend) { + if (verbose) { + pr_err("%s: Error: DMA timed out. rsp_sts: 0x%llx, emm_int: 0x%llx, dma_int: 0x%llx, rsp_lo: 0x%llx\n", + __func__, rsp_sts.u, + read_csr(mmc, MIO_EMM_INT()), + read_csr(mmc, MIO_EMM_DMA_INT()), + read_csr(mmc, MIO_EMM_RSP_LO())); + pr_err("%s: block count: 1, start: 0x%lx\n", + __func__, start); + octeontx_mmc_print_registers(mmc); + } + return blkcnt - count; + } + WATCHDOG_RESET(); + } while (--count); + } +#ifdef DEBUG + debug("%s(%s): Read %lu (0x%lx) blocks starting at block %u (0x%x) to address %p (dma address 0x%llx)\n", + __func__, mmc->dev->name, blkcnt, blkcnt, + cmd->cmdarg, cmd->cmdarg, data->dest, + dm_pci_virt_to_mem(host->dev, data->dest)); + print_buffer(0, data->dest, 1, 0x200, 0); +#endif + return blkcnt; +} + +static int octeontx_mmc_poll_ready(struct mmc *mmc, ulong timeout) +{ + ulong start; + struct mmc_cmd cmd; + int err; + bool not_ready = false; + + memset(&cmd, 0, sizeof(cmd)); + cmd.cmdidx = MMC_CMD_SEND_STATUS; + cmd.cmdarg = mmc->rca << 16; + cmd.resp_type = MMC_RSP_R1; + start = get_timer(0); + do { + err = octeontx_mmc_send_cmd(mmc, &cmd, NULL); + if (err) { + pr_err("%s(%s): MMC command error: %d; Retry...\n", + __func__, mmc->dev->name, err); + not_ready = true; + } else if (cmd.response[0] & R1_READY_FOR_DATA) { + return 0; + } + WATCHDOG_RESET(); + } while (get_timer(start) < timeout); + + if (not_ready) + pr_err("%s(%s): MMC command error; Retry timeout\n", + __func__, mmc->dev->name); + return -ETIMEDOUT; +} + +static ulong octeontx_mmc_write_blocks(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct octeontx_mmc_host *host = mmc_to_host(mmc); + ulong start = cmd->cmdarg; + ulong blkcnt = data->blocks; + dma_addr_t dma_addr; + union mio_emm_rsp_sts rsp_sts; + union mio_emm_sts_mask emm_sts_mask; + ulong timeout; + int count; + bool timed_out = false; + bool multi_xfer = (blkcnt > 1) && + ((IS_SD(mmc) && mmc->scr[0] & 2) || !IS_SD(mmc)); + + octeontx_mmc_switch_to(mmc); + emm_sts_mask.u = 0; + emm_sts_mask.s.sts_msk = R1_BLOCK_WRITE_MASK; + write_csr(mmc, MIO_EMM_STS_MASK(), emm_sts_mask.u); + + if (octeontx_mmc_poll_ready(mmc, 10000)) { + pr_err("%s(%s): Ready timed out\n", __func__, mmc->dev->name); + return 0; + } + flush_dcache_range((u64)data->src, + (u64)data->src + blkcnt * mmc->write_bl_len); + dma_addr = (u64)dm_pci_virt_to_mem(host->dev, (void *)data->src); + if (multi_xfer) { + timeout = 5000 + 100 * blkcnt; + octeontx_mmc_start_dma(mmc, true, false, start, dma_addr, + blkcnt, timeout); + timed_out = !!octeontx_mmc_wait_dma(mmc, true, timeout, true); + rsp_sts.u = read_csr(mmc, MIO_EMM_RSP_STS()); + if (timed_out || rsp_sts.s.dma_val || rsp_sts.s.dma_pend) { + pr_err("%s(%s): Error: multi-DMA timed out after %lums. rsp_sts: 0x%llx, emm_int: 0x%llx, emm_dma_int: 0x%llx, rsp_sts_lo: 0x%llx, emm_dma: 0x%llx\n", + __func__, mmc->dev->name, timeout, + rsp_sts.u, + read_csr(mmc, MIO_EMM_INT()), + read_csr(mmc, MIO_EMM_DMA_INT()), + read_csr(mmc, MIO_EMM_RSP_LO()), + read_csr(mmc, MIO_EMM_DMA())); + return 0; + } + } else { + timeout = 5000; + count = blkcnt; + do { + octeontx_mmc_start_dma(mmc, true, false, start, + dma_addr, 1, timeout); + dma_addr += mmc->read_bl_len; + start++; + + timed_out = !!octeontx_mmc_wait_dma(mmc, true, timeout, + true); + rsp_sts.u = read_csr(mmc, MIO_EMM_RSP_STS()); + if (timed_out || rsp_sts.s.dma_val || + rsp_sts.s.dma_pend) { + pr_err("%s(%s): Error: single-DMA timed out after %lums. rsp_sts: 0x%llx, emm_int: 0x%llx, emm_dma_int: 0x%llx, rsp_sts_lo: 0x%llx, emm_dma: 0x%llx\n", + __func__, mmc->dev->name, timeout, + rsp_sts.u, + read_csr(mmc, MIO_EMM_RSP_STS()), + read_csr(mmc, MIO_EMM_DMA_INT()), + read_csr(mmc, MIO_EMM_RSP_LO()), + read_csr(mmc, MIO_EMM_DMA())); + return blkcnt - count; + } + WATCHDOG_RESET(); + } while (--count); + } + + return blkcnt; +} + +/** + * Send a command to the eMMC/SD device + * + * @param mmc mmc device + * @param cmd cmd to send and response + * @param data additional data + * @param flags + * @return 0 for success, otherwise error + */ +static int octeontx_mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + const char *name = slot->dev->name; + struct octeontx_mmc_cr_mods mods = {0, 0}; + union mio_emm_rsp_sts rsp_sts; + union mio_emm_cmd emm_cmd; + union mio_emm_rsp_lo rsp_lo; + union mio_emm_buf_idx emm_buf_idx; + union mio_emm_buf_dat emm_buf_dat; + ulong start; + int i; + ulong blkcnt; + + /** + * This constant has a 1 bit for each command which should have a short + * timeout and a 0 for each bit with a long timeout. Currently the + * following commands have a long timeout: + * CMD6, CMD17, CMD18, CMD24, CMD25, CMD32, CMD33, CMD35, CMD36 and + * CMD38. + */ + static const u64 timeout_short = 0xFFFFFFA4FCF9FFDFull; + uint timeout; + + if (cmd->cmdidx == MMC_CMD_SEND_EXT_CSD) { + union mio_emm_rca emm_rca; + + emm_rca.u = 0; + emm_rca.s.card_rca = mmc->rca; + write_csr(mmc, MIO_EMM_RCA(), emm_rca.u); + } + + if (timeout_short & (1ull << cmd->cmdidx)) + timeout = MMC_TIMEOUT_SHORT; + else if (cmd->cmdidx == MMC_CMD_SWITCH && IS_SD(mmc)) + timeout = 2560; + else if (cmd->cmdidx == MMC_CMD_ERASE) + timeout = MMC_TIMEOUT_ERASE; + else + timeout = MMC_TIMEOUT_LONG; + + debug("%s(%s): cmd idx: %u, arg: 0x%x, resp type: 0x%x, timeout: %u\n", + __func__, name, cmd->cmdidx, cmd->cmdarg, cmd->resp_type, + timeout); + if (data) + debug(" data: addr: %p, flags: 0x%x, blocks: %u, blocksize: %u\n", + data->dest, data->flags, data->blocks, data->blocksize); + + octeontx_mmc_switch_to(mmc); + + /* Clear any interrupts */ + write_csr(mmc, MIO_EMM_INT(), read_csr(mmc, MIO_EMM_INT())); + + /* + * We need to override the default command types and response types + * when dealing with SD cards. + */ + mods = octeontx_mmc_get_cr_mods(mmc, cmd, data); + + /* Handle block read/write/stop operations */ + switch (cmd->cmdidx) { + case MMC_CMD_GO_IDLE_STATE: + slot->tuned = false; + slot->hs200_tuned = false; + slot->hs400_tuned = false; + break; + case MMC_CMD_STOP_TRANSMISSION: + return 0; + case MMC_CMD_READ_MULTIPLE_BLOCK: + case MMC_CMD_READ_SINGLE_BLOCK: + pr_debug("%s(%s): Reading blocks\n", __func__, name); + blkcnt = octeontx_mmc_read_blocks(mmc, cmd, data, true); + return (blkcnt > 0) ? 0 : -1; + case MMC_CMD_WRITE_MULTIPLE_BLOCK: + case MMC_CMD_WRITE_SINGLE_BLOCK: + blkcnt = octeontx_mmc_write_blocks(mmc, cmd, data); + return (blkcnt > 0) ? 0 : -1; + case MMC_CMD_SELECT_CARD: + /* Set the RCA register (is it set automatically?) */ + if (IS_SD(mmc)) { + union mio_emm_rca emm_rca; + + emm_rca.u = 0; + emm_rca.s.card_rca = (cmd->cmdarg >> 16); + write_csr(mmc, MIO_EMM_RCA(), emm_rca.u); + debug("%s: Set SD relative address (RCA) to 0x%x\n", + __func__, emm_rca.s.card_rca); + } + break; + + case MMC_CMD_SWITCH: + if (!data && !slot->is_acmd) + octeontx_mmc_track_switch(mmc, cmd->cmdarg); + break; + } + + emm_cmd.u = 0; + emm_cmd.s.cmd_val = 1; + emm_cmd.s.bus_id = slot->bus_id; + emm_cmd.s.cmd_idx = cmd->cmdidx; + emm_cmd.s.arg = cmd->cmdarg; + emm_cmd.s.ctype_xor = mods.ctype_xor; + emm_cmd.s.rtype_xor = mods.rtype_xor; + if (data && data->blocks == 1 && data->blocksize != 512) { + emm_cmd.s.offset = + 64 - ((data->blocks * data->blocksize) / 8); + debug("%s: offset set to %u\n", __func__, emm_cmd.s.offset); + } + + if (data && data->flags & MMC_DATA_WRITE) { + u8 *src = (u8 *)data->src; + + if (!src) { + pr_err("%s(%s): Error: data source for cmd 0x%x is NULL!\n", + __func__, name, cmd->cmdidx); + return -1; + } + if (data->blocksize > 512) { + pr_err("%s(%s): Error: data for cmd 0x%x exceeds 512 bytes\n", + __func__, name, cmd->cmdidx); + return -1; + } +#ifdef DEBUG + debug("%s: Sending %d bytes data\n", __func__, data->blocksize); + print_buffer(0, src, 1, data->blocksize, 0); +#endif + emm_buf_idx.u = 0; + emm_buf_idx.s.inc = 1; + write_csr(mmc, MIO_EMM_BUF_IDX(), emm_buf_idx.u); + for (i = 0; i < (data->blocksize + 7) / 8; i++) { + memcpy(&emm_buf_dat.u, src, sizeof(emm_buf_dat.u)); + write_csr(mmc, MIO_EMM_BUF_DAT(), + cpu_to_be64(emm_buf_dat.u)); + src += sizeof(emm_buf_dat.u); + } + write_csr(mmc, MIO_EMM_BUF_IDX(), 0); + } + debug("%s(%s): Sending command %u (emm_cmd: 0x%llx)\n", __func__, + name, cmd->cmdidx, emm_cmd.u); + set_wdog(mmc, timeout * 1000); + write_csr(mmc, MIO_EMM_CMD(), emm_cmd.u); + + /* Wait for command to finish or time out */ + start = get_timer(0); + do { + rsp_sts.u = read_csr(mmc, MIO_EMM_RSP_STS()); + WATCHDOG_RESET(); + } while (!rsp_sts.s.cmd_done && !rsp_sts.s.rsp_timeout && + (get_timer(start) < timeout + 10)); + octeontx_mmc_print_rsp_errors(mmc, rsp_sts); + if (rsp_sts.s.rsp_timeout || !rsp_sts.s.cmd_done) { + debug("%s(%s): Error: command %u(0x%x) timed out. rsp_sts: 0x%llx\n", + __func__, name, cmd->cmdidx, cmd->cmdarg, rsp_sts.u); + octeontx_mmc_print_registers(mmc); + return -ETIMEDOUT; + } + if (rsp_sts.s.rsp_crc_err) { + debug("%s(%s): RSP CRC error, rsp_sts: 0x%llx, cmdidx: %u, arg: 0x%08x\n", + __func__, name, rsp_sts.u, cmd->cmdidx, cmd->cmdarg); + octeontx_mmc_print_registers(mmc); + return -1; + } + if (slot->bus_id != rsp_sts.s.bus_id) { + pr_warn("%s(%s): bus id mismatch, got %d, expected %d for command 0x%x(0x%x)\n", + __func__, name, + rsp_sts.s.bus_id, slot->bus_id, + cmd->cmdidx, cmd->cmdarg); + goto error; + } + if (rsp_sts.s.rsp_bad_sts) { + rsp_lo.u = read_csr(mmc, MIO_EMM_RSP_LO()); + debug("%s: Bad response for bus id %d, cmd id %d:\n" + " rsp_timeout: %d\n" + " rsp_bad_sts: %d\n" + " rsp_crc_err: %d\n", + __func__, slot->bus_id, cmd->cmdidx, + rsp_sts.s.rsp_timeout, + rsp_sts.s.rsp_bad_sts, + rsp_sts.s.rsp_crc_err); + if (rsp_sts.s.rsp_type == 1 && rsp_sts.s.rsp_bad_sts) { + debug(" Response status: 0x%llx\n", + (rsp_lo.u >> 8) & 0xffffffff); +#ifdef DEBUG + mmc_print_status((rsp_lo.u >> 8) & 0xffffffff); +#endif + } + goto error; + } + if (rsp_sts.s.cmd_idx != cmd->cmdidx) { + debug("%s(%s): Command response index %d does not match command index %d\n", + __func__, name, rsp_sts.s.cmd_idx, cmd->cmdidx); + octeontx_print_rsp_sts(mmc); + debug("%s: rsp_lo: 0x%llx\n", __func__, + read_csr(mmc, MIO_EMM_RSP_LO())); + + goto error; + } + + slot->is_acmd = (cmd->cmdidx == MMC_CMD_APP_CMD); + + if (!cmd->resp_type & MMC_RSP_PRESENT) + debug(" Response type: 0x%x, no response expected\n", + cmd->resp_type); + /* Get the response if present */ + if (rsp_sts.s.rsp_val && (cmd->resp_type & MMC_RSP_PRESENT)) { + union mio_emm_rsp_hi rsp_hi; + + rsp_lo.u = read_csr(mmc, MIO_EMM_RSP_LO()); + + switch (rsp_sts.s.rsp_type) { + case 1: + case 3: + case 4: + case 5: + cmd->response[0] = (rsp_lo.u >> 8) & 0xffffffffull; + debug(" response: 0x%08x\n", + cmd->response[0]); + cmd->response[1] = 0; + cmd->response[2] = 0; + cmd->response[3] = 0; + break; + case 2: + cmd->response[3] = rsp_lo.u & 0xffffffff; + cmd->response[2] = (rsp_lo.u >> 32) & 0xffffffff; + rsp_hi.u = read_csr(mmc, MIO_EMM_RSP_HI()); + cmd->response[1] = rsp_hi.u & 0xffffffff; + cmd->response[0] = (rsp_hi.u >> 32) & 0xffffffff; + debug(" response: 0x%08x 0x%08x 0x%08x 0x%08x\n", + cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + break; + default: + pr_err("%s(%s): Unknown response type 0x%x for command %d, arg: 0x%x, rsp_sts: 0x%llx\n", + __func__, name, rsp_sts.s.rsp_type, cmd->cmdidx, + cmd->cmdarg, rsp_sts.u); + return -1; + } + } else { + debug(" Response not expected\n"); + } + + if (data && data->flags & MMC_DATA_READ) { + u8 *dest = (u8 *)data->dest; + + if (!dest) { + pr_err("%s(%s): Error, destination buffer NULL!\n", + __func__, mmc->dev->name); + goto error; + } + if (data->blocksize > 512) { + printf("%s(%s): Error: data size %u exceeds 512\n", + __func__, mmc->dev->name, + data->blocksize); + goto error; + } + emm_buf_idx.u = 0; + emm_buf_idx.s.inc = 1; + write_csr(mmc, MIO_EMM_BUF_IDX(), emm_buf_idx.u); + for (i = 0; i < (data->blocksize + 7) / 8; i++) { + emm_buf_dat.u = read_csr(mmc, MIO_EMM_BUF_DAT()); + emm_buf_dat.u = be64_to_cpu(emm_buf_dat.u); + memcpy(dest, &emm_buf_dat.u, sizeof(emm_buf_dat.u)); + dest += sizeof(emm_buf_dat.u); + } + write_csr(mmc, MIO_EMM_BUF_IDX(), 0); +#ifdef DEBUG + debug("%s: Received %d bytes data\n", __func__, + data->blocksize); + print_buffer(0, data->dest, 1, data->blocksize, 0); +#endif + } + + return 0; +error: +#ifdef DEBUG + octeontx_mmc_print_registers(mmc); +#endif + return -1; +} + +static int octeontx_mmc_dev_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + return octeontx_mmc_send_cmd(dev_to_mmc(dev), cmd, data); +} + +#ifdef MMC_SUPPORTS_TUNING +static int octeontx_mmc_test_cmd(struct mmc *mmc, u32 opcode, int *statp) +{ + struct mmc_cmd cmd; + int err; + + memset(&cmd, 0, sizeof(cmd)); + + debug("%s(%s, %u, %p)\n", __func__, mmc->dev->name, opcode, statp); + cmd.cmdidx = opcode; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = mmc->rca << 16; + + err = octeontx_mmc_send_cmd(mmc, &cmd, NULL); + if (err) + debug("%s(%s, %u) returned %d\n", __func__, + mmc->dev->name, opcode, err); + if (statp) + *statp = cmd.response[0]; + return err; +} + +static int octeontx_mmc_test_get_ext_csd(struct mmc *mmc, u32 opcode, + int *statp) +{ + struct mmc_cmd cmd; + struct mmc_data data; + int err; + u8 ext_csd[MMC_MAX_BLOCK_LEN]; + + debug("%s(%s, %u, %p)\n", __func__, mmc->dev->name, opcode, statp); + memset(&cmd, 0, sizeof(cmd)); + + cmd.cmdidx = MMC_CMD_SEND_EXT_CSD; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 0; + + data.dest = (char *)ext_csd; + data.blocks = 1; + data.blocksize = MMC_MAX_BLOCK_LEN; + data.flags = MMC_DATA_READ; + + err = octeontx_mmc_send_cmd(mmc, &cmd, &data); + if (statp) + *statp = cmd.response[0]; + + return err; +} + +/** + * Wrapper to set the MIO_EMM_TIMING register + * + * @param mmc pointer to mmc data structure + * @param emm_timing New emm_timing register value + * + * On some devices it is possible that changing the data out value can + * cause a glitch on an internal fifo. This works around this problem + * by performing a soft-reset immediately before setting the timing register. + * + * Note: this function should not be called from any function that + * performs DMA or block operations since not all registers are + * preserved. + */ +static void octeontx_mmc_set_emm_timing(struct mmc *mmc, + union mio_emm_timing emm_timing) +{ + union mio_emm_cfg emm_cfg; + struct octeontx_mmc_slot *slot = mmc->priv; + union mio_emm_debug emm_debug; + + debug("%s(%s, 0x%llx) din: %u\n", __func__, mmc->dev->name, + emm_timing.u, emm_timing.s.data_in_tap); + + udelay(1); + if (slot->host->tap_requires_noclk) { + /* Turn off the clock */ + emm_debug.u = read_csr(mmc, MIO_EMM_DEBUG()); + emm_debug.s.emmc_clk_disable = 1; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + udelay(1); + emm_debug.s.rdsync_rst = 1; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + } + emm_cfg.u = read_csr(mmc, MIO_EMM_CFG()); + emm_cfg.s.bus_ena = 1 << 3; + write_csr(mmc, MIO_EMM_CFG(), emm_cfg.u); + + udelay(1); + write_csr(mmc, MIO_EMM_TIMING(), emm_timing.u); + udelay(1); + + if (slot->host->tap_requires_noclk) { + /* Turn on the clock */ + emm_debug.s.rdsync_rst = 0; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + udelay(1); + emm_debug.s.emmc_clk_disable = 0; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + udelay(1); + } + emm_cfg.s.bus_ena = 1 << mmc_to_slot(mmc)->bus_id; + write_csr(mmc, MIO_EMM_CFG(), emm_cfg.u); +} + +static const u8 octeontx_hs400_tuning_block[512] = { + 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, + 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc, + 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff, + 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff, + 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd, + 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, + 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff, + 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, + 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, + 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, + 0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, + 0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, + 0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, + 0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, + 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, + 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc, + 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff, + 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff, + 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd, + 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, + 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff, + 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, + 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, + 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, + 0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, + 0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, + 0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, + 0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, + 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, + 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, 0xcc, + 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff, + 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff, + 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd, + 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, + 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff, + 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, + 0x00, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0x33, 0xcc, + 0xcc, 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, + 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, + 0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, + 0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, + 0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, + 0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, + 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, + 0x00, 0xff, 0x00, 0xff, 0x55, 0xaa, 0x55, 0xaa, + 0xcc, 0x33, 0x33, 0xcc, 0xcc, 0xcc, 0xff, 0xff, + 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, 0xff, + 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, 0xdd, + 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, + 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, 0xff, + 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, 0xff, + 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, + 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, + 0x01, 0xfe, 0x01, 0xfe, 0xcc, 0xcc, 0xcc, 0xff, + 0xff, 0xff, 0xee, 0xff, 0xff, 0xff, 0xee, 0xee, + 0xff, 0xff, 0xff, 0xdd, 0xff, 0xff, 0xff, 0xdd, + 0xdd, 0xff, 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, + 0xbb, 0xbb, 0xff, 0xff, 0xff, 0x77, 0xff, 0xff, + 0xff, 0x77, 0x77, 0xff, 0x77, 0xbb, 0xdd, 0xee, + +}; + +/** + * Perform tuning in HS400 mode + * + * @param[in] mmc mmc data structure + * + * @ret 0 for success, otherwise error + */ +static int octeontx_tune_hs400(struct mmc *mmc) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + struct mmc_cmd cmd; + struct mmc_data data; + union mio_emm_timing emm_timing; + u8 buffer[mmc->read_bl_len]; + int tap_adj; + int err = -1; + int tap; + int run = 0; + int start_run = -1; + int best_run = 0; + int best_start = -1; + bool prev_ok = false; + char env_name[64]; + char how[MAX_NO_OF_TAPS + 1] = ""; + + if (slot->hs400_tuning_block == -1) + return 0; + + /* The eMMC standard disables all tuning support when operating in + * DDR modes like HS400. The problem with this is that there are + * many cases where the HS200 tuning does not work for HS400 mode. + * In order to perform this tuning, while in HS200 a block is written + * to a block specified in the device tree (marvell,hs400-tuning-block) + * which is used for tuning in this function by repeatedly reading + * this block and comparing the data and return code. This function + * chooses the data input tap in the middle of the longest run of + * successful read operations. + */ + + emm_timing = slot->hs200_taps; + debug("%s(%s): Start ci: %d, co: %d, di: %d, do: %d\n", + __func__, mmc->dev->name, emm_timing.s.cmd_in_tap, + emm_timing.s.cmd_out_tap, emm_timing.s.data_in_tap, + emm_timing.s.data_out_tap); + memset(buffer, 0xdb, sizeof(buffer)); + + snprintf(env_name, sizeof(env_name), "emmc%d_data_in_tap_hs400", + slot->bus_id); + tap = env_get_ulong(env_name, 10, -1L); + if (tap >= 0 && tap < MAX_NO_OF_TAPS) { + printf("Overriding data input tap for HS400 mode to %d\n", tap); + emm_timing.s.data_in_tap = tap; + octeontx_mmc_set_emm_timing(mmc, emm_timing); + return 0; + } + + for (tap = 0; tap <= MAX_NO_OF_TAPS; tap++, prev_ok = !err) { + if (tap < MAX_NO_OF_TAPS) { + debug("%s: Testing data in tap %d\n", __func__, tap); + emm_timing.s.data_in_tap = tap; + octeontx_mmc_set_emm_timing(mmc, emm_timing); + + cmd.cmdidx = MMC_CMD_READ_SINGLE_BLOCK; + cmd.cmdarg = slot->hs400_tuning_block; + cmd.resp_type = MMC_RSP_R1; + data.dest = (void *)buffer; + data.blocks = 1; + data.blocksize = mmc->read_bl_len; + data.flags = MMC_DATA_READ; + err = !octeontx_mmc_read_blocks(mmc, &cmd, &data, + false); + if (err || memcmp(buffer, octeontx_hs400_tuning_block, + sizeof(buffer))) { +#ifdef DEBUG + if (!err) { + debug("%s: data mismatch. Read:\n", + __func__); + print_buffer(0, buffer, 1, + sizeof(buffer), 0); + debug("\nExpected:\n"); + print_buffer(0, + octeontx_hs400_tuning_block, 1, + sizeof(octeontx_hs400_tuning_block), + 0); + } else { + debug("%s: Error %d reading block\n", + __func__, err); + } +#endif + err = -EINVAL; + } else { + debug("%s: tap %d good\n", __func__, tap); + } + how[tap] = "-+"[!err]; + } else { + err = -EINVAL; + } + + if (!err) { + if (!prev_ok) + start_run = tap; + } else if (prev_ok) { + run = tap - 1 - start_run; + if (start_run >= 0 && run > best_run) { + best_start = start_run; + best_run = run; + } + } + } + + how[tap - 1] = '\0'; + if (best_start < 0) { + printf("%s(%s): %lldMHz tuning failed for HS400\n", + __func__, mmc->dev->name, slot->clock / 1000000); + return -EINVAL; + } + tap = best_start + best_run / 2; + + snprintf(env_name, sizeof(env_name), "emmc%d_data_in_tap_adj_hs400", + slot->bus_id); + tap_adj = env_get_ulong(env_name, 10, slot->hs400_tap_adj); + /* + * Keep it in range and if out of range force it back in with a small + * buffer. + */ + if (best_run > 3) { + tap = tap + tap_adj; + if (tap >= best_start + best_run) + tap = best_start + best_run - 2; + if (tap <= best_start) + tap = best_start + 2; + } + how[tap] = '@'; + debug("Tuning: %s\n", how); + debug("%s(%s): HS400 tap: best run start: %d, length: %d, tap: %d\n", + __func__, mmc->dev->name, best_start, best_run, tap); + slot->hs400_taps = slot->hs200_taps; + slot->hs400_taps.s.data_in_tap = tap; + slot->hs400_tuned = true; + if (env_get_yesno("emmc_export_hs400_taps") > 0) { + debug("%s(%s): Exporting HS400 taps\n", + __func__, mmc->dev->name); + env_set_ulong("emmc_timing_tap", slot->host->timing_taps); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs400_data_in_tap_debug", + slot->bus_id); + env_set(env_name, how); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs400_data_in_tap_val", + slot->bus_id); + env_set_ulong(env_name, tap); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs400_data_in_tap_start", + slot->bus_id); + env_set_ulong(env_name, best_start); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs400_data_in_tap_end", + slot->bus_id); + env_set_ulong(env_name, best_start + best_run); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs400_cmd_in_tap", + slot->bus_id); + env_set_ulong(env_name, slot->hs400_taps.s.cmd_in_tap); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs400_cmd_out_tap", + slot->bus_id); + env_set_ulong(env_name, slot->hs400_taps.s.cmd_out_tap); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs400_cmd_out_delay", + slot->bus_id); + env_set_ulong(env_name, slot->cmd_out_hs400_delay); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs400_data_out_tap", + slot->bus_id); + env_set_ulong(env_name, slot->hs400_taps.s.data_out_tap); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs400_data_out_delay", + slot->bus_id); + env_set_ulong(env_name, slot->data_out_hs400_delay); + } else { + debug("%s(%s): HS400 environment export disabled\n", + __func__, mmc->dev->name); + } + octeontx_mmc_set_timing(mmc); + + return 0; +} + +struct adj { + const char *name; + u8 mask_shift; + int (*test)(struct mmc *mmc, u32 opcode, int *error); + u32 opcode; + bool ddr_only; + bool hs200_only; + bool not_hs200_only; + u8 num_runs; +}; + +struct adj adj[] = { + { "CMD_IN", 48, octeontx_mmc_test_cmd, MMC_CMD_SEND_STATUS, + false, false, false, 2, }, +/* { "CMD_OUT", 32, octeontx_mmc_test_cmd, MMC_CMD_SEND_STATUS, },*/ + { "DATA_IN(HS200)", 16, mmc_send_tuning, + MMC_CMD_SEND_TUNING_BLOCK_HS200, false, true, false, 2, }, + { "DATA_IN", 16, octeontx_mmc_test_get_ext_csd, 0, false, false, + true, 2, }, +/* { "DATA_OUT", 0, octeontx_mmc_test_cmd, 0, true, false},*/ + { NULL, }, +}; + +/** + * Perform tuning tests to find optimal timing + * + * @param mmc mmc device + * @param adj parameter to tune + * @param opcode command opcode to use + * + * @return 0 for success, -1 if tuning failed + */ +static int octeontx_mmc_adjust_tuning(struct mmc *mmc, struct adj *adj, + u32 opcode) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + union mio_emm_timing timing; + union mio_emm_debug emm_debug; + int tap; + int err = -1; + int run = 0; + int count; + int start_run = -1; + int best_run = 0; + int best_start = -1; + bool prev_ok = false; + u64 tap_status = 0; + const int tap_adj = slot->hs200_tap_adj; + char how[MAX_NO_OF_TAPS + 1] = ""; + bool is_hs200 = mmc->selected_mode == MMC_HS_200; + + debug("%s(%s, %s, %d), hs200: %d\n", __func__, mmc->dev->name, + adj->name, opcode, is_hs200); + octeontx_mmc_set_emm_timing(mmc, + is_hs200 ? slot->hs200_taps : slot->taps); + +#ifdef DEBUG + if (opcode == MMC_CMD_SEND_TUNING_BLOCK_HS200) { + printf("%s(%s): Before tuning %s, opcode: %d\n", + __func__, mmc->dev->name, adj->name, opcode); + octeontx_mmc_print_registers2(mmc, NULL); + } +#endif + + /* + * The algorithm to find the optimal timing is to start + * at the end and work backwards and select the second + * value that passes. Each test is repeated twice. + */ + for (tap = 0; tap <= MAX_NO_OF_TAPS; tap++, prev_ok = !err) { + if (tap < MAX_NO_OF_TAPS) { + if (slot->host->tap_requires_noclk) { + /* Turn off the clock */ + emm_debug.u = read_csr(mmc, MIO_EMM_DEBUG()); + emm_debug.s.emmc_clk_disable = 1; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + udelay(1); + emm_debug.s.rdsync_rst = 1; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + udelay(1); + } + + timing.u = read_csr(mmc, MIO_EMM_TIMING()); + timing.u &= ~(0x3full << adj->mask_shift); + timing.u |= (u64)tap << adj->mask_shift; + write_csr(mmc, MIO_EMM_TIMING(), timing.u); + debug("%s(%s): Testing ci: %d, co: %d, di: %d, do: %d\n", + __func__, mmc->dev->name, timing.s.cmd_in_tap, + timing.s.cmd_out_tap, timing.s.data_in_tap, + timing.s.data_out_tap); + + if (slot->host->tap_requires_noclk) { + /* Turn off the clock */ + emm_debug.s.rdsync_rst = 0; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + udelay(1); + emm_debug.u = read_csr(mmc, MIO_EMM_DEBUG()); + emm_debug.s.emmc_clk_disable = 0; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + udelay(1); + } + for (count = 0; count < 2; count++) { + err = adj->test(mmc, opcode, NULL); + if (err) { + debug("%s(%s, %s): tap %d failed, count: %d, rsp_sts: 0x%llx, rsp_lo: 0x%llx\n", + __func__, mmc->dev->name, + adj->name, tap, count, + read_csr(mmc, + MIO_EMM_RSP_STS()), + read_csr(mmc, + MIO_EMM_RSP_LO())); + debug("%s(%s, %s): tap: %d, do: %d, di: %d, co: %d, ci: %d\n", + __func__, mmc->dev->name, + adj->name, tap, + timing.s.data_out_tap, + timing.s.data_in_tap, + timing.s.cmd_out_tap, + timing.s.cmd_in_tap); + break; + } + debug("%s(%s, %s): tap %d passed, count: %d, rsp_sts: 0x%llx, rsp_lo: 0x%llx\n", + __func__, mmc->dev->name, adj->name, tap, + count, + read_csr(mmc, MIO_EMM_RSP_STS()), + read_csr(mmc, MIO_EMM_RSP_LO())); + } + tap_status |= (u64)(!err) << tap; + how[tap] = "-+"[!err]; + } else { + /* + * Putting the end+1 case in the loop simplifies + * logic, allowing 'prev_ok' to process a sweet + * spot in tuning which extends to the wall. + */ + err = -EINVAL; + } + if (!err) { + /* + * If no CRC/etc errors in the response, but previous + * failed, note the start of a new run. + */ + debug(" prev_ok: %d\n", prev_ok); + if (!prev_ok) + start_run = tap; + } else if (prev_ok) { + run = tap - 1 - start_run; + /* did we just exit a wider sweet spot? */ + if (start_run >= 0 && run > best_run) { + best_start = start_run; + best_run = run; + } + } + } + how[tap - 1] = '\0'; + if (best_start < 0) { + printf("%s(%s, %s): %lldMHz tuning %s failed\n", __func__, + mmc->dev->name, adj->name, slot->clock / 1000000, + adj->name); + return -EINVAL; + } + + tap = best_start + best_run / 2; + debug(" tap %d is center, start: %d, run: %d\n", tap, + best_start, best_run); + if (is_hs200) { + slot->hs200_taps.u &= ~(0x3full << adj->mask_shift); + slot->hs200_taps.u |= (u64)tap << adj->mask_shift; + } else { + slot->taps.u &= ~(0x3full << adj->mask_shift); + slot->taps.u |= (u64)tap << adj->mask_shift; + } + if (best_start < 0) { + printf("%s(%s, %s): %lldMHz tuning %s failed\n", __func__, + mmc->dev->name, adj->name, slot->clock / 1000000, + adj->name); + return -EINVAL; + } + + tap = best_start + best_run / 2; + if (is_hs200 && (tap + tap_adj >= 0) && (tap + tap_adj < 64) && + tap_status & (1ULL << (tap + tap_adj))) { + debug("Adjusting tap from %d by %d to %d\n", + tap, tap_adj, tap + tap_adj); + tap += tap_adj; + } + how[tap] = '@'; + debug("%s/%s %d/%d/%d %s\n", mmc->dev->name, + adj->name, best_start, tap, best_start + best_run, how); + + if (is_hs200) { + slot->hs200_taps.u &= ~(0x3full << adj->mask_shift); + slot->hs200_taps.u |= (u64)tap << adj->mask_shift; + } else { + slot->taps.u &= ~(0x3full << adj->mask_shift); + slot->taps.u |= (u64)tap << adj->mask_shift; + } + +#ifdef DEBUG + if (opcode == MMC_CMD_SEND_TUNING_BLOCK_HS200) { + debug("%s(%s, %s): After successful tuning\n", + __func__, mmc->dev->name, adj->name); + debug("%s(%s, %s): tap: %d, new do: %d, di: %d, co: %d, ci: %d\n", + __func__, mmc->dev->name, adj->name, tap, + slot->taps.s.data_out_tap, + slot->taps.s.data_in_tap, + slot->taps.s.cmd_out_tap, + slot->taps.s.cmd_in_tap); + debug("%s(%s, %s): tap: %d, new do HS200: %d, di: %d, co: %d, ci: %d\n", + __func__, mmc->dev->name, adj->name, tap, + slot->hs200_taps.s.data_out_tap, + slot->hs200_taps.s.data_in_tap, + slot->hs200_taps.s.cmd_out_tap, + slot->hs200_taps.s.cmd_in_tap); + } +#endif + octeontx_mmc_set_timing(mmc); + + if (is_hs200 && env_get_yesno("emmc_export_hs200_taps")) { + char env_name[64]; + + env_set_ulong("emmc_timing_tap", slot->host->timing_taps); + switch (opcode) { + case MMC_CMD_SEND_TUNING_BLOCK: + snprintf(env_name, sizeof(env_name), + "emmc%d_hs200_data_in_tap_debug", + slot->bus_id); + env_set(env_name, how); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs200_data_in_tap_val", slot->bus_id); + env_set_ulong(env_name, tap); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs200_data_in_tap_start", + slot->bus_id); + env_set_ulong(env_name, best_start); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs200_data_in_tap_end", + slot->bus_id); + env_set_ulong(env_name, best_start + best_run); + break; + case MMC_CMD_SEND_STATUS: + snprintf(env_name, sizeof(env_name), + "emmc%d_hs200_cmd_in_tap_debug", + slot->bus_id); + env_set(env_name, how); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs200_cmd_in_tap_val", slot->bus_id); + env_set_ulong(env_name, tap); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs200_cmd_in_tap_start", + slot->bus_id); + env_set_ulong(env_name, best_start); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs200_cmd_in_tap_end", + slot->bus_id); + env_set_ulong(env_name, best_start + best_run); + break; + default: + snprintf(env_name, sizeof(env_name), + "emmc%d_hs200_data_out_tap", slot->bus_id); + env_set_ulong(env_name, slot->data_out_hs200_delay); + snprintf(env_name, sizeof(env_name), + "emmc%d_hs200_cmd_out_tap", slot->bus_id); + env_set_ulong(env_name, slot->cmd_out_hs200_delay); + break; + } + } + + return 0; +} + +static int octeontx_mmc_execute_tuning(struct udevice *dev, u32 opcode) +{ + struct mmc *mmc = dev_to_mmc(dev); + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + union mio_emm_timing emm_timing; + int err; + struct adj *a; + bool is_hs200; + char env_name[64]; + + pr_info("%s re-tuning, opcode 0x%x\n", dev->name, opcode); + + if (slot->is_asim || slot->is_emul) + return 0; + + is_hs200 = (mmc->selected_mode == MMC_HS_200); + if (is_hs200) { + slot->hs200_tuned = false; + slot->hs400_tuned = false; + } else { + slot->tuned = false; + } + octeontx_mmc_set_output_bus_timing(mmc); + octeontx_mmc_set_input_bus_timing(mmc); + emm_timing.u = read_csr(mmc, MIO_EMM_TIMING()); + if (mmc->selected_mode == MMC_HS_200) { + slot->hs200_taps.s.cmd_out_tap = emm_timing.s.cmd_out_tap; + slot->hs200_taps.s.data_out_tap = emm_timing.s.data_out_tap; + } else { + slot->taps.s.cmd_out_tap = emm_timing.s.cmd_out_tap; + slot->taps.s.data_out_tap = emm_timing.s.data_out_tap; + } + octeontx_mmc_set_input_bus_timing(mmc); + octeontx_mmc_set_output_bus_timing(mmc); + + for (a = adj; a->name; a++) { + ulong in_tap; + + if (!strcmp(a->name, "CMD_IN")) { + snprintf(env_name, sizeof(env_name), + "emmc%d_cmd_in_tap", slot->bus_id); + in_tap = env_get_ulong(env_name, 10, (ulong)-1); + if (in_tap != (ulong)-1) { + if (mmc->selected_mode == MMC_HS_200 || + a->hs200_only) { + slot->hs200_taps.s.cmd_in_tap = in_tap; + slot->hs400_taps.s.cmd_in_tap = in_tap; + } else { + slot->taps.s.cmd_in_tap = in_tap; + } + continue; + } + } else if (a->hs200_only && + !strcmp(a->name, "DATA_IN(HS200)")) { + snprintf(env_name, sizeof(env_name), + "emmc%d_data_in_tap_hs200", slot->bus_id); + in_tap = env_get_ulong(env_name, 10, (ulong)-1); + if (in_tap != (ulong)-1) { + debug("%s(%s): Overriding HS200 data in tap to %d\n", + __func__, dev->name, (int)in_tap); + slot->hs200_taps.s.data_in_tap = in_tap; + continue; + } + } else if (!a->hs200_only && !strcmp(a->name, "DATA_IN")) { + snprintf(env_name, sizeof(env_name), + "emmc%d_data_in_tap", slot->bus_id); + in_tap = env_get_ulong(env_name, 10, (ulong)-1); + if (in_tap != (ulong)-1) { + debug("%s(%s): Overriding non-HS200 data in tap to %d\n", + __func__, dev->name, (int)in_tap); + slot->taps.s.data_in_tap = in_tap; + continue; + } + } + + debug("%s(%s): Testing: %s, mode: %s, opcode: %u\n", __func__, + dev->name, a->name, mmc_mode_name(mmc->selected_mode), + opcode); + + /* Skip DDR only test when not in DDR mode */ + if (a->ddr_only && !mmc->ddr_mode) { + debug("%s(%s): Skipping %s due to non-DDR mode\n", + __func__, dev->name, a->name); + continue; + } + /* Skip hs200 tests in non-hs200 mode and + * non-hs200 tests in hs200 mode + */ + if (is_hs200) { + if (a->not_hs200_only) { + debug("%s(%s): Skipping %s\n", __func__, + dev->name, a->name); + continue; + } + } else { + if (a->hs200_only) { + debug("%s(%s): Skipping %s\n", __func__, + dev->name, a->name); + continue; + } + } + + err = octeontx_mmc_adjust_tuning(mmc, a, a->opcode ? + a->opcode : opcode); + if (err) { + pr_err("%s(%s, %u): tuning %s failed\n", __func__, + dev->name, opcode, a->name); + return err; + } + } + + octeontx_mmc_set_timing(mmc); + if (is_hs200) + slot->hs200_tuned = true; + else + slot->tuned = true; + + if (slot->hs400_tuning_block != -1) { + struct mmc_cmd cmd; + struct mmc_data data; + u8 buffer[mmc->read_bl_len]; + + cmd.cmdidx = MMC_CMD_READ_SINGLE_BLOCK; + cmd.cmdarg = slot->hs400_tuning_block; + cmd.resp_type = MMC_RSP_R1; + data.dest = (void *)buffer; + data.blocks = 1; + data.blocksize = mmc->read_bl_len; + data.flags = MMC_DATA_READ; + err = octeontx_mmc_read_blocks(mmc, &cmd, &data, true) != 1; + + if (err) { + printf("%s: Cannot read HS400 tuning block %u\n", + dev->name, slot->hs400_tuning_block); + return err; + } + if (memcmp(buffer, octeontx_hs400_tuning_block, + sizeof(buffer))) { + debug("%s(%s): Writing new HS400 tuning block to block %d\n", + __func__, dev->name, slot->hs400_tuning_block); + cmd.cmdidx = MMC_CMD_WRITE_SINGLE_BLOCK; + data.src = (void *)octeontx_hs400_tuning_block; + data.flags = MMC_DATA_WRITE; + err = !octeontx_mmc_write_blocks(mmc, &cmd, &data); + if (err) { + printf("%s: Cannot write HS400 tuning block %u\n", + dev->name, slot->hs400_tuning_block); + return -EINVAL; + } + } + } + + return 0; +} +#else /* MMC_SUPPORTS_TUNING */ +static void octeontx_mmc_set_emm_timing(struct mmc *mmc, + union mio_emm_timing emm_timing) +{ +} +#endif /* MMC_SUPPORTS_TUNING */ + +/** + * Calculate the clock period with rounding up + * + * @param mmc mmc device + * @return clock period in system clocks for clk_lo + clk_hi + */ +static u32 octeontx_mmc_calc_clk_period(struct mmc *mmc) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + struct octeontx_mmc_host *host = slot->host; + + if (mmc->clock) + return DIV_ROUND_UP(host->sys_freq, mmc->clock); + + return 0; +} + +static int octeontx_mmc_set_ios(struct udevice *dev) +{ + struct octeontx_mmc_slot *slot = dev_to_mmc_slot(dev); + struct mmc *mmc = &slot->mmc; + struct octeontx_mmc_host *host = slot->host; + union mio_emm_switch emm_switch; + union mio_emm_modex mode; + uint clock; + int bus_width = 0; + int clk_period = 0; + int power_class = 10; + int err = 0; + bool is_hs200 = false; + bool is_hs400 = false; + + debug("%s(%s): Entry\n", __func__, dev->name); + debug(" clock: %u, bus width: %u, mode: %u\n", mmc->clock, + mmc->bus_width, mmc->selected_mode); + debug(" host caps: 0x%x, card caps: 0x%x\n", mmc->host_caps, + mmc->card_caps); + octeontx_mmc_switch_to(mmc); + + clock = mmc->clock; + if (!clock) + clock = mmc->cfg->f_min; + + switch (mmc->bus_width) { + case 8: + bus_width = 2; + break; + case 4: + bus_width = 1; + break; + case 1: + bus_width = 0; + break; + default: + pr_warn("%s(%s): Invalid bus width %d, defaulting to 1\n", + __func__, dev->name, mmc->bus_width); + bus_width = 0; + } + + /* DDR is available for 4/8 bit bus width */ + if (mmc->ddr_mode && bus_width) + bus_width |= 4; + + debug("%s: sys_freq: %llu\n", __func__, host->sys_freq); + clk_period = octeontx_mmc_calc_clk_period(mmc); + + emm_switch.u = 0; + emm_switch.s.bus_width = bus_width; + emm_switch.s.power_class = power_class; + emm_switch.s.clk_hi = clk_period / 2; + emm_switch.s.clk_lo = clk_period / 2; + + debug("%s: last mode: %d, mode: %d, last clock: %u, clock: %u, ddr: %d\n", + __func__, slot->last_mode, mmc->selected_mode, + slot->last_clock, mmc->clock, mmc->ddr_mode); + switch (mmc->selected_mode) { + case MMC_LEGACY: + break; + case MMC_HS: + case SD_HS: + case MMC_HS_52: + emm_switch.s.hs_timing = 1; + break; + case MMC_HS_200: + is_hs200 = true; + fallthrough; + case UHS_SDR12: + case UHS_SDR25: + case UHS_SDR50: + case UHS_SDR104: +#if !defined(CONFIG_ARCH_OCTEON) + emm_switch.s.hs200_timing = 1; +#endif + break; + case MMC_HS_400: + is_hs400 = true; + fallthrough; + case UHS_DDR50: + case MMC_DDR_52: +#if !defined(CONFIG_ARCH_OCTEON) + emm_switch.s.hs400_timing = 1; +#endif + break; + default: + pr_err("%s(%s): Unsupported mode 0x%x\n", __func__, dev->name, + mmc->selected_mode); + return -1; + } + emm_switch.s.bus_id = slot->bus_id; + + if (!is_hs200 && !is_hs400 && + (mmc->selected_mode != slot->last_mode || + mmc->clock != slot->last_clock) && + !mmc->ddr_mode) { + slot->tuned = false; + slot->last_mode = mmc->selected_mode; + slot->last_clock = mmc->clock; + } + + if (CONFIG_IS_ENABLED(MMC_VERBOSE)) { + debug("%s(%s): Setting bus mode to %s\n", __func__, dev->name, + mmc_mode_name(mmc->selected_mode)); + } else { + debug("%s(%s): Setting bus mode to 0x%x\n", __func__, dev->name, + mmc->selected_mode); + } + +#if !defined(CONFIG_ARCH_OCTEON) + debug(" Trying switch 0x%llx w%d hs:%d hs200:%d hs400:%d\n", + emm_switch.u, emm_switch.s.bus_width, emm_switch.s.hs_timing, + emm_switch.s.hs200_timing, emm_switch.s.hs400_timing); +#endif + + set_wdog(mmc, 1000); + do_switch(mmc, emm_switch); + mdelay(100); + mode.u = read_csr(mmc, MIO_EMM_MODEX(slot->bus_id)); +#if !defined(CONFIG_ARCH_OCTEON) + debug("%s(%s): mode: 0x%llx w:%d, hs:%d, hs200:%d, hs400:%d\n", + __func__, dev->name, mode.u, mode.s.bus_width, + mode.s.hs_timing, mode.s.hs200_timing, mode.s.hs400_timing); +#endif + + err = octeontx_mmc_configure_delay(mmc); + +#ifdef MMC_SUPPORTS_TUNING + if (!err && mmc->selected_mode == MMC_HS_400 && !slot->hs400_tuned) { + debug("%s: Tuning HS400 mode\n", __func__); + err = octeontx_tune_hs400(mmc); + } +#endif + + return err; +} + +/** + * Gets the status of the card detect pin + */ +static int octeontx_mmc_get_cd(struct udevice *dev) +{ + struct octeontx_mmc_slot *slot = dev_to_mmc_slot(dev); + int val = 1; + + if (dm_gpio_is_valid(&slot->cd_gpio)) { + val = dm_gpio_get_value(&slot->cd_gpio); + val ^= slot->cd_inverted; + } + debug("%s(%s): cd: %d\n", __func__, dev->name, val); + return val; +} + +/** + * Gets the status of the write protect pin + */ +static int octeontx_mmc_get_wp(struct udevice *dev) +{ + struct octeontx_mmc_slot *slot = dev_to_mmc_slot(dev); + int val = 0; + + if (dm_gpio_is_valid(&slot->wp_gpio)) { + val = dm_gpio_get_value(&slot->wp_gpio); + val ^= slot->wp_inverted; + } + debug("%s(%s): wp: %d\n", __func__, dev->name, val); + return val; +} + +#if defined(CONFIG_ARCH_OCTEON) +static int octeontx_mmc_configure_delay(struct mmc *mmc) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + union mio_emm_sample emm_sample; + + debug("%s(%s)\n", __func__, mmc->dev->name); + + emm_sample.u = 0; + emm_sample.s.cmd_cnt = slot->cmd_cnt; + emm_sample.s.dat_cnt = slot->dat_cnt; + write_csr(mmc, MIO_EMM_SAMPLE(), emm_sample.u); + + return 0; +} + +static void octeontx_mmc_io_drive_setup(struct mmc *mmc) +{ +} +#else +static void octeontx_mmc_set_timing(struct mmc *mmc) +{ + union mio_emm_timing timing; + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + + switch (mmc->selected_mode) { + case MMC_HS_200: + timing = slot->hs200_taps; + break; + case MMC_HS_400: + timing = slot->hs400_tuned ? + slot->hs400_taps : slot->hs200_taps; + break; + default: + timing = slot->taps; + break; + } + + debug("%s(%s):\n cmd_in_tap: %u\n cmd_out_tap: %u\n data_in_tap: %u\n data_out_tap: %u\n", + __func__, mmc->dev->name, timing.s.cmd_in_tap, + timing.s.cmd_out_tap, timing.s.data_in_tap, + timing.s.data_out_tap); + + octeontx_mmc_set_emm_timing(mmc, timing); +} + +static int octeontx_mmc_configure_delay(struct mmc *mmc) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + struct octeontx_mmc_host *host __maybe_unused = slot->host; + bool __maybe_unused is_hs200; + bool __maybe_unused is_hs400; + + debug("%s(%s)\n", __func__, mmc->dev->name); + + if (IS_ENABLED(CONFIG_ARCH_OCTEON) || + IS_ENABLED(CONFIG_ARCH_OCTEONTX)) { + union mio_emm_sample emm_sample; + + emm_sample.u = 0; + emm_sample.s.cmd_cnt = slot->cmd_cnt; + emm_sample.s.dat_cnt = slot->dat_cnt; + write_csr(mmc, MIO_EMM_SAMPLE(), emm_sample.u); + } else { + is_hs200 = (mmc->selected_mode == MMC_HS_200); + is_hs400 = (mmc->selected_mode == MMC_HS_400); + + if ((is_hs200 && slot->hs200_tuned) || + (is_hs400 && slot->hs400_tuned) || + (!is_hs200 && !is_hs400 && slot->tuned)) { + octeontx_mmc_set_output_bus_timing(mmc); + } else { + int half = MAX_NO_OF_TAPS / 2; + int dout, cout; + + switch (mmc->selected_mode) { + case MMC_LEGACY: + if (IS_SD(mmc)) { + cout = MMC_SD_LEGACY_DEFAULT_CMD_OUT_TAP; + dout = MMC_SD_LEGACY_DEFAULT_DATA_OUT_TAP; + } else { + cout = MMC_LEGACY_DEFAULT_CMD_OUT_TAP; + dout = MMC_LEGACY_DEFAULT_DATA_OUT_TAP; + } + break; + case MMC_HS: + cout = MMC_HS_CMD_OUT_TAP; + dout = MMC_HS_DATA_OUT_TAP; + break; + case SD_HS: + case UHS_SDR12: + case UHS_SDR25: + case UHS_SDR50: + cout = MMC_SD_HS_CMD_OUT_TAP; + dout = MMC_SD_HS_DATA_OUT_TAP; + break; + case UHS_SDR104: + case UHS_DDR50: + case MMC_HS_52: + case MMC_DDR_52: + cout = MMC_DEFAULT_CMD_OUT_TAP; + dout = MMC_DEFAULT_DATA_OUT_TAP; + break; + case MMC_HS_200: + cout = -1; + dout = -1; + if (host->timing_calibrated) { + cout = octeontx2_mmc_calc_delay( + mmc, slot->cmd_out_hs200_delay); + dout = octeontx2_mmc_calc_delay( + mmc, + slot->data_out_hs200_delay); + debug("%s(%s): Calibrated HS200/HS400 cmd out delay: %dps tap: %d, data out delay: %d, tap: %d\n", + __func__, mmc->dev->name, + slot->cmd_out_hs200_delay, cout, + slot->data_out_hs200_delay, dout); + } else { + cout = MMC_DEFAULT_HS200_CMD_OUT_TAP; + dout = MMC_DEFAULT_HS200_DATA_OUT_TAP; + } + is_hs200 = true; + break; + case MMC_HS_400: + cout = -1; + dout = -1; + if (host->timing_calibrated) { + if (slot->cmd_out_hs400_delay) + cout = octeontx2_mmc_calc_delay( + mmc, + slot->cmd_out_hs400_delay); + if (slot->data_out_hs400_delay) + dout = octeontx2_mmc_calc_delay( + mmc, + slot->data_out_hs400_delay); + debug("%s(%s): Calibrated HS200/HS400 cmd out delay: %dps tap: %d, data out delay: %d, tap: %d\n", + __func__, mmc->dev->name, + slot->cmd_out_hs400_delay, cout, + slot->data_out_hs400_delay, dout); + } else { + cout = MMC_DEFAULT_HS400_CMD_OUT_TAP; + dout = MMC_DEFAULT_HS400_DATA_OUT_TAP; + } + is_hs400 = true; + break; + default: + pr_err("%s(%s): Invalid mode %d\n", __func__, + mmc->dev->name, mmc->selected_mode); + return -1; + } + debug("%s(%s): Not tuned, hs200: %d, hs200 tuned: %d, hs400: %d, hs400 tuned: %d, tuned: %d\n", + __func__, mmc->dev->name, is_hs200, + slot->hs200_tuned, + is_hs400, slot->hs400_tuned, slot->tuned); + /* Set some defaults */ + if (is_hs200) { + slot->hs200_taps.u = 0; + slot->hs200_taps.s.cmd_out_tap = cout; + slot->hs200_taps.s.data_out_tap = dout; + slot->hs200_taps.s.cmd_in_tap = half; + slot->hs200_taps.s.data_in_tap = half; + } else if (is_hs400) { + slot->hs400_taps.u = 0; + slot->hs400_taps.s.cmd_out_tap = cout; + slot->hs400_taps.s.data_out_tap = dout; + slot->hs400_taps.s.cmd_in_tap = half; + slot->hs400_taps.s.data_in_tap = half; + } else { + slot->taps.u = 0; + slot->taps.s.cmd_out_tap = cout; + slot->taps.s.data_out_tap = dout; + slot->taps.s.cmd_in_tap = half; + slot->taps.s.data_in_tap = half; + } + } + + if (is_hs200) + debug("%s(%s): hs200 taps: ci: %u, co: %u, di: %u, do: %u\n", + __func__, mmc->dev->name, + slot->hs200_taps.s.cmd_in_tap, + slot->hs200_taps.s.cmd_out_tap, + slot->hs200_taps.s.data_in_tap, + slot->hs200_taps.s.data_out_tap); + else if (is_hs400) + debug("%s(%s): hs400 taps: ci: %u, co: %u, di: %u, do: %u\n", + __func__, mmc->dev->name, + slot->hs400_taps.s.cmd_in_tap, + slot->hs400_taps.s.cmd_out_tap, + slot->hs400_taps.s.data_in_tap, + slot->hs400_taps.s.data_out_tap); + else + debug("%s(%s): taps: ci: %u, co: %u, di: %u, do: %u\n", + __func__, mmc->dev->name, slot->taps.s.cmd_in_tap, + slot->taps.s.cmd_out_tap, + slot->taps.s.data_in_tap, + slot->taps.s.data_out_tap); + octeontx_mmc_set_timing(mmc); + debug("%s: Done\n", __func__); + } + + return 0; +} + +/** + * Set the IO drive strength and slew + * + * @param mmc mmc device + */ +static void octeontx_mmc_io_drive_setup(struct mmc *mmc) +{ + if (IS_ENABLED(CONFIG_ARCH_OCTEONTX2)) { + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + union mio_emm_io_ctl io_ctl; + + if (slot->drive < 0 || slot->slew < 0) + return; + + io_ctl.u = 0; + io_ctl.s.drive = slot->drive; + io_ctl.s.slew = slot->slew; + write_csr(mmc, MIO_EMM_IO_CTL(), io_ctl.u); + } +} +#endif + +/** + * Sets the MMC watchdog timer in microseconds + * + * @param mmc mmc device + * @param us timeout in microseconds, 0 for maximum timeout + */ +static void set_wdog(struct mmc *mmc, u64 us) +{ + union mio_emm_wdog wdog; + u64 val; + + val = (us * mmc->clock) / 1000000; + if (val >= (1 << 26) || !us) { + if (us) + pr_debug("%s: warning: timeout %llu exceeds max value %llu, truncating\n", + __func__, us, + (u64)(((1ULL << 26) - 1) * 1000000ULL) / + mmc->clock); + val = (1 << 26) - 1; + } + wdog.u = 0; + wdog.s.clk_cnt = val; + write_csr(mmc, MIO_EMM_WDOG(), wdog.u); +} + +/** + * Print switch errors + * + * @param mmc mmc device + */ +static void check_switch_errors(struct mmc *mmc) +{ + union mio_emm_switch emm_switch; + + emm_switch.u = read_csr(mmc, MIO_EMM_SWITCH()); + if (emm_switch.s.switch_err0) + pr_err("%s: Switch power class error\n", mmc->cfg->name); + if (emm_switch.s.switch_err1) + pr_err("%s: Switch HS timing error\n", mmc->cfg->name); + if (emm_switch.s.switch_err2) + pr_err("%s: Switch bus width error\n", mmc->cfg->name); +} + +static void do_switch(struct mmc *mmc, union mio_emm_switch emm_switch) +{ + union mio_emm_rsp_sts rsp_sts; + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + int bus_id = emm_switch.s.bus_id; + ulong start; + + if (emm_switch.s.bus_id != 0) { + emm_switch.s.bus_id = 0; + write_csr(mmc, MIO_EMM_SWITCH(), emm_switch.u); + udelay(100); + emm_switch.s.bus_id = bus_id; + } + debug("%s(%s, 0x%llx)\n", __func__, mmc->dev->name, emm_switch.u); + write_csr(mmc, MIO_EMM_SWITCH(), emm_switch.u); + + start = get_timer(0); + do { + rsp_sts.u = read_csr(mmc, MIO_EMM_RSP_STS()); + if (!rsp_sts.s.switch_val) + break; + udelay(100); + } while (get_timer(start) < 10); + if (rsp_sts.s.switch_val) { + pr_warn("%s(%s): Warning: writing 0x%llx to emm_switch timed out, status: 0x%llx\n", + __func__, mmc->dev->name, emm_switch.u, rsp_sts.u); + } + slot->cached_switch = emm_switch; + check_switch_errors(mmc); + slot->cached_switch.u = emm_switch.u; + debug("%s: emm_switch: 0x%llx, rsp_lo: 0x%llx\n", + __func__, read_csr(mmc, MIO_EMM_SWITCH()), + read_csr(mmc, MIO_EMM_RSP_LO())); +} + +/** + * Calibrates the delay based on the internal clock + * + * @param mmc Pointer to mmc data structure + * + * @return 0 for success or -ETIMEDOUT on error + * + * NOTE: On error a default value will be calculated. + */ +#if defined(CONFIG_ARCH_OCTEON) +static int octeontx_mmc_set_input_bus_timing(struct mmc *mmc) +{ + return 0; +} + +static int octeontx_mmc_set_output_bus_timing(struct mmc *mmc) +{ + return 0; +} + +static int octeontx_mmc_calibrate_delay(struct mmc *mmc) +{ + return 0; +} +#else +/** + * Given a delay in ps, return the tap delay count + * + * @param mmc mmc data structure + * @param delay delay in picoseconds + * + * @return Number of tap cycles or error if -1 + */ +static int octeontx2_mmc_calc_delay(struct mmc *mmc, int delay) +{ + struct octeontx_mmc_host *host = mmc_to_host(mmc); + + if (host->is_asim || host->is_emul) + return 63; + + if (!host->timing_taps) { + pr_err("%s(%s): Error: host timing not calibrated\n", + __func__, mmc->dev->name); + return -1; + } + debug("%s(%s, %d) timing taps: %llu\n", __func__, mmc->dev->name, + delay, host->timing_taps); + return min_t(int, DIV_ROUND_UP(delay, host->timing_taps), 63); +} + +static int octeontx_mmc_calibrate_delay(struct mmc *mmc) +{ + union mio_emm_calb emm_calb; + union mio_emm_tap emm_tap; + union mio_emm_cfg emm_cfg; + union mio_emm_io_ctl emm_io_ctl; + union mio_emm_switch emm_switch; + union mio_emm_wdog emm_wdog; + union mio_emm_sts_mask emm_sts_mask; + union mio_emm_debug emm_debug; + union mio_emm_timing emm_timing; + struct octeontx_mmc_host *host = mmc_to_host(mmc); + ulong start; + u8 bus_id, bus_ena; + + debug("%s: Calibrating delay\n", __func__); + if (host->is_asim || host->is_emul) { + debug(" No calibration for ASIM\n"); + return 0; + } + emm_tap.u = 0; + if (host->calibrate_glitch) { + emm_tap.s.delay = MMC_DEFAULT_TAP_DELAY; + } else { + /* Save registers */ + emm_cfg.u = read_csr(mmc, MIO_EMM_CFG()); + emm_io_ctl.u = read_csr(mmc, MIO_EMM_IO_CTL()); + emm_switch.u = read_csr(mmc, MIO_EMM_SWITCH()); + emm_wdog.u = read_csr(mmc, MIO_EMM_WDOG()); + emm_sts_mask.u = read_csr(mmc, MIO_EMM_STS_MASK()); + emm_debug.u = read_csr(mmc, MIO_EMM_DEBUG()); + emm_timing.u = read_csr(mmc, MIO_EMM_TIMING()); + bus_ena = emm_cfg.s.bus_ena; + bus_id = emm_switch.s.bus_id; + emm_cfg.s.bus_ena = 0; + write_csr(mmc, MIO_EMM_CFG(), emm_cfg.u); + udelay(1); + emm_cfg.s.bus_ena = 1ULL << 3; + write_csr(mmc, MIO_EMM_CFG(), emm_cfg.u); + mdelay(1); + emm_calb.u = 0; + write_csr(mmc, MIO_EMM_CALB(), emm_calb.u); + emm_calb.s.start = 1; + write_csr(mmc, MIO_EMM_CALB(), emm_calb.u); + start = get_timer(0); + /* This should only take 3 microseconds */ + do { + udelay(5); + emm_tap.u = read_csr(mmc, MIO_EMM_TAP()); + } while (!emm_tap.s.delay && get_timer(start) < 10); + + emm_calb.s.start = 0; + write_csr(mmc, MIO_EMM_CALB(), emm_calb.u); + + emm_cfg.s.bus_ena = 0; + write_csr(mmc, MIO_EMM_CFG(), emm_cfg.u); + udelay(1); + /* Restore registers */ + emm_cfg.s.bus_ena = bus_ena; + write_csr(mmc, MIO_EMM_CFG(), emm_cfg.u); + if (host->tap_requires_noclk) { + /* Turn off the clock */ + emm_debug.u = read_csr(mmc, MIO_EMM_DEBUG()); + emm_debug.s.emmc_clk_disable = 1; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + udelay(1); + emm_debug.s.rdsync_rst = 1; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + udelay(1); + } + + write_csr(mmc, MIO_EMM_TIMING(), emm_timing.u); + if (host->tap_requires_noclk) { + /* Turn the clock back on */ + udelay(1); + emm_debug.s.rdsync_rst = 0; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + udelay(1); + emm_debug.s.emmc_clk_disable = 0; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + } + udelay(1); + write_csr(mmc, MIO_EMM_IO_CTL(), emm_io_ctl.u); + bus_id = emm_switch.s.bus_id; + emm_switch.s.bus_id = 0; + write_csr(mmc, MIO_EMM_SWITCH(), emm_switch.u); + emm_switch.s.bus_id = bus_id; + write_csr(mmc, MIO_EMM_SWITCH(), emm_switch.u); + write_csr(mmc, MIO_EMM_WDOG(), emm_wdog.u); + write_csr(mmc, MIO_EMM_STS_MASK(), emm_sts_mask.u); + write_csr(mmc, MIO_EMM_RCA(), mmc->rca); + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + + if (!emm_tap.s.delay) { + pr_err("%s: Error: delay calibration failed, timed out.\n", + __func__); + /* Set to default value if timed out */ + emm_tap.s.delay = MMC_DEFAULT_TAP_DELAY; + return -ETIMEDOUT; + } + } + /* Round up */ + host->timing_taps = (10 * 1000 * emm_tap.s.delay) / TOTAL_NO_OF_TAPS; + debug("%s(%s): timing taps: %llu, delay: %u\n", + __func__, mmc->dev->name, host->timing_taps, emm_tap.s.delay); + host->timing_calibrated = true; + return 0; +} + +static int octeontx_mmc_set_input_bus_timing(struct mmc *mmc) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + + if (IS_ENABLED(CONFIG_ARCH_OCTEONTX)) { + union mio_emm_sample sample; + + sample.u = 0; + sample.s.cmd_cnt = slot->cmd_clk_skew; + sample.s.dat_cnt = slot->dat_clk_skew; + write_csr(mmc, MIO_EMM_SAMPLE(), sample.u); + } else { + union mio_emm_timing timing; + + timing.u = read_csr(mmc, MIO_EMM_TIMING()); + if (mmc->selected_mode == MMC_HS_200) { + if (slot->hs200_tuned) { + timing.s.cmd_in_tap = + slot->hs200_taps.s.cmd_in_tap; + timing.s.data_in_tap = + slot->hs200_taps.s.data_in_tap; + } else { + pr_warn("%s(%s): Warning: hs200 timing not tuned\n", + __func__, mmc->dev->name); + timing.s.cmd_in_tap = + MMC_DEFAULT_HS200_CMD_IN_TAP; + timing.s.data_in_tap = + MMC_DEFAULT_HS200_DATA_IN_TAP; + } + } else if (mmc->selected_mode == MMC_HS_400) { + if (slot->hs400_tuned) { + timing.s.cmd_in_tap = + slot->hs400_taps.s.cmd_in_tap; + timing.s.data_in_tap = + slot->hs400_taps.s.data_in_tap; + } else if (slot->hs200_tuned) { + timing.s.cmd_in_tap = + slot->hs200_taps.s.cmd_in_tap; + timing.s.data_in_tap = + slot->hs200_taps.s.data_in_tap; + } else { + pr_warn("%s(%s): Warning: hs400 timing not tuned\n", + __func__, mmc->dev->name); + timing.s.cmd_in_tap = + MMC_DEFAULT_HS200_CMD_IN_TAP; + timing.s.data_in_tap = + MMC_DEFAULT_HS200_DATA_IN_TAP; + } + } else if (slot->tuned) { + timing.s.cmd_in_tap = slot->taps.s.cmd_in_tap; + timing.s.data_in_tap = slot->taps.s.data_in_tap; + } else { + timing.s.cmd_in_tap = MMC_DEFAULT_CMD_IN_TAP; + timing.s.data_in_tap = MMC_DEFAULT_DATA_IN_TAP; + } + octeontx_mmc_set_emm_timing(mmc, timing); + } + + return 0; +} + +/** + * Sets the default bus timing for the current mode. + * + * @param mmc mmc data structure + * + * @return 0 for success, error otherwise + */ +static int octeontx_mmc_set_output_bus_timing(struct mmc *mmc) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + union mio_emm_timing timing; + int cout_bdelay, dout_bdelay; + unsigned int cout_delay, dout_delay; + char env_name[32]; + + if (IS_ENABLED(CONFIG_ARCH_OCTEONTX)) + return 0; + + debug("%s(%s)\n", __func__, mmc->dev->name); + if (slot->is_asim || slot->is_emul) + return 0; + + octeontx_mmc_calibrate_delay(mmc); + + if (mmc->clock < 26000000) { + cout_delay = 5000; + dout_delay = 5000; + } else if (mmc->clock <= 52000000) { + cout_delay = 2500; + dout_delay = 2500; + } else if (!mmc_is_mode_ddr(mmc->selected_mode)) { + cout_delay = slot->cmd_out_hs200_delay; + dout_delay = slot->data_out_hs200_delay; + } else { + cout_delay = slot->cmd_out_hs400_delay; + dout_delay = slot->data_out_hs400_delay; + } + + snprintf(env_name, sizeof(env_name), "mmc%d_hs200_dout_delay_ps", + slot->bus_id); + dout_delay = env_get_ulong(env_name, 10, dout_delay); + debug("%s: dout_delay: %u\n", __func__, dout_delay); + + cout_bdelay = octeontx2_mmc_calc_delay(mmc, cout_delay); + dout_bdelay = octeontx2_mmc_calc_delay(mmc, dout_delay); + + debug("%s: cmd output delay: %u, data output delay: %u, cmd bdelay: %d, data bdelay: %d, clock: %d\n", + __func__, cout_delay, dout_delay, cout_bdelay, dout_bdelay, + mmc->clock); + if (cout_bdelay < 0 || dout_bdelay < 0) { + pr_err("%s: Error: could not calculate command and/or data clock skew\n", + __func__); + return -1; + } + timing.u = read_csr(mmc, MIO_EMM_TIMING()); + timing.s.cmd_out_tap = cout_bdelay; + timing.s.data_out_tap = dout_bdelay; + if (mmc->selected_mode == MMC_HS_200) { + slot->hs200_taps.s.cmd_out_tap = cout_bdelay; + slot->hs200_taps.s.data_out_tap = dout_bdelay; + } else if (mmc->selected_mode == MMC_HS_400) { + slot->hs400_taps.s.cmd_out_tap = cout_bdelay; + slot->hs400_taps.s.data_out_tap = dout_bdelay; + } else { + slot->taps.s.cmd_out_tap = cout_bdelay; + slot->taps.s.data_out_tap = dout_bdelay; + } + octeontx_mmc_set_emm_timing(mmc, timing); + debug("%s(%s): bdelay: %d/%d, clock: %d, ddr: %s, timing taps: %llu, do: %d, di: %d, co: %d, ci: %d\n", + __func__, mmc->dev->name, cout_bdelay, dout_bdelay, mmc->clock, + mmc->ddr_mode ? "yes" : "no", + mmc_to_host(mmc)->timing_taps, + timing.s.data_out_tap, + timing.s.data_in_tap, + timing.s.cmd_out_tap, + timing.s.cmd_in_tap); + + return 0; +} +#endif + +static void octeontx_mmc_set_clock(struct mmc *mmc) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + uint clock; + + clock = min(mmc->cfg->f_max, (uint)slot->clock); + clock = max(mmc->cfg->f_min, clock); + debug("%s(%s): f_min: %u, f_max: %u, clock: %u\n", __func__, + mmc->dev->name, mmc->cfg->f_min, mmc->cfg->f_max, clock); + slot->clock = clock; + mmc->clock = clock; +} + +/** + * This switches I/O power as needed when switching between slots. + * + * @param mmc mmc data structure + */ +static void octeontx_mmc_switch_io(struct mmc *mmc) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + struct octeontx_mmc_host *host = slot->host; + struct mmc *last_mmc = host->last_mmc; + static struct udevice *last_reg; + union mio_emm_cfg emm_cfg; + int bus; + static bool initialized; + + /* First time? */ + if (!initialized || mmc != host->last_mmc) { + struct mmc *ommc; + + /* Switch to bus 3 which is unused */ + emm_cfg.u = read_csr(mmc, MIO_EMM_CFG()); + emm_cfg.s.bus_ena = 1 << 3; + write_csr(mmc, MIO_EMM_CFG(), emm_cfg.u); + + /* Turn off all other I/O interfaces with first initialization + * if at least one supply was found. + */ + for (bus = 0; bus <= OCTEONTX_MAX_MMC_SLOT; bus++) { + ommc = &host->slots[bus].mmc; + + /* Handle self case later */ + if (ommc == mmc || !ommc->vqmmc_supply) + continue; + + /* Skip if we're not switching regulators */ + if (last_reg == mmc->vqmmc_supply) + continue; + + /* Turn off other regulators */ + if (ommc->vqmmc_supply != mmc->vqmmc_supply) + regulator_set_enable(ommc->vqmmc_supply, false); + } + /* Turn ourself on */ + if (mmc->vqmmc_supply && last_reg != mmc->vqmmc_supply) + regulator_set_enable(mmc->vqmmc_supply, true); + mdelay(1); /* Settle time */ + /* Switch to new bus */ + emm_cfg.s.bus_ena = 1 << slot->bus_id; + write_csr(mmc, MIO_EMM_CFG(), emm_cfg.u); + last_reg = mmc->vqmmc_supply; + initialized = true; + return; + } + + /* No change in device */ + if (last_mmc == mmc) + return; + + if (!last_mmc) { + pr_warn("%s(%s): No previous slot detected in IO slot switch!\n", + __func__, mmc->dev->name); + return; + } + + debug("%s(%s): last: %s, supply: %p\n", __func__, mmc->dev->name, + last_mmc->dev->name, mmc->vqmmc_supply); + + /* The supply is the same so we do nothing */ + if (last_mmc->vqmmc_supply == mmc->vqmmc_supply) + return; + + /* Turn off the old slot I/O supply */ + if (last_mmc->vqmmc_supply) { + debug("%s(%s): Turning off IO to %s, supply: %s\n", + __func__, mmc->dev->name, last_mmc->dev->name, + last_mmc->vqmmc_supply->name); + regulator_set_enable(last_mmc->vqmmc_supply, false); + } + /* Turn on the new slot I/O supply */ + if (mmc->vqmmc_supply) { + debug("%s(%s): Turning on IO to slot %d, supply: %s\n", + __func__, mmc->dev->name, slot->bus_id, + mmc->vqmmc_supply->name); + regulator_set_enable(mmc->vqmmc_supply, true); + } + /* Allow power to settle */ + mdelay(1); +} + +/** + * Called to switch between mmc devices + * + * @param mmc new mmc device + */ +static void octeontx_mmc_switch_to(struct mmc *mmc) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + struct octeontx_mmc_slot *old_slot; + struct octeontx_mmc_host *host = slot->host; + union mio_emm_switch emm_switch; + union mio_emm_sts_mask emm_sts_mask; + union mio_emm_rca emm_rca; + + if (slot->bus_id == host->last_slotid) + return; + + debug("%s(%s) switching from slot %d to slot %d\n", __func__, + mmc->dev->name, host->last_slotid, slot->bus_id); + octeontx_mmc_switch_io(mmc); + + if (host->last_slotid >= 0 && slot->valid) { + old_slot = &host->slots[host->last_slotid]; + old_slot->cached_switch.u = read_csr(mmc, MIO_EMM_SWITCH()); + old_slot->cached_rca.u = read_csr(mmc, MIO_EMM_RCA()); + } + if (mmc->rca) + write_csr(mmc, MIO_EMM_RCA(), mmc->rca); + emm_switch = slot->cached_switch; + do_switch(mmc, emm_switch); + emm_rca.u = 0; + emm_rca.s.card_rca = mmc->rca; + write_csr(mmc, MIO_EMM_RCA(), emm_rca.u); + mdelay(100); + + set_wdog(mmc, 100000); + if (octeontx_mmc_set_output_bus_timing(mmc) || + octeontx_mmc_set_input_bus_timing(mmc)) + pr_err("%s(%s): Error setting bus timing\n", __func__, + mmc->dev->name); + octeontx_mmc_io_drive_setup(mmc); + + emm_sts_mask.u = 0; + emm_sts_mask.s.sts_msk = 1 << 7 | 1 << 22 | 1 << 23 | 1 << 19; + write_csr(mmc, MIO_EMM_STS_MASK(), emm_sts_mask.u); + host->last_slotid = slot->bus_id; + host->last_mmc = mmc; + mdelay(10); +} + +/** + * Perform initial timing configuration + * + * @param mmc mmc device + * + * @return 0 for success + * + * NOTE: This will need to be updated when new silicon comes out + */ +static int octeontx_mmc_init_timing(struct mmc *mmc) +{ + union mio_emm_timing timing; + + if (mmc_to_slot(mmc)->is_asim || mmc_to_slot(mmc)->is_emul) + return 0; + + debug("%s(%s)\n", __func__, mmc->dev->name); + timing.u = 0; + timing.s.cmd_out_tap = MMC_DEFAULT_CMD_OUT_TAP; + timing.s.data_out_tap = MMC_DEFAULT_DATA_OUT_TAP; + timing.s.cmd_in_tap = MMC_DEFAULT_CMD_IN_TAP; + timing.s.data_in_tap = MMC_DEFAULT_DATA_IN_TAP; + octeontx_mmc_set_emm_timing(mmc, timing); + return 0; +} + +/** + * Perform low-level initialization + * + * @param mmc mmc device + * + * @return 0 for success, error otherwise + */ +static int octeontx_mmc_init_lowlevel(struct mmc *mmc) +{ + struct octeontx_mmc_slot *slot = mmc_to_slot(mmc); + struct octeontx_mmc_host *host = slot->host; + union mio_emm_switch emm_switch; + u32 clk_period; + + debug("%s(%s): lowlevel init for slot %d\n", __func__, + mmc->dev->name, slot->bus_id); + host->emm_cfg.s.bus_ena &= ~(1 << slot->bus_id); + write_csr(mmc, MIO_EMM_CFG(), host->emm_cfg.u); + udelay(100); + host->emm_cfg.s.bus_ena |= 1 << slot->bus_id; + write_csr(mmc, MIO_EMM_CFG(), host->emm_cfg.u); + udelay(10); + slot->clock = mmc->cfg->f_min; + octeontx_mmc_set_clock(&slot->mmc); + + if (IS_ENABLED(CONFIG_ARCH_OCTEONTX2)) { + if (host->cond_clock_glitch) { + union mio_emm_debug emm_debug; + + emm_debug.u = read_csr(mmc, MIO_EMM_DEBUG()); + emm_debug.s.clk_on = 1; + write_csr(mmc, MIO_EMM_DEBUG(), emm_debug.u); + } + octeontx_mmc_calibrate_delay(&slot->mmc); + } + + clk_period = octeontx_mmc_calc_clk_period(mmc); + emm_switch.u = 0; + emm_switch.s.power_class = 10; + emm_switch.s.clk_lo = clk_period / 2; + emm_switch.s.clk_hi = clk_period / 2; + + emm_switch.s.bus_id = slot->bus_id; + debug("%s: Performing switch\n", __func__); + do_switch(mmc, emm_switch); + slot->cached_switch.u = emm_switch.u; + + if (!IS_ENABLED(CONFIG_ARCH_OCTEONTX)) + octeontx_mmc_init_timing(mmc); + + set_wdog(mmc, 1000000); /* Set to 1 second */ + write_csr(mmc, MIO_EMM_STS_MASK(), 0xe4390080ull); + write_csr(mmc, MIO_EMM_RCA(), 1); + mdelay(10); + debug("%s: done\n", __func__); + return 0; +} + +/** + * Translates a voltage number to bits in MMC register + * + * @param voltage voltage in microvolts + * + * @return MMC register value for voltage + */ +static u32 xlate_voltage(u32 voltage) +{ + u32 volt = 0; + + /* Convert to millivolts. Only necessary on ARM Octeon TX/TX2 */ + if (!IS_ENABLED(CONFIG_ARCH_OCTEON)) + voltage /= 1000; + + if (voltage >= 1650 && voltage <= 1950) + volt |= MMC_VDD_165_195; + if (voltage >= 2000 && voltage <= 2100) + volt |= MMC_VDD_20_21; + if (voltage >= 2100 && voltage <= 2200) + volt |= MMC_VDD_21_22; + if (voltage >= 2200 && voltage <= 2300) + volt |= MMC_VDD_22_23; + if (voltage >= 2300 && voltage <= 2400) + volt |= MMC_VDD_23_24; + if (voltage >= 2400 && voltage <= 2500) + volt |= MMC_VDD_24_25; + if (voltage >= 2500 && voltage <= 2600) + volt |= MMC_VDD_25_26; + if (voltage >= 2600 && voltage <= 2700) + volt |= MMC_VDD_26_27; + if (voltage >= 2700 && voltage <= 2800) + volt |= MMC_VDD_27_28; + if (voltage >= 2800 && voltage <= 2900) + volt |= MMC_VDD_28_29; + if (voltage >= 2900 && voltage <= 3000) + volt |= MMC_VDD_29_30; + if (voltage >= 3000 && voltage <= 3100) + volt |= MMC_VDD_30_31; + if (voltage >= 3100 && voltage <= 3200) + volt |= MMC_VDD_31_32; + if (voltage >= 3200 && voltage <= 3300) + volt |= MMC_VDD_32_33; + if (voltage >= 3300 && voltage <= 3400) + volt |= MMC_VDD_33_34; + if (voltage >= 3400 && voltage <= 3500) + volt |= MMC_VDD_34_35; + if (voltage >= 3500 && voltage <= 3600) + volt |= MMC_VDD_35_36; + + return volt; +} + +/** + * Check if a slot is valid in the device tree + * + * @param dev slot device to check + * + * @return true if status reports "ok" or "okay" or if no status, + * false otherwise. + */ +static bool octeontx_mmc_get_valid(struct udevice *dev) +{ + const char *stat = ofnode_read_string(dev_ofnode(dev), "status"); + + if (!stat || !strncmp(stat, "ok", 2)) + return true; + else + return false; +} + +/** + * Reads slot configuration from the device tree + * + * @param dev slot device + * + * @return 0 on success, otherwise error + */ +static int octeontx_mmc_get_config(struct udevice *dev) +{ + struct octeontx_mmc_slot *slot = dev_to_mmc_slot(dev); + uint voltages[2]; + uint low, high; + char env_name[32]; + int err; + ofnode node = dev_ofnode(dev); + int bus_width = 1; + ulong new_max_freq; + + debug("%s(%s)", __func__, dev->name); + slot->cfg.name = dev->name; + + slot->cfg.f_max = ofnode_read_s32_default(dev_ofnode(dev), + "max-frequency", + 26000000); + snprintf(env_name, sizeof(env_name), "mmc_max_frequency%d", + slot->bus_id); + + new_max_freq = env_get_ulong(env_name, 10, slot->cfg.f_max); + debug("Reading %s, got %lu\n", env_name, new_max_freq); + + if (new_max_freq != slot->cfg.f_max) { + printf("Overriding device tree MMC maximum frequency %u to %lu\n", + slot->cfg.f_max, new_max_freq); + slot->cfg.f_max = new_max_freq; + } + slot->cfg.f_min = 400000; + slot->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + if (IS_ENABLED(CONFIG_ARCH_OCTEONTX2)) { + slot->hs400_tuning_block = + ofnode_read_s32_default(dev_ofnode(dev), + "marvell,hs400-tuning-block", + -1); + debug("%s(%s): mmc HS400 tuning block: %d\n", __func__, + dev->name, slot->hs400_tuning_block); + + slot->hs200_tap_adj = + ofnode_read_s32_default(dev_ofnode(dev), + "marvell,hs200-tap-adjust", 0); + debug("%s(%s): hs200-tap-adjust: %d\n", __func__, dev->name, + slot->hs200_tap_adj); + slot->hs400_tap_adj = + ofnode_read_s32_default(dev_ofnode(dev), + "marvell,hs400-tap-adjust", 0); + debug("%s(%s): hs400-tap-adjust: %d\n", __func__, dev->name, + slot->hs400_tap_adj); + } + + err = ofnode_read_u32_array(dev_ofnode(dev), "voltage-ranges", + voltages, 2); + if (err) { + slot->cfg.voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + } else { + low = xlate_voltage(voltages[0]); + high = xlate_voltage(voltages[1]); + debug(" low voltage: 0x%x (%u), high: 0x%x (%u)\n", + low, voltages[0], high, voltages[1]); + if (low > high || !low || !high) { + pr_err("Invalid MMC voltage range [%u-%u] specified for %s\n", + low, high, dev->name); + return -1; + } + slot->cfg.voltages = 0; + do { + slot->cfg.voltages |= low; + low <<= 1; + } while (low <= high); + } + debug("%s: config voltages: 0x%x\n", __func__, slot->cfg.voltages); + slot->slew = ofnode_read_s32_default(node, "cavium,clk-slew", -1); + slot->drive = ofnode_read_s32_default(node, "cavium,drv-strength", -1); + gpio_request_by_name(dev, "cd-gpios", 0, &slot->cd_gpio, GPIOD_IS_IN); + slot->cd_inverted = ofnode_read_bool(node, "cd-inverted"); + gpio_request_by_name(dev, "wp-gpios", 0, &slot->wp_gpio, GPIOD_IS_IN); + slot->wp_inverted = ofnode_read_bool(node, "wp-inverted"); + if (slot->cfg.voltages & MMC_VDD_165_195) { + slot->is_1_8v = true; + slot->is_3_3v = false; + } else if (slot->cfg.voltages & (MMC_VDD_30_31 | MMC_VDD_31_32 | + MMC_VDD_33_34 | MMC_VDD_34_35 | + MMC_VDD_35_36)) { + slot->is_1_8v = false; + slot->is_3_3v = true; + } + + bus_width = ofnode_read_u32_default(node, "bus-width", 1); + /* Note fall-through */ + switch (bus_width) { + case 8: + slot->cfg.host_caps |= MMC_MODE_8BIT; + case 4: + slot->cfg.host_caps |= MMC_MODE_4BIT; + case 1: + slot->cfg.host_caps |= MMC_MODE_1BIT; + break; + } + if (ofnode_read_bool(node, "no-1-8-v")) { + slot->is_3_3v = true; + slot->is_1_8v = false; + if (!(slot->cfg.voltages & (MMC_VDD_32_33 | MMC_VDD_33_34))) + pr_warn("%s(%s): voltages indicate 3.3v but 3.3v not supported\n", + __func__, dev->name); + } + if (ofnode_read_bool(node, "mmc-ddr-3-3v")) { + slot->is_3_3v = true; + slot->is_1_8v = false; + if (!(slot->cfg.voltages & (MMC_VDD_32_33 | MMC_VDD_33_34))) + pr_warn("%s(%s): voltages indicate 3.3v but 3.3v not supported\n", + __func__, dev->name); + } + if (ofnode_read_bool(node, "cap-sd-highspeed") || + ofnode_read_bool(node, "cap-mmc-highspeed") || + ofnode_read_bool(node, "sd-uhs-sdr25")) + slot->cfg.host_caps |= MMC_MODE_HS; + if (slot->cfg.f_max >= 50000000 && + slot->cfg.host_caps & MMC_MODE_HS) + slot->cfg.host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + if (ofnode_read_bool(node, "sd-uhs-sdr50")) + slot->cfg.host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + if (ofnode_read_bool(node, "sd-uhs-ddr50")) + slot->cfg.host_caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz | + MMC_MODE_DDR_52MHz; + + if (IS_ENABLED(CONFIG_ARCH_OCTEONTX2)) { + if (!slot->is_asim && !slot->is_emul) { + if (ofnode_read_bool(node, "mmc-hs200-1_8v")) + slot->cfg.host_caps |= MMC_MODE_HS200 | + MMC_MODE_HS_52MHz; + if (ofnode_read_bool(node, "mmc-hs400-1_8v")) + slot->cfg.host_caps |= MMC_MODE_HS400 | + MMC_MODE_HS_52MHz | + MMC_MODE_HS200 | + MMC_MODE_DDR_52MHz; + slot->cmd_out_hs200_delay = + ofnode_read_u32_default(node, + "marvell,cmd-out-hs200-dly", + MMC_DEFAULT_HS200_CMD_OUT_DLY); + debug("%s(%s): HS200 cmd out delay: %d\n", + __func__, dev->name, slot->cmd_out_hs200_delay); + slot->data_out_hs200_delay = + ofnode_read_u32_default(node, + "marvell,data-out-hs200-dly", + MMC_DEFAULT_HS200_DATA_OUT_DLY); + debug("%s(%s): HS200 data out delay: %d\n", + __func__, dev->name, slot->data_out_hs200_delay); + slot->cmd_out_hs400_delay = + ofnode_read_u32_default(node, + "marvell,cmd-out-hs400-dly", + MMC_DEFAULT_HS400_CMD_OUT_DLY); + debug("%s(%s): HS400 cmd out delay: %d\n", + __func__, dev->name, slot->cmd_out_hs400_delay); + slot->data_out_hs400_delay = + ofnode_read_u32_default(node, + "marvell,data-out-hs400-dly", + MMC_DEFAULT_HS400_DATA_OUT_DLY); + debug("%s(%s): HS400 data out delay: %d\n", + __func__, dev->name, slot->data_out_hs400_delay); + } + } + + slot->disable_ddr = ofnode_read_bool(node, "marvell,disable-ddr"); + slot->non_removable = ofnode_read_bool(node, "non-removable"); + slot->cmd_clk_skew = ofnode_read_u32_default(node, + "cavium,cmd-clk-skew", 0); + slot->dat_clk_skew = ofnode_read_u32_default(node, + "cavium,dat-clk-skew", 0); + debug("%s(%s): host caps: 0x%x\n", __func__, + dev->name, slot->cfg.host_caps); + return 0; +} + +/** + * Probes a MMC slot + * + * @param dev mmc device + * + * @return 0 for success, error otherwise + */ +static int octeontx_mmc_slot_probe(struct udevice *dev) +{ + struct octeontx_mmc_slot *slot; + struct mmc *mmc; + int err; + + debug("%s(%s)\n", __func__, dev->name); + if (!host_probed) { + pr_err("%s(%s): Error: host not probed yet\n", + __func__, dev->name); + } + slot = dev_to_mmc_slot(dev); + mmc = &slot->mmc; + mmc->dev = dev; + + slot->valid = false; + if (!octeontx_mmc_get_valid(dev)) { + debug("%s(%s): slot is invalid\n", __func__, dev->name); + return -ENODEV; + } + + debug("%s(%s): Getting config\n", __func__, dev->name); + err = octeontx_mmc_get_config(dev); + if (err) { + pr_err("probe(%s): Error getting config\n", dev->name); + return err; + } + + debug("%s(%s): mmc bind, mmc: %p\n", __func__, dev->name, &slot->mmc); + err = mmc_bind(dev, &slot->mmc, &slot->cfg); + if (err) { + pr_err("%s(%s): Error binding mmc\n", __func__, dev->name); + return -1; + } + + /* For some reason, mmc_bind always assigns priv to the device */ + slot->mmc.priv = slot; + + debug("%s(%s): lowlevel init\n", __func__, dev->name); + err = octeontx_mmc_init_lowlevel(mmc); + if (err) { + pr_err("probe(%s): Low-level init failed\n", dev->name); + return err; + } + + slot->valid = true; + + debug("%s(%s):\n" + " base address : %p\n" + " bus id : %d\n", __func__, dev->name, + slot->base_addr, slot->bus_id); + + return err; +} + +/** + * MMC slot driver operations + */ +static const struct dm_mmc_ops octeontx_hsmmc_ops = { + .send_cmd = octeontx_mmc_dev_send_cmd, + .set_ios = octeontx_mmc_set_ios, + .get_cd = octeontx_mmc_get_cd, + .get_wp = octeontx_mmc_get_wp, +#ifdef MMC_SUPPORTS_TUNING + .execute_tuning = octeontx_mmc_execute_tuning, +#endif +}; + +static const struct udevice_id octeontx_hsmmc_ids[] = { + { .compatible = "mmc-slot" }, + { } +}; + +U_BOOT_DRIVER(octeontx_hsmmc_slot) = { + .name = "octeontx_hsmmc_slot", + .id = UCLASS_MMC, + .of_match = of_match_ptr(octeontx_hsmmc_ids), + .probe = octeontx_mmc_slot_probe, + .ops = &octeontx_hsmmc_ops, +}; + +/***************************************************************** + * PCI host driver + * + * The PCI host driver contains the resources used by all of the + * slot drivers. + * + * The slot drivers are pseudo drivers. + */ + +/** + * Probe the MMC host controller + * + * @param dev mmc host controller device + * + * @return 0 for success, -1 on error + */ +static int octeontx_mmc_host_probe(struct udevice *dev) +{ + struct octeontx_mmc_host *host = dev_get_priv(dev); + union mio_emm_int emm_int; + struct clk clk; + int ret; + u8 rev; + + debug("%s(%s): Entry host: %p\n", __func__, dev->name, host); + + if (!octeontx_mmc_get_valid(dev)) { + debug("%s(%s): mmc host not valid\n", __func__, dev->name); + return -ENODEV; + } + memset(host, 0, sizeof(*host)); + + /* Octeon TX & TX2 use PCI based probing */ + if (device_is_compatible(dev, "cavium,thunder-8890-mmc")) { + host->base_addr = dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, + PCI_REGION_MEM); + if (!host->base_addr) { + pr_err("%s: Error: MMC base address not found\n", + __func__); + return -1; + } + } else { + host->base_addr = dev_remap_addr(dev); + } + + host->dev = dev; + debug("%s(%s): Base address: %p\n", __func__, dev->name, + host->base_addr); + if (!dev_has_ofnode(dev)) { + pr_err("%s: No device tree information found\n", __func__); + return -1; + } + host->node = dev_ofnode(dev); + host->last_slotid = -1; +#if !defined(CONFIG_ARCH_OCTEON) + if (otx_is_platform(PLATFORM_ASIM)) + host->is_asim = true; + if (otx_is_platform(PLATFORM_EMULATOR)) + host->is_emul = true; +#endif + host->dma_wait_delay = + ofnode_read_u32_default(dev_ofnode(dev), + "marvell,dma-wait-delay", 1); + /* Force reset of eMMC */ + writeq(0, host->base_addr + MIO_EMM_CFG()); + debug("%s: Clearing MIO_EMM_CFG\n", __func__); + udelay(100); + emm_int.u = readq(host->base_addr + MIO_EMM_INT()); + debug("%s: Writing 0x%llx to MIO_EMM_INT\n", __func__, emm_int.u); + writeq(emm_int.u, host->base_addr + MIO_EMM_INT()); + + debug("%s(%s): Getting I/O clock\n", __func__, dev->name); + ret = clk_get_by_index(dev, 0, &clk); + if (ret < 0) + return ret; + + ret = clk_enable(&clk); + if (ret) + return ret; + + host->sys_freq = clk_get_rate(&clk); + debug("%s(%s): I/O clock %llu\n", __func__, dev->name, host->sys_freq); + + if (IS_ENABLED(CONFIG_ARCH_OCTEONTX2)) { + /* Flags for issues to work around */ + dm_pci_read_config8(dev, PCI_REVISION_ID, &rev); + if (otx_is_soc(CN96XX)) { + debug("%s: CN96XX revision %d\n", __func__, rev); + switch (rev) { + case 0: + host->calibrate_glitch = true; + host->cond_clock_glitch = true; + break; + case 1: + break; + case 2: + break; + case 0x10: /* C0 */ + host->hs400_skew_needed = true; + debug("HS400 skew support enabled\n"); + fallthrough; + default: + debug("CN96XX rev C0+ detected\n"); + host->tap_requires_noclk = true; + break; + } + } else if (otx_is_soc(CN95XX)) { + if (!rev) + host->cond_clock_glitch = true; + } + } + + host_probed = true; + + return 0; +} + +/** + * This performs some initial setup before a probe occurs. + * + * @param dev: MMC slot device + * + * @return 0 for success, -1 on failure + * + * Do some pre-initialization before probing a slot. + */ +static int octeontx_mmc_host_child_pre_probe(struct udevice *dev) +{ + struct octeontx_mmc_host *host = dev_get_priv(dev_get_parent(dev)); + struct octeontx_mmc_slot *slot; + struct mmc_uclass_priv *upriv; + ofnode node = dev_ofnode(dev); + u32 bus_id; + char name[16]; + int err; + + debug("%s(%s) Pre-Probe\n", __func__, dev->name); + if (ofnode_read_u32(node, "reg", &bus_id)) { + pr_err("%s(%s): Error: \"reg\" not found in device tree\n", + __func__, dev->name); + return -1; + } + if (bus_id > OCTEONTX_MAX_MMC_SLOT) { + pr_err("%s(%s): Error: \"reg\" out of range of 0..%d\n", + __func__, dev->name, OCTEONTX_MAX_MMC_SLOT); + return -1; + } + + slot = &host->slots[bus_id]; + dev_set_priv(dev, slot); + slot->host = host; + slot->bus_id = bus_id; + slot->dev = dev; + slot->base_addr = host->base_addr; + slot->is_asim = host->is_asim; + slot->is_emul = host->is_emul; + + snprintf(name, sizeof(name), "octeontx-mmc%d", bus_id); + err = device_set_name(dev, name); + + /* FIXME: This code should not be needed */ + if (!dev_get_uclass_priv(dev)) { + debug("%s(%s): Allocating uclass priv\n", __func__, + dev->name); + upriv = calloc(1, sizeof(struct mmc_uclass_priv)); + if (!upriv) + return -ENOMEM; + + /* + * FIXME: This is not allowed + * dev_set_uclass_priv(dev, upriv); + * uclass_set_priv(dev->uclass, upriv); + */ + } else { + upriv = dev_get_uclass_priv(dev); + } + + upriv->mmc = &slot->mmc; + debug("%s: uclass priv: %p, mmc: %p\n", dev->name, upriv, upriv->mmc); + + debug("%s: ret: %d\n", __func__, err); + return err; +} + +static const struct udevice_id octeontx_hsmmc_host_ids[] = { + { .compatible = "cavium,thunder-8890-mmc" }, + { .compatible = "cavium,octeon-7360-mmc" }, + { } +}; + +U_BOOT_DRIVER(octeontx_hsmmc_host) = { + .name = "octeontx_hsmmc_host", + /* FIXME: Why is this not UCLASS_MMC? */ + .id = UCLASS_MISC, + .of_match = of_match_ptr(octeontx_hsmmc_host_ids), + .probe = octeontx_mmc_host_probe, + .priv_auto = sizeof(struct octeontx_mmc_host), + .child_pre_probe = octeontx_mmc_host_child_pre_probe, + .flags = DM_FLAG_PRE_RELOC, +}; + +static struct pci_device_id octeontx_mmc_supported[] = { + { PCI_VDEVICE(CAVIUM, PCI_DEVICE_ID_CAVIUM_EMMC) }, + { }, +}; + +U_BOOT_PCI_DEVICE(octeontx_hsmmc_host, octeontx_mmc_supported); diff --git a/roms/u-boot/drivers/mmc/octeontx_hsmmc.h b/roms/u-boot/drivers/mmc/octeontx_hsmmc.h new file mode 100644 index 000000000..70844b1cb --- /dev/null +++ b/roms/u-boot/drivers/mmc/octeontx_hsmmc.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2019 Marvell International Ltd. + * + * https://spdx.org/licenses + */ +#ifndef __OCTEONTX_HSMMC_H__ +#define __OCTEONTX_HSMMC_H__ +#include <asm/gpio.h> + +/** Name of our driver */ +#define OCTEONTX_MMC_DRIVER_NAME "octeontx-hsmmc" + +/** Maximum supported MMC slots */ +#define OCTEONTX_MAX_MMC_SLOT 3 + +#define POWER_ON_TIME 40 /** See SD 4.1 spec figure 6-5 */ + +/** + * Timeout used when waiting for commands to complete. We need to keep this + * above the hardware watchdog timeout which is usually limited to 1000ms + */ +#define WATCHDOG_COUNT (1100) /* in msecs */ + +/** + * Long timeout for commands which might take a while to complete. + */ +#define MMC_TIMEOUT_LONG 1000 + +/** + * Short timeout used for most commands in msecs + */ +#define MMC_TIMEOUT_SHORT 20 + +#define NSEC_PER_SEC 1000000000L + +#define MAX_NO_OF_TAPS 64 + +#define EXT_CSD_POWER_CLASS 187 /* R/W */ + +/* default HS400 tuning block number */ +#define DEFAULT_HS400_TUNING_BLOCK 1 + +struct octeontx_mmc_host; + +/** MMC/SD slot data structure */ +struct octeontx_mmc_slot { + struct mmc mmc; + struct mmc_config cfg; + struct octeontx_mmc_host *host; + struct udevice *dev; + void *base_addr; /** Same as host base_addr */ + u64 clock; + int bus_id; /** slot number */ + uint bus_width; + uint max_width; + int hs200_tap_adj; + int hs400_tap_adj; + int hs400_tuning_block; + struct gpio_desc cd_gpio; + struct gpio_desc wp_gpio; + struct gpio_desc power_gpio; + enum bus_mode mode; + union mio_emm_switch cached_switch; + union mio_emm_switch want_switch; + union mio_emm_rca cached_rca; + union mio_emm_timing taps; /* otx2: MIO_EMM_TIMING */ + union mio_emm_timing hs200_taps; + union mio_emm_timing hs400_taps; + /* These are used to see if our tuning is still valid or not */ + enum bus_mode last_mode; + u32 last_clock; + u32 block_len; + u32 block_count; + int cmd_clk_skew; + int dat_clk_skew; + uint cmd_cnt; /* otx: sample cmd in delay */ + uint dat_cnt; /* otx: sample data in delay */ + uint drive; /* Current drive */ + uint slew; /* clock skew */ + uint cmd_out_hs200_delay; + uint data_out_hs200_delay; + uint cmd_out_hs400_delay; + uint data_out_hs400_delay; + uint clk_period; + bool valid:1; + bool is_acmd:1; + bool tuned:1; + bool hs200_tuned:1; + bool hs400_tuned:1; + bool is_1_8v:1; + bool is_3_3v:1; + bool is_ddr:1; + bool is_asim:1; + bool is_emul:1; + bool cd_inverted:1; + bool wp_inverted:1; + bool disable_ddr:1; + bool non_removable:1; +}; + +struct octeontx_mmc_cr_mods { + u8 ctype_xor; + u8 rtype_xor; +}; + +struct octeontx_mmc_cr { + u8 c; + u8 r; +}; + +struct octeontx_sd_mods { + struct octeontx_mmc_cr mmc; + struct octeontx_mmc_cr sd; + struct octeontx_mmc_cr sdacmd; +}; + +/** Host controller data structure */ +struct octeontx_mmc_host { + struct udevice *dev; + void *base_addr; + struct octeontx_mmc_slot slots[OCTEONTX_MAX_MMC_SLOT + 1]; + pci_dev_t pdev; + u64 sys_freq; + union mio_emm_cfg emm_cfg; + u64 timing_taps; + struct mmc *last_mmc; /** Last mmc used */ + ofnode node; + int cur_slotid; + int last_slotid; + int max_width; + uint per_tap_delay; + uint num_slots; + uint dma_wait_delay; /* Delay before polling DMA in usecs */ + bool initialized:1; + bool timing_calibrated:1; + bool is_asim:1; + bool is_emul:1; + bool calibrate_glitch:1; + bool cond_clock_glitch:1; + bool tap_requires_noclk:1; + bool hs400_skew_needed:1; +}; + +/* + * NOTE: This was copied from the Linux kernel. + * + * MMC status in R1, for native mode (SPI bits are different) + * Type + * e:error bit + * s:status bit + * r:detected and set for the actual command response + * x:detected and set during command execution. the host must poll + * the card by sending status command in order to read these bits. + * Clear condition + * a:according to the card state + * b:always related to the previous command. Reception of + * a valid command will clear it (with a delay of one command) + * c:clear by read + */ +#define R1_OUT_OF_RANGE BIT(31) /* er, c */ +#define R1_ADDRESS_ERROR BIT(30) /* erx, c */ +#define R1_BLOCK_LEN_ERROR BIT(29) /* er, c */ +#define R1_ERASE_SEQ_ERROR BIT(28) /* er, c */ +#define R1_ERASE_PARAM BIT(27) /* ex, c */ +#define R1_WP_VIOLATION BIT(26) /* erx, c */ +#define R1_CARD_IS_LOCKED BIT(25) /* sx, a */ +#define R1_LOCK_UNLOCK_FAILED BIT(24) /* erx, c */ +#define R1_COM_CRC_ERROR BIT(23) /* er, b */ +/*#define R1_ILLEGAL_COMMAND BIT(22)*/ /* er, b */ +#define R1_CARD_ECC_FAILED BIT(21) /* ex, c */ +#define R1_CC_ERROR BIT(20) /* erx, c */ +#define R1_ERROR BIT(19) /* erx, c */ +#define R1_UNDERRUN BIT(18) /* ex, c */ +#define R1_OVERRUN BIT(17) /* ex, c */ +#define R1_CID_CSD_OVERWRITE BIT(16) /* erx, c, CID/CSD overwrite */ +#define R1_WP_ERASE_SKIP BIT(15) /* sx, c */ +#define R1_CARD_ECC_DISABLED BIT(14) /* sx, a */ +#define R1_ERASE_RESET BIT(13) /* sr, c */ +#define R1_STATUS(x) ((x) & 0xFFFFE000) +#define R1_CURRENT_STATE(x) (((x) & 0x00001E00) >> 9) /* sx, b (4 bits) */ +#define R1_READY_FOR_DATA BIT(8) /* sx, a */ +#define R1_SWITCH_ERROR BIT(7) /* sx, c */ + +#define R1_BLOCK_READ_MASK R1_OUT_OF_RANGE | \ + R1_ADDRESS_ERROR | \ + R1_BLOCK_LEN_ERROR | \ + R1_CARD_IS_LOCKED | \ + R1_COM_CRC_ERROR | \ + R1_ILLEGAL_COMMAND | \ + R1_CARD_ECC_FAILED | \ + R1_CC_ERROR | \ + R1_ERROR +#define R1_BLOCK_WRITE_MASK R1_OUT_OF_RANGE | \ + R1_ADDRESS_ERROR | \ + R1_BLOCK_LEN_ERROR | \ + R1_WP_VIOLATION | \ + R1_CARD_IS_LOCKED | \ + R1_COM_CRC_ERROR | \ + R1_ILLEGAL_COMMAND | \ + R1_CARD_ECC_FAILED | \ + R1_CC_ERROR | \ + R1_ERROR | \ + R1_UNDERRUN | \ + R1_OVERRUN + +#endif /* __OCTEONTX_HSMMC_H__ */ diff --git a/roms/u-boot/drivers/mmc/omap_hsmmc.c b/roms/u-boot/drivers/mmc/omap_hsmmc.c new file mode 100644 index 000000000..da44511d9 --- /dev/null +++ b/roms/u-boot/drivers/mmc/omap_hsmmc.c @@ -0,0 +1,2045 @@ +/* + * (C) Copyright 2008 + * Texas Instruments, <www.ti.com> + * Sukumar Ghorai <s-ghorai@ti.com> + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation's version 2 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <config.h> +#include <common.h> +#include <cpu_func.h> +#include <log.h> +#include <malloc.h> +#include <memalign.h> +#include <mmc.h> +#include <part.h> +#include <i2c.h> +#if defined(CONFIG_OMAP54XX) || defined(CONFIG_OMAP44XX) +#include <palmas.h> +#endif +#include <asm/cache.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <asm/arch/mmc_host_def.h> +#ifdef CONFIG_OMAP54XX +#include <asm/arch/mux_dra7xx.h> +#include <asm/arch/dra7xx_iodelay.h> +#endif +#if !defined(CONFIG_SOC_KEYSTONE) +#include <asm/gpio.h> +#include <asm/arch/sys_proto.h> +#endif +#ifdef CONFIG_MMC_OMAP36XX_PINS +#include <asm/arch/mux.h> +#endif +#include <dm.h> +#include <dm/devres.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <power/regulator.h> +#include <thermal.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* simplify defines to OMAP_HSMMC_USE_GPIO */ +#if (defined(CONFIG_OMAP_GPIO) && !defined(CONFIG_SPL_BUILD)) || \ + (defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_GPIO_SUPPORT)) +#define OMAP_HSMMC_USE_GPIO +#else +#undef OMAP_HSMMC_USE_GPIO +#endif + +/* common definitions for all OMAPs */ +#define SYSCTL_SRC (1 << 25) +#define SYSCTL_SRD (1 << 26) + +#ifdef CONFIG_IODELAY_RECALIBRATION +struct omap_hsmmc_pinctrl_state { + struct pad_conf_entry *padconf; + int npads; + struct iodelay_cfg_entry *iodelay; + int niodelays; +}; +#endif + +struct omap_hsmmc_data { + struct hsmmc *base_addr; +#if !CONFIG_IS_ENABLED(DM_MMC) + struct mmc_config cfg; +#endif + uint bus_width; + uint clock; + ushort last_cmd; +#ifdef OMAP_HSMMC_USE_GPIO +#if CONFIG_IS_ENABLED(DM_MMC) + struct gpio_desc cd_gpio; /* Change Detect GPIO */ + struct gpio_desc wp_gpio; /* Write Protect GPIO */ +#else + int cd_gpio; + int wp_gpio; +#endif +#endif +#if CONFIG_IS_ENABLED(DM_MMC) + enum bus_mode mode; +#endif + u8 controller_flags; +#ifdef CONFIG_MMC_OMAP_HS_ADMA + struct omap_hsmmc_adma_desc *adma_desc_table; + uint desc_slot; +#endif + const char *hw_rev; + struct udevice *pbias_supply; + uint signal_voltage; +#ifdef CONFIG_IODELAY_RECALIBRATION + struct omap_hsmmc_pinctrl_state *default_pinctrl_state; + struct omap_hsmmc_pinctrl_state *hs_pinctrl_state; + struct omap_hsmmc_pinctrl_state *hs200_1_8v_pinctrl_state; + struct omap_hsmmc_pinctrl_state *ddr_1_8v_pinctrl_state; + struct omap_hsmmc_pinctrl_state *sdr12_pinctrl_state; + struct omap_hsmmc_pinctrl_state *sdr25_pinctrl_state; + struct omap_hsmmc_pinctrl_state *ddr50_pinctrl_state; + struct omap_hsmmc_pinctrl_state *sdr50_pinctrl_state; + struct omap_hsmmc_pinctrl_state *sdr104_pinctrl_state; +#endif +}; + +struct omap_mmc_of_data { + u8 controller_flags; +}; + +#ifdef CONFIG_MMC_OMAP_HS_ADMA +struct omap_hsmmc_adma_desc { + u8 attr; + u8 reserved; + u16 len; + u32 addr; +}; + +#define ADMA_MAX_LEN 63488 + +/* Decriptor table defines */ +#define ADMA_DESC_ATTR_VALID BIT(0) +#define ADMA_DESC_ATTR_END BIT(1) +#define ADMA_DESC_ATTR_INT BIT(2) +#define ADMA_DESC_ATTR_ACT1 BIT(4) +#define ADMA_DESC_ATTR_ACT2 BIT(5) + +#define ADMA_DESC_TRANSFER_DATA ADMA_DESC_ATTR_ACT2 +#define ADMA_DESC_LINK_DESC (ADMA_DESC_ATTR_ACT1 | ADMA_DESC_ATTR_ACT2) +#endif + +/* If we fail after 1 second wait, something is really bad */ +#define MAX_RETRY_MS 1000 +#define MMC_TIMEOUT_MS 20 + +/* DMA transfers can take a long time if a lot a data is transferred. + * The timeout must take in account the amount of data. Let's assume + * that the time will never exceed 333 ms per MB (in other word we assume + * that the bandwidth is always above 3MB/s). + */ +#define DMA_TIMEOUT_PER_MB 333 +#define OMAP_HSMMC_SUPPORTS_DUAL_VOLT BIT(0) +#define OMAP_HSMMC_NO_1_8_V BIT(1) +#define OMAP_HSMMC_USE_ADMA BIT(2) +#define OMAP_HSMMC_REQUIRE_IODELAY BIT(3) + +static int mmc_read_data(struct hsmmc *mmc_base, char *buf, unsigned int size); +static int mmc_write_data(struct hsmmc *mmc_base, const char *buf, + unsigned int siz); +static void omap_hsmmc_start_clock(struct hsmmc *mmc_base); +static void omap_hsmmc_stop_clock(struct hsmmc *mmc_base); +static void mmc_reset_controller_fsm(struct hsmmc *mmc_base, u32 bit); + +static inline struct omap_hsmmc_data *omap_hsmmc_get_data(struct mmc *mmc) +{ +#if CONFIG_IS_ENABLED(DM_MMC) + return dev_get_priv(mmc->dev); +#else + return (struct omap_hsmmc_data *)mmc->priv; +#endif +} + +#if defined(CONFIG_OMAP34XX) || defined(CONFIG_IODELAY_RECALIBRATION) +static inline struct mmc_config *omap_hsmmc_get_cfg(struct mmc *mmc) +{ +#if CONFIG_IS_ENABLED(DM_MMC) + struct omap_hsmmc_plat *plat = dev_get_plat(mmc->dev); + return &plat->cfg; +#else + return &((struct omap_hsmmc_data *)mmc->priv)->cfg; +#endif +} +#endif + +#if defined(OMAP_HSMMC_USE_GPIO) && !CONFIG_IS_ENABLED(DM_MMC) +static int omap_mmc_setup_gpio_in(int gpio, const char *label) +{ + int ret; + +#if !CONFIG_IS_ENABLED(DM_GPIO) + if (!gpio_is_valid(gpio)) + return -1; +#endif + ret = gpio_request(gpio, label); + if (ret) + return ret; + + ret = gpio_direction_input(gpio); + if (ret) + return ret; + + return gpio; +} +#endif + +static unsigned char mmc_board_init(struct mmc *mmc) +{ +#if defined(CONFIG_OMAP34XX) + struct mmc_config *cfg = omap_hsmmc_get_cfg(mmc); + t2_t *t2_base = (t2_t *)T2_BASE; + struct prcm *prcm_base = (struct prcm *)PRCM_BASE; + u32 pbias_lite; +#ifdef CONFIG_MMC_OMAP36XX_PINS + u32 wkup_ctrl = readl(OMAP34XX_CTRL_WKUP_CTRL); +#endif + + pbias_lite = readl(&t2_base->pbias_lite); + pbias_lite &= ~(PBIASLITEPWRDNZ1 | PBIASLITEPWRDNZ0); +#ifdef CONFIG_TARGET_OMAP3_CAIRO + /* for cairo board, we need to set up 1.8 Volt bias level on MMC1 */ + pbias_lite &= ~PBIASLITEVMODE0; +#endif +#ifdef CONFIG_TARGET_OMAP3_LOGIC + /* For Logic PD board, 1.8V bias to go enable gpio127 for mmc_cd */ + pbias_lite &= ~PBIASLITEVMODE1; +#endif +#ifdef CONFIG_MMC_OMAP36XX_PINS + if (get_cpu_family() == CPU_OMAP36XX) { + /* Disable extended drain IO before changing PBIAS */ + wkup_ctrl &= ~OMAP34XX_CTRL_WKUP_CTRL_GPIO_IO_PWRDNZ; + writel(wkup_ctrl, OMAP34XX_CTRL_WKUP_CTRL); + } +#endif + writel(pbias_lite, &t2_base->pbias_lite); + + writel(pbias_lite | PBIASLITEPWRDNZ1 | + PBIASSPEEDCTRL0 | PBIASLITEPWRDNZ0, + &t2_base->pbias_lite); + +#ifdef CONFIG_MMC_OMAP36XX_PINS + if (get_cpu_family() == CPU_OMAP36XX) + /* Enable extended drain IO after changing PBIAS */ + writel(wkup_ctrl | + OMAP34XX_CTRL_WKUP_CTRL_GPIO_IO_PWRDNZ, + OMAP34XX_CTRL_WKUP_CTRL); +#endif + writel(readl(&t2_base->devconf0) | MMCSDIO1ADPCLKISEL, + &t2_base->devconf0); + + writel(readl(&t2_base->devconf1) | MMCSDIO2ADPCLKISEL, + &t2_base->devconf1); + + /* Change from default of 52MHz to 26MHz if necessary */ + if (!(cfg->host_caps & MMC_MODE_HS_52MHz)) + writel(readl(&t2_base->ctl_prog_io1) & ~CTLPROGIO1SPEEDCTRL, + &t2_base->ctl_prog_io1); + + writel(readl(&prcm_base->fclken1_core) | + EN_MMC1 | EN_MMC2 | EN_MMC3, + &prcm_base->fclken1_core); + + writel(readl(&prcm_base->iclken1_core) | + EN_MMC1 | EN_MMC2 | EN_MMC3, + &prcm_base->iclken1_core); +#endif + +#if (defined(CONFIG_OMAP54XX) || defined(CONFIG_OMAP44XX)) &&\ + !CONFIG_IS_ENABLED(DM_REGULATOR) + /* PBIAS config needed for MMC1 only */ + if (mmc_get_blk_desc(mmc)->devnum == 0) + vmmc_pbias_config(LDO_VOLT_3V3); +#endif + + return 0; +} + +void mmc_init_stream(struct hsmmc *mmc_base) +{ + ulong start; + + writel(readl(&mmc_base->con) | INIT_INITSTREAM, &mmc_base->con); + + writel(MMC_CMD0, &mmc_base->cmd); + start = get_timer(0); + while (!(readl(&mmc_base->stat) & CC_MASK)) { + if (get_timer(0) - start > MAX_RETRY_MS) { + printf("%s: timedout waiting for cc!\n", __func__); + return; + } + } + writel(CC_MASK, &mmc_base->stat) + ; + writel(MMC_CMD0, &mmc_base->cmd) + ; + start = get_timer(0); + while (!(readl(&mmc_base->stat) & CC_MASK)) { + if (get_timer(0) - start > MAX_RETRY_MS) { + printf("%s: timedout waiting for cc2!\n", __func__); + return; + } + } + writel(readl(&mmc_base->con) & ~INIT_INITSTREAM, &mmc_base->con); +} + +#if CONFIG_IS_ENABLED(DM_MMC) +#ifdef CONFIG_IODELAY_RECALIBRATION +static void omap_hsmmc_io_recalibrate(struct mmc *mmc) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + struct omap_hsmmc_pinctrl_state *pinctrl_state; + + switch (priv->mode) { + case MMC_HS_200: + pinctrl_state = priv->hs200_1_8v_pinctrl_state; + break; + case UHS_SDR104: + pinctrl_state = priv->sdr104_pinctrl_state; + break; + case UHS_SDR50: + pinctrl_state = priv->sdr50_pinctrl_state; + break; + case UHS_DDR50: + pinctrl_state = priv->ddr50_pinctrl_state; + break; + case UHS_SDR25: + pinctrl_state = priv->sdr25_pinctrl_state; + break; + case UHS_SDR12: + pinctrl_state = priv->sdr12_pinctrl_state; + break; + case SD_HS: + case MMC_HS: + case MMC_HS_52: + pinctrl_state = priv->hs_pinctrl_state; + break; + case MMC_DDR_52: + pinctrl_state = priv->ddr_1_8v_pinctrl_state; + default: + pinctrl_state = priv->default_pinctrl_state; + break; + } + + if (!pinctrl_state) + pinctrl_state = priv->default_pinctrl_state; + + if (priv->controller_flags & OMAP_HSMMC_REQUIRE_IODELAY) { + if (pinctrl_state->iodelay) + late_recalibrate_iodelay(pinctrl_state->padconf, + pinctrl_state->npads, + pinctrl_state->iodelay, + pinctrl_state->niodelays); + else + do_set_mux32((*ctrl)->control_padconf_core_base, + pinctrl_state->padconf, + pinctrl_state->npads); + } +} +#endif +static void omap_hsmmc_set_timing(struct mmc *mmc) +{ + u32 val; + struct hsmmc *mmc_base; + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + + mmc_base = priv->base_addr; + + omap_hsmmc_stop_clock(mmc_base); + val = readl(&mmc_base->ac12); + val &= ~AC12_UHSMC_MASK; + priv->mode = mmc->selected_mode; + + if (mmc_is_mode_ddr(priv->mode)) + writel(readl(&mmc_base->con) | DDR, &mmc_base->con); + else + writel(readl(&mmc_base->con) & ~DDR, &mmc_base->con); + + switch (priv->mode) { + case MMC_HS_200: + case UHS_SDR104: + val |= AC12_UHSMC_SDR104; + break; + case UHS_SDR50: + val |= AC12_UHSMC_SDR50; + break; + case MMC_DDR_52: + case UHS_DDR50: + val |= AC12_UHSMC_DDR50; + break; + case SD_HS: + case MMC_HS_52: + case UHS_SDR25: + val |= AC12_UHSMC_SDR25; + break; + case MMC_LEGACY: + case MMC_HS: + case UHS_SDR12: + val |= AC12_UHSMC_SDR12; + break; + default: + val |= AC12_UHSMC_RES; + break; + } + writel(val, &mmc_base->ac12); + +#ifdef CONFIG_IODELAY_RECALIBRATION + omap_hsmmc_io_recalibrate(mmc); +#endif + omap_hsmmc_start_clock(mmc_base); +} + +static void omap_hsmmc_conf_bus_power(struct mmc *mmc, uint signal_voltage) +{ + struct hsmmc *mmc_base; + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + u32 hctl, ac12; + + mmc_base = priv->base_addr; + + hctl = readl(&mmc_base->hctl) & ~SDVS_MASK; + ac12 = readl(&mmc_base->ac12) & ~AC12_V1V8_SIGEN; + + switch (signal_voltage) { + case MMC_SIGNAL_VOLTAGE_330: + hctl |= SDVS_3V3; + break; + case MMC_SIGNAL_VOLTAGE_180: + hctl |= SDVS_1V8; + ac12 |= AC12_V1V8_SIGEN; + break; + } + + writel(hctl, &mmc_base->hctl); + writel(ac12, &mmc_base->ac12); +} + +static int omap_hsmmc_wait_dat0(struct udevice *dev, int state, int timeout_us) +{ + int ret = -ETIMEDOUT; + u32 con; + bool dat0_high; + bool target_dat0_high = !!state; + struct omap_hsmmc_data *priv = dev_get_priv(dev); + struct hsmmc *mmc_base = priv->base_addr; + + con = readl(&mmc_base->con); + writel(con | CON_CLKEXTFREE | CON_PADEN, &mmc_base->con); + + timeout_us = DIV_ROUND_UP(timeout_us, 10); /* check every 10 us. */ + while (timeout_us--) { + dat0_high = !!(readl(&mmc_base->pstate) & PSTATE_DLEV_DAT0); + if (dat0_high == target_dat0_high) { + ret = 0; + break; + } + udelay(10); + } + writel(con, &mmc_base->con); + + return ret; +} + +#if CONFIG_IS_ENABLED(MMC_IO_VOLTAGE) +#if CONFIG_IS_ENABLED(DM_REGULATOR) +static int omap_hsmmc_set_io_regulator(struct mmc *mmc, int mV) +{ + int ret = 0; + int uV = mV * 1000; + + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + + if (!mmc->vqmmc_supply) + return 0; + + /* Disable PBIAS */ + ret = regulator_set_enable_if_allowed(priv->pbias_supply, false); + if (ret) + return ret; + + /* Turn off IO voltage */ + ret = regulator_set_enable_if_allowed(mmc->vqmmc_supply, false); + if (ret) + return ret; + /* Program a new IO voltage value */ + ret = regulator_set_value(mmc->vqmmc_supply, uV); + if (ret) + return ret; + /* Turn on IO voltage */ + ret = regulator_set_enable_if_allowed(mmc->vqmmc_supply, true); + if (ret) + return ret; + + /* Program PBIAS voltage*/ + ret = regulator_set_value(priv->pbias_supply, uV); + if (ret && ret != -ENOSYS) + return ret; + /* Enable PBIAS */ + ret = regulator_set_enable_if_allowed(priv->pbias_supply, true); + if (ret) + return ret; + + return 0; +} +#endif + +static int omap_hsmmc_set_signal_voltage(struct mmc *mmc) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + struct hsmmc *mmc_base = priv->base_addr; + int mv = mmc_voltage_to_mv(mmc->signal_voltage); + u32 capa_mask; + __maybe_unused u8 palmas_ldo_volt; + u32 val; + + if (mv < 0) + return -EINVAL; + + if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_330) { + mv = 3300; + capa_mask = VS33_3V3SUP; + palmas_ldo_volt = LDO_VOLT_3V3; + } else if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180) { + capa_mask = VS18_1V8SUP; + palmas_ldo_volt = LDO_VOLT_1V8; + } else { + return -EOPNOTSUPP; + } + + val = readl(&mmc_base->capa); + if (!(val & capa_mask)) + return -EOPNOTSUPP; + + priv->signal_voltage = mmc->signal_voltage; + + omap_hsmmc_conf_bus_power(mmc, mmc->signal_voltage); + +#if CONFIG_IS_ENABLED(DM_REGULATOR) + return omap_hsmmc_set_io_regulator(mmc, mv); +#elif (defined(CONFIG_OMAP54XX) || defined(CONFIG_OMAP44XX)) && \ + defined(CONFIG_PALMAS_POWER) + if (mmc_get_blk_desc(mmc)->devnum == 0) + vmmc_pbias_config(palmas_ldo_volt); + return 0; +#else + return 0; +#endif +} +#endif + +static uint32_t omap_hsmmc_set_capabilities(struct mmc *mmc) +{ + struct hsmmc *mmc_base; + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + u32 val; + + mmc_base = priv->base_addr; + val = readl(&mmc_base->capa); + + if (priv->controller_flags & OMAP_HSMMC_SUPPORTS_DUAL_VOLT) { + val |= (VS33_3V3SUP | VS18_1V8SUP); + } else if (priv->controller_flags & OMAP_HSMMC_NO_1_8_V) { + val |= VS33_3V3SUP; + val &= ~VS18_1V8SUP; + } else { + val |= VS18_1V8SUP; + val &= ~VS33_3V3SUP; + } + + writel(val, &mmc_base->capa); + + return val; +} + +#ifdef MMC_SUPPORTS_TUNING +static void omap_hsmmc_disable_tuning(struct mmc *mmc) +{ + struct hsmmc *mmc_base; + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + u32 val; + + mmc_base = priv->base_addr; + val = readl(&mmc_base->ac12); + val &= ~(AC12_SCLK_SEL); + writel(val, &mmc_base->ac12); + + val = readl(&mmc_base->dll); + val &= ~(DLL_FORCE_VALUE | DLL_SWT); + writel(val, &mmc_base->dll); +} + +static void omap_hsmmc_set_dll(struct mmc *mmc, int count) +{ + int i; + struct hsmmc *mmc_base; + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + u32 val; + + mmc_base = priv->base_addr; + val = readl(&mmc_base->dll); + val |= DLL_FORCE_VALUE; + val &= ~(DLL_FORCE_SR_C_MASK << DLL_FORCE_SR_C_SHIFT); + val |= (count << DLL_FORCE_SR_C_SHIFT); + writel(val, &mmc_base->dll); + + val |= DLL_CALIB; + writel(val, &mmc_base->dll); + for (i = 0; i < 1000; i++) { + if (readl(&mmc_base->dll) & DLL_CALIB) + break; + } + val &= ~DLL_CALIB; + writel(val, &mmc_base->dll); +} + +static int omap_hsmmc_execute_tuning(struct udevice *dev, uint opcode) +{ + struct omap_hsmmc_data *priv = dev_get_priv(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct mmc *mmc = upriv->mmc; + struct hsmmc *mmc_base; + u32 val; + u8 cur_match, prev_match = 0; + int ret; + u32 phase_delay = 0; + u32 start_window = 0, max_window = 0; + u32 length = 0, max_len = 0; + bool single_point_failure = false; + struct udevice *thermal_dev; + int temperature; + int i; + + mmc_base = priv->base_addr; + val = readl(&mmc_base->capa2); + + /* clock tuning is not needed for upto 52MHz */ + if (!((mmc->selected_mode == MMC_HS_200) || + (mmc->selected_mode == UHS_SDR104) || + ((mmc->selected_mode == UHS_SDR50) && (val & CAPA2_TSDR50)))) + return 0; + + ret = uclass_first_device(UCLASS_THERMAL, &thermal_dev); + if (ret) { + printf("Couldn't get thermal device for tuning\n"); + return ret; + } + ret = thermal_get_temp(thermal_dev, &temperature); + if (ret) { + printf("Couldn't get temperature for tuning\n"); + return ret; + } + val = readl(&mmc_base->dll); + val |= DLL_SWT; + writel(val, &mmc_base->dll); + + /* + * Stage 1: Search for a maximum pass window ignoring any + * any single point failures. If the tuning value ends up + * near it, move away from it in stage 2 below + */ + while (phase_delay <= MAX_PHASE_DELAY) { + omap_hsmmc_set_dll(mmc, phase_delay); + + cur_match = !mmc_send_tuning(mmc, opcode, NULL); + + if (cur_match) { + if (prev_match) { + length++; + } else if (single_point_failure) { + /* ignore single point failure */ + length++; + single_point_failure = false; + } else { + start_window = phase_delay; + length = 1; + } + } else { + single_point_failure = prev_match; + } + + if (length > max_len) { + max_window = start_window; + max_len = length; + } + + prev_match = cur_match; + phase_delay += 4; + } + + if (!max_len) { + ret = -EIO; + goto tuning_error; + } + + val = readl(&mmc_base->ac12); + if (!(val & AC12_SCLK_SEL)) { + ret = -EIO; + goto tuning_error; + } + /* + * Assign tuning value as a ratio of maximum pass window based + * on temperature + */ + if (temperature < -20000) + phase_delay = min(max_window + 4 * max_len - 24, + max_window + + DIV_ROUND_UP(13 * max_len, 16) * 4); + else if (temperature < 20000) + phase_delay = max_window + DIV_ROUND_UP(9 * max_len, 16) * 4; + else if (temperature < 40000) + phase_delay = max_window + DIV_ROUND_UP(8 * max_len, 16) * 4; + else if (temperature < 70000) + phase_delay = max_window + DIV_ROUND_UP(7 * max_len, 16) * 4; + else if (temperature < 90000) + phase_delay = max_window + DIV_ROUND_UP(5 * max_len, 16) * 4; + else if (temperature < 120000) + phase_delay = max_window + DIV_ROUND_UP(4 * max_len, 16) * 4; + else + phase_delay = max_window + DIV_ROUND_UP(3 * max_len, 16) * 4; + + /* + * Stage 2: Search for a single point failure near the chosen tuning + * value in two steps. First in the +3 to +10 range and then in the + * +2 to -10 range. If found, move away from it in the appropriate + * direction by the appropriate amount depending on the temperature. + */ + for (i = 3; i <= 10; i++) { + omap_hsmmc_set_dll(mmc, phase_delay + i); + if (mmc_send_tuning(mmc, opcode, NULL)) { + if (temperature < 10000) + phase_delay += i + 6; + else if (temperature < 20000) + phase_delay += i - 12; + else if (temperature < 70000) + phase_delay += i - 8; + else if (temperature < 90000) + phase_delay += i - 6; + else + phase_delay += i - 6; + + goto single_failure_found; + } + } + + for (i = 2; i >= -10; i--) { + omap_hsmmc_set_dll(mmc, phase_delay + i); + if (mmc_send_tuning(mmc, opcode, NULL)) { + if (temperature < 10000) + phase_delay += i + 12; + else if (temperature < 20000) + phase_delay += i + 8; + else if (temperature < 70000) + phase_delay += i + 8; + else if (temperature < 90000) + phase_delay += i + 10; + else + phase_delay += i + 12; + + goto single_failure_found; + } + } + +single_failure_found: + + omap_hsmmc_set_dll(mmc, phase_delay); + + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRD); + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRC); + + return 0; + +tuning_error: + + omap_hsmmc_disable_tuning(mmc); + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRD); + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRC); + + return ret; +} +#endif +#endif + +static void mmc_enable_irq(struct mmc *mmc, struct mmc_cmd *cmd) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + struct hsmmc *mmc_base = priv->base_addr; + u32 irq_mask = INT_EN_MASK; + + /* + * TODO: Errata i802 indicates only DCRC interrupts can occur during + * tuning procedure and DCRC should be disabled. But see occurences + * of DEB, CIE, CEB, CCRC interupts during tuning procedure. These + * interrupts occur along with BRR, so the data is actually in the + * buffer. It has to be debugged why these interrutps occur + */ + if (cmd && mmc_is_tuning_cmd(cmd->cmdidx)) + irq_mask &= ~(IE_DEB | IE_DCRC | IE_CIE | IE_CEB | IE_CCRC); + + writel(irq_mask, &mmc_base->ie); +} + +static int omap_hsmmc_init_setup(struct mmc *mmc) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + struct hsmmc *mmc_base; + unsigned int reg_val; + unsigned int dsor; + ulong start; + + mmc_base = priv->base_addr; + mmc_board_init(mmc); + + writel(readl(&mmc_base->sysconfig) | MMC_SOFTRESET, + &mmc_base->sysconfig); + start = get_timer(0); + while ((readl(&mmc_base->sysstatus) & RESETDONE) == 0) { + if (get_timer(0) - start > MAX_RETRY_MS) { + printf("%s: timedout waiting for cc2!\n", __func__); + return -ETIMEDOUT; + } + } + writel(readl(&mmc_base->sysctl) | SOFTRESETALL, &mmc_base->sysctl); + start = get_timer(0); + while ((readl(&mmc_base->sysctl) & SOFTRESETALL) != 0x0) { + if (get_timer(0) - start > MAX_RETRY_MS) { + printf("%s: timedout waiting for softresetall!\n", + __func__); + return -ETIMEDOUT; + } + } +#ifdef CONFIG_MMC_OMAP_HS_ADMA + reg_val = readl(&mmc_base->hl_hwinfo); + if (reg_val & MADMA_EN) + priv->controller_flags |= OMAP_HSMMC_USE_ADMA; +#endif + +#if CONFIG_IS_ENABLED(DM_MMC) + reg_val = omap_hsmmc_set_capabilities(mmc); + omap_hsmmc_conf_bus_power(mmc, (reg_val & VS33_3V3SUP) ? + MMC_SIGNAL_VOLTAGE_330 : MMC_SIGNAL_VOLTAGE_180); +#else + writel(DTW_1_BITMODE | SDBP_PWROFF | SDVS_3V3, &mmc_base->hctl); + writel(readl(&mmc_base->capa) | VS33_3V3SUP | VS18_1V8SUP, + &mmc_base->capa); +#endif + + reg_val = readl(&mmc_base->con) & RESERVED_MASK; + + writel(CTPL_MMC_SD | reg_val | WPP_ACTIVEHIGH | CDP_ACTIVEHIGH | + MIT_CTO | DW8_1_4BITMODE | MODE_FUNC | STR_BLOCK | + HR_NOHOSTRESP | INIT_NOINIT | NOOPENDRAIN, &mmc_base->con); + + dsor = 240; + mmc_reg_out(&mmc_base->sysctl, (ICE_MASK | DTO_MASK | CEN_MASK), + (ICE_STOP | DTO_15THDTO)); + mmc_reg_out(&mmc_base->sysctl, ICE_MASK | CLKD_MASK, + (dsor << CLKD_OFFSET) | ICE_OSCILLATE); + start = get_timer(0); + while ((readl(&mmc_base->sysctl) & ICS_MASK) == ICS_NOTREADY) { + if (get_timer(0) - start > MAX_RETRY_MS) { + printf("%s: timedout waiting for ics!\n", __func__); + return -ETIMEDOUT; + } + } + writel(readl(&mmc_base->sysctl) | CEN_ENABLE, &mmc_base->sysctl); + + writel(readl(&mmc_base->hctl) | SDBP_PWRON, &mmc_base->hctl); + + mmc_enable_irq(mmc, NULL); + +#if !CONFIG_IS_ENABLED(DM_MMC) + mmc_init_stream(mmc_base); +#endif + + return 0; +} + +/* + * MMC controller internal finite state machine reset + * + * Used to reset command or data internal state machines, using respectively + * SRC or SRD bit of SYSCTL register + */ +static void mmc_reset_controller_fsm(struct hsmmc *mmc_base, u32 bit) +{ + ulong start; + + mmc_reg_out(&mmc_base->sysctl, bit, bit); + + /* + * CMD(DAT) lines reset procedures are slightly different + * for OMAP3 and OMAP4(AM335x,OMAP5,DRA7xx). + * According to OMAP3 TRM: + * Set SRC(SRD) bit in MMCHS_SYSCTL register to 0x1 and wait until it + * returns to 0x0. + * According to OMAP4(AM335x,OMAP5,DRA7xx) TRMs, CMD(DATA) lines reset + * procedure steps must be as follows: + * 1. Initiate CMD(DAT) line reset by writing 0x1 to SRC(SRD) bit in + * MMCHS_SYSCTL register (SD_SYSCTL for AM335x). + * 2. Poll the SRC(SRD) bit until it is set to 0x1. + * 3. Wait until the SRC (SRD) bit returns to 0x0 + * (reset procedure is completed). + */ +#if defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX) || \ + defined(CONFIG_AM33XX) || defined(CONFIG_AM43XX) + if (!(readl(&mmc_base->sysctl) & bit)) { + start = get_timer(0); + while (!(readl(&mmc_base->sysctl) & bit)) { + if (get_timer(0) - start > MMC_TIMEOUT_MS) + return; + } + } +#endif + start = get_timer(0); + while ((readl(&mmc_base->sysctl) & bit) != 0) { + if (get_timer(0) - start > MAX_RETRY_MS) { + printf("%s: timedout waiting for sysctl %x to clear\n", + __func__, bit); + return; + } + } +} + +#ifdef CONFIG_MMC_OMAP_HS_ADMA +static void omap_hsmmc_adma_desc(struct mmc *mmc, char *buf, u16 len, bool end) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + struct omap_hsmmc_adma_desc *desc; + u8 attr; + + desc = &priv->adma_desc_table[priv->desc_slot]; + + attr = ADMA_DESC_ATTR_VALID | ADMA_DESC_TRANSFER_DATA; + if (!end) + priv->desc_slot++; + else + attr |= ADMA_DESC_ATTR_END; + + desc->len = len; + desc->addr = (u32)buf; + desc->reserved = 0; + desc->attr = attr; +} + +static void omap_hsmmc_prepare_adma_table(struct mmc *mmc, + struct mmc_data *data) +{ + uint total_len = data->blocksize * data->blocks; + uint desc_count = DIV_ROUND_UP(total_len, ADMA_MAX_LEN); + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + int i = desc_count; + char *buf; + + priv->desc_slot = 0; + priv->adma_desc_table = (struct omap_hsmmc_adma_desc *) + memalign(ARCH_DMA_MINALIGN, desc_count * + sizeof(struct omap_hsmmc_adma_desc)); + + if (data->flags & MMC_DATA_READ) + buf = data->dest; + else + buf = (char *)data->src; + + while (--i) { + omap_hsmmc_adma_desc(mmc, buf, ADMA_MAX_LEN, false); + buf += ADMA_MAX_LEN; + total_len -= ADMA_MAX_LEN; + } + + omap_hsmmc_adma_desc(mmc, buf, total_len, true); + + flush_dcache_range((long)priv->adma_desc_table, + (long)priv->adma_desc_table + + ROUND(desc_count * + sizeof(struct omap_hsmmc_adma_desc), + ARCH_DMA_MINALIGN)); +} + +static void omap_hsmmc_prepare_data(struct mmc *mmc, struct mmc_data *data) +{ + struct hsmmc *mmc_base; + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + u32 val; + char *buf; + + mmc_base = priv->base_addr; + omap_hsmmc_prepare_adma_table(mmc, data); + + if (data->flags & MMC_DATA_READ) + buf = data->dest; + else + buf = (char *)data->src; + + val = readl(&mmc_base->hctl); + val |= DMA_SELECT; + writel(val, &mmc_base->hctl); + + val = readl(&mmc_base->con); + val |= DMA_MASTER; + writel(val, &mmc_base->con); + + writel((u32)priv->adma_desc_table, &mmc_base->admasal); + + flush_dcache_range((u32)buf, + (u32)buf + + ROUND(data->blocksize * data->blocks, + ARCH_DMA_MINALIGN)); +} + +static void omap_hsmmc_dma_cleanup(struct mmc *mmc) +{ + struct hsmmc *mmc_base; + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + u32 val; + + mmc_base = priv->base_addr; + + val = readl(&mmc_base->con); + val &= ~DMA_MASTER; + writel(val, &mmc_base->con); + + val = readl(&mmc_base->hctl); + val &= ~DMA_SELECT; + writel(val, &mmc_base->hctl); + + kfree(priv->adma_desc_table); +} +#else +#define omap_hsmmc_adma_desc +#define omap_hsmmc_prepare_adma_table +#define omap_hsmmc_prepare_data +#define omap_hsmmc_dma_cleanup +#endif + +#if !CONFIG_IS_ENABLED(DM_MMC) +static int omap_hsmmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); +#else +static int omap_hsmmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct omap_hsmmc_data *priv = dev_get_priv(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct mmc *mmc = upriv->mmc; +#endif + struct hsmmc *mmc_base; + unsigned int flags, mmc_stat; + ulong start; + priv->last_cmd = cmd->cmdidx; + + mmc_base = priv->base_addr; + + if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION) + return 0; + + start = get_timer(0); + while ((readl(&mmc_base->pstate) & (DATI_MASK | CMDI_MASK)) != 0) { + if (get_timer(0) - start > MAX_RETRY_MS) { + printf("%s: timedout waiting on cmd inhibit to clear\n", + __func__); + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRD); + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRC); + return -ETIMEDOUT; + } + } + writel(0xFFFFFFFF, &mmc_base->stat); + if (readl(&mmc_base->stat)) { + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRD); + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRC); + } + + /* + * CMDREG + * CMDIDX[13:8] : Command index + * DATAPRNT[5] : Data Present Select + * ENCMDIDX[4] : Command Index Check Enable + * ENCMDCRC[3] : Command CRC Check Enable + * RSPTYP[1:0] + * 00 = No Response + * 01 = Length 136 + * 10 = Length 48 + * 11 = Length 48 Check busy after response + */ + /* Delay added before checking the status of frq change + * retry not supported by mmc.c(core file) + */ + if (cmd->cmdidx == SD_CMD_APP_SEND_SCR) + udelay(50000); /* wait 50 ms */ + + if (!(cmd->resp_type & MMC_RSP_PRESENT)) + flags = 0; + else if (cmd->resp_type & MMC_RSP_136) + flags = RSP_TYPE_LGHT136 | CICE_NOCHECK; + else if (cmd->resp_type & MMC_RSP_BUSY) + flags = RSP_TYPE_LGHT48B; + else + flags = RSP_TYPE_LGHT48; + + /* enable default flags */ + flags = flags | (CMD_TYPE_NORMAL | CICE_NOCHECK | CCCE_NOCHECK | + MSBS_SGLEBLK); + flags &= ~(ACEN_ENABLE | BCE_ENABLE | DE_ENABLE); + + if (cmd->resp_type & MMC_RSP_CRC) + flags |= CCCE_CHECK; + if (cmd->resp_type & MMC_RSP_OPCODE) + flags |= CICE_CHECK; + + if (data) { + if ((cmd->cmdidx == MMC_CMD_READ_MULTIPLE_BLOCK) || + (cmd->cmdidx == MMC_CMD_WRITE_MULTIPLE_BLOCK)) { + flags |= (MSBS_MULTIBLK | BCE_ENABLE | ACEN_ENABLE); + data->blocksize = 512; + writel(data->blocksize | (data->blocks << 16), + &mmc_base->blk); + } else + writel(data->blocksize | NBLK_STPCNT, &mmc_base->blk); + + if (data->flags & MMC_DATA_READ) + flags |= (DP_DATA | DDIR_READ); + else + flags |= (DP_DATA | DDIR_WRITE); + +#ifdef CONFIG_MMC_OMAP_HS_ADMA + if ((priv->controller_flags & OMAP_HSMMC_USE_ADMA) && + !mmc_is_tuning_cmd(cmd->cmdidx)) { + omap_hsmmc_prepare_data(mmc, data); + flags |= DE_ENABLE; + } +#endif + } + + mmc_enable_irq(mmc, cmd); + + writel(cmd->cmdarg, &mmc_base->arg); + udelay(20); /* To fix "No status update" error on eMMC */ + writel((cmd->cmdidx << 24) | flags, &mmc_base->cmd); + + start = get_timer(0); + do { + mmc_stat = readl(&mmc_base->stat); + if (get_timer(start) > MAX_RETRY_MS) { + printf("%s : timeout: No status update\n", __func__); + return -ETIMEDOUT; + } + } while (!mmc_stat); + + if ((mmc_stat & IE_CTO) != 0) { + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRC); + return -ETIMEDOUT; + } else if ((mmc_stat & ERRI_MASK) != 0) + return -1; + + if (mmc_stat & CC_MASK) { + writel(CC_MASK, &mmc_base->stat); + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) { + /* response type 2 */ + cmd->response[3] = readl(&mmc_base->rsp10); + cmd->response[2] = readl(&mmc_base->rsp32); + cmd->response[1] = readl(&mmc_base->rsp54); + cmd->response[0] = readl(&mmc_base->rsp76); + } else + /* response types 1, 1b, 3, 4, 5, 6 */ + cmd->response[0] = readl(&mmc_base->rsp10); + } + } + +#ifdef CONFIG_MMC_OMAP_HS_ADMA + if ((priv->controller_flags & OMAP_HSMMC_USE_ADMA) && data && + !mmc_is_tuning_cmd(cmd->cmdidx)) { + u32 sz_mb, timeout; + + if (mmc_stat & IE_ADMAE) { + omap_hsmmc_dma_cleanup(mmc); + return -EIO; + } + + sz_mb = DIV_ROUND_UP(data->blocksize * data->blocks, 1 << 20); + timeout = sz_mb * DMA_TIMEOUT_PER_MB; + if (timeout < MAX_RETRY_MS) + timeout = MAX_RETRY_MS; + + start = get_timer(0); + do { + mmc_stat = readl(&mmc_base->stat); + if (mmc_stat & TC_MASK) { + writel(readl(&mmc_base->stat) | TC_MASK, + &mmc_base->stat); + break; + } + if (get_timer(start) > timeout) { + printf("%s : DMA timeout: No status update\n", + __func__); + return -ETIMEDOUT; + } + } while (1); + + omap_hsmmc_dma_cleanup(mmc); + return 0; + } +#endif + + if (data && (data->flags & MMC_DATA_READ)) { + mmc_read_data(mmc_base, data->dest, + data->blocksize * data->blocks); + } else if (data && (data->flags & MMC_DATA_WRITE)) { + mmc_write_data(mmc_base, data->src, + data->blocksize * data->blocks); + } + return 0; +} + +static int mmc_read_data(struct hsmmc *mmc_base, char *buf, unsigned int size) +{ + unsigned int *output_buf = (unsigned int *)buf; + unsigned int mmc_stat; + unsigned int count; + + /* + * Start Polled Read + */ + count = (size > MMCSD_SECTOR_SIZE) ? MMCSD_SECTOR_SIZE : size; + count /= 4; + + while (size) { + ulong start = get_timer(0); + do { + mmc_stat = readl(&mmc_base->stat); + if (get_timer(0) - start > MAX_RETRY_MS) { + printf("%s: timedout waiting for status!\n", + __func__); + return -ETIMEDOUT; + } + } while (mmc_stat == 0); + + if ((mmc_stat & (IE_DTO | IE_DCRC | IE_DEB)) != 0) + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRD); + + if ((mmc_stat & ERRI_MASK) != 0) + return 1; + + if (mmc_stat & BRR_MASK) { + unsigned int k; + + writel(readl(&mmc_base->stat) | BRR_MASK, + &mmc_base->stat); + for (k = 0; k < count; k++) { + *output_buf = readl(&mmc_base->data); + output_buf++; + } + size -= (count*4); + } + + if (mmc_stat & BWR_MASK) + writel(readl(&mmc_base->stat) | BWR_MASK, + &mmc_base->stat); + + if (mmc_stat & TC_MASK) { + writel(readl(&mmc_base->stat) | TC_MASK, + &mmc_base->stat); + break; + } + } + return 0; +} + +#if CONFIG_IS_ENABLED(MMC_WRITE) +static int mmc_write_data(struct hsmmc *mmc_base, const char *buf, + unsigned int size) +{ + unsigned int *input_buf = (unsigned int *)buf; + unsigned int mmc_stat; + unsigned int count; + + /* + * Start Polled Write + */ + count = (size > MMCSD_SECTOR_SIZE) ? MMCSD_SECTOR_SIZE : size; + count /= 4; + + while (size) { + ulong start = get_timer(0); + do { + mmc_stat = readl(&mmc_base->stat); + if (get_timer(0) - start > MAX_RETRY_MS) { + printf("%s: timedout waiting for status!\n", + __func__); + return -ETIMEDOUT; + } + } while (mmc_stat == 0); + + if ((mmc_stat & (IE_DTO | IE_DCRC | IE_DEB)) != 0) + mmc_reset_controller_fsm(mmc_base, SYSCTL_SRD); + + if ((mmc_stat & ERRI_MASK) != 0) + return 1; + + if (mmc_stat & BWR_MASK) { + unsigned int k; + + writel(readl(&mmc_base->stat) | BWR_MASK, + &mmc_base->stat); + for (k = 0; k < count; k++) { + writel(*input_buf, &mmc_base->data); + input_buf++; + } + size -= (count*4); + } + + if (mmc_stat & BRR_MASK) + writel(readl(&mmc_base->stat) | BRR_MASK, + &mmc_base->stat); + + if (mmc_stat & TC_MASK) { + writel(readl(&mmc_base->stat) | TC_MASK, + &mmc_base->stat); + break; + } + } + return 0; +} +#else +static int mmc_write_data(struct hsmmc *mmc_base, const char *buf, + unsigned int size) +{ + return -ENOTSUPP; +} +#endif +static void omap_hsmmc_stop_clock(struct hsmmc *mmc_base) +{ + writel(readl(&mmc_base->sysctl) & ~CEN_ENABLE, &mmc_base->sysctl); +} + +static void omap_hsmmc_start_clock(struct hsmmc *mmc_base) +{ + writel(readl(&mmc_base->sysctl) | CEN_ENABLE, &mmc_base->sysctl); +} + +static void omap_hsmmc_set_clock(struct mmc *mmc) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + struct hsmmc *mmc_base; + unsigned int dsor = 0; + ulong start; + + mmc_base = priv->base_addr; + omap_hsmmc_stop_clock(mmc_base); + + /* TODO: Is setting DTO required here? */ + mmc_reg_out(&mmc_base->sysctl, (ICE_MASK | DTO_MASK), + (ICE_STOP | DTO_15THDTO)); + + if (mmc->clock != 0) { + dsor = DIV_ROUND_UP(MMC_CLOCK_REFERENCE * 1000000, mmc->clock); + if (dsor > CLKD_MAX) + dsor = CLKD_MAX; + } else { + dsor = CLKD_MAX; + } + + mmc_reg_out(&mmc_base->sysctl, ICE_MASK | CLKD_MASK, + (dsor << CLKD_OFFSET) | ICE_OSCILLATE); + + start = get_timer(0); + while ((readl(&mmc_base->sysctl) & ICS_MASK) == ICS_NOTREADY) { + if (get_timer(0) - start > MAX_RETRY_MS) { + printf("%s: timedout waiting for ics!\n", __func__); + return; + } + } + + priv->clock = MMC_CLOCK_REFERENCE * 1000000 / dsor; + mmc->clock = priv->clock; + omap_hsmmc_start_clock(mmc_base); +} + +static void omap_hsmmc_set_bus_width(struct mmc *mmc) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + struct hsmmc *mmc_base; + + mmc_base = priv->base_addr; + /* configue bus width */ + switch (mmc->bus_width) { + case 8: + writel(readl(&mmc_base->con) | DTW_8_BITMODE, + &mmc_base->con); + break; + + case 4: + writel(readl(&mmc_base->con) & ~DTW_8_BITMODE, + &mmc_base->con); + writel(readl(&mmc_base->hctl) | DTW_4_BITMODE, + &mmc_base->hctl); + break; + + case 1: + default: + writel(readl(&mmc_base->con) & ~DTW_8_BITMODE, + &mmc_base->con); + writel(readl(&mmc_base->hctl) & ~DTW_4_BITMODE, + &mmc_base->hctl); + break; + } + + priv->bus_width = mmc->bus_width; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +static int omap_hsmmc_set_ios(struct mmc *mmc) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); +#else +static int omap_hsmmc_set_ios(struct udevice *dev) +{ + struct omap_hsmmc_data *priv = dev_get_priv(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct mmc *mmc = upriv->mmc; +#endif + struct hsmmc *mmc_base = priv->base_addr; + int ret = 0; + + if (priv->bus_width != mmc->bus_width) + omap_hsmmc_set_bus_width(mmc); + + if (priv->clock != mmc->clock) + omap_hsmmc_set_clock(mmc); + + if (mmc->clk_disable) + omap_hsmmc_stop_clock(mmc_base); + else + omap_hsmmc_start_clock(mmc_base); + +#if CONFIG_IS_ENABLED(DM_MMC) + if (priv->mode != mmc->selected_mode) + omap_hsmmc_set_timing(mmc); + +#if CONFIG_IS_ENABLED(MMC_IO_VOLTAGE) + if (priv->signal_voltage != mmc->signal_voltage) + ret = omap_hsmmc_set_signal_voltage(mmc); +#endif +#endif + return ret; +} + +#ifdef OMAP_HSMMC_USE_GPIO +#if CONFIG_IS_ENABLED(DM_MMC) +static int omap_hsmmc_getcd(struct udevice *dev) +{ + int value = -1; +#if CONFIG_IS_ENABLED(DM_GPIO) + struct omap_hsmmc_data *priv = dev_get_priv(dev); + value = dm_gpio_get_value(&priv->cd_gpio); +#endif + /* if no CD return as 1 */ + if (value < 0) + return 1; + + return value; +} + +static int omap_hsmmc_getwp(struct udevice *dev) +{ + int value = 0; +#if CONFIG_IS_ENABLED(DM_GPIO) + struct omap_hsmmc_data *priv = dev_get_priv(dev); + value = dm_gpio_get_value(&priv->wp_gpio); +#endif + /* if no WP return as 0 */ + if (value < 0) + return 0; + return value; +} +#else +static int omap_hsmmc_getcd(struct mmc *mmc) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + int cd_gpio; + + /* if no CD return as 1 */ + cd_gpio = priv->cd_gpio; + if (cd_gpio < 0) + return 1; + + /* NOTE: assumes card detect signal is active-low */ + return !gpio_get_value(cd_gpio); +} + +static int omap_hsmmc_getwp(struct mmc *mmc) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + int wp_gpio; + + /* if no WP return as 0 */ + wp_gpio = priv->wp_gpio; + if (wp_gpio < 0) + return 0; + + /* NOTE: assumes write protect signal is active-high */ + return gpio_get_value(wp_gpio); +} +#endif +#endif + +#if CONFIG_IS_ENABLED(DM_MMC) +static const struct dm_mmc_ops omap_hsmmc_ops = { + .send_cmd = omap_hsmmc_send_cmd, + .set_ios = omap_hsmmc_set_ios, +#ifdef OMAP_HSMMC_USE_GPIO + .get_cd = omap_hsmmc_getcd, + .get_wp = omap_hsmmc_getwp, +#endif +#ifdef MMC_SUPPORTS_TUNING + .execute_tuning = omap_hsmmc_execute_tuning, +#endif + .wait_dat0 = omap_hsmmc_wait_dat0, +}; +#else +static const struct mmc_ops omap_hsmmc_ops = { + .send_cmd = omap_hsmmc_send_cmd, + .set_ios = omap_hsmmc_set_ios, + .init = omap_hsmmc_init_setup, +#ifdef OMAP_HSMMC_USE_GPIO + .getcd = omap_hsmmc_getcd, + .getwp = omap_hsmmc_getwp, +#endif +}; +#endif + +#if !CONFIG_IS_ENABLED(DM_MMC) +int omap_mmc_init(int dev_index, uint host_caps_mask, uint f_max, int cd_gpio, + int wp_gpio) +{ + struct mmc *mmc; + struct omap_hsmmc_data *priv; + struct mmc_config *cfg; + uint host_caps_val; + + priv = calloc(1, sizeof(*priv)); + if (priv == NULL) + return -1; + + host_caps_val = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS; + + switch (dev_index) { + case 0: + priv->base_addr = (struct hsmmc *)OMAP_HSMMC1_BASE; + break; +#ifdef OMAP_HSMMC2_BASE + case 1: + priv->base_addr = (struct hsmmc *)OMAP_HSMMC2_BASE; +#if (defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX) || \ + defined(CONFIG_DRA7XX) || defined(CONFIG_AM33XX) || \ + defined(CONFIG_AM43XX) || defined(CONFIG_SOC_KEYSTONE)) && \ + defined(CONFIG_HSMMC2_8BIT) + /* Enable 8-bit interface for eMMC on OMAP4/5 or DRA7XX */ + host_caps_val |= MMC_MODE_8BIT; +#endif + break; +#endif +#ifdef OMAP_HSMMC3_BASE + case 2: + priv->base_addr = (struct hsmmc *)OMAP_HSMMC3_BASE; +#if defined(CONFIG_DRA7XX) && defined(CONFIG_HSMMC3_8BIT) + /* Enable 8-bit interface for eMMC on DRA7XX */ + host_caps_val |= MMC_MODE_8BIT; +#endif + break; +#endif + default: + priv->base_addr = (struct hsmmc *)OMAP_HSMMC1_BASE; + return 1; + } +#ifdef OMAP_HSMMC_USE_GPIO + /* on error gpio values are set to -1, which is what we want */ + priv->cd_gpio = omap_mmc_setup_gpio_in(cd_gpio, "mmc_cd"); + priv->wp_gpio = omap_mmc_setup_gpio_in(wp_gpio, "mmc_wp"); +#endif + + cfg = &priv->cfg; + + cfg->name = "OMAP SD/MMC"; + cfg->ops = &omap_hsmmc_ops; + + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + cfg->host_caps = host_caps_val & ~host_caps_mask; + + cfg->f_min = 400000; + + if (f_max != 0) + cfg->f_max = f_max; + else { + if (cfg->host_caps & MMC_MODE_HS) { + if (cfg->host_caps & MMC_MODE_HS_52MHz) + cfg->f_max = 52000000; + else + cfg->f_max = 26000000; + } else + cfg->f_max = 20000000; + } + + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + +#if defined(CONFIG_OMAP34XX) + /* + * Silicon revs 2.1 and older do not support multiblock transfers. + */ + if ((get_cpu_family() == CPU_OMAP34XX) && (get_cpu_rev() <= CPU_3XX_ES21)) + cfg->b_max = 1; +#endif + + mmc = mmc_create(cfg, priv); + if (mmc == NULL) + return -1; + + return 0; +} +#else + +#ifdef CONFIG_IODELAY_RECALIBRATION +static struct pad_conf_entry * +omap_hsmmc_get_pad_conf_entry(const fdt32_t *pinctrl, int count) +{ + int index = 0; + struct pad_conf_entry *padconf; + + padconf = (struct pad_conf_entry *)malloc(sizeof(*padconf) * count); + if (!padconf) { + debug("failed to allocate memory\n"); + return 0; + } + + while (index < count) { + padconf[index].offset = fdt32_to_cpu(pinctrl[2 * index]); + padconf[index].val = fdt32_to_cpu(pinctrl[2 * index + 1]); + index++; + } + + return padconf; +} + +static struct iodelay_cfg_entry * +omap_hsmmc_get_iodelay_cfg_entry(const fdt32_t *pinctrl, int count) +{ + int index = 0; + struct iodelay_cfg_entry *iodelay; + + iodelay = (struct iodelay_cfg_entry *)malloc(sizeof(*iodelay) * count); + if (!iodelay) { + debug("failed to allocate memory\n"); + return 0; + } + + while (index < count) { + iodelay[index].offset = fdt32_to_cpu(pinctrl[3 * index]); + iodelay[index].a_delay = fdt32_to_cpu(pinctrl[3 * index + 1]); + iodelay[index].g_delay = fdt32_to_cpu(pinctrl[3 * index + 2]); + index++; + } + + return iodelay; +} + +static const fdt32_t *omap_hsmmc_get_pinctrl_entry(u32 phandle, + const char *name, int *len) +{ + const void *fdt = gd->fdt_blob; + int offset; + const fdt32_t *pinctrl; + + offset = fdt_node_offset_by_phandle(fdt, phandle); + if (offset < 0) { + debug("failed to get pinctrl node %s.\n", + fdt_strerror(offset)); + return 0; + } + + pinctrl = fdt_getprop(fdt, offset, name, len); + if (!pinctrl) { + debug("failed to get property %s\n", name); + return 0; + } + + return pinctrl; +} + +static uint32_t omap_hsmmc_get_pad_conf_phandle(struct mmc *mmc, + char *prop_name) +{ + const void *fdt = gd->fdt_blob; + const __be32 *phandle; + int node = dev_of_offset(mmc->dev); + + phandle = fdt_getprop(fdt, node, prop_name, NULL); + if (!phandle) { + debug("failed to get property %s\n", prop_name); + return 0; + } + + return fdt32_to_cpu(*phandle); +} + +static uint32_t omap_hsmmc_get_iodelay_phandle(struct mmc *mmc, + char *prop_name) +{ + const void *fdt = gd->fdt_blob; + const __be32 *phandle; + int len; + int count; + int node = dev_of_offset(mmc->dev); + + phandle = fdt_getprop(fdt, node, prop_name, &len); + if (!phandle) { + debug("failed to get property %s\n", prop_name); + return 0; + } + + /* No manual mode iodelay values if count < 2 */ + count = len / sizeof(*phandle); + if (count < 2) + return 0; + + return fdt32_to_cpu(*(phandle + 1)); +} + +static struct pad_conf_entry * +omap_hsmmc_get_pad_conf(struct mmc *mmc, char *prop_name, int *npads) +{ + int len; + int count; + struct pad_conf_entry *padconf; + u32 phandle; + const fdt32_t *pinctrl; + + phandle = omap_hsmmc_get_pad_conf_phandle(mmc, prop_name); + if (!phandle) + return ERR_PTR(-EINVAL); + + pinctrl = omap_hsmmc_get_pinctrl_entry(phandle, "pinctrl-single,pins", + &len); + if (!pinctrl) + return ERR_PTR(-EINVAL); + + count = (len / sizeof(*pinctrl)) / 2; + padconf = omap_hsmmc_get_pad_conf_entry(pinctrl, count); + if (!padconf) + return ERR_PTR(-EINVAL); + + *npads = count; + + return padconf; +} + +static struct iodelay_cfg_entry * +omap_hsmmc_get_iodelay(struct mmc *mmc, char *prop_name, int *niodelay) +{ + int len; + int count; + struct iodelay_cfg_entry *iodelay; + u32 phandle; + const fdt32_t *pinctrl; + + phandle = omap_hsmmc_get_iodelay_phandle(mmc, prop_name); + /* Not all modes have manual mode iodelay values. So its not fatal */ + if (!phandle) + return 0; + + pinctrl = omap_hsmmc_get_pinctrl_entry(phandle, "pinctrl-pin-array", + &len); + if (!pinctrl) + return ERR_PTR(-EINVAL); + + count = (len / sizeof(*pinctrl)) / 3; + iodelay = omap_hsmmc_get_iodelay_cfg_entry(pinctrl, count); + if (!iodelay) + return ERR_PTR(-EINVAL); + + *niodelay = count; + + return iodelay; +} + +static struct omap_hsmmc_pinctrl_state * +omap_hsmmc_get_pinctrl_by_mode(struct mmc *mmc, char *mode) +{ + int index; + int npads = 0; + int niodelays = 0; + const void *fdt = gd->fdt_blob; + int node = dev_of_offset(mmc->dev); + char prop_name[11]; + struct omap_hsmmc_pinctrl_state *pinctrl_state; + + pinctrl_state = (struct omap_hsmmc_pinctrl_state *) + malloc(sizeof(*pinctrl_state)); + if (!pinctrl_state) { + debug("failed to allocate memory\n"); + return 0; + } + + index = fdt_stringlist_search(fdt, node, "pinctrl-names", mode); + if (index < 0) { + debug("fail to find %s mode %s\n", mode, fdt_strerror(index)); + goto err_pinctrl_state; + } + + sprintf(prop_name, "pinctrl-%d", index); + + pinctrl_state->padconf = omap_hsmmc_get_pad_conf(mmc, prop_name, + &npads); + if (IS_ERR(pinctrl_state->padconf)) + goto err_pinctrl_state; + pinctrl_state->npads = npads; + + pinctrl_state->iodelay = omap_hsmmc_get_iodelay(mmc, prop_name, + &niodelays); + if (IS_ERR(pinctrl_state->iodelay)) + goto err_padconf; + pinctrl_state->niodelays = niodelays; + + return pinctrl_state; + +err_padconf: + kfree(pinctrl_state->padconf); + +err_pinctrl_state: + kfree(pinctrl_state); + return 0; +} + +#define OMAP_HSMMC_SETUP_PINCTRL(capmask, mode, optional) \ + do { \ + struct omap_hsmmc_pinctrl_state *s = NULL; \ + char str[20]; \ + if (!(cfg->host_caps & capmask)) \ + break; \ + \ + if (priv->hw_rev) { \ + sprintf(str, "%s-%s", #mode, priv->hw_rev); \ + s = omap_hsmmc_get_pinctrl_by_mode(mmc, str); \ + } \ + \ + if (!s) \ + s = omap_hsmmc_get_pinctrl_by_mode(mmc, #mode); \ + \ + if (!s && !optional) { \ + debug("%s: no pinctrl for %s\n", \ + mmc->dev->name, #mode); \ + cfg->host_caps &= ~(capmask); \ + } else { \ + priv->mode##_pinctrl_state = s; \ + } \ + } while (0) + +static int omap_hsmmc_get_pinctrl_state(struct mmc *mmc) +{ + struct omap_hsmmc_data *priv = omap_hsmmc_get_data(mmc); + struct mmc_config *cfg = omap_hsmmc_get_cfg(mmc); + struct omap_hsmmc_pinctrl_state *default_pinctrl; + + if (!(priv->controller_flags & OMAP_HSMMC_REQUIRE_IODELAY)) + return 0; + + default_pinctrl = omap_hsmmc_get_pinctrl_by_mode(mmc, "default"); + if (!default_pinctrl) { + printf("no pinctrl state for default mode\n"); + return -EINVAL; + } + + priv->default_pinctrl_state = default_pinctrl; + + OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(UHS_SDR104), sdr104, false); + OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(UHS_SDR50), sdr50, false); + OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(UHS_DDR50), ddr50, false); + OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(UHS_SDR25), sdr25, false); + OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(UHS_SDR12), sdr12, false); + + OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(MMC_HS_200), hs200_1_8v, false); + OMAP_HSMMC_SETUP_PINCTRL(MMC_CAP(MMC_DDR_52), ddr_1_8v, false); + OMAP_HSMMC_SETUP_PINCTRL(MMC_MODE_HS, hs, true); + + return 0; +} +#endif + +#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA) +#ifdef CONFIG_OMAP54XX +__weak const struct mmc_platform_fixups *platform_fixups_mmc(uint32_t addr) +{ + return NULL; +} +#endif + +static int omap_hsmmc_of_to_plat(struct udevice *dev) +{ + struct omap_hsmmc_plat *plat = dev_get_plat(dev); + struct omap_mmc_of_data *of_data = (void *)dev_get_driver_data(dev); + + struct mmc_config *cfg = &plat->cfg; +#ifdef CONFIG_OMAP54XX + const struct mmc_platform_fixups *fixups; +#endif + const void *fdt = gd->fdt_blob; + int node = dev_of_offset(dev); + int ret; + + plat->base_addr = map_physmem(dev_read_addr(dev), + sizeof(struct hsmmc *), + MAP_NOCACHE); + + ret = mmc_of_parse(dev, cfg); + if (ret < 0) + return ret; + + if (!cfg->f_max) + cfg->f_max = 52000000; + cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + cfg->f_min = 400000; + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + if (fdtdec_get_bool(fdt, node, "ti,dual-volt")) + plat->controller_flags |= OMAP_HSMMC_SUPPORTS_DUAL_VOLT; + if (fdtdec_get_bool(fdt, node, "no-1-8-v")) + plat->controller_flags |= OMAP_HSMMC_NO_1_8_V; + if (of_data) + plat->controller_flags |= of_data->controller_flags; + +#ifdef CONFIG_OMAP54XX + fixups = platform_fixups_mmc(dev_read_addr(dev)); + if (fixups) { + plat->hw_rev = fixups->hw_rev; + cfg->host_caps &= ~fixups->unsupported_caps; + cfg->f_max = fixups->max_freq; + } +#endif + + return 0; +} +#endif + +#ifdef CONFIG_BLK + +static int omap_hsmmc_bind(struct udevice *dev) +{ + struct omap_hsmmc_plat *plat = dev_get_plat(dev); + plat->mmc = calloc(1, sizeof(struct mmc)); + return mmc_bind(dev, plat->mmc, &plat->cfg); +} +#endif +static int omap_hsmmc_probe(struct udevice *dev) +{ + struct omap_hsmmc_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct omap_hsmmc_data *priv = dev_get_priv(dev); + struct mmc_config *cfg = &plat->cfg; + struct mmc *mmc; +#ifdef CONFIG_IODELAY_RECALIBRATION + int ret; +#endif + + cfg->name = "OMAP SD/MMC"; + priv->base_addr = plat->base_addr; + priv->controller_flags = plat->controller_flags; + priv->hw_rev = plat->hw_rev; + +#ifdef CONFIG_BLK + mmc = plat->mmc; +#else + mmc = mmc_create(cfg, priv); + if (mmc == NULL) + return -1; +#endif +#if CONFIG_IS_ENABLED(DM_REGULATOR) + device_get_supply_regulator(dev, "pbias-supply", + &priv->pbias_supply); +#endif +#if defined(OMAP_HSMMC_USE_GPIO) +#if CONFIG_IS_ENABLED(OF_CONTROL) && CONFIG_IS_ENABLED(DM_GPIO) + gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio, GPIOD_IS_IN); + gpio_request_by_name(dev, "wp-gpios", 0, &priv->wp_gpio, GPIOD_IS_IN); +#endif +#endif + + mmc->dev = dev; + upriv->mmc = mmc; + +#ifdef CONFIG_IODELAY_RECALIBRATION + ret = omap_hsmmc_get_pinctrl_state(mmc); + /* + * disable high speed modes for the platforms that require IO delay + * and for which we don't have this information + */ + if ((ret < 0) && + (priv->controller_flags & OMAP_HSMMC_REQUIRE_IODELAY)) { + priv->controller_flags &= ~OMAP_HSMMC_REQUIRE_IODELAY; + cfg->host_caps &= ~(MMC_CAP(MMC_HS_200) | MMC_CAP(MMC_DDR_52) | + UHS_CAPS); + } +#endif + + return omap_hsmmc_init_setup(mmc); +} + +#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA) + +static const struct omap_mmc_of_data dra7_mmc_of_data = { + .controller_flags = OMAP_HSMMC_REQUIRE_IODELAY, +}; + +static const struct udevice_id omap_hsmmc_ids[] = { + { .compatible = "ti,omap3-hsmmc" }, + { .compatible = "ti,omap4-hsmmc" }, + { .compatible = "ti,am33xx-hsmmc" }, + { .compatible = "ti,dra7-hsmmc", .data = (ulong)&dra7_mmc_of_data }, + { } +}; +#endif + +U_BOOT_DRIVER(omap_hsmmc) = { + .name = "omap_hsmmc", + .id = UCLASS_MMC, +#if CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA) + .of_match = omap_hsmmc_ids, + .of_to_plat = omap_hsmmc_of_to_plat, + .plat_auto = sizeof(struct omap_hsmmc_plat), +#endif +#ifdef CONFIG_BLK + .bind = omap_hsmmc_bind, +#endif + .ops = &omap_hsmmc_ops, + .probe = omap_hsmmc_probe, + .priv_auto = sizeof(struct omap_hsmmc_data), +#if !CONFIG_IS_ENABLED(OF_CONTROL) + .flags = DM_FLAG_PRE_RELOC, +#endif +}; +#endif diff --git a/roms/u-boot/drivers/mmc/pci_mmc.c b/roms/u-boot/drivers/mmc/pci_mmc.c new file mode 100644 index 000000000..b9ab064b6 --- /dev/null +++ b/roms/u-boot/drivers/mmc/pci_mmc.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2015, Google, Inc + * Copyright (C) 2014, Bin Meng <bmeng.cn@gmail.com> + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <mapmem.h> +#include <mmc.h> +#include <sdhci.h> +#include <acpi/acpigen.h> +#include <acpi/acpi_device.h> +#include <acpi/acpi_dp.h> +#include <asm-generic/gpio.h> +#include <dm/acpi.h> + +/* Type of MMC device */ +enum { + TYPE_SD, + TYPE_EMMC, +}; + +struct pci_mmc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct pci_mmc_priv { + struct sdhci_host host; + void *base; + struct gpio_desc cd_gpio; +}; + +static int pci_mmc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct pci_mmc_plat *plat = dev_get_plat(dev); + struct pci_mmc_priv *priv = dev_get_priv(dev); + struct sdhci_host *host = &priv->host; + struct blk_desc *desc; + int ret; + + ret = mmc_of_parse(dev, &plat->cfg); + if (ret) + return ret; + desc = mmc_get_blk_desc(&plat->mmc); + desc->removable = !(plat->cfg.host_caps & MMC_CAP_NONREMOVABLE); + + host->ioaddr = (void *)dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, + PCI_REGION_MEM); + host->name = dev->name; + host->cd_gpio = priv->cd_gpio; + host->mmc = &plat->mmc; + host->mmc->dev = dev; + ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0); + if (ret) + return ret; + host->mmc->priv = &priv->host; + upriv->mmc = host->mmc; + + return sdhci_probe(dev); +} + +static int pci_mmc_of_to_plat(struct udevice *dev) +{ + if (CONFIG_IS_ENABLED(DM_GPIO)) { + struct pci_mmc_priv *priv = dev_get_priv(dev); + int ret; + + ret = gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio, + GPIOD_IS_IN); + log_debug("cd-gpio %s done, ret=%d\n", dev->name, ret); + } + + return 0; +} + +static int pci_mmc_bind(struct udevice *dev) +{ + struct pci_mmc_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static int pci_mmc_acpi_fill_ssdt(const struct udevice *dev, + struct acpi_ctx *ctx) +{ + struct pci_mmc_priv *priv = dev_get_priv(dev); + char path[ACPI_PATH_MAX]; + struct acpi_gpio gpio; + struct acpi_dp *dp; + int ret; + + if (!dev_has_ofnode(dev)) + return 0; + if (dev_get_driver_data(dev) == TYPE_EMMC) + return 0; + + ret = gpio_get_acpi(&priv->cd_gpio, &gpio); + if (ret) + return log_msg_ret("gpio", ret); + gpio.type = ACPI_GPIO_TYPE_INTERRUPT; + gpio.pull = ACPI_GPIO_PULL_NONE; + gpio.irq.mode = ACPI_IRQ_EDGE_TRIGGERED; + gpio.irq.polarity = ACPI_IRQ_ACTIVE_BOTH; + gpio.irq.shared = ACPI_IRQ_SHARED; + gpio.irq.wake = ACPI_IRQ_WAKE; + gpio.interrupt_debounce_timeout = 10000; /* 100ms */ + + /* Use device path as the Scope for the SSDT */ + ret = acpi_device_path(dev, path, sizeof(path)); + if (ret) + return log_msg_ret("path", ret); + acpigen_write_scope(ctx, path); + acpigen_write_name(ctx, "_CRS"); + + /* Write GpioInt() as default (if set) or custom from devicetree */ + acpigen_write_resourcetemplate_header(ctx); + acpi_device_write_gpio(ctx, &gpio); + acpigen_write_resourcetemplate_footer(ctx); + + /* Bind the cd-gpio name to the GpioInt() resource */ + dp = acpi_dp_new_table("_DSD"); + if (!dp) + return -ENOMEM; + acpi_dp_add_gpio(dp, "cd-gpio", path, 0, 0, 1); + ret = acpi_dp_write(ctx, dp); + if (ret) + return log_msg_ret("cd", ret); + + acpigen_pop_len(ctx); + + return 0; +} + +struct acpi_ops pci_mmc_acpi_ops = { + .fill_ssdt = pci_mmc_acpi_fill_ssdt, +}; + +static const struct udevice_id pci_mmc_match[] = { + { .compatible = "intel,apl-sd", .data = TYPE_SD }, + { .compatible = "intel,apl-emmc", .data = TYPE_EMMC }, + { } +}; + +U_BOOT_DRIVER(pci_mmc) = { + .name = "pci_mmc", + .id = UCLASS_MMC, + .of_match = pci_mmc_match, + .bind = pci_mmc_bind, + .of_to_plat = pci_mmc_of_to_plat, + .probe = pci_mmc_probe, + .ops = &sdhci_ops, + .priv_auto = sizeof(struct pci_mmc_priv), + .plat_auto = sizeof(struct pci_mmc_plat), + ACPI_OPS_PTR(&pci_mmc_acpi_ops) +}; + +static struct pci_device_id mmc_supported[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_SYSTEM_SDHCI << 8, 0xffff00) }, + {}, +}; + +U_BOOT_PCI_DEVICE(pci_mmc, mmc_supported); diff --git a/roms/u-boot/drivers/mmc/pic32_sdhci.c b/roms/u-boot/drivers/mmc/pic32_sdhci.c new file mode 100644 index 000000000..fe555106a --- /dev/null +++ b/roms/u-boot/drivers/mmc/pic32_sdhci.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Support of SDHCI for Microchip PIC32 SoC. + * + * Copyright (C) 2015 Microchip Technology Inc. + * Andrei Pistirica <andrei.pistirica@microchip.com> + */ + +#include <dm.h> +#include <sdhci.h> +#include <clk.h> +#include <linux/errno.h> +#include <mach/pic32.h> + +struct pic32_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +static int pic32_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct pic32_sdhci_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + + struct clk clk; + ulong clk_rate; + int ret; + + ret = clk_get_by_name(dev, "base_clk", &clk); + if (ret) + return ret; + + clk_rate = clk_get_rate(&clk); + clk_free(&clk); + + if (IS_ERR_VALUE(clk_rate)) + return clk_rate; + + host->ioaddr = dev_remap_addr(dev); + + if (!host->ioaddr) + return -EINVAL; + + host->name = dev->name; + host->quirks = SDHCI_QUIRK_NO_HISPD_BIT; + host->bus_width = dev_read_u32_default(dev, "bus-width", 4); + host->max_clk = clk_rate; + + host->mmc = &plat->mmc; + host->mmc->dev = dev; + + ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0); + if (ret) + return ret; + + host->mmc->priv = host; + upriv->mmc = host->mmc; + + ret = sdhci_probe(dev); + if (ret) + return ret; + + if (!dev_read_bool(dev, "microchip,use-sdcd")) { + // Use workaround 1 for erratum #15 by default + u8 ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + ctrl = (ctrl & ~SDHCI_CTRL_CD_TEST_INS) | SDHCI_CTRL_CD_TEST; + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + } + + return 0; +} + +static int pic32_sdhci_bind(struct udevice *dev) +{ + struct pic32_sdhci_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id pic32_sdhci_ids[] = { + { .compatible = "microchip,pic32mzda-sdhci" }, + { } +}; + +U_BOOT_DRIVER(pic32_sdhci_drv) = { + .name = "pic32_sdhci", + .id = UCLASS_MMC, + .of_match = pic32_sdhci_ids, + .ops = &sdhci_ops, + .bind = pic32_sdhci_bind, + .probe = pic32_sdhci_probe, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct pic32_sdhci_plat) +}; diff --git a/roms/u-boot/drivers/mmc/pxa_mmc_gen.c b/roms/u-boot/drivers/mmc/pxa_mmc_gen.c new file mode 100644 index 000000000..2b45549a1 --- /dev/null +++ b/roms/u-boot/drivers/mmc/pxa_mmc_gen.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.com> + * + * Modified to add driver model (DM) support + * Copyright (C) 2019 Marcel Ziswiler <marcel@ziswiler.com> + * + * Loosely based on the old code and Linux's PXA MMC driver + */ + +#include <common.h> +#include <asm/arch/hardware.h> +#include <asm/arch/regs-mmc.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <asm/io.h> +#include <dm.h> +#include <dm/platform_data/pxa_mmc_gen.h> +#include <malloc.h> +#include <mmc.h> + +/* PXAMMC Generic default config for various CPUs */ +#if defined(CONFIG_CPU_PXA25X) +#define PXAMMC_FIFO_SIZE 1 +#define PXAMMC_MIN_SPEED 312500 +#define PXAMMC_MAX_SPEED 20000000 +#define PXAMMC_HOST_CAPS (0) +#elif defined(CONFIG_CPU_PXA27X) +#define PXAMMC_CRC_SKIP +#define PXAMMC_FIFO_SIZE 32 +#define PXAMMC_MIN_SPEED 304000 +#define PXAMMC_MAX_SPEED 19500000 +#define PXAMMC_HOST_CAPS (MMC_MODE_4BIT) +#elif defined(CONFIG_CPU_MONAHANS) +#define PXAMMC_FIFO_SIZE 32 +#define PXAMMC_MIN_SPEED 304000 +#define PXAMMC_MAX_SPEED 26000000 +#define PXAMMC_HOST_CAPS (MMC_MODE_4BIT | MMC_MODE_HS) +#else +#error "This CPU isn't supported by PXA MMC!" +#endif + +#define MMC_STAT_ERRORS \ + (MMC_STAT_RES_CRC_ERROR | MMC_STAT_SPI_READ_ERROR_TOKEN | \ + MMC_STAT_CRC_READ_ERROR | MMC_STAT_TIME_OUT_RESPONSE | \ + MMC_STAT_READ_TIME_OUT | MMC_STAT_CRC_WRITE_ERROR) + +/* 1 millisecond (in wait cycles below it's 100 x 10uS waits) */ +#define PXA_MMC_TIMEOUT 100 + +struct pxa_mmc_priv { + struct pxa_mmc_regs *regs; +}; + +/* Wait for bit to be set */ +static int pxa_mmc_wait(struct mmc *mmc, uint32_t mask) +{ + struct pxa_mmc_priv *priv = mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + unsigned int timeout = PXA_MMC_TIMEOUT; + + /* Wait for bit to be set */ + while (--timeout) { + if (readl(®s->stat) & mask) + break; + udelay(10); + } + + if (!timeout) + return -ETIMEDOUT; + + return 0; +} + +static int pxa_mmc_stop_clock(struct mmc *mmc) +{ + struct pxa_mmc_priv *priv = mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + unsigned int timeout = PXA_MMC_TIMEOUT; + + /* If the clock aren't running, exit */ + if (!(readl(®s->stat) & MMC_STAT_CLK_EN)) + return 0; + + /* Tell the controller to turn off the clock */ + writel(MMC_STRPCL_STOP_CLK, ®s->strpcl); + + /* Wait until the clock are off */ + while (--timeout) { + if (!(readl(®s->stat) & MMC_STAT_CLK_EN)) + break; + udelay(10); + } + + /* The clock refused to stop, scream and die a painful death */ + if (!timeout) + return -ETIMEDOUT; + + /* The clock stopped correctly */ + return 0; +} + +static int pxa_mmc_start_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + uint32_t cmdat) +{ + struct pxa_mmc_priv *priv = mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + int ret; + + /* The card can send a "busy" response */ + if (cmd->resp_type & MMC_RSP_BUSY) + cmdat |= MMC_CMDAT_BUSY; + + /* Inform the controller about response type */ + switch (cmd->resp_type) { + case MMC_RSP_R1: + case MMC_RSP_R1b: + cmdat |= MMC_CMDAT_R1; + break; + case MMC_RSP_R2: + cmdat |= MMC_CMDAT_R2; + break; + case MMC_RSP_R3: + cmdat |= MMC_CMDAT_R3; + break; + default: + break; + } + + /* Load command and it's arguments into the controller */ + writel(cmd->cmdidx, ®s->cmd); + writel(cmd->cmdarg >> 16, ®s->argh); + writel(cmd->cmdarg & 0xffff, ®s->argl); + writel(cmdat, ®s->cmdat); + + /* Start the controller clock and wait until they are started */ + writel(MMC_STRPCL_START_CLK, ®s->strpcl); + + ret = pxa_mmc_wait(mmc, MMC_STAT_CLK_EN); + if (ret) + return ret; + + /* Correct and happy end */ + return 0; +} + +static int pxa_mmc_cmd_done(struct mmc *mmc, struct mmc_cmd *cmd) +{ + struct pxa_mmc_priv *priv = mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + u32 a, b, c; + int i; + int stat; + + /* Read the controller status */ + stat = readl(®s->stat); + + /* + * Linux says: + * Did I mention this is Sick. We always need to + * discard the upper 8 bits of the first 16-bit word. + */ + a = readl(®s->res) & 0xffff; + for (i = 0; i < 4; i++) { + b = readl(®s->res) & 0xffff; + c = readl(®s->res) & 0xffff; + cmd->response[i] = (a << 24) | (b << 8) | (c >> 8); + a = c; + } + + /* The command response didn't arrive */ + if (stat & MMC_STAT_TIME_OUT_RESPONSE) { + return -ETIMEDOUT; + } else if (stat & MMC_STAT_RES_CRC_ERROR && + cmd->resp_type & MMC_RSP_CRC) { +#ifdef PXAMMC_CRC_SKIP + if (cmd->resp_type & MMC_RSP_136 && + cmd->response[0] & (1 << 31)) + printf("Ignoring CRC, this may be dangerous!\n"); + else +#endif + return -EILSEQ; + } + + /* The command response was successfully read */ + return 0; +} + +static int pxa_mmc_do_read_xfer(struct mmc *mmc, struct mmc_data *data) +{ + struct pxa_mmc_priv *priv = mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + u32 len; + u32 *buf = (uint32_t *)data->dest; + int size; + int ret; + + len = data->blocks * data->blocksize; + + while (len) { + /* The controller has data ready */ + if (readl(®s->i_reg) & MMC_I_REG_RXFIFO_RD_REQ) { + size = min(len, (uint32_t)PXAMMC_FIFO_SIZE); + len -= size; + size /= 4; + + /* Read data into the buffer */ + while (size--) + *buf++ = readl(®s->rxfifo); + } + + if (readl(®s->stat) & MMC_STAT_ERRORS) + return -EIO; + } + + /* Wait for the transmission-done interrupt */ + ret = pxa_mmc_wait(mmc, MMC_STAT_DATA_TRAN_DONE); + if (ret) + return ret; + + return 0; +} + +static int pxa_mmc_do_write_xfer(struct mmc *mmc, struct mmc_data *data) +{ + struct pxa_mmc_priv *priv = mmc->priv; + struct pxa_mmc_regs *regs = priv->regs; + u32 len; + u32 *buf = (uint32_t *)data->src; + int size; + int ret; + + len = data->blocks * data->blocksize; + + while (len) { + /* The controller is ready to receive data */ + if (readl(®s->i_reg) & MMC_I_REG_TXFIFO_WR_REQ) { + size = min(len, (uint32_t)PXAMMC_FIFO_SIZE); + len -= size; + size /= 4; + + while (size--) + writel(*buf++, ®s->txfifo); + + if (min(len, (uint32_t)PXAMMC_FIFO_SIZE) < 32) + writel(MMC_PRTBUF_BUF_PART_FULL, ®s->prtbuf); + } + + if (readl(®s->stat) & MMC_STAT_ERRORS) + return -EIO; + } + + /* Wait for the transmission-done interrupt */ + ret = pxa_mmc_wait(mmc, MMC_STAT_DATA_TRAN_DONE); + if (ret) + return ret; + + /* Wait until the data are really written to the card */ + ret = pxa_mmc_wait(mmc, MMC_STAT_PRG_DONE); + if (ret) + return ret; + + return 0; +} + +static int pxa_mmc_send_cmd_common(struct pxa_mmc_priv *priv, struct mmc *mmc, + struct mmc_cmd *cmd, struct mmc_data *data) +{ + struct pxa_mmc_regs *regs = priv->regs; + u32 cmdat = 0; + int ret; + + /* Stop the controller */ + ret = pxa_mmc_stop_clock(mmc); + if (ret) + return ret; + + /* If we're doing data transfer, configure the controller accordingly */ + if (data) { + writel(data->blocks, ®s->nob); + writel(data->blocksize, ®s->blklen); + /* This delay can be optimized, but stick with max value */ + writel(0xffff, ®s->rdto); + cmdat |= MMC_CMDAT_DATA_EN; + if (data->flags & MMC_DATA_WRITE) + cmdat |= MMC_CMDAT_WRITE; + } + + /* Run in 4bit mode if the card can do it */ + if (mmc->bus_width == 4) + cmdat |= MMC_CMDAT_SD_4DAT; + + /* Execute the command */ + ret = pxa_mmc_start_cmd(mmc, cmd, cmdat); + if (ret) + return ret; + + /* Wait until the command completes */ + ret = pxa_mmc_wait(mmc, MMC_STAT_END_CMD_RES); + if (ret) + return ret; + + /* Read back the result */ + ret = pxa_mmc_cmd_done(mmc, cmd); + if (ret) + return ret; + + /* In case there was a data transfer scheduled, do it */ + if (data) { + if (data->flags & MMC_DATA_WRITE) + pxa_mmc_do_write_xfer(mmc, data); + else + pxa_mmc_do_read_xfer(mmc, data); + } + + return 0; +} + +static int pxa_mmc_set_ios_common(struct pxa_mmc_priv *priv, struct mmc *mmc) +{ + struct pxa_mmc_regs *regs = priv->regs; + u32 tmp; + u32 pxa_mmc_clock; + + if (!mmc->clock) { + pxa_mmc_stop_clock(mmc); + return 0; + } + + /* PXA3xx can do 26MHz with special settings. */ + if (mmc->clock == 26000000) { + writel(0x7, ®s->clkrt); + return 0; + } + + /* Set clock to the card the usual way. */ + pxa_mmc_clock = 0; + tmp = mmc->cfg->f_max / mmc->clock; + tmp += tmp % 2; + + while (tmp > 1) { + pxa_mmc_clock++; + tmp >>= 1; + } + + writel(pxa_mmc_clock, ®s->clkrt); + + return 0; +} + +static int pxa_mmc_init_common(struct pxa_mmc_priv *priv, struct mmc *mmc) +{ + struct pxa_mmc_regs *regs = priv->regs; + + /* Make sure the clock are stopped */ + pxa_mmc_stop_clock(mmc); + + /* Turn off SPI mode */ + writel(0, ®s->spi); + + /* Set up maximum timeout to wait for command response */ + writel(MMC_RES_TO_MAX_MASK, ®s->resto); + + /* Mask all interrupts */ + writel(~(MMC_I_MASK_TXFIFO_WR_REQ | MMC_I_MASK_RXFIFO_RD_REQ), + ®s->i_mask); + + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +static int pxa_mmc_init(struct mmc *mmc) +{ + struct pxa_mmc_priv *priv = mmc->priv; + + return pxa_mmc_init_common(priv, mmc); +} + +static int pxa_mmc_request(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct pxa_mmc_priv *priv = mmc->priv; + + return pxa_mmc_send_cmd_common(priv, mmc, cmd, data); +} + +static int pxa_mmc_set_ios(struct mmc *mmc) +{ + struct pxa_mmc_priv *priv = mmc->priv; + + return pxa_mmc_set_ios_common(priv, mmc); +} + +static const struct mmc_ops pxa_mmc_ops = { + .send_cmd = pxa_mmc_request, + .set_ios = pxa_mmc_set_ios, + .init = pxa_mmc_init, +}; + +static struct mmc_config pxa_mmc_cfg = { + .name = "PXA MMC", + .ops = &pxa_mmc_ops, + .voltages = MMC_VDD_32_33 | MMC_VDD_33_34, + .f_max = PXAMMC_MAX_SPEED, + .f_min = PXAMMC_MIN_SPEED, + .host_caps = PXAMMC_HOST_CAPS, + .b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT, +}; + +int pxa_mmc_register(int card_index) +{ + struct mmc *mmc; + struct pxa_mmc_priv *priv; + u32 reg; + int ret = -ENOMEM; + + priv = malloc(sizeof(struct pxa_mmc_priv)); + if (!priv) + goto err0; + + memset(priv, 0, sizeof(*priv)); + + switch (card_index) { + case 0: + priv->regs = (struct pxa_mmc_regs *)MMC0_BASE; + break; + case 1: + priv->regs = (struct pxa_mmc_regs *)MMC1_BASE; + break; + default: + ret = -EINVAL; + printf("PXA MMC: Invalid MMC controller ID (card_index = %d)\n", + card_index); + goto err1; + } + +#ifndef CONFIG_CPU_MONAHANS /* PXA2xx */ + reg = readl(CKEN); + reg |= CKEN12_MMC; + writel(reg, CKEN); +#else /* PXA3xx */ + reg = readl(CKENA); + reg |= CKENA_12_MMC0 | CKENA_13_MMC1; + writel(reg, CKENA); +#endif + + mmc = mmc_create(&pxa_mmc_cfg, priv); + if (!mmc) + goto err1; + + return 0; + +err1: + free(priv); +err0: + return ret; +} +#else /* !CONFIG_IS_ENABLED(DM_MMC) */ +static int pxa_mmc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct pxa_mmc_plat *plat = dev_get_plat(dev); + struct mmc_config *cfg = &plat->cfg; + struct mmc *mmc = &plat->mmc; + struct pxa_mmc_priv *priv = dev_get_priv(dev); + u32 reg; + + upriv->mmc = mmc; + + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + cfg->f_max = PXAMMC_MAX_SPEED; + cfg->f_min = PXAMMC_MIN_SPEED; + cfg->host_caps = PXAMMC_HOST_CAPS; + cfg->name = dev->name; + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + + mmc->priv = priv; + + priv->regs = plat->base; + +#ifndef CONFIG_CPU_MONAHANS /* PXA2xx */ + reg = readl(CKEN); + reg |= CKEN12_MMC; + writel(reg, CKEN); +#else /* PXA3xx */ + reg = readl(CKENA); + reg |= CKENA_12_MMC0 | CKENA_13_MMC1; + writel(reg, CKENA); +#endif + + return pxa_mmc_init_common(priv, mmc); +} + +static int pxa_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct pxa_mmc_plat *plat = dev_get_plat(dev); + struct pxa_mmc_priv *priv = dev_get_priv(dev); + + return pxa_mmc_send_cmd_common(priv, &plat->mmc, cmd, data); +} + +static int pxa_mmc_set_ios(struct udevice *dev) +{ + struct pxa_mmc_plat *plat = dev_get_plat(dev); + struct pxa_mmc_priv *priv = dev_get_priv(dev); + + return pxa_mmc_set_ios_common(priv, &plat->mmc); +} + +static const struct dm_mmc_ops pxa_mmc_ops = { + .get_cd = NULL, + .send_cmd = pxa_mmc_send_cmd, + .set_ios = pxa_mmc_set_ios, +}; + +#if CONFIG_IS_ENABLED(BLK) +static int pxa_mmc_bind(struct udevice *dev) +{ + struct pxa_mmc_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} +#endif + +U_BOOT_DRIVER(pxa_mmc) = { +#if CONFIG_IS_ENABLED(BLK) + .bind = pxa_mmc_bind, +#endif + .id = UCLASS_MMC, + .name = "pxa_mmc", + .ops = &pxa_mmc_ops, + .priv_auto = sizeof(struct pxa_mmc_priv), + .probe = pxa_mmc_probe, +}; +#endif /* !CONFIG_IS_ENABLED(DM_MMC) */ diff --git a/roms/u-boot/drivers/mmc/renesas-sdhi.c b/roms/u-boot/drivers/mmc/renesas-sdhi.c new file mode 100644 index 000000000..9ad92648a --- /dev/null +++ b/roms/u-boot/drivers/mmc/renesas-sdhi.c @@ -0,0 +1,1024 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018 Marek Vasut <marek.vasut@gmail.com> + */ + +#include <common.h> +#include <bouncebuf.h> +#include <clk.h> +#include <fdtdec.h> +#include <log.h> +#include <malloc.h> +#include <mmc.h> +#include <dm.h> +#include <asm/global_data.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/compat.h> +#include <linux/delay.h> +#include <linux/dma-direction.h> +#include <linux/io.h> +#include <linux/sizes.h> +#include <power/regulator.h> +#include <asm/unaligned.h> +#include "tmio-common.h" + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + +/* SCC registers */ +#define RENESAS_SDHI_SCC_DTCNTL 0x800 +#define RENESAS_SDHI_SCC_DTCNTL_TAPEN BIT(0) +#define RENESAS_SDHI_SCC_DTCNTL_TAPNUM_SHIFT 16 +#define RENESAS_SDHI_SCC_DTCNTL_TAPNUM_MASK 0xff +#define RENESAS_SDHI_SCC_TAPSET 0x804 +#define RENESAS_SDHI_SCC_DT2FF 0x808 +#define RENESAS_SDHI_SCC_CKSEL 0x80c +#define RENESAS_SDHI_SCC_CKSEL_DTSEL BIT(0) +#define RENESAS_SDHI_SCC_RVSCNTL 0x810 +#define RENESAS_SDHI_SCC_RVSCNTL_RVSEN BIT(0) +#define RENESAS_SDHI_SCC_RVSREQ 0x814 +#define RENESAS_SDHI_SCC_RVSREQ_RVSERR BIT(2) +#define RENESAS_SDHI_SCC_RVSREQ_REQTAPUP BIT(1) +#define RENESAS_SDHI_SCC_RVSREQ_REQTAPDOWN BIT(0) +#define RENESAS_SDHI_SCC_SMPCMP 0x818 +#define RENESAS_SDHI_SCC_SMPCMP_CMD_ERR (BIT(24) | BIT(8)) +#define RENESAS_SDHI_SCC_SMPCMP_CMD_REQUP BIT(24) +#define RENESAS_SDHI_SCC_SMPCMP_CMD_REQDOWN BIT(8) +#define RENESAS_SDHI_SCC_TMPPORT2 0x81c +#define RENESAS_SDHI_SCC_TMPPORT2_HS400EN BIT(31) +#define RENESAS_SDHI_SCC_TMPPORT2_HS400OSEL BIT(4) +#define RENESAS_SDHI_SCC_TMPPORT3 0x828 +#define RENESAS_SDHI_SCC_TMPPORT3_OFFSET_0 3 +#define RENESAS_SDHI_SCC_TMPPORT3_OFFSET_1 2 +#define RENESAS_SDHI_SCC_TMPPORT3_OFFSET_2 1 +#define RENESAS_SDHI_SCC_TMPPORT3_OFFSET_3 0 +#define RENESAS_SDHI_SCC_TMPPORT3_OFFSET_MASK 0x3 +#define RENESAS_SDHI_SCC_TMPPORT4 0x82c +#define RENESAS_SDHI_SCC_TMPPORT4_DLL_ACC_START BIT(0) +#define RENESAS_SDHI_SCC_TMPPORT5 0x830 +#define RENESAS_SDHI_SCC_TMPPORT5_DLL_RW_SEL_R BIT(8) +#define RENESAS_SDHI_SCC_TMPPORT5_DLL_RW_SEL_W (0 << 8) +#define RENESAS_SDHI_SCC_TMPPORT5_DLL_ADR_MASK 0x3F +#define RENESAS_SDHI_SCC_TMPPORT6 0x834 +#define RENESAS_SDHI_SCC_TMPPORT7 0x838 +#define RENESAS_SDHI_SCC_TMPPORT_DISABLE_WP_CODE 0xa5000000 +#define RENESAS_SDHI_SCC_TMPPORT_CALIB_CODE_MASK 0x1f +#define RENESAS_SDHI_SCC_TMPPORT_MANUAL_MODE BIT(7) + +#define RENESAS_SDHI_MAX_TAP 3 + +#define CALIB_TABLE_MAX (RENESAS_SDHI_SCC_TMPPORT_CALIB_CODE_MASK + 1) + +static const u8 r8a7795_calib_table[2][CALIB_TABLE_MAX] = { + { 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 5, 6, 6, 7, 11, + 15, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 19, 20, 21, 21 }, + { 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9, 10, 11, 12, 15, + 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 19, 20, 21, 22, 22 } +}; + +static const u8 r8a7796_rev1_calib_table[2][CALIB_TABLE_MAX] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 9, + 15, 15, 15, 16, 16, 16, 16, 16, 17, 18, 19, 20, 21, 21, 22, 22 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 2, 9, 16, 17, 17, 17, 18, 18, 18, 18, 19, 20, 21, 22, 23, 24} +}; + +static const u8 r8a7796_rev3_calib_table[2][CALIB_TABLE_MAX] = { + { 0, 0, 0, 0, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 9, 10, + 11, 12, 13, 15, 16, 17, 17, 18, 19, 19, 20, 21, 21, 22, 23, 23 }, + { 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 9, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 22, 22, 23, 24, 24 } +}; + +static const u8 r8a77965_calib_table[2][CALIB_TABLE_MAX] = { + { 0, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, + 16, 17, 18, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 29 }, + { 0, 1, 2, 2, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 15, + 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 31 } +}; + +static const u8 r8a77990_calib_table[2][CALIB_TABLE_MAX] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 1, 2, 3, 4, 4, 4, 4, 5, 5, 6, 7, 8, 10, 11, + 12, 13, 14, 16, 17, 18, 18, 18, 19, 19, 20, 24, 26, 26, 26, 26 } +}; + +static int rmobile_is_gen3_mmc0(struct tmio_sd_priv *priv) +{ + /* On R-Car Gen3, MMC0 is at 0xee140000 */ + return (uintptr_t)(priv->regbase) == 0xee140000; +} + +static u32 sd_scc_tmpport_read32(struct tmio_sd_priv *priv, u32 addr) +{ + /* read mode */ + tmio_sd_writel(priv, RENESAS_SDHI_SCC_TMPPORT5_DLL_RW_SEL_R | + (RENESAS_SDHI_SCC_TMPPORT5_DLL_ADR_MASK & addr), + RENESAS_SDHI_SCC_TMPPORT5); + + /* access start and stop */ + tmio_sd_writel(priv, RENESAS_SDHI_SCC_TMPPORT4_DLL_ACC_START, + RENESAS_SDHI_SCC_TMPPORT4); + tmio_sd_writel(priv, 0, RENESAS_SDHI_SCC_TMPPORT4); + + return tmio_sd_readl(priv, RENESAS_SDHI_SCC_TMPPORT7); +} + +static void sd_scc_tmpport_write32(struct tmio_sd_priv *priv, u32 addr, u32 val) +{ + /* write mode */ + tmio_sd_writel(priv, RENESAS_SDHI_SCC_TMPPORT5_DLL_RW_SEL_W | + (RENESAS_SDHI_SCC_TMPPORT5_DLL_ADR_MASK & addr), + RENESAS_SDHI_SCC_TMPPORT5); + tmio_sd_writel(priv, val, RENESAS_SDHI_SCC_TMPPORT6); + + /* access start and stop */ + tmio_sd_writel(priv, RENESAS_SDHI_SCC_TMPPORT4_DLL_ACC_START, + RENESAS_SDHI_SCC_TMPPORT4); + tmio_sd_writel(priv, 0, RENESAS_SDHI_SCC_TMPPORT4); +} + +static bool renesas_sdhi_check_scc_error(struct udevice *dev) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + unsigned long new_tap = priv->tap_set; + unsigned long error_tap = priv->tap_set; + u32 reg, smpcmp; + + if ((priv->caps & TMIO_SD_CAP_RCAR_UHS) && + (mmc->selected_mode != UHS_SDR104) && + (mmc->selected_mode != MMC_HS_200) && + (mmc->selected_mode != MMC_HS_400) && + (priv->nrtaps != 4)) + return false; + + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSCNTL); + /* Handle automatic tuning correction */ + if (reg & RENESAS_SDHI_SCC_RVSCNTL_RVSEN) { + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSREQ); + if (reg & RENESAS_SDHI_SCC_RVSREQ_RVSERR) { + tmio_sd_writel(priv, 0, RENESAS_SDHI_SCC_RVSREQ); + return true; + } + + return false; + } + + /* Handle manual tuning correction */ + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSREQ); + if (!reg) /* No error */ + return false; + + tmio_sd_writel(priv, 0, RENESAS_SDHI_SCC_RVSREQ); + + if (mmc->selected_mode == MMC_HS_400) { + /* + * Correction Error Status contains CMD and DAT signal status. + * In HS400, DAT signal based on DS signal, not CLK. + * Therefore, use only CMD status. + */ + smpcmp = tmio_sd_readl(priv, RENESAS_SDHI_SCC_SMPCMP) & + RENESAS_SDHI_SCC_SMPCMP_CMD_ERR; + + switch (smpcmp) { + case 0: + return false; /* No error in CMD signal */ + case RENESAS_SDHI_SCC_SMPCMP_CMD_REQUP: + new_tap = (priv->tap_set + + priv->tap_num + 1) % priv->tap_num; + error_tap = (priv->tap_set + + priv->tap_num - 1) % priv->tap_num; + break; + case RENESAS_SDHI_SCC_SMPCMP_CMD_REQDOWN: + new_tap = (priv->tap_set + + priv->tap_num - 1) % priv->tap_num; + error_tap = (priv->tap_set + + priv->tap_num + 1) % priv->tap_num; + break; + default: + return true; /* Need re-tune */ + } + + if (priv->hs400_bad_tap & BIT(new_tap)) { + /* + * New tap is bad tap (cannot change). + * Compare with HS200 tuning result. + * In HS200 tuning, when smpcmp[error_tap] + * is OK, retune is executed. + */ + if (priv->smpcmp & BIT(error_tap)) + return true; /* Need retune */ + + return false; /* cannot change */ + } + + priv->tap_set = new_tap; + } else { + if (reg & RENESAS_SDHI_SCC_RVSREQ_RVSERR) + return true; /* Need re-tune */ + else if (reg & RENESAS_SDHI_SCC_RVSREQ_REQTAPUP) + priv->tap_set = (priv->tap_set + + priv->tap_num + 1) % priv->tap_num; + else if (reg & RENESAS_SDHI_SCC_RVSREQ_REQTAPDOWN) + priv->tap_set = (priv->tap_set + + priv->tap_num - 1) % priv->tap_num; + else + return false; + } + + /* Set TAP position */ + tmio_sd_writel(priv, priv->tap_set >> ((priv->nrtaps == 4) ? 1 : 0), + RENESAS_SDHI_SCC_TAPSET); + + return false; +} + +static void renesas_sdhi_adjust_hs400_mode_enable(struct tmio_sd_priv *priv) +{ + u32 calib_code; + + if (!priv->adjust_hs400_enable) + return; + + if (!priv->needs_adjust_hs400) + return; + + if (!priv->adjust_hs400_calib_table) + return; + + /* + * Enabled Manual adjust HS400 mode + * + * 1) Disabled Write Protect + * W(addr=0x00, WP_DISABLE_CODE) + * + * 2) Read Calibration code + * read_value = R(addr=0x26) + * 3) Refer to calibration table + * Calibration code = table[read_value] + * 4) Enabled Manual Calibration + * W(addr=0x22, manual mode | Calibration code) + * 5) Set Offset value to TMPPORT3 Reg + */ + sd_scc_tmpport_write32(priv, 0x00, + RENESAS_SDHI_SCC_TMPPORT_DISABLE_WP_CODE); + calib_code = sd_scc_tmpport_read32(priv, 0x26); + calib_code &= RENESAS_SDHI_SCC_TMPPORT_CALIB_CODE_MASK; + sd_scc_tmpport_write32(priv, 0x22, + RENESAS_SDHI_SCC_TMPPORT_MANUAL_MODE | + priv->adjust_hs400_calib_table[calib_code]); + tmio_sd_writel(priv, priv->adjust_hs400_offset, + RENESAS_SDHI_SCC_TMPPORT3); + + /* Clear flag */ + priv->needs_adjust_hs400 = false; +} + +static void renesas_sdhi_adjust_hs400_mode_disable(struct tmio_sd_priv *priv) +{ + + /* Disabled Manual adjust HS400 mode + * + * 1) Disabled Write Protect + * W(addr=0x00, WP_DISABLE_CODE) + * 2) Disabled Manual Calibration + * W(addr=0x22, 0) + * 3) Clear offset value to TMPPORT3 Reg + */ + sd_scc_tmpport_write32(priv, 0x00, + RENESAS_SDHI_SCC_TMPPORT_DISABLE_WP_CODE); + sd_scc_tmpport_write32(priv, 0x22, 0); + tmio_sd_writel(priv, 0, RENESAS_SDHI_SCC_TMPPORT3); +} + +static unsigned int renesas_sdhi_init_tuning(struct tmio_sd_priv *priv) +{ + u32 reg; + + /* Initialize SCC */ + tmio_sd_writel(priv, 0, TMIO_SD_INFO1); + + reg = tmio_sd_readl(priv, TMIO_SD_CLKCTL); + reg &= ~TMIO_SD_CLKCTL_SCLKEN; + tmio_sd_writel(priv, reg, TMIO_SD_CLKCTL); + + /* Set sampling clock selection range */ + tmio_sd_writel(priv, (0x8 << RENESAS_SDHI_SCC_DTCNTL_TAPNUM_SHIFT) | + RENESAS_SDHI_SCC_DTCNTL_TAPEN, + RENESAS_SDHI_SCC_DTCNTL); + + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_CKSEL); + reg |= RENESAS_SDHI_SCC_CKSEL_DTSEL; + tmio_sd_writel(priv, reg, RENESAS_SDHI_SCC_CKSEL); + + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSCNTL); + reg &= ~RENESAS_SDHI_SCC_RVSCNTL_RVSEN; + tmio_sd_writel(priv, reg, RENESAS_SDHI_SCC_RVSCNTL); + + tmio_sd_writel(priv, 0x300 /* scc_tappos */, + RENESAS_SDHI_SCC_DT2FF); + + reg = tmio_sd_readl(priv, TMIO_SD_CLKCTL); + reg |= TMIO_SD_CLKCTL_SCLKEN; + tmio_sd_writel(priv, reg, TMIO_SD_CLKCTL); + + /* Read TAPNUM */ + return (tmio_sd_readl(priv, RENESAS_SDHI_SCC_DTCNTL) >> + RENESAS_SDHI_SCC_DTCNTL_TAPNUM_SHIFT) & + RENESAS_SDHI_SCC_DTCNTL_TAPNUM_MASK; +} + +static void renesas_sdhi_reset_tuning(struct tmio_sd_priv *priv) +{ + u32 reg; + + /* Reset SCC */ + reg = tmio_sd_readl(priv, TMIO_SD_CLKCTL); + reg &= ~TMIO_SD_CLKCTL_SCLKEN; + tmio_sd_writel(priv, reg, TMIO_SD_CLKCTL); + + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_CKSEL); + reg &= ~RENESAS_SDHI_SCC_CKSEL_DTSEL; + tmio_sd_writel(priv, reg, RENESAS_SDHI_SCC_CKSEL); + + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_TMPPORT2); + reg &= ~(RENESAS_SDHI_SCC_TMPPORT2_HS400EN | + RENESAS_SDHI_SCC_TMPPORT2_HS400OSEL); + tmio_sd_writel(priv, reg, RENESAS_SDHI_SCC_TMPPORT2); + + /* Disable HS400 mode adjustment */ + renesas_sdhi_adjust_hs400_mode_disable(priv); + + reg = tmio_sd_readl(priv, TMIO_SD_CLKCTL); + reg |= TMIO_SD_CLKCTL_SCLKEN; + tmio_sd_writel(priv, reg, TMIO_SD_CLKCTL); + + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSCNTL); + reg &= ~RENESAS_SDHI_SCC_RVSCNTL_RVSEN; + tmio_sd_writel(priv, reg, RENESAS_SDHI_SCC_RVSCNTL); + + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSCNTL); + reg &= ~RENESAS_SDHI_SCC_RVSCNTL_RVSEN; + tmio_sd_writel(priv, reg, RENESAS_SDHI_SCC_RVSCNTL); +} + +static int renesas_sdhi_hs400(struct udevice *dev) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + bool hs400 = (mmc->selected_mode == MMC_HS_400); + int ret, taps = hs400 ? priv->nrtaps : 8; + unsigned long new_tap; + u32 reg; + + if (taps == 4) /* HS400 on 4tap SoC needs different clock */ + ret = clk_set_rate(&priv->clk, 400000000); + else + ret = clk_set_rate(&priv->clk, 200000000); + if (ret < 0) + return ret; + + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSCNTL); + reg &= ~RENESAS_SDHI_SCC_RVSCNTL_RVSEN; + tmio_sd_writel(priv, reg, RENESAS_SDHI_SCC_RVSCNTL); + + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_TMPPORT2); + if (hs400) { + reg |= RENESAS_SDHI_SCC_TMPPORT2_HS400EN | + RENESAS_SDHI_SCC_TMPPORT2_HS400OSEL; + } else { + reg &= ~(RENESAS_SDHI_SCC_TMPPORT2_HS400EN | + RENESAS_SDHI_SCC_TMPPORT2_HS400OSEL); + } + + tmio_sd_writel(priv, reg, RENESAS_SDHI_SCC_TMPPORT2); + + /* Disable HS400 mode adjustment */ + if (!hs400) + renesas_sdhi_adjust_hs400_mode_disable(priv); + + tmio_sd_writel(priv, (0x8 << RENESAS_SDHI_SCC_DTCNTL_TAPNUM_SHIFT) | + RENESAS_SDHI_SCC_DTCNTL_TAPEN, + RENESAS_SDHI_SCC_DTCNTL); + + /* Avoid bad TAP */ + if (priv->hs400_bad_tap & BIT(priv->tap_set)) { + new_tap = (priv->tap_set + + priv->tap_num + 1) % priv->tap_num; + + if (priv->hs400_bad_tap & BIT(new_tap)) + new_tap = (priv->tap_set + + priv->tap_num - 1) % priv->tap_num; + + if (priv->hs400_bad_tap & BIT(new_tap)) { + new_tap = priv->tap_set; + debug("Three consecutive bad tap is prohibited\n"); + } + + priv->tap_set = new_tap; + tmio_sd_writel(priv, priv->tap_set, RENESAS_SDHI_SCC_TAPSET); + } + + if (taps == 4) { + tmio_sd_writel(priv, priv->tap_set >> 1, + RENESAS_SDHI_SCC_TAPSET); + tmio_sd_writel(priv, hs400 ? 0x100 : 0x300, + RENESAS_SDHI_SCC_DT2FF); + } else { + tmio_sd_writel(priv, priv->tap_set, RENESAS_SDHI_SCC_TAPSET); + tmio_sd_writel(priv, 0x300, RENESAS_SDHI_SCC_DT2FF); + } + + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_CKSEL); + reg |= RENESAS_SDHI_SCC_CKSEL_DTSEL; + tmio_sd_writel(priv, reg, RENESAS_SDHI_SCC_CKSEL); + + /* Execute adjust hs400 offset after setting to HS400 mode */ + if (hs400) + priv->needs_adjust_hs400 = true; + + return 0; +} + +static void renesas_sdhi_prepare_tuning(struct tmio_sd_priv *priv, + unsigned long tap) +{ + /* Set sampling clock position */ + tmio_sd_writel(priv, tap, RENESAS_SDHI_SCC_TAPSET); +} + +static unsigned int renesas_sdhi_compare_scc_data(struct tmio_sd_priv *priv) +{ + /* Get comparison of sampling data */ + return tmio_sd_readl(priv, RENESAS_SDHI_SCC_SMPCMP); +} + +static int renesas_sdhi_select_tuning(struct tmio_sd_priv *priv, + unsigned int taps) +{ + unsigned long tap_cnt; /* counter of tuning success */ + unsigned long tap_start;/* start position of tuning success */ + unsigned long tap_end; /* end position of tuning success */ + unsigned long ntap; /* temporary counter of tuning success */ + unsigned long match_cnt;/* counter of matching data */ + unsigned long i; + bool select = false; + u32 reg; + + priv->needs_adjust_hs400 = false; + + /* Clear SCC_RVSREQ */ + tmio_sd_writel(priv, 0, RENESAS_SDHI_SCC_RVSREQ); + + /* Merge the results */ + for (i = 0; i < priv->tap_num * 2; i++) { + if (!(taps & BIT(i))) { + taps &= ~BIT(i % priv->tap_num); + taps &= ~BIT((i % priv->tap_num) + priv->tap_num); + } + if (!(priv->smpcmp & BIT(i))) { + priv->smpcmp &= ~BIT(i % priv->tap_num); + priv->smpcmp &= ~BIT((i % priv->tap_num) + priv->tap_num); + } + } + + /* + * Find the longest consecutive run of successful probes. If that + * is more than RENESAS_SDHI_MAX_TAP probes long then use the + * center index as the tap. + */ + tap_cnt = 0; + ntap = 0; + tap_start = 0; + tap_end = 0; + for (i = 0; i < priv->tap_num * 2; i++) { + if (taps & BIT(i)) + ntap++; + else { + if (ntap > tap_cnt) { + tap_start = i - ntap; + tap_end = i - 1; + tap_cnt = ntap; + } + ntap = 0; + } + } + + if (ntap > tap_cnt) { + tap_start = i - ntap; + tap_end = i - 1; + tap_cnt = ntap; + } + + /* + * If all of the TAP is OK, the sampling clock position is selected by + * identifying the change point of data. + */ + if (tap_cnt == priv->tap_num * 2) { + match_cnt = 0; + ntap = 0; + tap_start = 0; + tap_end = 0; + for (i = 0; i < priv->tap_num * 2; i++) { + if (priv->smpcmp & BIT(i)) + ntap++; + else { + if (ntap > match_cnt) { + tap_start = i - ntap; + tap_end = i - 1; + match_cnt = ntap; + } + ntap = 0; + } + } + if (ntap > match_cnt) { + tap_start = i - ntap; + tap_end = i - 1; + match_cnt = ntap; + } + if (match_cnt) + select = true; + } else if (tap_cnt >= RENESAS_SDHI_MAX_TAP) + select = true; + + if (select) + priv->tap_set = ((tap_start + tap_end) / 2) % priv->tap_num; + else + return -EIO; + + /* Set SCC */ + tmio_sd_writel(priv, priv->tap_set, RENESAS_SDHI_SCC_TAPSET); + + /* Enable auto re-tuning */ + reg = tmio_sd_readl(priv, RENESAS_SDHI_SCC_RVSCNTL); + reg |= RENESAS_SDHI_SCC_RVSCNTL_RVSEN; + tmio_sd_writel(priv, reg, RENESAS_SDHI_SCC_RVSCNTL); + + return 0; +} + +int renesas_sdhi_execute_tuning(struct udevice *dev, uint opcode) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct mmc *mmc = upriv->mmc; + unsigned int tap_num; + unsigned int taps = 0; + int i, ret = 0; + u32 caps; + + /* Only supported on Renesas RCar */ + if (!(priv->caps & TMIO_SD_CAP_RCAR_UHS)) + return -EINVAL; + + /* clock tuning is not needed for upto 52MHz */ + if (!((mmc->selected_mode == MMC_HS_200) || + (mmc->selected_mode == MMC_HS_400) || + (mmc->selected_mode == UHS_SDR104) || + (mmc->selected_mode == UHS_SDR50))) + return 0; + + tap_num = renesas_sdhi_init_tuning(priv); + if (!tap_num) + /* Tuning is not supported */ + goto out; + + priv->tap_num = tap_num; + + if (priv->tap_num * 2 >= sizeof(taps) * 8) { + dev_err(dev, + "Too many taps, skipping tuning. Please consider updating size of taps field of tmio_mmc_host\n"); + goto out; + } + + priv->smpcmp = 0; + + /* Issue CMD19 twice for each tap */ + for (i = 0; i < 2 * priv->tap_num; i++) { + renesas_sdhi_prepare_tuning(priv, i % priv->tap_num); + + /* Force PIO for the tuning */ + caps = priv->caps; + priv->caps &= ~TMIO_SD_CAP_DMA_INTERNAL; + + ret = mmc_send_tuning(mmc, opcode, NULL); + + priv->caps = caps; + + if (ret == 0) + taps |= BIT(i); + + ret = renesas_sdhi_compare_scc_data(priv); + if (ret == 0) + priv->smpcmp |= BIT(i); + + mdelay(1); + } + + ret = renesas_sdhi_select_tuning(priv, taps); + +out: + if (ret < 0) { + dev_warn(dev, "Tuning procedure failed\n"); + renesas_sdhi_reset_tuning(priv); + } + + return ret; +} +#else +static int renesas_sdhi_hs400(struct udevice *dev) +{ + return 0; +} +#endif + +static int renesas_sdhi_set_ios(struct udevice *dev) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + u32 tmp; + int ret; + + /* Stop the clock before changing its rate to avoid a glitch signal */ + tmp = tmio_sd_readl(priv, TMIO_SD_CLKCTL); + tmp &= ~TMIO_SD_CLKCTL_SCLKEN; + tmio_sd_writel(priv, tmp, TMIO_SD_CLKCTL); + + ret = renesas_sdhi_hs400(dev); + if (ret) + return ret; + + ret = tmio_sd_set_ios(dev); + + mdelay(10); + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + struct mmc *mmc = mmc_get_mmc_dev(dev); + if ((priv->caps & TMIO_SD_CAP_RCAR_UHS) && + (mmc->selected_mode != UHS_SDR104) && + (mmc->selected_mode != MMC_HS_200) && + (mmc->selected_mode != MMC_HS_400)) { + renesas_sdhi_reset_tuning(priv); + } +#endif + + return ret; +} + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) +static int renesas_sdhi_wait_dat0(struct udevice *dev, int state, + int timeout_us) +{ + int ret = -ETIMEDOUT; + bool dat0_high; + bool target_dat0_high = !!state; + struct tmio_sd_priv *priv = dev_get_priv(dev); + + timeout_us = DIV_ROUND_UP(timeout_us, 10); /* check every 10 us. */ + while (timeout_us--) { + dat0_high = !!(tmio_sd_readl(priv, TMIO_SD_INFO2) & TMIO_SD_INFO2_DAT0); + if (dat0_high == target_dat0_high) { + ret = 0; + break; + } + udelay(10); + } + + return ret; +} +#endif + +#define RENESAS_SDHI_DMA_ALIGNMENT 128 + +static int renesas_sdhi_addr_aligned_gen(uintptr_t ubuf, + size_t len, size_t len_aligned) +{ + /* Check if start is aligned */ + if (!IS_ALIGNED(ubuf, RENESAS_SDHI_DMA_ALIGNMENT)) { + debug("Unaligned buffer address %lx\n", ubuf); + return 0; + } + + /* Check if length is aligned */ + if (len != len_aligned) { + debug("Unaligned buffer length %zu\n", len); + return 0; + } + +#ifdef CONFIG_PHYS_64BIT + /* Check if below 32bit boundary */ + if ((ubuf >> 32) || (ubuf + len_aligned) >> 32) { + debug("Buffer above 32bit boundary %lx-%lx\n", + ubuf, ubuf + len_aligned); + return 0; + } +#endif + + /* Aligned */ + return 1; +} + +static int renesas_sdhi_addr_aligned(struct bounce_buffer *state) +{ + uintptr_t ubuf = (uintptr_t)state->user_buffer; + + return renesas_sdhi_addr_aligned_gen(ubuf, state->len, + state->len_aligned); +} + +static int renesas_sdhi_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct bounce_buffer bbstate; + unsigned int bbflags; + bool bbok = false; + size_t len; + void *buf; + int ret; + + if (data) { + if (data->flags & MMC_DATA_READ) { + buf = data->dest; + bbflags = GEN_BB_WRITE; + } else { + buf = (void *)data->src; + bbflags = GEN_BB_READ; + } + len = data->blocks * data->blocksize; + + ret = bounce_buffer_start_extalign(&bbstate, buf, len, bbflags, + RENESAS_SDHI_DMA_ALIGNMENT, + renesas_sdhi_addr_aligned); + /* + * If the amount of data to transfer is too large, we can get + * -ENOMEM when starting the bounce buffer. If that happens, + * fall back to PIO as it was before, otherwise use the BB. + */ + if (!ret) { + bbok = true; + if (data->flags & MMC_DATA_READ) + data->dest = bbstate.bounce_buffer; + else + data->src = bbstate.bounce_buffer; + } + } + + ret = tmio_sd_send_cmd(dev, cmd, data); + + if (data && bbok) { + buf = bbstate.user_buffer; + + bounce_buffer_stop(&bbstate); + + if (data->flags & MMC_DATA_READ) + data->dest = buf; + else + data->src = buf; + } + + if (ret) + return ret; + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + struct tmio_sd_priv *priv = dev_get_priv(dev); + + renesas_sdhi_check_scc_error(dev); + + if (cmd->cmdidx == MMC_CMD_SEND_STATUS) + renesas_sdhi_adjust_hs400_mode_enable(priv); +#endif + + return 0; +} + +int renesas_sdhi_get_b_max(struct udevice *dev, void *dst, lbaint_t blkcnt) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct mmc *mmc = upriv->mmc; + size_t len = blkcnt * mmc->read_bl_len; + size_t len_align = roundup(len, RENESAS_SDHI_DMA_ALIGNMENT); + + if (renesas_sdhi_addr_aligned_gen((uintptr_t)dst, len, len_align)) { + if (priv->quirks & TMIO_SD_CAP_16BIT) + return U16_MAX; + else + return U32_MAX; + } else { + return (CONFIG_SYS_MALLOC_LEN / 4) / mmc->read_bl_len; + } +} + +static const struct dm_mmc_ops renesas_sdhi_ops = { + .send_cmd = renesas_sdhi_send_cmd, + .set_ios = renesas_sdhi_set_ios, + .get_cd = tmio_sd_get_cd, +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + .execute_tuning = renesas_sdhi_execute_tuning, +#endif +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) + .wait_dat0 = renesas_sdhi_wait_dat0, +#endif + .get_b_max = renesas_sdhi_get_b_max, +}; + +#define RENESAS_GEN2_QUIRKS TMIO_SD_CAP_RCAR_GEN2 +#define RENESAS_GEN3_QUIRKS \ + TMIO_SD_CAP_64BIT | TMIO_SD_CAP_RCAR_GEN3 | TMIO_SD_CAP_RCAR_UHS + +static const struct udevice_id renesas_sdhi_match[] = { + { .compatible = "renesas,sdhi-r8a7790", .data = RENESAS_GEN2_QUIRKS }, + { .compatible = "renesas,sdhi-r8a7791", .data = RENESAS_GEN2_QUIRKS }, + { .compatible = "renesas,sdhi-r8a7792", .data = RENESAS_GEN2_QUIRKS }, + { .compatible = "renesas,sdhi-r8a7793", .data = RENESAS_GEN2_QUIRKS }, + { .compatible = "renesas,sdhi-r8a7794", .data = RENESAS_GEN2_QUIRKS }, + { .compatible = "renesas,sdhi-r8a7795", .data = RENESAS_GEN3_QUIRKS }, + { .compatible = "renesas,sdhi-r8a7796", .data = RENESAS_GEN3_QUIRKS }, + { .compatible = "renesas,rcar-gen3-sdhi", .data = RENESAS_GEN3_QUIRKS }, + { .compatible = "renesas,sdhi-r8a77965", .data = RENESAS_GEN3_QUIRKS }, + { .compatible = "renesas,sdhi-r8a77970", .data = RENESAS_GEN3_QUIRKS }, + { .compatible = "renesas,sdhi-r8a77990", .data = RENESAS_GEN3_QUIRKS }, + { .compatible = "renesas,sdhi-r8a77995", .data = RENESAS_GEN3_QUIRKS }, + { /* sentinel */ } +}; + +static ulong renesas_sdhi_clk_get_rate(struct tmio_sd_priv *priv) +{ + return clk_get_rate(&priv->clk); +} + +static void renesas_sdhi_filter_caps(struct udevice *dev) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + + if (!(priv->caps & TMIO_SD_CAP_RCAR_GEN3)) + return; + + if (priv->caps & TMIO_SD_CAP_DMA_INTERNAL) + priv->idma_bus_width = TMIO_SD_DMA_MODE_BUS_WIDTH; + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + struct tmio_sd_plat *plat = dev_get_plat(dev); + + /* HS400 is not supported on H3 ES1.x and M3W ES1.0, ES1.1 */ + if (((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7795) && + (rmobile_get_cpu_rev_integer() <= 1)) || + ((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7796) && + (rmobile_get_cpu_rev_integer() == 1) && + (rmobile_get_cpu_rev_fraction() < 2))) + plat->cfg.host_caps &= ~MMC_MODE_HS400; + + /* H3 ES2.0, ES3.0 and M3W ES1.2 and M3N bad taps */ + if (((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7795) && + (rmobile_get_cpu_rev_integer() >= 2)) || + ((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7796) && + (rmobile_get_cpu_rev_integer() == 1) && + (rmobile_get_cpu_rev_fraction() == 2)) || + (rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A77965)) + priv->hs400_bad_tap = BIT(2) | BIT(3) | BIT(6) | BIT(7); + + /* H3 ES3.0 can use HS400 with manual adjustment */ + if ((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7795) && + (rmobile_get_cpu_rev_integer() >= 3)) { + priv->adjust_hs400_enable = true; + priv->adjust_hs400_offset = 0; + priv->adjust_hs400_calib_table = + r8a7795_calib_table[!rmobile_is_gen3_mmc0(priv)]; + } + + /* M3W ES1.2 can use HS400 with manual adjustment */ + if ((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7796) && + (rmobile_get_cpu_rev_integer() == 1) && + (rmobile_get_cpu_rev_fraction() == 2)) { + priv->adjust_hs400_enable = true; + priv->adjust_hs400_offset = 3; + priv->adjust_hs400_calib_table = + r8a7796_rev1_calib_table[!rmobile_is_gen3_mmc0(priv)]; + } + + /* M3W ES1.x for x>2 can use HS400 with manual adjustment and taps */ + if ((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7796) && + (rmobile_get_cpu_rev_integer() == 1) && + (rmobile_get_cpu_rev_fraction() > 2)) { + priv->adjust_hs400_enable = true; + priv->adjust_hs400_offset = 0; + priv->hs400_bad_tap = BIT(1) | BIT(3) | BIT(5) | BIT(7); + priv->adjust_hs400_calib_table = + r8a7796_rev3_calib_table[!rmobile_is_gen3_mmc0(priv)]; + } + + /* M3N can use HS400 with manual adjustment */ + if (rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A77965) { + priv->adjust_hs400_enable = true; + priv->adjust_hs400_offset = 3; + priv->adjust_hs400_calib_table = + r8a77965_calib_table[!rmobile_is_gen3_mmc0(priv)]; + } + + /* E3 can use HS400 with manual adjustment */ + if (rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A77990) { + priv->adjust_hs400_enable = true; + priv->adjust_hs400_offset = 3; + priv->adjust_hs400_calib_table = + r8a77990_calib_table[!rmobile_is_gen3_mmc0(priv)]; + } + + /* H3 ES1.x, ES2.0 and M3W ES1.0, ES1.1, ES1.2 uses 4 tuning taps */ + if (((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7795) && + (rmobile_get_cpu_rev_integer() <= 2)) || + ((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7796) && + (rmobile_get_cpu_rev_integer() == 1) && + (rmobile_get_cpu_rev_fraction() <= 2))) + priv->nrtaps = 4; + else + priv->nrtaps = 8; +#endif + /* H3 ES1.x and M3W ES1.0 uses bit 17 for DTRAEND */ + if (((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7795) && + (rmobile_get_cpu_rev_integer() <= 1)) || + ((rmobile_get_cpu_type() == RMOBILE_CPU_TYPE_R8A7796) && + (rmobile_get_cpu_rev_integer() == 1) && + (rmobile_get_cpu_rev_fraction() == 0))) + priv->read_poll_flag = TMIO_SD_DMA_INFO1_END_RD; + else + priv->read_poll_flag = TMIO_SD_DMA_INFO1_END_RD2; +} + +static int renesas_sdhi_probe(struct udevice *dev) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + u32 quirks = dev_get_driver_data(dev); + struct fdt_resource reg_res; + DECLARE_GLOBAL_DATA_PTR; + int ret; + + priv->clk_get_rate = renesas_sdhi_clk_get_rate; + + if (quirks == RENESAS_GEN2_QUIRKS) { + ret = fdt_get_resource(gd->fdt_blob, dev_of_offset(dev), + "reg", 0, ®_res); + if (ret < 0) { + dev_err(dev, "\"reg\" resource not found, ret=%i\n", + ret); + return ret; + } + + if (fdt_resource_size(®_res) == 0x100) + quirks |= TMIO_SD_CAP_16BIT; + } + + ret = clk_get_by_index(dev, 0, &priv->clk); + if (ret < 0) { + dev_err(dev, "failed to get host clock\n"); + return ret; + } + + /* set to max rate */ + ret = clk_set_rate(&priv->clk, 200000000); + if (ret < 0) { + dev_err(dev, "failed to set rate for host clock\n"); + clk_free(&priv->clk); + return ret; + } + + ret = clk_enable(&priv->clk); + if (ret) { + dev_err(dev, "failed to enable host clock\n"); + return ret; + } + + priv->quirks = quirks; + ret = tmio_sd_probe(dev, quirks); + + renesas_sdhi_filter_caps(dev); + +#if CONFIG_IS_ENABLED(MMC_UHS_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS200_SUPPORT) || \ + CONFIG_IS_ENABLED(MMC_HS400_SUPPORT) + if (!ret && (priv->caps & TMIO_SD_CAP_RCAR_UHS)) + renesas_sdhi_reset_tuning(priv); +#endif + return ret; +} + +U_BOOT_DRIVER(renesas_sdhi) = { + .name = "renesas-sdhi", + .id = UCLASS_MMC, + .of_match = renesas_sdhi_match, + .bind = tmio_sd_bind, + .probe = renesas_sdhi_probe, + .priv_auto = sizeof(struct tmio_sd_priv), + .plat_auto = sizeof(struct tmio_sd_plat), + .ops = &renesas_sdhi_ops, +}; diff --git a/roms/u-boot/drivers/mmc/rockchip_dw_mmc.c b/roms/u-boot/drivers/mmc/rockchip_dw_mmc.c new file mode 100644 index 000000000..d7d5361fd --- /dev/null +++ b/roms/u-boot/drivers/mmc/rockchip_dw_mmc.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2013 Google, Inc + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <dt-structs.h> +#include <dwmmc.h> +#include <errno.h> +#include <log.h> +#include <mapmem.h> +#include <pwrseq.h> +#include <syscon.h> +#include <asm/gpio.h> +#include <asm/arch-rockchip/clock.h> +#include <asm/arch-rockchip/periph.h> +#include <linux/delay.h> +#include <linux/err.h> + +struct rockchip_mmc_plat { +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct dtd_rockchip_rk3288_dw_mshc dtplat; +#endif + struct mmc_config cfg; + struct mmc mmc; +}; + +struct rockchip_dwmmc_priv { + struct clk clk; + struct dwmci_host host; + int fifo_depth; + bool fifo_mode; + u32 minmax[2]; +}; + +static uint rockchip_dwmmc_get_mmc_clk(struct dwmci_host *host, uint freq) +{ + struct udevice *dev = host->priv; + struct rockchip_dwmmc_priv *priv = dev_get_priv(dev); + int ret; + + ret = clk_set_rate(&priv->clk, freq); + if (ret < 0) { + debug("%s: err=%d\n", __func__, ret); + return ret; + } + + return freq; +} + +static int rockchip_dwmmc_of_to_plat(struct udevice *dev) +{ +#if !CONFIG_IS_ENABLED(OF_PLATDATA) + struct rockchip_dwmmc_priv *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + + host->name = dev->name; + host->ioaddr = dev_read_addr_ptr(dev); + host->buswidth = dev_read_u32_default(dev, "bus-width", 4); + host->get_mmc_clk = rockchip_dwmmc_get_mmc_clk; + host->priv = dev; + + /* use non-removeable as sdcard and emmc as judgement */ + if (dev_read_bool(dev, "non-removable")) + host->dev_index = 0; + else + host->dev_index = 1; + + priv->fifo_depth = dev_read_u32_default(dev, "fifo-depth", 0); + + if (priv->fifo_depth < 0) + return -EINVAL; + priv->fifo_mode = dev_read_bool(dev, "fifo-mode"); + +#ifdef CONFIG_SPL_BUILD + if (!priv->fifo_mode) + priv->fifo_mode = dev_read_bool(dev, "u-boot,spl-fifo-mode"); +#endif + + /* + * 'clock-freq-min-max' is deprecated + * (see https://github.com/torvalds/linux/commit/b023030f10573de738bbe8df63d43acab64c9f7b) + */ + if (dev_read_u32_array(dev, "clock-freq-min-max", priv->minmax, 2)) { + int val = dev_read_u32_default(dev, "max-frequency", -EINVAL); + + if (val < 0) + return val; + + priv->minmax[0] = 400000; /* 400 kHz */ + priv->minmax[1] = val; + } else { + debug("%s: 'clock-freq-min-max' property was deprecated.\n", + __func__); + } +#endif + return 0; +} + +static int rockchip_dwmmc_probe(struct udevice *dev) +{ + struct rockchip_mmc_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct rockchip_dwmmc_priv *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + int ret; + +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct dtd_rockchip_rk3288_dw_mshc *dtplat = &plat->dtplat; + + host->name = dev->name; + host->ioaddr = map_sysmem(dtplat->reg[0], dtplat->reg[1]); + host->buswidth = dtplat->bus_width; + host->get_mmc_clk = rockchip_dwmmc_get_mmc_clk; + host->priv = dev; + host->dev_index = 0; + priv->fifo_depth = dtplat->fifo_depth; + priv->fifo_mode = 0; + priv->minmax[0] = 400000; /* 400 kHz */ + priv->minmax[1] = dtplat->max_frequency; + + ret = clk_get_by_driver_info(dev, dtplat->clocks, &priv->clk); + if (ret < 0) + return ret; +#else + ret = clk_get_by_index(dev, 0, &priv->clk); + if (ret < 0) + return ret; +#endif + host->fifoth_val = MSIZE(0x2) | + RX_WMARK(priv->fifo_depth / 2 - 1) | + TX_WMARK(priv->fifo_depth / 2); + + host->fifo_mode = priv->fifo_mode; + +#ifdef CONFIG_MMC_PWRSEQ + /* Enable power if needed */ + ret = mmc_pwrseq_get_power(dev, &plat->cfg); + if (!ret) { + ret = pwrseq_set_power(plat->cfg.pwr_dev, true); + if (ret) + return ret; + } +#endif + dwmci_setup_cfg(&plat->cfg, host, priv->minmax[1], priv->minmax[0]); + host->mmc = &plat->mmc; + host->mmc->priv = &priv->host; + host->mmc->dev = dev; + upriv->mmc = host->mmc; + + return dwmci_probe(dev); +} + +static int rockchip_dwmmc_bind(struct udevice *dev) +{ + struct rockchip_mmc_plat *plat = dev_get_plat(dev); + + return dwmci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id rockchip_dwmmc_ids[] = { + { .compatible = "rockchip,rk2928-dw-mshc" }, + { .compatible = "rockchip,rk3288-dw-mshc" }, + { } +}; + +U_BOOT_DRIVER(rockchip_rk3288_dw_mshc) = { + .name = "rockchip_rk3288_dw_mshc", + .id = UCLASS_MMC, + .of_match = rockchip_dwmmc_ids, + .of_to_plat = rockchip_dwmmc_of_to_plat, + .ops = &dm_dwmci_ops, + .bind = rockchip_dwmmc_bind, + .probe = rockchip_dwmmc_probe, + .priv_auto = sizeof(struct rockchip_dwmmc_priv), + .plat_auto = sizeof(struct rockchip_mmc_plat), +}; + +DM_DRIVER_ALIAS(rockchip_rk3288_dw_mshc, rockchip_rk3328_dw_mshc) +DM_DRIVER_ALIAS(rockchip_rk3288_dw_mshc, rockchip_rk3368_dw_mshc) diff --git a/roms/u-boot/drivers/mmc/rockchip_sdhci.c b/roms/u-boot/drivers/mmc/rockchip_sdhci.c new file mode 100644 index 000000000..d95f8b2a1 --- /dev/null +++ b/roms/u-boot/drivers/mmc/rockchip_sdhci.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2016 Fuzhou Rockchip Electronics Co., Ltd + * + * Rockchip SD Host Controller Interface + */ + +#include <common.h> +#include <dm.h> +#include <dt-structs.h> +#include <linux/err.h> +#include <linux/libfdt.h> +#include <malloc.h> +#include <mapmem.h> +#include <sdhci.h> +#include <clk.h> + +/* 400KHz is max freq for card ID etc. Use that as min */ +#define EMMC_MIN_FREQ 400000 + +struct rockchip_sdhc_plat { +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct dtd_rockchip_rk3399_sdhci_5_1 dtplat; +#endif + struct mmc_config cfg; + struct mmc mmc; +}; + +struct rockchip_sdhc { + struct sdhci_host host; + void *base; +}; + +static int arasan_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct rockchip_sdhc_plat *plat = dev_get_plat(dev); + struct rockchip_sdhc *prv = dev_get_priv(dev); + struct sdhci_host *host = &prv->host; + int max_frequency, ret; + struct clk clk; + +#if CONFIG_IS_ENABLED(OF_PLATDATA) + struct dtd_rockchip_rk3399_sdhci_5_1 *dtplat = &plat->dtplat; + + host->name = dev->name; + host->ioaddr = map_sysmem(dtplat->reg[0], dtplat->reg[1]); + max_frequency = dtplat->max_frequency; + ret = clk_get_by_driver_info(dev, dtplat->clocks, &clk); +#else + max_frequency = dev_read_u32_default(dev, "max-frequency", 0); + ret = clk_get_by_index(dev, 0, &clk); +#endif + if (!ret) { + ret = clk_set_rate(&clk, max_frequency); + if (IS_ERR_VALUE(ret)) + printf("%s clk set rate fail!\n", __func__); + } else { + printf("%s fail to get clk\n", __func__); + } + + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD; + host->max_clk = max_frequency; + /* + * The sdhci-driver only supports 4bit and 8bit, as sdhci_setup_cfg + * doesn't allow us to clear MMC_MODE_4BIT. Consequently, we don't + * check for other bus-width values. + */ + if (host->bus_width == 8) + host->host_caps |= MMC_MODE_8BIT; + + host->mmc = &plat->mmc; + host->mmc->priv = &prv->host; + host->mmc->dev = dev; + upriv->mmc = host->mmc; + + ret = sdhci_setup_cfg(&plat->cfg, host, 0, EMMC_MIN_FREQ); + if (ret) + return ret; + + return sdhci_probe(dev); +} + +static int arasan_sdhci_of_to_plat(struct udevice *dev) +{ +#if !CONFIG_IS_ENABLED(OF_PLATDATA) + struct sdhci_host *host = dev_get_priv(dev); + + host->name = dev->name; + host->ioaddr = dev_read_addr_ptr(dev); + host->bus_width = dev_read_u32_default(dev, "bus-width", 4); +#endif + + return 0; +} + +static int rockchip_sdhci_bind(struct udevice *dev) +{ + struct rockchip_sdhc_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id arasan_sdhci_ids[] = { + { .compatible = "arasan,sdhci-5.1" }, + { } +}; + +U_BOOT_DRIVER(arasan_sdhci_drv) = { + .name = "rockchip_rk3399_sdhci_5_1", + .id = UCLASS_MMC, + .of_match = arasan_sdhci_ids, + .of_to_plat = arasan_sdhci_of_to_plat, + .ops = &sdhci_ops, + .bind = rockchip_sdhci_bind, + .probe = arasan_sdhci_probe, + .priv_auto = sizeof(struct rockchip_sdhc), + .plat_auto = sizeof(struct rockchip_sdhc_plat), +}; diff --git a/roms/u-boot/drivers/mmc/rpmb.c b/roms/u-boot/drivers/mmc/rpmb.c new file mode 100644 index 000000000..ea7e50666 --- /dev/null +++ b/roms/u-boot/drivers/mmc/rpmb.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2014, Staubli Faverges + * Pierre Aubert + * + * eMMC- Replay Protected Memory Block + * According to JEDEC Standard No. 84-A441 + */ + +#include <config.h> +#include <common.h> +#include <log.h> +#include <memalign.h> +#include <mmc.h> +#include <sdhci.h> +#include <u-boot/sha256.h> +#include "mmc_private.h" + +/* Request codes */ +#define RPMB_REQ_KEY 1 +#define RPMB_REQ_WCOUNTER 2 +#define RPMB_REQ_WRITE_DATA 3 +#define RPMB_REQ_READ_DATA 4 +#define RPMB_REQ_STATUS 5 + +/* Response code */ +#define RPMB_RESP_KEY 0x0100 +#define RPMB_RESP_WCOUNTER 0x0200 +#define RPMB_RESP_WRITE_DATA 0x0300 +#define RPMB_RESP_READ_DATA 0x0400 + +/* Error codes */ +#define RPMB_OK 0 +#define RPMB_ERR_GENERAL 1 +#define RPMB_ERR_AUTH 2 +#define RPMB_ERR_COUNTER 3 +#define RPMB_ERR_ADDRESS 4 +#define RPMB_ERR_WRITE 5 +#define RPMB_ERR_READ 6 +#define RPMB_ERR_KEY 7 +#define RPMB_ERR_CNT_EXPIRED 0x80 +#define RPMB_ERR_MSK 0x7 + +/* Sizes of RPMB data frame */ +#define RPMB_SZ_STUFF 196 +#define RPMB_SZ_MAC 32 +#define RPMB_SZ_DATA 256 +#define RPMB_SZ_NONCE 16 + +#define SHA256_BLOCK_SIZE 64 + +/* Error messages */ +static const char * const rpmb_err_msg[] = { + "", + "General failure", + "Authentication failure", + "Counter failure", + "Address failure", + "Write failure", + "Read failure", + "Authentication key not yet programmed", +}; + + +/* Structure of RPMB data frame. */ +struct s_rpmb { + unsigned char stuff[RPMB_SZ_STUFF]; + unsigned char mac[RPMB_SZ_MAC]; + unsigned char data[RPMB_SZ_DATA]; + unsigned char nonce[RPMB_SZ_NONCE]; + unsigned int write_counter; + unsigned short address; + unsigned short block_count; + unsigned short result; + unsigned short request; +}; + +static int mmc_set_blockcount(struct mmc *mmc, unsigned int blockcount, + bool is_rel_write) +{ + struct mmc_cmd cmd = {0}; + + cmd.cmdidx = MMC_CMD_SET_BLOCK_COUNT; + cmd.cmdarg = blockcount & 0x0000FFFF; + if (is_rel_write) + cmd.cmdarg |= 1 << 31; + cmd.resp_type = MMC_RSP_R1; + + return mmc_send_cmd(mmc, &cmd, NULL); +} +static int mmc_rpmb_request(struct mmc *mmc, const struct s_rpmb *s, + unsigned int count, bool is_rel_write) +{ + struct mmc_cmd cmd = {0}; + struct mmc_data data; + struct sdhci_host *host = mmc->priv; + int ret; + + ret = mmc_set_blockcount(mmc, count, is_rel_write); + if (ret) { +#ifdef CONFIG_MMC_RPMB_TRACE + printf("%s:mmc_set_blockcount-> %d\n", __func__, ret); +#endif + return 1; + } + + cmd.cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1; + + if (host->quirks & SDHCI_QUIRK_BROKEN_R1B) + cmd.resp_type = MMC_RSP_R1; + + data.src = (const char *)s; + data.blocks = 1; + data.blocksize = MMC_MAX_BLOCK_LEN; + data.flags = MMC_DATA_WRITE; + + ret = mmc_send_cmd(mmc, &cmd, &data); + if (ret) { +#ifdef CONFIG_MMC_RPMB_TRACE + printf("%s:mmc_send_cmd-> %d\n", __func__, ret); +#endif + return 1; + } + return 0; +} +static int mmc_rpmb_response(struct mmc *mmc, struct s_rpmb *s, + unsigned short expected) +{ + struct mmc_cmd cmd = {0}; + struct mmc_data data; + int ret; + + ret = mmc_set_blockcount(mmc, 1, false); + if (ret) { +#ifdef CONFIG_MMC_RPMB_TRACE + printf("%s:mmc_set_blockcount-> %d\n", __func__, ret); +#endif + return -1; + } + cmd.cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK; + cmd.cmdarg = 0; + cmd.resp_type = MMC_RSP_R1; + + data.dest = (char *)s; + data.blocks = 1; + data.blocksize = MMC_MAX_BLOCK_LEN; + data.flags = MMC_DATA_READ; + + ret = mmc_send_cmd(mmc, &cmd, &data); + if (ret) { +#ifdef CONFIG_MMC_RPMB_TRACE + printf("%s:mmc_send_cmd-> %d\n", __func__, ret); +#endif + return -1; + } + /* Check the response and the status */ + if (be16_to_cpu(s->request) != expected) { +#ifdef CONFIG_MMC_RPMB_TRACE + printf("%s:response= %x\n", __func__, + be16_to_cpu(s->request)); +#endif + return -1; + } + ret = be16_to_cpu(s->result); + if (ret) { + printf("%s %s\n", rpmb_err_msg[ret & RPMB_ERR_MSK], + (ret & RPMB_ERR_CNT_EXPIRED) ? + "Write counter has expired" : ""); + } + + /* Return the status of the command */ + return ret; +} +static int mmc_rpmb_status(struct mmc *mmc, unsigned short expected) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct s_rpmb, rpmb_frame, 1); + + memset(rpmb_frame, 0, sizeof(struct s_rpmb)); + rpmb_frame->request = cpu_to_be16(RPMB_REQ_STATUS); + if (mmc_rpmb_request(mmc, rpmb_frame, 1, false)) + return -1; + + /* Read the result */ + return mmc_rpmb_response(mmc, rpmb_frame, expected); +} +static void rpmb_hmac(unsigned char *key, unsigned char *buff, int len, + unsigned char *output) +{ + sha256_context ctx; + int i; + unsigned char k_ipad[SHA256_BLOCK_SIZE]; + unsigned char k_opad[SHA256_BLOCK_SIZE]; + + sha256_starts(&ctx); + + /* According to RFC 4634, the HMAC transform looks like: + SHA(K XOR opad, SHA(K XOR ipad, text)) + + where K is an n byte key. + ipad is the byte 0x36 repeated blocksize times + opad is the byte 0x5c repeated blocksize times + and text is the data being protected. + */ + + for (i = 0; i < RPMB_SZ_MAC; i++) { + k_ipad[i] = key[i] ^ 0x36; + k_opad[i] = key[i] ^ 0x5c; + } + /* remaining pad bytes are '\0' XOR'd with ipad and opad values */ + for ( ; i < SHA256_BLOCK_SIZE; i++) { + k_ipad[i] = 0x36; + k_opad[i] = 0x5c; + } + sha256_update(&ctx, k_ipad, SHA256_BLOCK_SIZE); + sha256_update(&ctx, buff, len); + sha256_finish(&ctx, output); + + /* Init context for second pass */ + sha256_starts(&ctx); + + /* start with outer pad */ + sha256_update(&ctx, k_opad, SHA256_BLOCK_SIZE); + + /* then results of 1st hash */ + sha256_update(&ctx, output, RPMB_SZ_MAC); + + /* finish up 2nd pass */ + sha256_finish(&ctx, output); +} +int mmc_rpmb_get_counter(struct mmc *mmc, unsigned long *pcounter) +{ + int ret; + ALLOC_CACHE_ALIGN_BUFFER(struct s_rpmb, rpmb_frame, 1); + + /* Fill the request */ + memset(rpmb_frame, 0, sizeof(struct s_rpmb)); + rpmb_frame->request = cpu_to_be16(RPMB_REQ_WCOUNTER); + if (mmc_rpmb_request(mmc, rpmb_frame, 1, false)) + return -1; + + /* Read the result */ + ret = mmc_rpmb_response(mmc, rpmb_frame, RPMB_RESP_WCOUNTER); + if (ret) + return ret; + + *pcounter = be32_to_cpu(rpmb_frame->write_counter); + return 0; +} +int mmc_rpmb_set_key(struct mmc *mmc, void *key) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct s_rpmb, rpmb_frame, 1); + /* Fill the request */ + memset(rpmb_frame, 0, sizeof(struct s_rpmb)); + rpmb_frame->request = cpu_to_be16(RPMB_REQ_KEY); + memcpy(rpmb_frame->mac, key, RPMB_SZ_MAC); + + if (mmc_rpmb_request(mmc, rpmb_frame, 1, true)) + return -1; + + /* read the operation status */ + return mmc_rpmb_status(mmc, RPMB_RESP_KEY); +} +int mmc_rpmb_read(struct mmc *mmc, void *addr, unsigned short blk, + unsigned short cnt, unsigned char *key) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct s_rpmb, rpmb_frame, 1); + int i; + + for (i = 0; i < cnt; i++) { + /* Fill the request */ + memset(rpmb_frame, 0, sizeof(struct s_rpmb)); + rpmb_frame->address = cpu_to_be16(blk + i); + rpmb_frame->request = cpu_to_be16(RPMB_REQ_READ_DATA); + if (mmc_rpmb_request(mmc, rpmb_frame, 1, false)) + break; + + /* Read the result */ + if (mmc_rpmb_response(mmc, rpmb_frame, RPMB_RESP_READ_DATA)) + break; + + /* Check the HMAC if key is provided */ + if (key) { + unsigned char ret_hmac[RPMB_SZ_MAC]; + + rpmb_hmac(key, rpmb_frame->data, 284, ret_hmac); + if (memcmp(ret_hmac, rpmb_frame->mac, RPMB_SZ_MAC)) { + printf("MAC error on block #%d\n", i); + break; + } + } + /* Copy data */ + memcpy(addr + i * RPMB_SZ_DATA, rpmb_frame->data, RPMB_SZ_DATA); + } + return i; +} +int mmc_rpmb_write(struct mmc *mmc, void *addr, unsigned short blk, + unsigned short cnt, unsigned char *key) +{ + ALLOC_CACHE_ALIGN_BUFFER(struct s_rpmb, rpmb_frame, 1); + unsigned long wcount; + int i; + + for (i = 0; i < cnt; i++) { + if (mmc_rpmb_get_counter(mmc, &wcount)) { + printf("Cannot read RPMB write counter\n"); + break; + } + + /* Fill the request */ + memset(rpmb_frame, 0, sizeof(struct s_rpmb)); + memcpy(rpmb_frame->data, addr + i * RPMB_SZ_DATA, RPMB_SZ_DATA); + rpmb_frame->address = cpu_to_be16(blk + i); + rpmb_frame->block_count = cpu_to_be16(1); + rpmb_frame->write_counter = cpu_to_be32(wcount); + rpmb_frame->request = cpu_to_be16(RPMB_REQ_WRITE_DATA); + /* Computes HMAC */ + rpmb_hmac(key, rpmb_frame->data, 284, rpmb_frame->mac); + + if (mmc_rpmb_request(mmc, rpmb_frame, 1, true)) + break; + + /* Get status */ + if (mmc_rpmb_status(mmc, RPMB_RESP_WRITE_DATA)) + break; + } + return i; +} + +static int send_write_mult_block(struct mmc *mmc, const struct s_rpmb *frm, + unsigned short cnt) +{ + struct mmc_cmd cmd = { + .cmdidx = MMC_CMD_WRITE_MULTIPLE_BLOCK, + .resp_type = MMC_RSP_R1, + }; + struct mmc_data data = { + .src = (const void *)frm, + .blocks = cnt, + .blocksize = sizeof(*frm), + .flags = MMC_DATA_WRITE, + }; + + return mmc_send_cmd(mmc, &cmd, &data); +} + +static int send_read_mult_block(struct mmc *mmc, struct s_rpmb *frm, + unsigned short cnt) +{ + struct mmc_cmd cmd = { + .cmdidx = MMC_CMD_READ_MULTIPLE_BLOCK, + .resp_type = MMC_RSP_R1, + }; + struct mmc_data data = { + .dest = (void *)frm, + .blocks = cnt, + .blocksize = sizeof(*frm), + .flags = MMC_DATA_READ, + }; + + return mmc_send_cmd(mmc, &cmd, &data); +} + +static int rpmb_route_write_req(struct mmc *mmc, struct s_rpmb *req, + unsigned short req_cnt, struct s_rpmb *rsp, + unsigned short rsp_cnt) +{ + int ret; + + /* + * Send the write request. + */ + ret = mmc_set_blockcount(mmc, req_cnt, true); + if (ret) + return ret; + + ret = send_write_mult_block(mmc, req, req_cnt); + if (ret) + return ret; + + /* + * Read the result of the request. + */ + ret = mmc_set_blockcount(mmc, 1, false); + if (ret) + return ret; + + memset(rsp, 0, sizeof(*rsp)); + rsp->request = cpu_to_be16(RPMB_REQ_STATUS); + ret = send_write_mult_block(mmc, rsp, 1); + if (ret) + return ret; + + ret = mmc_set_blockcount(mmc, 1, false); + if (ret) + return ret; + + return send_read_mult_block(mmc, rsp, 1); +} + +static int rpmb_route_read_req(struct mmc *mmc, struct s_rpmb *req, + unsigned short req_cnt, struct s_rpmb *rsp, + unsigned short rsp_cnt) +{ + int ret; + + /* + * Send the read request. + */ + ret = mmc_set_blockcount(mmc, 1, false); + if (ret) + return ret; + + ret = send_write_mult_block(mmc, req, 1); + if (ret) + return ret; + + /* + * Read the result of the request. + */ + + ret = mmc_set_blockcount(mmc, rsp_cnt, false); + if (ret) + return ret; + + return send_read_mult_block(mmc, rsp, rsp_cnt); +} + +static int rpmb_route_frames(struct mmc *mmc, struct s_rpmb *req, + unsigned short req_cnt, struct s_rpmb *rsp, + unsigned short rsp_cnt) +{ + unsigned short n; + + /* + * If multiple request frames are provided, make sure that all are + * of the same type. + */ + for (n = 1; n < req_cnt; n++) + if (req[n].request != req->request) + return -EINVAL; + + switch (be16_to_cpu(req->request)) { + case RPMB_REQ_KEY: + if (req_cnt != 1 || rsp_cnt != 1) + return -EINVAL; + return rpmb_route_write_req(mmc, req, req_cnt, rsp, rsp_cnt); + + case RPMB_REQ_WRITE_DATA: + if (!req_cnt || rsp_cnt != 1) + return -EINVAL; + return rpmb_route_write_req(mmc, req, req_cnt, rsp, rsp_cnt); + + case RPMB_REQ_WCOUNTER: + if (req_cnt != 1 || rsp_cnt != 1) + return -EINVAL; + return rpmb_route_read_req(mmc, req, req_cnt, rsp, rsp_cnt); + + case RPMB_REQ_READ_DATA: + if (req_cnt != 1 || !req_cnt) + return -EINVAL; + return rpmb_route_read_req(mmc, req, req_cnt, rsp, rsp_cnt); + + default: + debug("Unsupported message type: %d\n", + be16_to_cpu(req->request)); + return -EINVAL; + } +} + +int mmc_rpmb_route_frames(struct mmc *mmc, void *req, unsigned long reqlen, + void *rsp, unsigned long rsplen) +{ + /* + * Whoever crafted the data supplied to this function knows how to + * format the PRMB frames and which response is expected. If + * there's some unexpected mismatch it's more helpful to report an + * error immediately than trying to guess what was the intention + * and possibly just delay an eventual error which will be harder + * to track down. + */ + + if (reqlen % sizeof(struct s_rpmb) || rsplen % sizeof(struct s_rpmb)) + return -EINVAL; + + return rpmb_route_frames(mmc, req, reqlen / sizeof(struct s_rpmb), + rsp, rsplen / sizeof(struct s_rpmb)); +} diff --git a/roms/u-boot/drivers/mmc/s5p_sdhci.c b/roms/u-boot/drivers/mmc/s5p_sdhci.c new file mode 100644 index 000000000..dee84263c --- /dev/null +++ b/roms/u-boot/drivers/mmc/s5p_sdhci.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2012 SAMSUNG Electronics + * Jaehoon Chung <jh80.chung@samsung.com> + */ + +#include <common.h> +#include <dm.h> +#include <log.h> +#include <malloc.h> +#include <sdhci.h> +#include <fdtdec.h> +#include <asm/global_data.h> +#include <linux/libfdt.h> +#include <asm/gpio.h> +#include <asm/arch/mmc.h> +#include <asm/arch/clk.h> +#include <errno.h> +#include <asm/arch/pinmux.h> + +#ifdef CONFIG_DM_MMC +struct s5p_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +DECLARE_GLOBAL_DATA_PTR; +#endif + +static char *S5P_NAME = "SAMSUNG SDHCI"; +static void s5p_sdhci_set_control_reg(struct sdhci_host *host) +{ + unsigned long val, ctrl; + /* + * SELCLKPADDS[17:16] + * 00 = 2mA + * 01 = 4mA + * 10 = 7mA + * 11 = 9mA + */ + sdhci_writel(host, SDHCI_CTRL4_DRIVE_MASK(0x3), SDHCI_CONTROL4); + + val = sdhci_readl(host, SDHCI_CONTROL2); + val &= SDHCI_CTRL2_SELBASECLK_MASK(3); + + val |= SDHCI_CTRL2_ENSTAASYNCCLR | + SDHCI_CTRL2_ENCMDCNFMSK | + SDHCI_CTRL2_ENFBCLKRX | + SDHCI_CTRL2_ENCLKOUTHOLD; + + sdhci_writel(host, val, SDHCI_CONTROL2); + + /* + * FCSEL3[31] FCSEL2[23] FCSEL1[15] FCSEL0[7] + * FCSel[1:0] : Rx Feedback Clock Delay Control + * Inverter delay means10ns delay if SDCLK 50MHz setting + * 01 = Delay1 (basic delay) + * 11 = Delay2 (basic delay + 2ns) + * 00 = Delay3 (inverter delay) + * 10 = Delay4 (inverter delay + 2ns) + */ + val = SDHCI_CTRL3_FCSEL0 | SDHCI_CTRL3_FCSEL1; + sdhci_writel(host, val, SDHCI_CONTROL3); + + /* + * SELBASECLK[5:4] + * 00/01 = HCLK + * 10 = EPLL + * 11 = XTI or XEXTCLK + */ + ctrl = sdhci_readl(host, SDHCI_CONTROL2); + ctrl &= ~SDHCI_CTRL2_SELBASECLK_MASK(0x3); + ctrl |= SDHCI_CTRL2_SELBASECLK_MASK(0x2); + sdhci_writel(host, ctrl, SDHCI_CONTROL2); +} + +static void s5p_set_clock(struct sdhci_host *host, u32 div) +{ + /* ToDo : Use the Clock Framework */ + set_mmc_clk(host->index, div); +} + +static const struct sdhci_ops s5p_sdhci_ops = { + .set_clock = &s5p_set_clock, + .set_control_reg = &s5p_sdhci_set_control_reg, +}; + +static int s5p_sdhci_core_init(struct sdhci_host *host) +{ + host->name = S5P_NAME; + + host->quirks = SDHCI_QUIRK_NO_HISPD_BIT | SDHCI_QUIRK_BROKEN_VOLTAGE | + SDHCI_QUIRK_32BIT_DMA_ADDR | + SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_USE_WIDE8; + host->max_clk = 52000000; + host->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + host->ops = &s5p_sdhci_ops; + + if (host->bus_width == 8) + host->host_caps |= MMC_MODE_8BIT; + +#ifndef CONFIG_BLK + return add_sdhci(host, 0, 400000); +#else + return 0; +#endif +} + +int s5p_sdhci_init(u32 regbase, int index, int bus_width) +{ + struct sdhci_host *host = calloc(1, sizeof(struct sdhci_host)); + if (!host) { + printf("sdhci__host allocation fail!\n"); + return -ENOMEM; + } + host->ioaddr = (void *)regbase; + host->index = index; + host->bus_width = bus_width; + + return s5p_sdhci_core_init(host); +} + +static int do_sdhci_init(struct sdhci_host *host) +{ + int dev_id, flag, ret; + + flag = host->bus_width == 8 ? PINMUX_FLAG_8BIT_MODE : PINMUX_FLAG_NONE; + dev_id = host->index + PERIPH_ID_SDMMC0; + + ret = exynos_pinmux_config(dev_id, flag); + if (ret) { + printf("external SD not configured\n"); + return ret; + } + + if (dm_gpio_is_valid(&host->pwr_gpio)) { + dm_gpio_set_value(&host->pwr_gpio, 1); + ret = exynos_pinmux_config(dev_id, flag); + if (ret) { + debug("MMC not configured\n"); + return ret; + } + } + + if (dm_gpio_is_valid(&host->cd_gpio)) { + ret = dm_gpio_get_value(&host->cd_gpio); + if (ret) { + debug("no SD card detected (%d)\n", ret); + return -ENODEV; + } + } + + return s5p_sdhci_core_init(host); +} + +static int sdhci_get_config(const void *blob, int node, struct sdhci_host *host) +{ + int bus_width, dev_id; + unsigned int base; + + /* Get device id */ + dev_id = pinmux_decode_periph_id(blob, node); + if (dev_id < PERIPH_ID_SDMMC0 || dev_id > PERIPH_ID_SDMMC3) { + debug("MMC: Can't get device id\n"); + return -EINVAL; + } + host->index = dev_id - PERIPH_ID_SDMMC0; + + /* Get bus width */ + bus_width = fdtdec_get_int(blob, node, "samsung,bus-width", 0); + if (bus_width <= 0) { + debug("MMC: Can't get bus-width\n"); + return -EINVAL; + } + host->bus_width = bus_width; + + /* Get the base address from the device node */ + base = fdtdec_get_addr(blob, node, "reg"); + if (!base) { + debug("MMC: Can't get base address\n"); + return -EINVAL; + } + host->ioaddr = (void *)base; + + gpio_request_by_name_nodev(offset_to_ofnode(node), "pwr-gpios", 0, + &host->pwr_gpio, GPIOD_IS_OUT); + gpio_request_by_name_nodev(offset_to_ofnode(node), "cd-gpios", 0, + &host->cd_gpio, GPIOD_IS_IN); + + return 0; +} + +#ifdef CONFIG_DM_MMC +static int s5p_sdhci_probe(struct udevice *dev) +{ + struct s5p_sdhci_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct sdhci_host *host = dev_get_priv(dev); + int ret; + + ret = sdhci_get_config(gd->fdt_blob, dev_of_offset(dev), host); + if (ret) + return ret; + + ret = do_sdhci_init(host); + if (ret) + return ret; + + ret = mmc_of_parse(dev, &plat->cfg); + if (ret) + return ret; + + host->mmc = &plat->mmc; + host->mmc->dev = dev; + + ret = sdhci_setup_cfg(&plat->cfg, host, 0, 400000); + if (ret) + return ret; + + host->mmc->priv = host; + upriv->mmc = host->mmc; + + return sdhci_probe(dev); +} + +static int s5p_sdhci_bind(struct udevice *dev) +{ + struct s5p_sdhci_plat *plat = dev_get_plat(dev); + int ret; + + ret = sdhci_bind(dev, &plat->mmc, &plat->cfg); + if (ret) + return ret; + + return 0; +} + +static const struct udevice_id s5p_sdhci_ids[] = { + { .compatible = "samsung,exynos4412-sdhci"}, + { } +}; + +U_BOOT_DRIVER(s5p_sdhci_drv) = { + .name = "s5p_sdhci", + .id = UCLASS_MMC, + .of_match = s5p_sdhci_ids, + .bind = s5p_sdhci_bind, + .ops = &sdhci_ops, + .probe = s5p_sdhci_probe, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct s5p_sdhci_plat), +}; +#endif /* CONFIG_DM_MMC */ diff --git a/roms/u-boot/drivers/mmc/sandbox_mmc.c b/roms/u-boot/drivers/mmc/sandbox_mmc.c new file mode 100644 index 000000000..18ba020aa --- /dev/null +++ b/roms/u-boot/drivers/mmc/sandbox_mmc.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2015 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <fdtdec.h> +#include <log.h> +#include <mmc.h> +#include <asm/test.h> + +struct sandbox_mmc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +#define MMC_CSIZE 0 +#define MMC_CMULT 8 /* 8 because the card is high-capacity */ +#define MMC_BL_LEN_SHIFT 10 +#define MMC_BL_LEN BIT(MMC_BL_LEN_SHIFT) +#define MMC_CAPACITY (((MMC_CSIZE + 1) << (MMC_CMULT + 2)) \ + * MMC_BL_LEN) /* 1 MiB */ + +struct sandbox_mmc_priv { + u8 buf[MMC_CAPACITY]; +}; + +/** + * sandbox_mmc_send_cmd() - Emulate SD commands + * + * This emulate an SD card version 2. Single-block reads result in zero data. + * Multiple-block reads return a test string. + */ +static int sandbox_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct sandbox_mmc_priv *priv = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + static ulong erase_start, erase_end; + + switch (cmd->cmdidx) { + case MMC_CMD_ALL_SEND_CID: + memset(cmd->response, '\0', sizeof(cmd->response)); + break; + case SD_CMD_SEND_RELATIVE_ADDR: + cmd->response[0] = 0 << 16; /* mmc->rca */ + case MMC_CMD_GO_IDLE_STATE: + break; + case SD_CMD_SEND_IF_COND: + cmd->response[0] = 0xaa; + break; + case MMC_CMD_SEND_STATUS: + cmd->response[0] = MMC_STATUS_RDY_FOR_DATA; + break; + case MMC_CMD_SELECT_CARD: + break; + case MMC_CMD_SEND_CSD: + cmd->response[0] = 0; + cmd->response[1] = (MMC_BL_LEN_SHIFT << 16) | + ((MMC_CSIZE >> 16) & 0x3f); + cmd->response[2] = (MMC_CSIZE & 0xffff) << 16; + cmd->response[3] = 0; + break; + case SD_CMD_SWITCH_FUNC: { + if (!data) + break; + u32 *resp = (u32 *)data->dest; + resp[3] = 0; + resp[7] = cpu_to_be32(SD_HIGHSPEED_BUSY); + if ((cmd->cmdarg & 0xF) == UHS_SDR12_BUS_SPEED) + resp[4] = (cmd->cmdarg & 0xF) << 24; + break; + } + case MMC_CMD_READ_SINGLE_BLOCK: + case MMC_CMD_READ_MULTIPLE_BLOCK: + memcpy(data->dest, &priv->buf[cmd->cmdarg * data->blocksize], + data->blocks * data->blocksize); + break; + case MMC_CMD_WRITE_SINGLE_BLOCK: + case MMC_CMD_WRITE_MULTIPLE_BLOCK: + memcpy(&priv->buf[cmd->cmdarg * data->blocksize], data->src, + data->blocks * data->blocksize); + break; + case MMC_CMD_STOP_TRANSMISSION: + break; + case SD_CMD_ERASE_WR_BLK_START: + erase_start = cmd->cmdarg; + break; + case SD_CMD_ERASE_WR_BLK_END: + erase_end = cmd->cmdarg; + break; + case MMC_CMD_ERASE: + memset(&priv->buf[erase_start * mmc->write_bl_len], '\0', + (erase_end - erase_start + 1) * mmc->write_bl_len); + break; + case SD_CMD_APP_SEND_OP_COND: + cmd->response[0] = OCR_BUSY | OCR_HCS; + cmd->response[1] = 0; + cmd->response[2] = 0; + break; + case MMC_CMD_APP_CMD: + break; + case MMC_CMD_SET_BLOCKLEN: + debug("block len %d\n", cmd->cmdarg); + break; + case SD_CMD_APP_SEND_SCR: { + u32 *scr = (u32 *)data->dest; + + scr[0] = cpu_to_be32(2 << 24 | 1 << 15); /* SD version 3 */ + break; + } + default: + debug("%s: Unknown command %d\n", __func__, cmd->cmdidx); + break; + } + + return 0; +} + +static int sandbox_mmc_set_ios(struct udevice *dev) +{ + return 0; +} + +static int sandbox_mmc_get_cd(struct udevice *dev) +{ + return 1; +} + +static const struct dm_mmc_ops sandbox_mmc_ops = { + .send_cmd = sandbox_mmc_send_cmd, + .set_ios = sandbox_mmc_set_ios, + .get_cd = sandbox_mmc_get_cd, +}; + +int sandbox_mmc_probe(struct udevice *dev) +{ + struct sandbox_mmc_plat *plat = dev_get_plat(dev); + + return mmc_init(&plat->mmc); +} + +int sandbox_mmc_bind(struct udevice *dev) +{ + struct sandbox_mmc_plat *plat = dev_get_plat(dev); + struct mmc_config *cfg = &plat->cfg; + + cfg->name = dev->name; + cfg->host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS | MMC_MODE_8BIT; + cfg->voltages = MMC_VDD_165_195 | MMC_VDD_32_33 | MMC_VDD_33_34; + cfg->f_min = 1000000; + cfg->f_max = 52000000; + cfg->b_max = U32_MAX; + + return mmc_bind(dev, &plat->mmc, cfg); +} + +int sandbox_mmc_unbind(struct udevice *dev) +{ + mmc_unbind(dev); + + return 0; +} + +static const struct udevice_id sandbox_mmc_ids[] = { + { .compatible = "sandbox,mmc" }, + { } +}; + +U_BOOT_DRIVER(mmc_sandbox) = { + .name = "mmc_sandbox", + .id = UCLASS_MMC, + .of_match = sandbox_mmc_ids, + .ops = &sandbox_mmc_ops, + .bind = sandbox_mmc_bind, + .unbind = sandbox_mmc_unbind, + .probe = sandbox_mmc_probe, + .priv_auto = sizeof(struct sandbox_mmc_priv), + .plat_auto = sizeof(struct sandbox_mmc_plat), +}; diff --git a/roms/u-boot/drivers/mmc/sdhci-adma.c b/roms/u-boot/drivers/mmc/sdhci-adma.c new file mode 100644 index 000000000..2ec057fbb --- /dev/null +++ b/roms/u-boot/drivers/mmc/sdhci-adma.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SDHCI ADMA2 helper functions. + */ + +#include <common.h> +#include <cpu_func.h> +#include <sdhci.h> +#include <malloc.h> +#include <asm/cache.h> + +static void sdhci_adma_desc(struct sdhci_adma_desc *desc, + dma_addr_t addr, u16 len, bool end) +{ + u8 attr; + + attr = ADMA_DESC_ATTR_VALID | ADMA_DESC_TRANSFER_DATA; + if (end) + attr |= ADMA_DESC_ATTR_END; + + desc->attr = attr; + desc->len = len; + desc->reserved = 0; + desc->addr_lo = lower_32_bits(addr); +#ifdef CONFIG_DMA_ADDR_T_64BIT + desc->addr_hi = upper_32_bits(addr); +#endif +} + +/** + * sdhci_prepare_adma_table() - Populate the ADMA table + * + * @table: Pointer to the ADMA table + * @data: Pointer to MMC data + * @addr: DMA address to write to or read from + * + * Fill the ADMA table according to the MMC data to read from or write to the + * given DMA address. + * Please note, that the table size depends on CONFIG_SYS_MMC_MAX_BLK_COUNT and + * we don't have to check for overflow. + */ +void sdhci_prepare_adma_table(struct sdhci_adma_desc *table, + struct mmc_data *data, dma_addr_t addr) +{ + uint trans_bytes = data->blocksize * data->blocks; + uint desc_count = DIV_ROUND_UP(trans_bytes, ADMA_MAX_LEN); + struct sdhci_adma_desc *desc = table; + int i = desc_count; + + while (--i) { + sdhci_adma_desc(desc, addr, ADMA_MAX_LEN, false); + addr += ADMA_MAX_LEN; + trans_bytes -= ADMA_MAX_LEN; + desc++; + } + + sdhci_adma_desc(desc, addr, trans_bytes, true); + + flush_cache((dma_addr_t)table, + ROUND(desc_count * sizeof(struct sdhci_adma_desc), + ARCH_DMA_MINALIGN)); +} + +/** + * sdhci_adma_init() - initialize the ADMA descriptor table + * + * @return pointer to the allocated descriptor table or NULL in case of an + * error. + */ +struct sdhci_adma_desc *sdhci_adma_init(void) +{ + return memalign(ARCH_DMA_MINALIGN, ADMA_TABLE_SZ); +} diff --git a/roms/u-boot/drivers/mmc/sdhci-cadence.c b/roms/u-boot/drivers/mmc/sdhci-cadence.c new file mode 100644 index 000000000..327a05ad1 --- /dev/null +++ b/roms/u-boot/drivers/mmc/sdhci-cadence.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Socionext Inc. + * Author: Masahiro Yamada <yamada.masahiro@socionext.com> + */ + +#include <common.h> +#include <dm.h> +#include <asm/global_data.h> +#include <dm/device_compat.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/bug.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/sizes.h> +#include <linux/libfdt.h> +#include <mmc.h> +#include <sdhci.h> + +/* HRS - Host Register Set (specific to Cadence) */ +#define SDHCI_CDNS_HRS04 0x10 /* PHY access port */ +#define SDHCI_CDNS_HRS04_ACK BIT(26) +#define SDHCI_CDNS_HRS04_RD BIT(25) +#define SDHCI_CDNS_HRS04_WR BIT(24) +#define SDHCI_CDNS_HRS04_RDATA GENMASK(23, 16) +#define SDHCI_CDNS_HRS04_WDATA GENMASK(15, 8) +#define SDHCI_CDNS_HRS04_ADDR GENMASK(5, 0) + +#define SDHCI_CDNS_HRS06 0x18 /* eMMC control */ +#define SDHCI_CDNS_HRS06_TUNE_UP BIT(15) +#define SDHCI_CDNS_HRS06_TUNE GENMASK(13, 8) +#define SDHCI_CDNS_HRS06_MODE GENMASK(2, 0) +#define SDHCI_CDNS_HRS06_MODE_SD 0x0 +#define SDHCI_CDNS_HRS06_MODE_MMC_SDR 0x2 +#define SDHCI_CDNS_HRS06_MODE_MMC_DDR 0x3 +#define SDHCI_CDNS_HRS06_MODE_MMC_HS200 0x4 +#define SDHCI_CDNS_HRS06_MODE_MMC_HS400 0x5 +#define SDHCI_CDNS_HRS06_MODE_MMC_HS400ES 0x6 + +/* SRS - Slot Register Set (SDHCI-compatible) */ +#define SDHCI_CDNS_SRS_BASE 0x200 + +/* PHY */ +#define SDHCI_CDNS_PHY_DLY_SD_HS 0x00 +#define SDHCI_CDNS_PHY_DLY_SD_DEFAULT 0x01 +#define SDHCI_CDNS_PHY_DLY_UHS_SDR12 0x02 +#define SDHCI_CDNS_PHY_DLY_UHS_SDR25 0x03 +#define SDHCI_CDNS_PHY_DLY_UHS_SDR50 0x04 +#define SDHCI_CDNS_PHY_DLY_UHS_DDR50 0x05 +#define SDHCI_CDNS_PHY_DLY_EMMC_LEGACY 0x06 +#define SDHCI_CDNS_PHY_DLY_EMMC_SDR 0x07 +#define SDHCI_CDNS_PHY_DLY_EMMC_DDR 0x08 +#define SDHCI_CDNS_PHY_DLY_SDCLK 0x0b +#define SDHCI_CDNS_PHY_DLY_HSMMC 0x0c +#define SDHCI_CDNS_PHY_DLY_STROBE 0x0d + +/* + * The tuned val register is 6 bit-wide, but not the whole of the range is + * available. The range 0-42 seems to be available (then 43 wraps around to 0) + * but I am not quite sure if it is official. Use only 0 to 39 for safety. + */ +#define SDHCI_CDNS_MAX_TUNING_LOOP 40 + +struct sdhci_cdns_plat { + struct mmc_config cfg; + struct mmc mmc; + void __iomem *hrs_addr; +}; + +struct sdhci_cdns_phy_cfg { + const char *property; + u8 addr; +}; + +static const struct sdhci_cdns_phy_cfg sdhci_cdns_phy_cfgs[] = { + { "cdns,phy-input-delay-sd-highspeed", SDHCI_CDNS_PHY_DLY_SD_HS, }, + { "cdns,phy-input-delay-legacy", SDHCI_CDNS_PHY_DLY_SD_DEFAULT, }, + { "cdns,phy-input-delay-sd-uhs-sdr12", SDHCI_CDNS_PHY_DLY_UHS_SDR12, }, + { "cdns,phy-input-delay-sd-uhs-sdr25", SDHCI_CDNS_PHY_DLY_UHS_SDR25, }, + { "cdns,phy-input-delay-sd-uhs-sdr50", SDHCI_CDNS_PHY_DLY_UHS_SDR50, }, + { "cdns,phy-input-delay-sd-uhs-ddr50", SDHCI_CDNS_PHY_DLY_UHS_DDR50, }, + { "cdns,phy-input-delay-mmc-highspeed", SDHCI_CDNS_PHY_DLY_EMMC_SDR, }, + { "cdns,phy-input-delay-mmc-ddr", SDHCI_CDNS_PHY_DLY_EMMC_DDR, }, + { "cdns,phy-dll-delay-sdclk", SDHCI_CDNS_PHY_DLY_SDCLK, }, + { "cdns,phy-dll-delay-sdclk-hsmmc", SDHCI_CDNS_PHY_DLY_HSMMC, }, + { "cdns,phy-dll-delay-strobe", SDHCI_CDNS_PHY_DLY_STROBE, }, +}; + +static int sdhci_cdns_write_phy_reg(struct sdhci_cdns_plat *plat, + u8 addr, u8 data) +{ + void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS04; + u32 tmp; + int ret; + + tmp = FIELD_PREP(SDHCI_CDNS_HRS04_WDATA, data) | + FIELD_PREP(SDHCI_CDNS_HRS04_ADDR, addr); + writel(tmp, reg); + + tmp |= SDHCI_CDNS_HRS04_WR; + writel(tmp, reg); + + ret = readl_poll_timeout(reg, tmp, tmp & SDHCI_CDNS_HRS04_ACK, 10); + if (ret) + return ret; + + tmp &= ~SDHCI_CDNS_HRS04_WR; + writel(tmp, reg); + + return 0; +} + +static int sdhci_cdns_phy_init(struct sdhci_cdns_plat *plat, + const void *fdt, int nodeoffset) +{ + const fdt32_t *prop; + int ret, i; + + for (i = 0; i < ARRAY_SIZE(sdhci_cdns_phy_cfgs); i++) { + prop = fdt_getprop(fdt, nodeoffset, + sdhci_cdns_phy_cfgs[i].property, NULL); + if (!prop) + continue; + + ret = sdhci_cdns_write_phy_reg(plat, + sdhci_cdns_phy_cfgs[i].addr, + fdt32_to_cpu(*prop)); + if (ret) + return ret; + } + + return 0; +} + +static void sdhci_cdns_set_control_reg(struct sdhci_host *host) +{ + struct mmc *mmc = host->mmc; + struct sdhci_cdns_plat *plat = dev_get_plat(mmc->dev); + unsigned int clock = mmc->clock; + u32 mode, tmp; + + /* + * REVISIT: + * The mode should be decided by MMC_TIMING_* like Linux, but + * U-Boot does not support timing. Use the clock frequency instead. + */ + if (clock <= 26000000) { + mode = SDHCI_CDNS_HRS06_MODE_SD; /* use this for Legacy */ + } else if (clock <= 52000000) { + if (mmc->ddr_mode) + mode = SDHCI_CDNS_HRS06_MODE_MMC_DDR; + else + mode = SDHCI_CDNS_HRS06_MODE_MMC_SDR; + } else { + if (mmc->ddr_mode) + mode = SDHCI_CDNS_HRS06_MODE_MMC_HS400; + else + mode = SDHCI_CDNS_HRS06_MODE_MMC_HS200; + } + + tmp = readl(plat->hrs_addr + SDHCI_CDNS_HRS06); + tmp &= ~SDHCI_CDNS_HRS06_MODE; + tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_MODE, mode); + writel(tmp, plat->hrs_addr + SDHCI_CDNS_HRS06); +} + +static const struct sdhci_ops sdhci_cdns_ops = { + .set_control_reg = sdhci_cdns_set_control_reg, +}; + +static int sdhci_cdns_set_tune_val(struct sdhci_cdns_plat *plat, + unsigned int val) +{ + void __iomem *reg = plat->hrs_addr + SDHCI_CDNS_HRS06; + u32 tmp; + int i, ret; + + if (WARN_ON(!FIELD_FIT(SDHCI_CDNS_HRS06_TUNE, val))) + return -EINVAL; + + tmp = readl(reg); + tmp &= ~SDHCI_CDNS_HRS06_TUNE; + tmp |= FIELD_PREP(SDHCI_CDNS_HRS06_TUNE, val); + + /* + * Workaround for IP errata: + * The IP6116 SD/eMMC PHY design has a timing issue on receive data + * path. Send tune request twice. + */ + for (i = 0; i < 2; i++) { + tmp |= SDHCI_CDNS_HRS06_TUNE_UP; + writel(tmp, reg); + + ret = readl_poll_timeout(reg, tmp, + !(tmp & SDHCI_CDNS_HRS06_TUNE_UP), 1); + if (ret) + return ret; + } + + return 0; +} + +static int __maybe_unused sdhci_cdns_execute_tuning(struct udevice *dev, + unsigned int opcode) +{ + struct sdhci_cdns_plat *plat = dev_get_plat(dev); + struct mmc *mmc = &plat->mmc; + int cur_streak = 0; + int max_streak = 0; + int end_of_streak = 0; + int i; + + /* + * This handler only implements the eMMC tuning that is specific to + * this controller. The tuning for SD timing should be handled by the + * SDHCI core. + */ + if (!IS_MMC(mmc)) + return -ENOTSUPP; + + if (WARN_ON(opcode != MMC_CMD_SEND_TUNING_BLOCK_HS200)) + return -EINVAL; + + for (i = 0; i < SDHCI_CDNS_MAX_TUNING_LOOP; i++) { + if (sdhci_cdns_set_tune_val(plat, i) || + mmc_send_tuning(mmc, opcode, NULL)) { /* bad */ + cur_streak = 0; + } else { /* good */ + cur_streak++; + if (cur_streak > max_streak) { + max_streak = cur_streak; + end_of_streak = i; + } + } + } + + if (!max_streak) { + dev_err(dev, "no tuning point found\n"); + return -EIO; + } + + return sdhci_cdns_set_tune_val(plat, end_of_streak - max_streak / 2); +} + +static struct dm_mmc_ops sdhci_cdns_mmc_ops; + +static int sdhci_cdns_bind(struct udevice *dev) +{ + struct sdhci_cdns_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static int sdhci_cdns_probe(struct udevice *dev) +{ + DECLARE_GLOBAL_DATA_PTR; + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct sdhci_cdns_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + fdt_addr_t base; + int ret; + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) + return -EINVAL; + + plat->hrs_addr = devm_ioremap(dev, base, SZ_1K); + if (!plat->hrs_addr) + return -ENOMEM; + + host->name = dev->name; + host->ioaddr = plat->hrs_addr + SDHCI_CDNS_SRS_BASE; + host->ops = &sdhci_cdns_ops; + host->quirks |= SDHCI_QUIRK_WAIT_SEND_CMD; + sdhci_cdns_mmc_ops = sdhci_ops; +#ifdef MMC_SUPPORTS_TUNING + sdhci_cdns_mmc_ops.execute_tuning = sdhci_cdns_execute_tuning; +#endif + + ret = mmc_of_parse(dev, &plat->cfg); + if (ret) + return ret; + + ret = sdhci_cdns_phy_init(plat, gd->fdt_blob, dev_of_offset(dev)); + if (ret) + return ret; + + host->mmc = &plat->mmc; + host->mmc->dev = dev; + ret = sdhci_setup_cfg(&plat->cfg, host, 0, 0); + if (ret) + return ret; + + upriv->mmc = &plat->mmc; + host->mmc->priv = host; + + return sdhci_probe(dev); +} + +static const struct udevice_id sdhci_cdns_match[] = { + { .compatible = "socionext,uniphier-sd4hc" }, + { .compatible = "cdns,sd4hc" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(sdhci_cdns) = { + .name = "sdhci-cdns", + .id = UCLASS_MMC, + .of_match = sdhci_cdns_match, + .bind = sdhci_cdns_bind, + .probe = sdhci_cdns_probe, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct sdhci_cdns_plat), + .ops = &sdhci_cdns_mmc_ops, +}; diff --git a/roms/u-boot/drivers/mmc/sdhci.c b/roms/u-boot/drivers/mmc/sdhci.c new file mode 100644 index 000000000..d9ab6a0a8 --- /dev/null +++ b/roms/u-boot/drivers/mmc/sdhci.c @@ -0,0 +1,967 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2011, Marvell Semiconductor Inc. + * Lei Wen <leiwen@marvell.com> + * + * Back ported to the 8xx platform (from the 8260 platform) by + * Murray.Jensen@cmst.csiro.au, 27-Jan-01. + */ + +#include <common.h> +#include <cpu_func.h> +#include <dm.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <mmc.h> +#include <sdhci.h> +#include <asm/cache.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <phys2bus.h> +#include <power/regulator.h> + +static void sdhci_reset(struct sdhci_host *host, u8 mask) +{ + unsigned long timeout; + + /* Wait max 100 ms */ + timeout = 100; + sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET); + while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) { + if (timeout == 0) { + printf("%s: Reset 0x%x never completed.\n", + __func__, (int)mask); + return; + } + timeout--; + udelay(1000); + } +} + +static void sdhci_cmd_done(struct sdhci_host *host, struct mmc_cmd *cmd) +{ + int i; + if (cmd->resp_type & MMC_RSP_136) { + /* CRC is stripped so we need to do some shifting. */ + for (i = 0; i < 4; i++) { + cmd->response[i] = sdhci_readl(host, + SDHCI_RESPONSE + (3-i)*4) << 8; + if (i != 3) + cmd->response[i] |= sdhci_readb(host, + SDHCI_RESPONSE + (3-i)*4-1); + } + } else { + cmd->response[0] = sdhci_readl(host, SDHCI_RESPONSE); + } +} + +static void sdhci_transfer_pio(struct sdhci_host *host, struct mmc_data *data) +{ + int i; + char *offs; + for (i = 0; i < data->blocksize; i += 4) { + offs = data->dest + i; + if (data->flags == MMC_DATA_READ) + *(u32 *)offs = sdhci_readl(host, SDHCI_BUFFER); + else + sdhci_writel(host, *(u32 *)offs, SDHCI_BUFFER); + } +} + +#if (defined(CONFIG_MMC_SDHCI_SDMA) || CONFIG_IS_ENABLED(MMC_SDHCI_ADMA)) +static void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data, + int *is_aligned, int trans_bytes) +{ + dma_addr_t dma_addr; + unsigned char ctrl; + void *buf; + + if (data->flags == MMC_DATA_READ) + buf = data->dest; + else + buf = (void *)data->src; + + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + ctrl &= ~SDHCI_CTRL_DMA_MASK; + if (host->flags & USE_ADMA64) + ctrl |= SDHCI_CTRL_ADMA64; + else if (host->flags & USE_ADMA) + ctrl |= SDHCI_CTRL_ADMA32; + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + + if (host->flags & USE_SDMA && + (host->force_align_buffer || + (host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR && + ((unsigned long)buf & 0x7) != 0x0))) { + *is_aligned = 0; + if (data->flags != MMC_DATA_READ) + memcpy(host->align_buffer, buf, trans_bytes); + buf = host->align_buffer; + } + + host->start_addr = dma_map_single(buf, trans_bytes, + mmc_get_dma_dir(data)); + + if (host->flags & USE_SDMA) { + dma_addr = dev_phys_to_bus(mmc_to_dev(host->mmc), host->start_addr); + sdhci_writel(host, dma_addr, SDHCI_DMA_ADDRESS); + } +#if CONFIG_IS_ENABLED(MMC_SDHCI_ADMA) + else if (host->flags & (USE_ADMA | USE_ADMA64)) { + sdhci_prepare_adma_table(host->adma_desc_table, data, + host->start_addr); + + sdhci_writel(host, lower_32_bits(host->adma_addr), + SDHCI_ADMA_ADDRESS); + if (host->flags & USE_ADMA64) + sdhci_writel(host, upper_32_bits(host->adma_addr), + SDHCI_ADMA_ADDRESS_HI); + } +#endif +} +#else +static void sdhci_prepare_dma(struct sdhci_host *host, struct mmc_data *data, + int *is_aligned, int trans_bytes) +{} +#endif +static int sdhci_transfer_data(struct sdhci_host *host, struct mmc_data *data) +{ + dma_addr_t start_addr = host->start_addr; + unsigned int stat, rdy, mask, timeout, block = 0; + bool transfer_done = false; + + timeout = 1000000; + rdy = SDHCI_INT_SPACE_AVAIL | SDHCI_INT_DATA_AVAIL; + mask = SDHCI_DATA_AVAILABLE | SDHCI_SPACE_AVAILABLE; + do { + stat = sdhci_readl(host, SDHCI_INT_STATUS); + if (stat & SDHCI_INT_ERROR) { + pr_debug("%s: Error detected in status(0x%X)!\n", + __func__, stat); + return -EIO; + } + if (!transfer_done && (stat & rdy)) { + if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & mask)) + continue; + sdhci_writel(host, rdy, SDHCI_INT_STATUS); + sdhci_transfer_pio(host, data); + data->dest += data->blocksize; + if (++block >= data->blocks) { + /* Keep looping until the SDHCI_INT_DATA_END is + * cleared, even if we finished sending all the + * blocks. + */ + transfer_done = true; + continue; + } + } + if ((host->flags & USE_DMA) && !transfer_done && + (stat & SDHCI_INT_DMA_END)) { + sdhci_writel(host, SDHCI_INT_DMA_END, SDHCI_INT_STATUS); + if (host->flags & USE_SDMA) { + start_addr &= + ~(SDHCI_DEFAULT_BOUNDARY_SIZE - 1); + start_addr += SDHCI_DEFAULT_BOUNDARY_SIZE; + start_addr = dev_phys_to_bus(mmc_to_dev(host->mmc), + start_addr); + sdhci_writel(host, start_addr, SDHCI_DMA_ADDRESS); + } + } + if (timeout-- > 0) + udelay(10); + else { + printf("%s: Transfer data timeout\n", __func__); + return -ETIMEDOUT; + } + } while (!(stat & SDHCI_INT_DATA_END)); + +#if (defined(CONFIG_MMC_SDHCI_SDMA) || CONFIG_IS_ENABLED(MMC_SDHCI_ADMA)) + dma_unmap_single(host->start_addr, data->blocks * data->blocksize, + mmc_get_dma_dir(data)); +#endif + + return 0; +} + +/* + * No command will be sent by driver if card is busy, so driver must wait + * for card ready state. + * Every time when card is busy after timeout then (last) timeout value will be + * increased twice but only if it doesn't exceed global defined maximum. + * Each function call will use last timeout value. + */ +#define SDHCI_CMD_MAX_TIMEOUT 3200 +#define SDHCI_CMD_DEFAULT_TIMEOUT 100 +#define SDHCI_READ_STATUS_TIMEOUT 1000 + +#ifdef CONFIG_DM_MMC +static int sdhci_send_command(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + +#else +static int sdhci_send_command(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ +#endif + struct sdhci_host *host = mmc->priv; + unsigned int stat = 0; + int ret = 0; + int trans_bytes = 0, is_aligned = 1; + u32 mask, flags, mode; + unsigned int time = 0; + int mmc_dev = mmc_get_blk_desc(mmc)->devnum; + ulong start = get_timer(0); + + host->start_addr = 0; + /* Timeout unit - ms */ + static unsigned int cmd_timeout = SDHCI_CMD_DEFAULT_TIMEOUT; + + mask = SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT; + + /* We shouldn't wait for data inihibit for stop commands, even + though they might use busy signaling */ + if (cmd->cmdidx == MMC_CMD_STOP_TRANSMISSION || + ((cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK || + cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200) && !data)) + mask &= ~SDHCI_DATA_INHIBIT; + + while (sdhci_readl(host, SDHCI_PRESENT_STATE) & mask) { + if (time >= cmd_timeout) { + printf("%s: MMC: %d busy ", __func__, mmc_dev); + if (2 * cmd_timeout <= SDHCI_CMD_MAX_TIMEOUT) { + cmd_timeout += cmd_timeout; + printf("timeout increasing to: %u ms.\n", + cmd_timeout); + } else { + puts("timeout.\n"); + return -ECOMM; + } + } + time++; + udelay(1000); + } + + sdhci_writel(host, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS); + + mask = SDHCI_INT_RESPONSE; + if ((cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK || + cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200) && !data) + mask = SDHCI_INT_DATA_AVAIL; + + if (!(cmd->resp_type & MMC_RSP_PRESENT)) + flags = SDHCI_CMD_RESP_NONE; + else if (cmd->resp_type & MMC_RSP_136) + flags = SDHCI_CMD_RESP_LONG; + else if (cmd->resp_type & MMC_RSP_BUSY) { + flags = SDHCI_CMD_RESP_SHORT_BUSY; + if (data) + mask |= SDHCI_INT_DATA_END; + } else + flags = SDHCI_CMD_RESP_SHORT; + + if (cmd->resp_type & MMC_RSP_CRC) + flags |= SDHCI_CMD_CRC; + if (cmd->resp_type & MMC_RSP_OPCODE) + flags |= SDHCI_CMD_INDEX; + if (data || cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK || + cmd->cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200) + flags |= SDHCI_CMD_DATA; + + /* Set Transfer mode regarding to data flag */ + if (data) { + sdhci_writeb(host, 0xe, SDHCI_TIMEOUT_CONTROL); + mode = SDHCI_TRNS_BLK_CNT_EN; + trans_bytes = data->blocks * data->blocksize; + if (data->blocks > 1) + mode |= SDHCI_TRNS_MULTI; + + if (data->flags == MMC_DATA_READ) + mode |= SDHCI_TRNS_READ; + + if (host->flags & USE_DMA) { + mode |= SDHCI_TRNS_DMA; + sdhci_prepare_dma(host, data, &is_aligned, trans_bytes); + } + + sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, + data->blocksize), + SDHCI_BLOCK_SIZE); + sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); + sdhci_writew(host, mode, SDHCI_TRANSFER_MODE); + } else if (cmd->resp_type & MMC_RSP_BUSY) { + sdhci_writeb(host, 0xe, SDHCI_TIMEOUT_CONTROL); + } + + sdhci_writel(host, cmd->cmdarg, SDHCI_ARGUMENT); + sdhci_writew(host, SDHCI_MAKE_CMD(cmd->cmdidx, flags), SDHCI_COMMAND); + start = get_timer(0); + do { + stat = sdhci_readl(host, SDHCI_INT_STATUS); + if (stat & SDHCI_INT_ERROR) + break; + + if (get_timer(start) >= SDHCI_READ_STATUS_TIMEOUT) { + if (host->quirks & SDHCI_QUIRK_BROKEN_R1B) { + return 0; + } else { + printf("%s: Timeout for status update!\n", + __func__); + return -ETIMEDOUT; + } + } + } while ((stat & mask) != mask); + + if ((stat & (SDHCI_INT_ERROR | mask)) == mask) { + sdhci_cmd_done(host, cmd); + sdhci_writel(host, mask, SDHCI_INT_STATUS); + } else + ret = -1; + + if (!ret && data) + ret = sdhci_transfer_data(host, data); + + if (host->quirks & SDHCI_QUIRK_WAIT_SEND_CMD) + udelay(1000); + + stat = sdhci_readl(host, SDHCI_INT_STATUS); + sdhci_writel(host, SDHCI_INT_ALL_MASK, SDHCI_INT_STATUS); + if (!ret) { + if ((host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) && + !is_aligned && (data->flags == MMC_DATA_READ)) + memcpy(data->dest, host->align_buffer, trans_bytes); + return 0; + } + + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); + if (stat & SDHCI_INT_TIMEOUT) + return -ETIMEDOUT; + else + return -ECOMM; +} + +#if defined(CONFIG_DM_MMC) && defined(MMC_SUPPORTS_TUNING) +static int sdhci_execute_tuning(struct udevice *dev, uint opcode) +{ + int err; + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct sdhci_host *host = mmc->priv; + + debug("%s\n", __func__); + + if (host->ops && host->ops->platform_execute_tuning) { + err = host->ops->platform_execute_tuning(mmc, opcode); + if (err) + return err; + return 0; + } + return 0; +} +#endif +int sdhci_set_clock(struct mmc *mmc, unsigned int clock) +{ + struct sdhci_host *host = mmc->priv; + unsigned int div, clk = 0, timeout; + + /* Wait max 20 ms */ + timeout = 200; + while (sdhci_readl(host, SDHCI_PRESENT_STATE) & + (SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT)) { + if (timeout == 0) { + printf("%s: Timeout to wait cmd & data inhibit\n", + __func__); + return -EBUSY; + } + + timeout--; + udelay(100); + } + + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + + if (clock == 0) + return 0; + + if (host->ops && host->ops->set_delay) + host->ops->set_delay(host); + + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) { + /* + * Check if the Host Controller supports Programmable Clock + * Mode. + */ + if (host->clk_mul) { + for (div = 1; div <= 1024; div++) { + if ((host->max_clk / div) <= clock) + break; + } + + /* + * Set Programmable Clock Mode in the Clock + * Control register. + */ + clk = SDHCI_PROG_CLOCK_MODE; + div--; + } else { + /* Version 3.00 divisors must be a multiple of 2. */ + if (host->max_clk <= clock) { + div = 1; + } else { + for (div = 2; + div < SDHCI_MAX_DIV_SPEC_300; + div += 2) { + if ((host->max_clk / div) <= clock) + break; + } + } + div >>= 1; + } + } else { + /* Version 2.00 divisors must be a power of 2. */ + for (div = 1; div < SDHCI_MAX_DIV_SPEC_200; div *= 2) { + if ((host->max_clk / div) <= clock) + break; + } + div >>= 1; + } + + if (host->ops && host->ops->set_clock) + host->ops->set_clock(host, div); + + clk |= (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT; + clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN) + << SDHCI_DIVIDER_HI_SHIFT; + clk |= SDHCI_CLOCK_INT_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + /* Wait max 20 ms */ + timeout = 20; + while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (timeout == 0) { + printf("%s: Internal clock never stabilised.\n", + __func__); + return -EBUSY; + } + timeout--; + udelay(1000); + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + return 0; +} + +static void sdhci_set_power(struct sdhci_host *host, unsigned short power) +{ + u8 pwr = 0; + + if (power != (unsigned short)-1) { + switch (1 << power) { + case MMC_VDD_165_195: + pwr = SDHCI_POWER_180; + break; + case MMC_VDD_29_30: + case MMC_VDD_30_31: + pwr = SDHCI_POWER_300; + break; + case MMC_VDD_32_33: + case MMC_VDD_33_34: + pwr = SDHCI_POWER_330; + break; + } + } + + if (pwr == 0) { + sdhci_writeb(host, 0, SDHCI_POWER_CONTROL); + return; + } + + pwr |= SDHCI_POWER_ON; + + sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); +} + +void sdhci_set_uhs_timing(struct sdhci_host *host) +{ + struct mmc *mmc = host->mmc; + u32 reg; + + reg = sdhci_readw(host, SDHCI_HOST_CONTROL2); + reg &= ~SDHCI_CTRL_UHS_MASK; + + switch (mmc->selected_mode) { + case UHS_SDR50: + case MMC_HS_52: + reg |= SDHCI_CTRL_UHS_SDR50; + break; + case UHS_DDR50: + case MMC_DDR_52: + reg |= SDHCI_CTRL_UHS_DDR50; + break; + case UHS_SDR104: + case MMC_HS_200: + reg |= SDHCI_CTRL_UHS_SDR104; + break; + default: + reg |= SDHCI_CTRL_UHS_SDR12; + } + + sdhci_writew(host, reg, SDHCI_HOST_CONTROL2); +} + +static void sdhci_set_voltage(struct sdhci_host *host) +{ + if (IS_ENABLED(CONFIG_MMC_IO_VOLTAGE)) { + struct mmc *mmc = (struct mmc *)host->mmc; + u32 ctrl; + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + + switch (mmc->signal_voltage) { + case MMC_SIGNAL_VOLTAGE_330: +#if CONFIG_IS_ENABLED(DM_REGULATOR) + if (mmc->vqmmc_supply) { + if (regulator_set_enable_if_allowed(mmc->vqmmc_supply, false)) { + pr_err("failed to disable vqmmc-supply\n"); + return; + } + + if (regulator_set_value(mmc->vqmmc_supply, 3300000)) { + pr_err("failed to set vqmmc-voltage to 3.3V\n"); + return; + } + + if (regulator_set_enable_if_allowed(mmc->vqmmc_supply, true)) { + pr_err("failed to enable vqmmc-supply\n"); + return; + } + } +#endif + if (IS_SD(mmc)) { + ctrl &= ~SDHCI_CTRL_VDD_180; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + } + + /* Wait for 5ms */ + mdelay(5); + + /* 3.3V regulator output should be stable within 5 ms */ + if (IS_SD(mmc)) { + if (ctrl & SDHCI_CTRL_VDD_180) { + pr_err("3.3V regulator output did not become stable\n"); + return; + } + } + + break; + case MMC_SIGNAL_VOLTAGE_180: +#if CONFIG_IS_ENABLED(DM_REGULATOR) + if (mmc->vqmmc_supply) { + if (regulator_set_enable_if_allowed(mmc->vqmmc_supply, false)) { + pr_err("failed to disable vqmmc-supply\n"); + return; + } + + if (regulator_set_value(mmc->vqmmc_supply, 1800000)) { + pr_err("failed to set vqmmc-voltage to 1.8V\n"); + return; + } + + if (regulator_set_enable_if_allowed(mmc->vqmmc_supply, true)) { + pr_err("failed to enable vqmmc-supply\n"); + return; + } + } +#endif + if (IS_SD(mmc)) { + ctrl |= SDHCI_CTRL_VDD_180; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + } + + /* Wait for 5 ms */ + mdelay(5); + + /* 1.8V regulator output has to be stable within 5 ms */ + if (IS_SD(mmc)) { + if (!(ctrl & SDHCI_CTRL_VDD_180)) { + pr_err("1.8V regulator output did not become stable\n"); + return; + } + } + + break; + default: + /* No signal voltage switch required */ + return; + } + } +} + +void sdhci_set_control_reg(struct sdhci_host *host) +{ + sdhci_set_voltage(host); + sdhci_set_uhs_timing(host); +} + +#ifdef CONFIG_DM_MMC +static int sdhci_set_ios(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); +#else +static int sdhci_set_ios(struct mmc *mmc) +{ +#endif + u32 ctrl; + struct sdhci_host *host = mmc->priv; + bool no_hispd_bit = false; + + if (host->ops && host->ops->set_control_reg) + host->ops->set_control_reg(host); + + if (mmc->clock != host->clock) + sdhci_set_clock(mmc, mmc->clock); + + if (mmc->clk_disable) + sdhci_set_clock(mmc, 0); + + /* Set bus width */ + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + if (mmc->bus_width == 8) { + ctrl &= ~SDHCI_CTRL_4BITBUS; + if ((SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) || + (host->quirks & SDHCI_QUIRK_USE_WIDE8)) + ctrl |= SDHCI_CTRL_8BITBUS; + } else { + if ((SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) || + (host->quirks & SDHCI_QUIRK_USE_WIDE8)) + ctrl &= ~SDHCI_CTRL_8BITBUS; + if (mmc->bus_width == 4) + ctrl |= SDHCI_CTRL_4BITBUS; + else + ctrl &= ~SDHCI_CTRL_4BITBUS; + } + + if ((host->quirks & SDHCI_QUIRK_NO_HISPD_BIT) || + (host->quirks & SDHCI_QUIRK_BROKEN_HISPD_MODE)) { + ctrl &= ~SDHCI_CTRL_HISPD; + no_hispd_bit = true; + } + + if (!no_hispd_bit) { + if (mmc->selected_mode == MMC_HS || + mmc->selected_mode == SD_HS || + mmc->selected_mode == MMC_DDR_52 || + mmc->selected_mode == MMC_HS_200 || + mmc->selected_mode == MMC_HS_400 || + mmc->selected_mode == UHS_SDR25 || + mmc->selected_mode == UHS_SDR50 || + mmc->selected_mode == UHS_SDR104 || + mmc->selected_mode == UHS_DDR50) + ctrl |= SDHCI_CTRL_HISPD; + else + ctrl &= ~SDHCI_CTRL_HISPD; + } + + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + + /* If available, call the driver specific "post" set_ios() function */ + if (host->ops && host->ops->set_ios_post) + return host->ops->set_ios_post(host); + + return 0; +} + +static int sdhci_init(struct mmc *mmc) +{ + struct sdhci_host *host = mmc->priv; +#if CONFIG_IS_ENABLED(DM_MMC) && CONFIG_IS_ENABLED(DM_GPIO) + struct udevice *dev = mmc->dev; + + gpio_request_by_name(dev, "cd-gpios", 0, + &host->cd_gpio, GPIOD_IS_IN); +#endif + + sdhci_reset(host, SDHCI_RESET_ALL); + +#if defined(CONFIG_FIXED_SDHCI_ALIGNED_BUFFER) + host->align_buffer = (void *)CONFIG_FIXED_SDHCI_ALIGNED_BUFFER; + /* + * Always use this bounce-buffer when CONFIG_FIXED_SDHCI_ALIGNED_BUFFER + * is defined. + */ + host->force_align_buffer = true; +#else + if (host->quirks & SDHCI_QUIRK_32BIT_DMA_ADDR) { + host->align_buffer = memalign(8, 512 * 1024); + if (!host->align_buffer) { + printf("%s: Aligned buffer alloc failed!!!\n", + __func__); + return -ENOMEM; + } + } +#endif + + sdhci_set_power(host, fls(mmc->cfg->voltages) - 1); + + if (host->ops && host->ops->get_cd) + host->ops->get_cd(host); + + /* Enable only interrupts served by the SD controller */ + sdhci_writel(host, SDHCI_INT_DATA_MASK | SDHCI_INT_CMD_MASK, + SDHCI_INT_ENABLE); + /* Mask all sdhci interrupt sources */ + sdhci_writel(host, 0x0, SDHCI_SIGNAL_ENABLE); + + return 0; +} + +#ifdef CONFIG_DM_MMC +int sdhci_probe(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + + return sdhci_init(mmc); +} + +static int sdhci_deferred_probe(struct udevice *dev) +{ + int err; + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct sdhci_host *host = mmc->priv; + + if (host->ops && host->ops->deferred_probe) { + err = host->ops->deferred_probe(host); + if (err) + return err; + } + return 0; +} + +static int sdhci_get_cd(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct sdhci_host *host = mmc->priv; + int value; + + /* If nonremovable, assume that the card is always present. */ + if (mmc->cfg->host_caps & MMC_CAP_NONREMOVABLE) + return 1; + /* If polling, assume that the card is always present. */ + if (mmc->cfg->host_caps & MMC_CAP_NEEDS_POLL) + return 1; + +#if CONFIG_IS_ENABLED(DM_GPIO) + value = dm_gpio_get_value(&host->cd_gpio); + if (value >= 0) { + if (mmc->cfg->host_caps & MMC_CAP_CD_ACTIVE_HIGH) + return !value; + else + return value; + } +#endif + value = !!(sdhci_readl(host, SDHCI_PRESENT_STATE) & + SDHCI_CARD_PRESENT); + if (mmc->cfg->host_caps & MMC_CAP_CD_ACTIVE_HIGH) + return !value; + else + return value; +} + +const struct dm_mmc_ops sdhci_ops = { + .send_cmd = sdhci_send_command, + .set_ios = sdhci_set_ios, + .get_cd = sdhci_get_cd, + .deferred_probe = sdhci_deferred_probe, +#ifdef MMC_SUPPORTS_TUNING + .execute_tuning = sdhci_execute_tuning, +#endif +}; +#else +static const struct mmc_ops sdhci_ops = { + .send_cmd = sdhci_send_command, + .set_ios = sdhci_set_ios, + .init = sdhci_init, +}; +#endif + +int sdhci_setup_cfg(struct mmc_config *cfg, struct sdhci_host *host, + u32 f_max, u32 f_min) +{ + u32 caps, caps_1 = 0; +#if CONFIG_IS_ENABLED(DM_MMC) + u64 dt_caps, dt_caps_mask; + + dt_caps_mask = dev_read_u64_default(host->mmc->dev, + "sdhci-caps-mask", 0); + dt_caps = dev_read_u64_default(host->mmc->dev, + "sdhci-caps", 0); + caps = ~lower_32_bits(dt_caps_mask) & + sdhci_readl(host, SDHCI_CAPABILITIES); + caps |= lower_32_bits(dt_caps); +#else + caps = sdhci_readl(host, SDHCI_CAPABILITIES); +#endif + debug("%s, caps: 0x%x\n", __func__, caps); + +#ifdef CONFIG_MMC_SDHCI_SDMA + if ((caps & SDHCI_CAN_DO_SDMA)) { + host->flags |= USE_SDMA; + } else { + debug("%s: Your controller doesn't support SDMA!!\n", + __func__); + } +#endif +#if CONFIG_IS_ENABLED(MMC_SDHCI_ADMA) + if (!(caps & SDHCI_CAN_DO_ADMA2)) { + printf("%s: Your controller doesn't support SDMA!!\n", + __func__); + return -EINVAL; + } + host->adma_desc_table = sdhci_adma_init(); + host->adma_addr = (dma_addr_t)host->adma_desc_table; + +#ifdef CONFIG_DMA_ADDR_T_64BIT + host->flags |= USE_ADMA64; +#else + host->flags |= USE_ADMA; +#endif +#endif + if (host->quirks & SDHCI_QUIRK_REG32_RW) + host->version = + sdhci_readl(host, SDHCI_HOST_VERSION - 2) >> 16; + else + host->version = sdhci_readw(host, SDHCI_HOST_VERSION); + + cfg->name = host->name; +#ifndef CONFIG_DM_MMC + cfg->ops = &sdhci_ops; +#endif + + /* Check whether the clock multiplier is supported or not */ + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) { +#if CONFIG_IS_ENABLED(DM_MMC) + caps_1 = ~upper_32_bits(dt_caps_mask) & + sdhci_readl(host, SDHCI_CAPABILITIES_1); + caps_1 |= upper_32_bits(dt_caps); +#else + caps_1 = sdhci_readl(host, SDHCI_CAPABILITIES_1); +#endif + debug("%s, caps_1: 0x%x\n", __func__, caps_1); + host->clk_mul = (caps_1 & SDHCI_CLOCK_MUL_MASK) >> + SDHCI_CLOCK_MUL_SHIFT; + } + + if (host->max_clk == 0) { + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) + host->max_clk = (caps & SDHCI_CLOCK_V3_BASE_MASK) >> + SDHCI_CLOCK_BASE_SHIFT; + else + host->max_clk = (caps & SDHCI_CLOCK_BASE_MASK) >> + SDHCI_CLOCK_BASE_SHIFT; + host->max_clk *= 1000000; + if (host->clk_mul) + host->max_clk *= host->clk_mul; + } + if (host->max_clk == 0) { + printf("%s: Hardware doesn't specify base clock frequency\n", + __func__); + return -EINVAL; + } + if (f_max && (f_max < host->max_clk)) + cfg->f_max = f_max; + else + cfg->f_max = host->max_clk; + if (f_min) + cfg->f_min = f_min; + else { + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) + cfg->f_min = cfg->f_max / SDHCI_MAX_DIV_SPEC_300; + else + cfg->f_min = cfg->f_max / SDHCI_MAX_DIV_SPEC_200; + } + cfg->voltages = 0; + if (caps & SDHCI_CAN_VDD_330) + cfg->voltages |= MMC_VDD_32_33 | MMC_VDD_33_34; + if (caps & SDHCI_CAN_VDD_300) + cfg->voltages |= MMC_VDD_29_30 | MMC_VDD_30_31; + if (caps & SDHCI_CAN_VDD_180) + cfg->voltages |= MMC_VDD_165_195; + + if (host->quirks & SDHCI_QUIRK_BROKEN_VOLTAGE) + cfg->voltages |= host->voltages; + + if (caps & SDHCI_CAN_DO_HISPD) + cfg->host_caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz; + + cfg->host_caps |= MMC_MODE_4BIT; + + /* Since Host Controller Version3.0 */ + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) { + if (!(caps & SDHCI_CAN_DO_8BIT)) + cfg->host_caps &= ~MMC_MODE_8BIT; + } + + if (host->quirks & SDHCI_QUIRK_BROKEN_HISPD_MODE) { + cfg->host_caps &= ~MMC_MODE_HS; + cfg->host_caps &= ~MMC_MODE_HS_52MHz; + } + + if (!(cfg->voltages & MMC_VDD_165_195) || + (host->quirks & SDHCI_QUIRK_NO_1_8_V)) + caps_1 &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | + SDHCI_SUPPORT_DDR50); + + if (caps_1 & (SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | + SDHCI_SUPPORT_DDR50)) + cfg->host_caps |= MMC_CAP(UHS_SDR12) | MMC_CAP(UHS_SDR25); + + if (caps_1 & SDHCI_SUPPORT_SDR104) { + cfg->host_caps |= MMC_CAP(UHS_SDR104) | MMC_CAP(UHS_SDR50); + /* + * SD3.0: SDR104 is supported so (for eMMC) the caps2 + * field can be promoted to support HS200. + */ + cfg->host_caps |= MMC_CAP(MMC_HS_200); + } else if (caps_1 & SDHCI_SUPPORT_SDR50) { + cfg->host_caps |= MMC_CAP(UHS_SDR50); + } + + if (caps_1 & SDHCI_SUPPORT_DDR50) + cfg->host_caps |= MMC_CAP(UHS_DDR50); + + if (host->host_caps) + cfg->host_caps |= host->host_caps; + + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + return 0; +} + +#ifdef CONFIG_BLK +int sdhci_bind(struct udevice *dev, struct mmc *mmc, struct mmc_config *cfg) +{ + return mmc_bind(dev, mmc, cfg); +} +#else +int add_sdhci(struct sdhci_host *host, u32 f_max, u32 f_min) +{ + int ret; + + ret = sdhci_setup_cfg(&host->cfg, host, f_max, f_min); + if (ret) + return ret; + + host->mmc = mmc_create(&host->cfg, host); + if (host->mmc == NULL) { + printf("%s: mmc create fail!\n", __func__); + return -ENOMEM; + } + + return 0; +} +#endif diff --git a/roms/u-boot/drivers/mmc/sh_mmcif.c b/roms/u-boot/drivers/mmc/sh_mmcif.c new file mode 100644 index 000000000..830e29cdd --- /dev/null +++ b/roms/u-boot/drivers/mmc/sh_mmcif.c @@ -0,0 +1,751 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MMCIF driver. + * + * Copyright (C) 2011 Renesas Solutions Corp. + */ + +#include <config.h> +#include <common.h> +#include <log.h> +#include <watchdog.h> +#include <command.h> +#include <mmc.h> +#include <clk.h> +#include <dm.h> +#include <malloc.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/compat.h> +#include <linux/io.h> +#include <linux/sizes.h> +#include "sh_mmcif.h" +#include <asm/global_data.h> + +#define DRIVER_NAME "sh_mmcif" + +static int sh_mmcif_intr(void *dev_id) +{ + struct sh_mmcif_host *host = dev_id; + u32 state = 0; + + state = sh_mmcif_read(&host->regs->ce_int); + state &= sh_mmcif_read(&host->regs->ce_int_mask); + + if (state & INT_RBSYE) { + sh_mmcif_write(~(INT_RBSYE | INT_CRSPE), &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MRBSYE, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_CRSPE) { + sh_mmcif_write(~INT_CRSPE, &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MCRSPE, &host->regs->ce_int_mask); + /* one more interrupt (INT_RBSYE) */ + if (sh_mmcif_read(&host->regs->ce_cmd_set) & CMD_SET_RBSY) + return -EAGAIN; + goto end; + } else if (state & INT_BUFREN) { + sh_mmcif_write(~INT_BUFREN, &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MBUFREN, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_BUFWEN) { + sh_mmcif_write(~INT_BUFWEN, &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MBUFWEN, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_CMD12DRE) { + sh_mmcif_write(~(INT_CMD12DRE | INT_CMD12RBE | INT_CMD12CRE | + INT_BUFRE), &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MCMD12DRE, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_BUFRE) { + sh_mmcif_write(~INT_BUFRE, &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MBUFRE, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_DTRANE) { + sh_mmcif_write(~INT_DTRANE, &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MDTRANE, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_CMD12RBE) { + sh_mmcif_write(~(INT_CMD12RBE | INT_CMD12CRE), + &host->regs->ce_int); + sh_mmcif_bitclr(MASK_MCMD12RBE, &host->regs->ce_int_mask); + goto end; + } else if (state & INT_ERR_STS) { + /* err interrupts */ + sh_mmcif_write(~state, &host->regs->ce_int); + sh_mmcif_bitclr(state, &host->regs->ce_int_mask); + goto err; + } else + return -EAGAIN; + +err: + host->sd_error = 1; + debug("%s: int err state = %08x\n", DRIVER_NAME, state); +end: + host->wait_int = 1; + return 0; +} + +static int mmcif_wait_interrupt_flag(struct sh_mmcif_host *host) +{ + int timeout = 10000000; + + while (1) { + timeout--; + if (timeout < 0) { + printf("timeout\n"); + return 0; + } + + if (!sh_mmcif_intr(host)) + break; + + udelay(1); /* 1 usec */ + } + + return 1; /* Return value: NOT 0 = complete waiting */ +} + +static void sh_mmcif_clock_control(struct sh_mmcif_host *host, unsigned int clk) +{ + sh_mmcif_bitclr(CLK_ENABLE, &host->regs->ce_clk_ctrl); + sh_mmcif_bitclr(CLK_CLEAR, &host->regs->ce_clk_ctrl); + + if (!clk) + return; + + if (clk == CLKDEV_EMMC_DATA) + sh_mmcif_bitset(CLK_PCLK, &host->regs->ce_clk_ctrl); + else + sh_mmcif_bitset((fls(DIV_ROUND_UP(host->clk, + clk) - 1) - 1) << 16, + &host->regs->ce_clk_ctrl); + sh_mmcif_bitset(CLK_ENABLE, &host->regs->ce_clk_ctrl); +} + +static void sh_mmcif_sync_reset(struct sh_mmcif_host *host) +{ + u32 tmp; + + tmp = sh_mmcif_read(&host->regs->ce_clk_ctrl) & (CLK_ENABLE | + CLK_CLEAR); + + sh_mmcif_write(SOFT_RST_ON, &host->regs->ce_version); + sh_mmcif_write(SOFT_RST_OFF, &host->regs->ce_version); + sh_mmcif_bitset(tmp | SRSPTO_256 | SRBSYTO_29 | SRWDTO_29 | SCCSTO_29, + &host->regs->ce_clk_ctrl); + /* byte swap on */ + sh_mmcif_bitset(BUF_ACC_ATYP, &host->regs->ce_buf_acc); +} + +static int sh_mmcif_error_manage(struct sh_mmcif_host *host) +{ + u32 state1, state2; + int ret, timeout = 10000000; + + host->sd_error = 0; + host->wait_int = 0; + + state1 = sh_mmcif_read(&host->regs->ce_host_sts1); + state2 = sh_mmcif_read(&host->regs->ce_host_sts2); + debug("%s: ERR HOST_STS1 = %08x\n", \ + DRIVER_NAME, sh_mmcif_read(&host->regs->ce_host_sts1)); + debug("%s: ERR HOST_STS2 = %08x\n", \ + DRIVER_NAME, sh_mmcif_read(&host->regs->ce_host_sts2)); + + if (state1 & STS1_CMDSEQ) { + debug("%s: Forced end of command sequence\n", DRIVER_NAME); + sh_mmcif_bitset(CMD_CTRL_BREAK, &host->regs->ce_cmd_ctrl); + sh_mmcif_bitset(~CMD_CTRL_BREAK, &host->regs->ce_cmd_ctrl); + while (1) { + timeout--; + if (timeout < 0) { + printf(DRIVER_NAME": Forceed end of " \ + "command sequence timeout err\n"); + return -EILSEQ; + } + if (!(sh_mmcif_read(&host->regs->ce_host_sts1) + & STS1_CMDSEQ)) + break; + } + sh_mmcif_sync_reset(host); + return -EILSEQ; + } + + if (state2 & STS2_CRC_ERR) + ret = -EILSEQ; + else if (state2 & STS2_TIMEOUT_ERR) + ret = -ETIMEDOUT; + else + ret = -EILSEQ; + return ret; +} + +static int sh_mmcif_single_read(struct sh_mmcif_host *host, + struct mmc_data *data) +{ + long time; + u32 blocksize, i; + unsigned long *p = (unsigned long *)data->dest; + + if ((unsigned long)p & 0x00000001) { + printf("%s: The data pointer is unaligned.", __func__); + return -EIO; + } + + host->wait_int = 0; + + /* buf read enable */ + sh_mmcif_bitset(MASK_MBUFREN, &host->regs->ce_int_mask); + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + blocksize = (BLOCK_SIZE_MASK & + sh_mmcif_read(&host->regs->ce_block_set)) + 3; + for (i = 0; i < blocksize / 4; i++) + *p++ = sh_mmcif_read(&host->regs->ce_data); + + /* buffer read end */ + sh_mmcif_bitset(MASK_MBUFRE, &host->regs->ce_int_mask); + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + return 0; +} + +static int sh_mmcif_multi_read(struct sh_mmcif_host *host, + struct mmc_data *data) +{ + long time; + u32 blocksize, i, j; + unsigned long *p = (unsigned long *)data->dest; + + if ((unsigned long)p & 0x00000001) { + printf("%s: The data pointer is unaligned.", __func__); + return -EIO; + } + + host->wait_int = 0; + blocksize = BLOCK_SIZE_MASK & sh_mmcif_read(&host->regs->ce_block_set); + for (j = 0; j < data->blocks; j++) { + sh_mmcif_bitset(MASK_MBUFREN, &host->regs->ce_int_mask); + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + for (i = 0; i < blocksize / 4; i++) + *p++ = sh_mmcif_read(&host->regs->ce_data); + + WATCHDOG_RESET(); + } + return 0; +} + +static int sh_mmcif_single_write(struct sh_mmcif_host *host, + struct mmc_data *data) +{ + long time; + u32 blocksize, i; + const unsigned long *p = (unsigned long *)data->dest; + + if ((unsigned long)p & 0x00000001) { + printf("%s: The data pointer is unaligned.", __func__); + return -EIO; + } + + host->wait_int = 0; + sh_mmcif_bitset(MASK_MBUFWEN, &host->regs->ce_int_mask); + + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + blocksize = (BLOCK_SIZE_MASK & + sh_mmcif_read(&host->regs->ce_block_set)) + 3; + for (i = 0; i < blocksize / 4; i++) + sh_mmcif_write(*p++, &host->regs->ce_data); + + /* buffer write end */ + sh_mmcif_bitset(MASK_MDTRANE, &host->regs->ce_int_mask); + + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + return 0; +} + +static int sh_mmcif_multi_write(struct sh_mmcif_host *host, + struct mmc_data *data) +{ + long time; + u32 i, j, blocksize; + const unsigned long *p = (unsigned long *)data->dest; + + if ((unsigned long)p & 0x00000001) { + printf("%s: The data pointer is unaligned.", __func__); + return -EIO; + } + + host->wait_int = 0; + blocksize = BLOCK_SIZE_MASK & sh_mmcif_read(&host->regs->ce_block_set); + for (j = 0; j < data->blocks; j++) { + sh_mmcif_bitset(MASK_MBUFWEN, &host->regs->ce_int_mask); + + time = mmcif_wait_interrupt_flag(host); + + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + host->wait_int = 0; + for (i = 0; i < blocksize / 4; i++) + sh_mmcif_write(*p++, &host->regs->ce_data); + + WATCHDOG_RESET(); + } + return 0; +} + +static void sh_mmcif_get_response(struct sh_mmcif_host *host, + struct mmc_cmd *cmd) +{ + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp3); + cmd->response[1] = sh_mmcif_read(&host->regs->ce_resp2); + cmd->response[2] = sh_mmcif_read(&host->regs->ce_resp1); + cmd->response[3] = sh_mmcif_read(&host->regs->ce_resp0); + debug(" RESP %08x, %08x, %08x, %08x\n", cmd->response[0], + cmd->response[1], cmd->response[2], cmd->response[3]); + } else { + cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp0); + } +} + +static void sh_mmcif_get_cmd12response(struct sh_mmcif_host *host, + struct mmc_cmd *cmd) +{ + cmd->response[0] = sh_mmcif_read(&host->regs->ce_resp_cmd12); +} + +static u32 sh_mmcif_set_cmd(struct sh_mmcif_host *host, + struct mmc_data *data, struct mmc_cmd *cmd) +{ + u32 tmp = 0; + u32 opc = cmd->cmdidx; + + /* Response Type check */ + switch (cmd->resp_type) { + case MMC_RSP_NONE: + tmp |= CMD_SET_RTYP_NO; + break; + case MMC_RSP_R1: + case MMC_RSP_R1b: + case MMC_RSP_R3: + tmp |= CMD_SET_RTYP_6B; + break; + case MMC_RSP_R2: + tmp |= CMD_SET_RTYP_17B; + break; + default: + printf(DRIVER_NAME": Not support type response.\n"); + break; + } + + /* RBSY */ + if (opc == MMC_CMD_SWITCH) + tmp |= CMD_SET_RBSY; + + /* WDAT / DATW */ + if (host->data) { + tmp |= CMD_SET_WDAT; + switch (host->bus_width) { + case MMC_BUS_WIDTH_1: + tmp |= CMD_SET_DATW_1; + break; + case MMC_BUS_WIDTH_4: + tmp |= CMD_SET_DATW_4; + break; + case MMC_BUS_WIDTH_8: + tmp |= CMD_SET_DATW_8; + break; + default: + printf(DRIVER_NAME": Not support bus width.\n"); + break; + } + } + /* DWEN */ + if (opc == MMC_CMD_WRITE_SINGLE_BLOCK || + opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) + tmp |= CMD_SET_DWEN; + /* CMLTE/CMD12EN */ + if (opc == MMC_CMD_READ_MULTIPLE_BLOCK || + opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) { + tmp |= CMD_SET_CMLTE | CMD_SET_CMD12EN; + sh_mmcif_bitset(data->blocks << 16, &host->regs->ce_block_set); + } + /* RIDXC[1:0] check bits */ + if (opc == MMC_CMD_SEND_OP_COND || opc == MMC_CMD_ALL_SEND_CID || + opc == MMC_CMD_SEND_CSD || opc == MMC_CMD_SEND_CID) + tmp |= CMD_SET_RIDXC_BITS; + /* RCRC7C[1:0] check bits */ + if (opc == MMC_CMD_SEND_OP_COND) + tmp |= CMD_SET_CRC7C_BITS; + /* RCRC7C[1:0] internal CRC7 */ + if (opc == MMC_CMD_ALL_SEND_CID || + opc == MMC_CMD_SEND_CSD || opc == MMC_CMD_SEND_CID) + tmp |= CMD_SET_CRC7C_INTERNAL; + + return opc = ((opc << 24) | tmp); +} + +static u32 sh_mmcif_data_trans(struct sh_mmcif_host *host, + struct mmc_data *data, u16 opc) +{ + u32 ret; + + switch (opc) { + case MMC_CMD_READ_MULTIPLE_BLOCK: + ret = sh_mmcif_multi_read(host, data); + break; + case MMC_CMD_WRITE_MULTIPLE_BLOCK: + ret = sh_mmcif_multi_write(host, data); + break; + case MMC_CMD_WRITE_SINGLE_BLOCK: + ret = sh_mmcif_single_write(host, data); + break; + case MMC_CMD_READ_SINGLE_BLOCK: + case MMC_CMD_SEND_EXT_CSD: + ret = sh_mmcif_single_read(host, data); + break; + default: + printf(DRIVER_NAME": NOT SUPPORT CMD = d'%08d\n", opc); + ret = -EINVAL; + break; + } + return ret; +} + +static int sh_mmcif_start_cmd(struct sh_mmcif_host *host, + struct mmc_data *data, struct mmc_cmd *cmd) +{ + long time; + int ret = 0, mask = 0; + u32 opc = cmd->cmdidx; + + if (opc == MMC_CMD_STOP_TRANSMISSION) { + /* MMCIF sends the STOP command automatically */ + if (host->last_cmd == MMC_CMD_READ_MULTIPLE_BLOCK) + sh_mmcif_bitset(MASK_MCMD12DRE, + &host->regs->ce_int_mask); + else + sh_mmcif_bitset(MASK_MCMD12RBE, + &host->regs->ce_int_mask); + + time = mmcif_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_mmcif_error_manage(host); + + sh_mmcif_get_cmd12response(host, cmd); + return 0; + } + if (opc == MMC_CMD_SWITCH) + mask = MASK_MRBSYE; + else + mask = MASK_MCRSPE; + + mask |= MASK_MCMDVIO | MASK_MBUFVIO | MASK_MWDATERR | + MASK_MRDATERR | MASK_MRIDXERR | MASK_MRSPERR | + MASK_MCCSTO | MASK_MCRCSTO | MASK_MWDATTO | + MASK_MRDATTO | MASK_MRBSYTO | MASK_MRSPTO; + + if (host->data) { + sh_mmcif_write(0, &host->regs->ce_block_set); + sh_mmcif_write(data->blocksize, &host->regs->ce_block_set); + } + opc = sh_mmcif_set_cmd(host, data, cmd); + + sh_mmcif_write(INT_START_MAGIC, &host->regs->ce_int); + sh_mmcif_write(mask, &host->regs->ce_int_mask); + + debug("CMD%d ARG:%08x\n", cmd->cmdidx, cmd->cmdarg); + /* set arg */ + sh_mmcif_write(cmd->cmdarg, &host->regs->ce_arg); + host->wait_int = 0; + /* set cmd */ + sh_mmcif_write(opc, &host->regs->ce_cmd_set); + + time = mmcif_wait_interrupt_flag(host); + if (time == 0) + return sh_mmcif_error_manage(host); + + if (host->sd_error) { + switch (cmd->cmdidx) { + case MMC_CMD_ALL_SEND_CID: + case MMC_CMD_SELECT_CARD: + case MMC_CMD_APP_CMD: + ret = -ETIMEDOUT; + break; + default: + printf(DRIVER_NAME": Cmd(d'%d) err\n", cmd->cmdidx); + ret = sh_mmcif_error_manage(host); + break; + } + host->sd_error = 0; + host->wait_int = 0; + return ret; + } + + /* if no response */ + if (!(opc & 0x00C00000)) + return 0; + + if (host->wait_int == 1) { + sh_mmcif_get_response(host, cmd); + host->wait_int = 0; + } + if (host->data) + ret = sh_mmcif_data_trans(host, data, cmd->cmdidx); + host->last_cmd = cmd->cmdidx; + + return ret; +} + +static int sh_mmcif_send_cmd_common(struct sh_mmcif_host *host, + struct mmc_cmd *cmd, struct mmc_data *data) +{ + int ret; + + WATCHDOG_RESET(); + + switch (cmd->cmdidx) { + case MMC_CMD_APP_CMD: + return -ETIMEDOUT; + case MMC_CMD_SEND_EXT_CSD: /* = SD_SEND_IF_COND (8) */ + if (data) + /* ext_csd */ + break; + else + /* send_if_cond cmd (not support) */ + return -ETIMEDOUT; + default: + break; + } + host->sd_error = 0; + host->data = data; + ret = sh_mmcif_start_cmd(host, data, cmd); + host->data = NULL; + + return ret; +} + +static int sh_mmcif_set_ios_common(struct sh_mmcif_host *host, struct mmc *mmc) +{ + if (mmc->clock) + sh_mmcif_clock_control(host, mmc->clock); + + if (mmc->bus_width == 8) + host->bus_width = MMC_BUS_WIDTH_8; + else if (mmc->bus_width == 4) + host->bus_width = MMC_BUS_WIDTH_4; + else + host->bus_width = MMC_BUS_WIDTH_1; + + debug("clock = %d, buswidth = %d\n", mmc->clock, mmc->bus_width); + + return 0; +} + +static int sh_mmcif_initialize_common(struct sh_mmcif_host *host) +{ + sh_mmcif_sync_reset(host); + sh_mmcif_write(MASK_ALL, &host->regs->ce_int_mask); + return 0; +} + +#ifndef CONFIG_DM_MMC +static void *mmc_priv(struct mmc *mmc) +{ + return (void *)mmc->priv; +} + +static int sh_mmcif_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct sh_mmcif_host *host = mmc_priv(mmc); + + return sh_mmcif_send_cmd_common(host, cmd, data); +} + +static int sh_mmcif_set_ios(struct mmc *mmc) +{ + struct sh_mmcif_host *host = mmc_priv(mmc); + + return sh_mmcif_set_ios_common(host, mmc); +} + +static int sh_mmcif_initialize(struct mmc *mmc) +{ + struct sh_mmcif_host *host = mmc_priv(mmc); + + return sh_mmcif_initialize_common(host); +} + +static const struct mmc_ops sh_mmcif_ops = { + .send_cmd = sh_mmcif_send_cmd, + .set_ios = sh_mmcif_set_ios, + .init = sh_mmcif_initialize, +}; + +static struct mmc_config sh_mmcif_cfg = { + .name = DRIVER_NAME, + .ops = &sh_mmcif_ops, + .host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_4BIT | + MMC_MODE_8BIT, + .voltages = MMC_VDD_32_33 | MMC_VDD_33_34, + .b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT, +}; + +int mmcif_mmc_init(void) +{ + struct mmc *mmc; + struct sh_mmcif_host *host = NULL; + + host = malloc(sizeof(struct sh_mmcif_host)); + if (!host) + return -ENOMEM; + memset(host, 0, sizeof(*host)); + + host->regs = (struct sh_mmcif_regs *)CONFIG_SH_MMCIF_ADDR; + host->clk = CONFIG_SH_MMCIF_CLK; + + sh_mmcif_cfg.f_min = MMC_CLK_DIV_MIN(host->clk); + sh_mmcif_cfg.f_max = MMC_CLK_DIV_MAX(host->clk); + + mmc = mmc_create(&sh_mmcif_cfg, host); + if (mmc == NULL) { + free(host); + return -ENOMEM; + } + + return 0; +} + +#else +struct sh_mmcif_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +int sh_mmcif_dm_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct sh_mmcif_host *host = dev_get_priv(dev); + + return sh_mmcif_send_cmd_common(host, cmd, data); +} + +int sh_mmcif_dm_set_ios(struct udevice *dev) +{ + struct sh_mmcif_host *host = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + + return sh_mmcif_set_ios_common(host, mmc); +} + +static const struct dm_mmc_ops sh_mmcif_dm_ops = { + .send_cmd = sh_mmcif_dm_send_cmd, + .set_ios = sh_mmcif_dm_set_ios, +}; + +static int sh_mmcif_dm_bind(struct udevice *dev) +{ + struct sh_mmcif_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static int sh_mmcif_dm_probe(struct udevice *dev) +{ + struct sh_mmcif_plat *plat = dev_get_plat(dev); + struct sh_mmcif_host *host = dev_get_priv(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct clk sh_mmcif_clk; + fdt_addr_t base; + int ret; + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) + return -EINVAL; + + host->regs = (struct sh_mmcif_regs *)devm_ioremap(dev, base, SZ_2K); + if (!host->regs) + return -ENOMEM; + + ret = clk_get_by_index(dev, 0, &sh_mmcif_clk); + if (ret) { + debug("failed to get clock, ret=%d\n", ret); + return ret; + } + + ret = clk_enable(&sh_mmcif_clk); + if (ret) { + debug("failed to enable clock, ret=%d\n", ret); + return ret; + } + + host->clk = clk_set_rate(&sh_mmcif_clk, 97500000); + + plat->cfg.name = dev->name; + plat->cfg.host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS; + + switch (fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), "bus-width", + 1)) { + case 8: + plat->cfg.host_caps |= MMC_MODE_8BIT; + break; + case 4: + plat->cfg.host_caps |= MMC_MODE_4BIT; + break; + case 1: + break; + default: + dev_err(dev, "Invalid \"bus-width\" value\n"); + return -EINVAL; + } + + sh_mmcif_initialize_common(host); + + plat->cfg.voltages = MMC_VDD_165_195 | MMC_VDD_32_33 | MMC_VDD_33_34; + plat->cfg.f_min = MMC_CLK_DIV_MIN(host->clk); + plat->cfg.f_max = MMC_CLK_DIV_MAX(host->clk); + plat->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + upriv->mmc = &plat->mmc; + + return 0; +} + +static const struct udevice_id sh_mmcif_sd_match[] = { + { .compatible = "renesas,sh-mmcif" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(sh_mmcif_mmc) = { + .name = "sh-mmcif", + .id = UCLASS_MMC, + .of_match = sh_mmcif_sd_match, + .bind = sh_mmcif_dm_bind, + .probe = sh_mmcif_dm_probe, + .priv_auto = sizeof(struct sh_mmcif_host), + .plat_auto = sizeof(struct sh_mmcif_plat), + .ops = &sh_mmcif_dm_ops, +}; +#endif diff --git a/roms/u-boot/drivers/mmc/sh_mmcif.h b/roms/u-boot/drivers/mmc/sh_mmcif.h new file mode 100644 index 000000000..66341e51d --- /dev/null +++ b/roms/u-boot/drivers/mmc/sh_mmcif.h @@ -0,0 +1,240 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * MMCIF driver. + * + * Copyright (C) 2011 Renesas Solutions Corp. + */ + +#ifndef _SH_MMCIF_H_ +#define _SH_MMCIF_H_ + +struct sh_mmcif_regs { + unsigned long ce_cmd_set; + unsigned long reserved; + unsigned long ce_arg; + unsigned long ce_arg_cmd12; + unsigned long ce_cmd_ctrl; + unsigned long ce_block_set; + unsigned long ce_clk_ctrl; + unsigned long ce_buf_acc; + unsigned long ce_resp3; + unsigned long ce_resp2; + unsigned long ce_resp1; + unsigned long ce_resp0; + unsigned long ce_resp_cmd12; + unsigned long ce_data; + unsigned long reserved2[2]; + unsigned long ce_int; + unsigned long ce_int_mask; + unsigned long ce_host_sts1; + unsigned long ce_host_sts2; + unsigned long reserved3[11]; + unsigned long ce_version; +}; + +/* CE_CMD_SET */ +#define CMD_MASK 0x3f000000 +#define CMD_SET_RTYP_NO ((0 << 23) | (0 << 22)) +/* R1/R1b/R3/R4/R5 */ +#define CMD_SET_RTYP_6B ((0 << 23) | (1 << 22)) +/* R2 */ +#define CMD_SET_RTYP_17B ((1 << 23) | (0 << 22)) +/* R1b */ +#define CMD_SET_RBSY (1 << 21) +#define CMD_SET_CCSEN (1 << 20) +/* 1: on data, 0: no data */ +#define CMD_SET_WDAT (1 << 19) +/* 1: write to card, 0: read from card */ +#define CMD_SET_DWEN (1 << 18) +/* 1: multi block trans, 0: single */ +#define CMD_SET_CMLTE (1 << 17) +/* 1: CMD12 auto issue */ +#define CMD_SET_CMD12EN (1 << 16) +/* index check */ +#define CMD_SET_RIDXC_INDEX ((0 << 15) | (0 << 14)) +/* check bits check */ +#define CMD_SET_RIDXC_BITS ((0 << 15) | (1 << 14)) +/* no check */ +#define CMD_SET_RIDXC_NO ((1 << 15) | (0 << 14)) +/* 1: CRC7 check*/ +#define CMD_SET_CRC7C ((0 << 13) | (0 << 12)) +/* 1: check bits check*/ +#define CMD_SET_CRC7C_BITS ((0 << 13) | (1 << 12)) +/* 1: internal CRC7 check*/ +#define CMD_SET_CRC7C_INTERNAL ((1 << 13) | (0 << 12)) +/* 1: CRC16 check*/ +#define CMD_SET_CRC16C (1 << 10) +/* 1: not receive CRC status */ +#define CMD_SET_CRCSTE (1 << 8) +/* 1: tran mission bit "Low" */ +#define CMD_SET_TBIT (1 << 7) +/* 1: open/drain */ +#define CMD_SET_OPDM (1 << 6) +#define CMD_SET_CCSH (1 << 5) +/* 1bit */ +#define CMD_SET_DATW_1 ((0 << 1) | (0 << 0)) +/* 4bit */ +#define CMD_SET_DATW_4 ((0 << 1) | (1 << 0)) +/* 8bit */ +#define CMD_SET_DATW_8 ((1 << 1) | (0 << 0)) + +/* CE_CMD_CTRL */ +#define CMD_CTRL_BREAK (1 << 0) + +/* CE_BLOCK_SET */ +#define BLOCK_SIZE_MASK 0x0000ffff + +/* CE_CLK_CTRL */ +#define CLK_ENABLE (1 << 24) +#define CLK_CLEAR ((1 << 19) | (1 << 18) | (1 << 17) | (1 << 16)) +#define CLK_PCLK ((1 << 19) | (1 << 18) | (1 << 17) | (1 << 16)) +/* respons timeout */ +#define SRSPTO_256 ((1 << 13) | (0 << 12)) +/* respons busy timeout */ +#define SRBSYTO_29 ((1 << 11) | (1 << 10) | (1 << 9) | (1 << 8)) +/* read/write timeout */ +#define SRWDTO_29 ((1 << 7) | (1 << 6) | (1 << 5) | (1 << 4)) +/* ccs timeout */ +#define SCCSTO_29 ((1 << 3) | (1 << 2) | (1 << 1) | (1 << 0)) + +/* CE_BUF_ACC */ +#define BUF_ACC_DMAWEN (1 << 25) +#define BUF_ACC_DMAREN (1 << 24) +#define BUF_ACC_BUSW_32 (0 << 17) +#define BUF_ACC_BUSW_16 (1 << 17) +#define BUF_ACC_ATYP (1 << 16) + +/* CE_INT */ +#define INT_CCSDE (1 << 29) +#define INT_CMD12DRE (1 << 26) +#define INT_CMD12RBE (1 << 25) +#define INT_CMD12CRE (1 << 24) +#define INT_DTRANE (1 << 23) +#define INT_BUFRE (1 << 22) +#define INT_BUFWEN (1 << 21) +#define INT_BUFREN (1 << 20) +#define INT_CCSRCV (1 << 19) +#define INT_RBSYE (1 << 17) +#define INT_CRSPE (1 << 16) +#define INT_CMDVIO (1 << 15) +#define INT_BUFVIO (1 << 14) +#define INT_WDATERR (1 << 11) +#define INT_RDATERR (1 << 10) +#define INT_RIDXERR (1 << 9) +#define INT_RSPERR (1 << 8) +#define INT_CCSTO (1 << 5) +#define INT_CRCSTO (1 << 4) +#define INT_WDATTO (1 << 3) +#define INT_RDATTO (1 << 2) +#define INT_RBSYTO (1 << 1) +#define INT_RSPTO (1 << 0) +#define INT_ERR_STS (INT_CMDVIO | INT_BUFVIO | INT_WDATERR | \ + INT_RDATERR | INT_RIDXERR | INT_RSPERR | \ + INT_CCSTO | INT_CRCSTO | INT_WDATTO | \ + INT_RDATTO | INT_RBSYTO | INT_RSPTO) +#define INT_START_MAGIC 0xD80430C0 + +/* CE_INT_MASK */ +#define MASK_ALL 0x00000000 +#define MASK_MCCSDE (1 << 29) +#define MASK_MCMD12DRE (1 << 26) +#define MASK_MCMD12RBE (1 << 25) +#define MASK_MCMD12CRE (1 << 24) +#define MASK_MDTRANE (1 << 23) +#define MASK_MBUFRE (1 << 22) +#define MASK_MBUFWEN (1 << 21) +#define MASK_MBUFREN (1 << 20) +#define MASK_MCCSRCV (1 << 19) +#define MASK_MRBSYE (1 << 17) +#define MASK_MCRSPE (1 << 16) +#define MASK_MCMDVIO (1 << 15) +#define MASK_MBUFVIO (1 << 14) +#define MASK_MWDATERR (1 << 11) +#define MASK_MRDATERR (1 << 10) +#define MASK_MRIDXERR (1 << 9) +#define MASK_MRSPERR (1 << 8) +#define MASK_MCCSTO (1 << 5) +#define MASK_MCRCSTO (1 << 4) +#define MASK_MWDATTO (1 << 3) +#define MASK_MRDATTO (1 << 2) +#define MASK_MRBSYTO (1 << 1) +#define MASK_MRSPTO (1 << 0) + +/* CE_HOST_STS1 */ +#define STS1_CMDSEQ (1 << 31) + +/* CE_HOST_STS2 */ +#define STS2_CRCSTE (1 << 31) +#define STS2_CRC16E (1 << 30) +#define STS2_AC12CRCE (1 << 29) +#define STS2_RSPCRC7E (1 << 28) +#define STS2_CRCSTEBE (1 << 27) +#define STS2_RDATEBE (1 << 26) +#define STS2_AC12REBE (1 << 25) +#define STS2_RSPEBE (1 << 24) +#define STS2_AC12IDXE (1 << 23) +#define STS2_RSPIDXE (1 << 22) +#define STS2_CCSTO (1 << 15) +#define STS2_RDATTO (1 << 14) +#define STS2_DATBSYTO (1 << 13) +#define STS2_CRCSTTO (1 << 12) +#define STS2_AC12BSYTO (1 << 11) +#define STS2_RSPBSYTO (1 << 10) +#define STS2_AC12RSPTO (1 << 9) +#define STS2_RSPTO (1 << 8) + +#define STS2_CRC_ERR (STS2_CRCSTE | STS2_CRC16E | \ + STS2_AC12CRCE | STS2_RSPCRC7E | STS2_CRCSTEBE) +#define STS2_TIMEOUT_ERR (STS2_CCSTO | STS2_RDATTO | \ + STS2_DATBSYTO | STS2_CRCSTTO | \ + STS2_AC12BSYTO | STS2_RSPBSYTO | \ + STS2_AC12RSPTO | STS2_RSPTO) + +/* CE_VERSION */ +#define SOFT_RST_ON (1 << 31) +#define SOFT_RST_OFF (0 << 31) + +#define CLKDEV_EMMC_DATA 52000000 /* 52MHz */ +#ifdef CONFIG_ARCH_RMOBILE +#define MMC_CLK_DIV_MIN(clk) (clk / (1 << 9)) +#define MMC_CLK_DIV_MAX(clk) (clk / (1 << 1)) +#else +#define MMC_CLK_DIV_MIN(clk) (clk / (1 << 8)) +#define MMC_CLK_DIV_MAX(clk) CLKDEV_EMMC_DATA +#endif + +#define MMC_BUS_WIDTH_1 0 +#define MMC_BUS_WIDTH_4 2 +#define MMC_BUS_WIDTH_8 3 + +struct sh_mmcif_host { + struct mmc_data *data; + struct sh_mmcif_regs *regs; + unsigned int clk; + int bus_width; + u16 wait_int; + u16 sd_error; + u8 last_cmd; +}; + +static inline u32 sh_mmcif_read(unsigned long *reg) +{ + return readl(reg); +} + +static inline void sh_mmcif_write(u32 val, unsigned long *reg) +{ + writel(val, reg); +} + +static inline void sh_mmcif_bitset(u32 val, unsigned long *reg) +{ + sh_mmcif_write(val | sh_mmcif_read(reg), reg); +} + +static inline void sh_mmcif_bitclr(u32 val, unsigned long *reg) +{ + sh_mmcif_write(~val & sh_mmcif_read(reg), reg); +} + +#endif /* _SH_MMCIF_H_ */ diff --git a/roms/u-boot/drivers/mmc/sh_sdhi.c b/roms/u-boot/drivers/mmc/sh_sdhi.c new file mode 100644 index 000000000..b2d0fac96 --- /dev/null +++ b/roms/u-boot/drivers/mmc/sh_sdhi.c @@ -0,0 +1,910 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/mmc/sh_sdhi.c + * + * SD/MMC driver for Renesas rmobile ARM SoCs. + * + * Copyright (C) 2011,2013-2017 Renesas Electronics Corporation + * Copyright (C) 2014 Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com> + * Copyright (C) 2008-2009 Renesas Solutions Corp. + */ + +#include <common.h> +#include <log.h> +#include <malloc.h> +#include <mmc.h> +#include <dm.h> +#include <part.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/compat.h> +#include <linux/io.h> +#include <linux/sizes.h> +#include <asm/arch/rmobile.h> +#include <asm/arch/sh_sdhi.h> +#include <asm/global_data.h> +#include <clk.h> + +#define DRIVER_NAME "sh-sdhi" + +struct sh_sdhi_host { + void __iomem *addr; + int ch; + int bus_shift; + unsigned long quirks; + unsigned char wait_int; + unsigned char sd_error; + unsigned char detect_waiting; + unsigned char app_cmd; +}; + +static inline void sh_sdhi_writeq(struct sh_sdhi_host *host, int reg, u64 val) +{ + writeq(val, host->addr + (reg << host->bus_shift)); +} + +static inline u64 sh_sdhi_readq(struct sh_sdhi_host *host, int reg) +{ + return readq(host->addr + (reg << host->bus_shift)); +} + +static inline void sh_sdhi_writew(struct sh_sdhi_host *host, int reg, u16 val) +{ + writew(val, host->addr + (reg << host->bus_shift)); +} + +static inline u16 sh_sdhi_readw(struct sh_sdhi_host *host, int reg) +{ + return readw(host->addr + (reg << host->bus_shift)); +} + +static void sh_sdhi_detect(struct sh_sdhi_host *host) +{ + sh_sdhi_writew(host, SDHI_OPTION, + OPT_BUS_WIDTH_1 | sh_sdhi_readw(host, SDHI_OPTION)); + + host->detect_waiting = 0; +} + +static int sh_sdhi_intr(void *dev_id) +{ + struct sh_sdhi_host *host = dev_id; + int state1 = 0, state2 = 0; + + state1 = sh_sdhi_readw(host, SDHI_INFO1); + state2 = sh_sdhi_readw(host, SDHI_INFO2); + + debug("%s: state1 = %x, state2 = %x\n", __func__, state1, state2); + + /* CARD Insert */ + if (state1 & INFO1_CARD_IN) { + sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_CARD_IN); + if (!host->detect_waiting) { + host->detect_waiting = 1; + sh_sdhi_detect(host); + } + sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END | + INFO1M_ACCESS_END | INFO1M_CARD_IN | + INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN); + return -EAGAIN; + } + /* CARD Removal */ + if (state1 & INFO1_CARD_RE) { + sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_CARD_RE); + if (!host->detect_waiting) { + host->detect_waiting = 1; + sh_sdhi_detect(host); + } + sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END | + INFO1M_ACCESS_END | INFO1M_CARD_RE | + INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN); + sh_sdhi_writew(host, SDHI_SDIO_INFO1_MASK, SDIO_INFO1M_ON); + sh_sdhi_writew(host, SDHI_SDIO_MODE, SDIO_MODE_OFF); + return -EAGAIN; + } + + if (state2 & INFO2_ALL_ERR) { + sh_sdhi_writew(host, SDHI_INFO2, + (unsigned short)~(INFO2_ALL_ERR)); + sh_sdhi_writew(host, SDHI_INFO2_MASK, + INFO2M_ALL_ERR | + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + host->sd_error = 1; + host->wait_int = 1; + return 0; + } + /* Respons End */ + if (state1 & INFO1_RESP_END) { + sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_RESP_END); + sh_sdhi_writew(host, SDHI_INFO1_MASK, + INFO1M_RESP_END | + sh_sdhi_readw(host, SDHI_INFO1_MASK)); + host->wait_int = 1; + return 0; + } + /* SD_BUF Read Enable */ + if (state2 & INFO2_BRE_ENABLE) { + sh_sdhi_writew(host, SDHI_INFO2, ~INFO2_BRE_ENABLE); + sh_sdhi_writew(host, SDHI_INFO2_MASK, + INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ | + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + host->wait_int = 1; + return 0; + } + /* SD_BUF Write Enable */ + if (state2 & INFO2_BWE_ENABLE) { + sh_sdhi_writew(host, SDHI_INFO2, ~INFO2_BWE_ENABLE); + sh_sdhi_writew(host, SDHI_INFO2_MASK, + INFO2_BWE_ENABLE | INFO2M_BUF_ILL_WRITE | + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + host->wait_int = 1; + return 0; + } + /* Access End */ + if (state1 & INFO1_ACCESS_END) { + sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_ACCESS_END); + sh_sdhi_writew(host, SDHI_INFO1_MASK, + INFO1_ACCESS_END | + sh_sdhi_readw(host, SDHI_INFO1_MASK)); + host->wait_int = 1; + return 0; + } + return -EAGAIN; +} + +static int sh_sdhi_wait_interrupt_flag(struct sh_sdhi_host *host) +{ + int timeout = 10000000; + + while (1) { + timeout--; + if (timeout < 0) { + debug(DRIVER_NAME": %s timeout\n", __func__); + return 0; + } + + if (!sh_sdhi_intr(host)) + break; + + udelay(1); /* 1 usec */ + } + + return 1; /* Return value: NOT 0 = complete waiting */ +} + +static int sh_sdhi_clock_control(struct sh_sdhi_host *host, unsigned long clk) +{ + u32 clkdiv, i, timeout; + + if (sh_sdhi_readw(host, SDHI_INFO2) & (1 << 14)) { + printf(DRIVER_NAME": Busy state ! Cannot change the clock\n"); + return -EBUSY; + } + + sh_sdhi_writew(host, SDHI_CLK_CTRL, + ~CLK_ENABLE & sh_sdhi_readw(host, SDHI_CLK_CTRL)); + + if (clk == 0) + return -EIO; + + clkdiv = 0x80; + i = CONFIG_SH_SDHI_FREQ >> (0x8 + 1); + for (; clkdiv && clk >= (i << 1); (clkdiv >>= 1)) + i <<= 1; + + sh_sdhi_writew(host, SDHI_CLK_CTRL, clkdiv); + + timeout = 100000; + /* Waiting for SD Bus busy to be cleared */ + while (timeout--) { + if ((sh_sdhi_readw(host, SDHI_INFO2) & 0x2000)) + break; + } + + if (timeout) + sh_sdhi_writew(host, SDHI_CLK_CTRL, + CLK_ENABLE | sh_sdhi_readw(host, SDHI_CLK_CTRL)); + else + return -EBUSY; + + return 0; +} + +static int sh_sdhi_sync_reset(struct sh_sdhi_host *host) +{ + u32 timeout; + sh_sdhi_writew(host, SDHI_SOFT_RST, SOFT_RST_ON); + sh_sdhi_writew(host, SDHI_SOFT_RST, SOFT_RST_OFF); + sh_sdhi_writew(host, SDHI_CLK_CTRL, + CLK_ENABLE | sh_sdhi_readw(host, SDHI_CLK_CTRL)); + + timeout = 100000; + while (timeout--) { + if (!(sh_sdhi_readw(host, SDHI_INFO2) & INFO2_CBUSY)) + break; + udelay(100); + } + + if (!timeout) + return -EBUSY; + + if (host->quirks & SH_SDHI_QUIRK_16BIT_BUF) + sh_sdhi_writew(host, SDHI_HOST_MODE, 1); + + return 0; +} + +static int sh_sdhi_error_manage(struct sh_sdhi_host *host) +{ + unsigned short e_state1, e_state2; + int ret; + + host->sd_error = 0; + host->wait_int = 0; + + e_state1 = sh_sdhi_readw(host, SDHI_ERR_STS1); + e_state2 = sh_sdhi_readw(host, SDHI_ERR_STS2); + if (e_state2 & ERR_STS2_SYS_ERROR) { + if (e_state2 & ERR_STS2_RES_STOP_TIMEOUT) + ret = -ETIMEDOUT; + else + ret = -EILSEQ; + debug("%s: ERR_STS2 = %04x\n", + DRIVER_NAME, sh_sdhi_readw(host, SDHI_ERR_STS2)); + sh_sdhi_sync_reset(host); + + sh_sdhi_writew(host, SDHI_INFO1_MASK, + INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN); + return ret; + } + if (e_state1 & ERR_STS1_CRC_ERROR || e_state1 & ERR_STS1_CMD_ERROR) + ret = -EILSEQ; + else + ret = -ETIMEDOUT; + + debug("%s: ERR_STS1 = %04x\n", + DRIVER_NAME, sh_sdhi_readw(host, SDHI_ERR_STS1)); + sh_sdhi_sync_reset(host); + sh_sdhi_writew(host, SDHI_INFO1_MASK, + INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN); + return ret; +} + +static int sh_sdhi_single_read(struct sh_sdhi_host *host, struct mmc_data *data) +{ + long time; + unsigned short blocksize, i; + unsigned short *p = (unsigned short *)data->dest; + u64 *q = (u64 *)data->dest; + + if ((unsigned long)p & 0x00000001) { + debug(DRIVER_NAME": %s: The data pointer is unaligned.", + __func__); + return -EIO; + } + + host->wait_int = 0; + sh_sdhi_writew(host, SDHI_INFO2_MASK, + ~(INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ) & + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + sh_sdhi_writew(host, SDHI_INFO1_MASK, + ~INFO1M_ACCESS_END & + sh_sdhi_readw(host, SDHI_INFO1_MASK)); + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + blocksize = sh_sdhi_readw(host, SDHI_SIZE); + if (host->quirks & SH_SDHI_QUIRK_64BIT_BUF) + for (i = 0; i < blocksize / 8; i++) + *q++ = sh_sdhi_readq(host, SDHI_BUF0); + else + for (i = 0; i < blocksize / 2; i++) + *p++ = sh_sdhi_readw(host, SDHI_BUF0); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + return 0; +} + +static int sh_sdhi_multi_read(struct sh_sdhi_host *host, struct mmc_data *data) +{ + long time; + unsigned short blocksize, i, sec; + unsigned short *p = (unsigned short *)data->dest; + u64 *q = (u64 *)data->dest; + + if ((unsigned long)p & 0x00000001) { + debug(DRIVER_NAME": %s: The data pointer is unaligned.", + __func__); + return -EIO; + } + + debug("%s: blocks = %d, blocksize = %d\n", + __func__, data->blocks, data->blocksize); + + host->wait_int = 0; + for (sec = 0; sec < data->blocks; sec++) { + sh_sdhi_writew(host, SDHI_INFO2_MASK, + ~(INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ) & + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + blocksize = sh_sdhi_readw(host, SDHI_SIZE); + if (host->quirks & SH_SDHI_QUIRK_64BIT_BUF) + for (i = 0; i < blocksize / 8; i++) + *q++ = sh_sdhi_readq(host, SDHI_BUF0); + else + for (i = 0; i < blocksize / 2; i++) + *p++ = sh_sdhi_readw(host, SDHI_BUF0); + } + + return 0; +} + +static int sh_sdhi_single_write(struct sh_sdhi_host *host, + struct mmc_data *data) +{ + long time; + unsigned short blocksize, i; + const unsigned short *p = (const unsigned short *)data->src; + const u64 *q = (const u64 *)data->src; + + if ((unsigned long)p & 0x00000001) { + debug(DRIVER_NAME": %s: The data pointer is unaligned.", + __func__); + return -EIO; + } + + debug("%s: blocks = %d, blocksize = %d\n", + __func__, data->blocks, data->blocksize); + + host->wait_int = 0; + sh_sdhi_writew(host, SDHI_INFO2_MASK, + ~(INFO2M_BWE_ENABLE | INFO2M_BUF_ILL_WRITE) & + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + sh_sdhi_writew(host, SDHI_INFO1_MASK, + ~INFO1M_ACCESS_END & + sh_sdhi_readw(host, SDHI_INFO1_MASK)); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + blocksize = sh_sdhi_readw(host, SDHI_SIZE); + if (host->quirks & SH_SDHI_QUIRK_64BIT_BUF) + for (i = 0; i < blocksize / 8; i++) + sh_sdhi_writeq(host, SDHI_BUF0, *q++); + else + for (i = 0; i < blocksize / 2; i++) + sh_sdhi_writew(host, SDHI_BUF0, *p++); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + return 0; +} + +static int sh_sdhi_multi_write(struct sh_sdhi_host *host, struct mmc_data *data) +{ + long time; + unsigned short i, sec, blocksize; + const unsigned short *p = (const unsigned short *)data->src; + const u64 *q = (const u64 *)data->src; + + debug("%s: blocks = %d, blocksize = %d\n", + __func__, data->blocks, data->blocksize); + + host->wait_int = 0; + for (sec = 0; sec < data->blocks; sec++) { + sh_sdhi_writew(host, SDHI_INFO2_MASK, + ~(INFO2M_BWE_ENABLE | INFO2M_BUF_ILL_WRITE) & + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + host->wait_int = 0; + blocksize = sh_sdhi_readw(host, SDHI_SIZE); + if (host->quirks & SH_SDHI_QUIRK_64BIT_BUF) + for (i = 0; i < blocksize / 8; i++) + sh_sdhi_writeq(host, SDHI_BUF0, *q++); + else + for (i = 0; i < blocksize / 2; i++) + sh_sdhi_writew(host, SDHI_BUF0, *p++); + } + + return 0; +} + +static void sh_sdhi_get_response(struct sh_sdhi_host *host, struct mmc_cmd *cmd) +{ + unsigned short i, j, cnt = 1; + unsigned short resp[8]; + + if (cmd->resp_type & MMC_RSP_136) { + cnt = 4; + resp[0] = sh_sdhi_readw(host, SDHI_RSP00); + resp[1] = sh_sdhi_readw(host, SDHI_RSP01); + resp[2] = sh_sdhi_readw(host, SDHI_RSP02); + resp[3] = sh_sdhi_readw(host, SDHI_RSP03); + resp[4] = sh_sdhi_readw(host, SDHI_RSP04); + resp[5] = sh_sdhi_readw(host, SDHI_RSP05); + resp[6] = sh_sdhi_readw(host, SDHI_RSP06); + resp[7] = sh_sdhi_readw(host, SDHI_RSP07); + + /* SDHI REGISTER SPECIFICATION */ + for (i = 7, j = 6; i > 0; i--) { + resp[i] = (resp[i] << 8) & 0xff00; + resp[i] |= (resp[j--] >> 8) & 0x00ff; + } + resp[0] = (resp[0] << 8) & 0xff00; + } else { + resp[0] = sh_sdhi_readw(host, SDHI_RSP00); + resp[1] = sh_sdhi_readw(host, SDHI_RSP01); + } + +#if defined(__BIG_ENDIAN_BITFIELD) + if (cnt == 4) { + cmd->response[0] = (resp[6] << 16) | resp[7]; + cmd->response[1] = (resp[4] << 16) | resp[5]; + cmd->response[2] = (resp[2] << 16) | resp[3]; + cmd->response[3] = (resp[0] << 16) | resp[1]; + } else { + cmd->response[0] = (resp[0] << 16) | resp[1]; + } +#else + if (cnt == 4) { + cmd->response[0] = (resp[7] << 16) | resp[6]; + cmd->response[1] = (resp[5] << 16) | resp[4]; + cmd->response[2] = (resp[3] << 16) | resp[2]; + cmd->response[3] = (resp[1] << 16) | resp[0]; + } else { + cmd->response[0] = (resp[1] << 16) | resp[0]; + } +#endif /* __BIG_ENDIAN_BITFIELD */ +} + +static unsigned short sh_sdhi_set_cmd(struct sh_sdhi_host *host, + struct mmc_data *data, unsigned short opc) +{ + if (host->app_cmd) { + if (!data) + host->app_cmd = 0; + return opc | BIT(6); + } + + switch (opc) { + case MMC_CMD_SWITCH: + return opc | (data ? 0x1c00 : 0x40); + case MMC_CMD_SEND_EXT_CSD: + return opc | (data ? 0x1c00 : 0); + case MMC_CMD_SEND_OP_COND: + return opc | 0x0700; + case MMC_CMD_APP_CMD: + host->app_cmd = 1; + default: + return opc; + } +} + +static unsigned short sh_sdhi_data_trans(struct sh_sdhi_host *host, + struct mmc_data *data, unsigned short opc) +{ + if (host->app_cmd) { + host->app_cmd = 0; + switch (opc) { + case SD_CMD_APP_SEND_SCR: + case SD_CMD_APP_SD_STATUS: + return sh_sdhi_single_read(host, data); + default: + printf(DRIVER_NAME": SD: NOT SUPPORT APP CMD = d'%04d\n", + opc); + return -EINVAL; + } + } else { + switch (opc) { + case MMC_CMD_WRITE_MULTIPLE_BLOCK: + return sh_sdhi_multi_write(host, data); + case MMC_CMD_READ_MULTIPLE_BLOCK: + return sh_sdhi_multi_read(host, data); + case MMC_CMD_WRITE_SINGLE_BLOCK: + return sh_sdhi_single_write(host, data); + case MMC_CMD_READ_SINGLE_BLOCK: + case MMC_CMD_SWITCH: + case MMC_CMD_SEND_EXT_CSD:; + return sh_sdhi_single_read(host, data); + default: + printf(DRIVER_NAME": SD: NOT SUPPORT CMD = d'%04d\n", opc); + return -EINVAL; + } + } +} + +static int sh_sdhi_start_cmd(struct sh_sdhi_host *host, + struct mmc_data *data, struct mmc_cmd *cmd) +{ + long time; + unsigned short shcmd, opc = cmd->cmdidx; + int ret = 0; + unsigned long timeout; + + debug("opc = %d, arg = %x, resp_type = %x\n", + opc, cmd->cmdarg, cmd->resp_type); + + if (opc == MMC_CMD_STOP_TRANSMISSION) { + /* SDHI sends the STOP command automatically by STOP reg */ + sh_sdhi_writew(host, SDHI_INFO1_MASK, ~INFO1M_ACCESS_END & + sh_sdhi_readw(host, SDHI_INFO1_MASK)); + + time = sh_sdhi_wait_interrupt_flag(host); + if (time == 0 || host->sd_error != 0) + return sh_sdhi_error_manage(host); + + sh_sdhi_get_response(host, cmd); + return 0; + } + + if (data) { + if ((opc == MMC_CMD_READ_MULTIPLE_BLOCK) || + opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) { + sh_sdhi_writew(host, SDHI_STOP, STOP_SEC_ENABLE); + sh_sdhi_writew(host, SDHI_SECCNT, data->blocks); + } + sh_sdhi_writew(host, SDHI_SIZE, data->blocksize); + } + + shcmd = sh_sdhi_set_cmd(host, data, opc); + + /* + * U-Boot cannot use interrupt. + * So this flag may not be clear by timing + */ + sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_RESP_END); + + sh_sdhi_writew(host, SDHI_INFO1_MASK, + INFO1M_RESP_END | sh_sdhi_readw(host, SDHI_INFO1_MASK)); + sh_sdhi_writew(host, SDHI_ARG0, + (unsigned short)(cmd->cmdarg & ARG0_MASK)); + sh_sdhi_writew(host, SDHI_ARG1, + (unsigned short)((cmd->cmdarg >> 16) & ARG1_MASK)); + + timeout = 100000; + /* Waiting for SD Bus busy to be cleared */ + while (timeout--) { + if ((sh_sdhi_readw(host, SDHI_INFO2) & 0x2000)) + break; + } + + host->wait_int = 0; + sh_sdhi_writew(host, SDHI_INFO1_MASK, + ~INFO1M_RESP_END & sh_sdhi_readw(host, SDHI_INFO1_MASK)); + sh_sdhi_writew(host, SDHI_INFO2_MASK, + ~(INFO2M_CMD_ERROR | INFO2M_CRC_ERROR | + INFO2M_END_ERROR | INFO2M_TIMEOUT | + INFO2M_RESP_TIMEOUT | INFO2M_ILA) & + sh_sdhi_readw(host, SDHI_INFO2_MASK)); + + sh_sdhi_writew(host, SDHI_CMD, (unsigned short)(shcmd & CMD_MASK)); + time = sh_sdhi_wait_interrupt_flag(host); + if (!time) { + host->app_cmd = 0; + return sh_sdhi_error_manage(host); + } + + if (host->sd_error) { + switch (cmd->cmdidx) { + case MMC_CMD_ALL_SEND_CID: + case MMC_CMD_SELECT_CARD: + case SD_CMD_SEND_IF_COND: + case MMC_CMD_APP_CMD: + ret = -ETIMEDOUT; + break; + default: + debug(DRIVER_NAME": Cmd(d'%d) err\n", opc); + debug(DRIVER_NAME": cmdidx = %d\n", cmd->cmdidx); + ret = sh_sdhi_error_manage(host); + break; + } + host->sd_error = 0; + host->wait_int = 0; + host->app_cmd = 0; + return ret; + } + + if (sh_sdhi_readw(host, SDHI_INFO1) & INFO1_RESP_END) { + host->app_cmd = 0; + return -EINVAL; + } + + if (host->wait_int) { + sh_sdhi_get_response(host, cmd); + host->wait_int = 0; + } + + if (data) + ret = sh_sdhi_data_trans(host, data, opc); + + debug("ret = %d, resp = %08x, %08x, %08x, %08x\n", + ret, cmd->response[0], cmd->response[1], + cmd->response[2], cmd->response[3]); + return ret; +} + +static int sh_sdhi_send_cmd_common(struct sh_sdhi_host *host, + struct mmc_cmd *cmd, struct mmc_data *data) +{ + host->sd_error = 0; + + return sh_sdhi_start_cmd(host, data, cmd); +} + +static int sh_sdhi_set_ios_common(struct sh_sdhi_host *host, struct mmc *mmc) +{ + int ret; + + ret = sh_sdhi_clock_control(host, mmc->clock); + if (ret) + return -EINVAL; + + if (mmc->bus_width == 8) + sh_sdhi_writew(host, SDHI_OPTION, + OPT_BUS_WIDTH_8 | (~OPT_BUS_WIDTH_M & + sh_sdhi_readw(host, SDHI_OPTION))); + else if (mmc->bus_width == 4) + sh_sdhi_writew(host, SDHI_OPTION, + OPT_BUS_WIDTH_4 | (~OPT_BUS_WIDTH_M & + sh_sdhi_readw(host, SDHI_OPTION))); + else + sh_sdhi_writew(host, SDHI_OPTION, + OPT_BUS_WIDTH_1 | (~OPT_BUS_WIDTH_M & + sh_sdhi_readw(host, SDHI_OPTION))); + + debug("clock = %d, buswidth = %d\n", mmc->clock, mmc->bus_width); + + return 0; +} + +static int sh_sdhi_initialize_common(struct sh_sdhi_host *host) +{ + int ret = sh_sdhi_sync_reset(host); + + sh_sdhi_writew(host, SDHI_PORTSEL, USE_1PORT); + +#if defined(__BIG_ENDIAN_BITFIELD) + sh_sdhi_writew(host, SDHI_EXT_SWAP, SET_SWAP); +#endif + + sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END | + INFO1M_ACCESS_END | INFO1M_CARD_RE | + INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN); + + return ret; +} + +#ifndef CONFIG_DM_MMC +static void *mmc_priv(struct mmc *mmc) +{ + return (void *)mmc->priv; +} + +static int sh_sdhi_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct sh_sdhi_host *host = mmc_priv(mmc); + + return sh_sdhi_send_cmd_common(host, cmd, data); +} + +static int sh_sdhi_set_ios(struct mmc *mmc) +{ + struct sh_sdhi_host *host = mmc_priv(mmc); + + return sh_sdhi_set_ios_common(host, mmc); +} + +static int sh_sdhi_initialize(struct mmc *mmc) +{ + struct sh_sdhi_host *host = mmc_priv(mmc); + + return sh_sdhi_initialize_common(host); +} + +static const struct mmc_ops sh_sdhi_ops = { + .send_cmd = sh_sdhi_send_cmd, + .set_ios = sh_sdhi_set_ios, + .init = sh_sdhi_initialize, +}; + +#ifdef CONFIG_RCAR_GEN3 +static struct mmc_config sh_sdhi_cfg = { + .name = DRIVER_NAME, + .ops = &sh_sdhi_ops, + .f_min = CLKDEV_INIT, + .f_max = CLKDEV_HS_DATA, + .voltages = MMC_VDD_165_195 | MMC_VDD_32_33 | MMC_VDD_33_34, + .host_caps = MMC_MODE_4BIT | MMC_MODE_8BIT | MMC_MODE_HS | + MMC_MODE_HS_52MHz, + .part_type = PART_TYPE_DOS, + .b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT, +}; +#else +static struct mmc_config sh_sdhi_cfg = { + .name = DRIVER_NAME, + .ops = &sh_sdhi_ops, + .f_min = CLKDEV_INIT, + .f_max = CLKDEV_HS_DATA, + .voltages = MMC_VDD_32_33 | MMC_VDD_33_34, + .host_caps = MMC_MODE_4BIT | MMC_MODE_HS, + .part_type = PART_TYPE_DOS, + .b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT, +}; +#endif + +int sh_sdhi_init(unsigned long addr, int ch, unsigned long quirks) +{ + int ret = 0; + struct mmc *mmc; + struct sh_sdhi_host *host = NULL; + + if (ch >= CONFIG_SYS_SH_SDHI_NR_CHANNEL) + return -ENODEV; + + host = malloc(sizeof(struct sh_sdhi_host)); + if (!host) + return -ENOMEM; + + mmc = mmc_create(&sh_sdhi_cfg, host); + if (!mmc) { + ret = -1; + goto error; + } + + host->ch = ch; + host->addr = (void __iomem *)addr; + host->quirks = quirks; + + if (host->quirks & SH_SDHI_QUIRK_64BIT_BUF) + host->bus_shift = 2; + else if (host->quirks & SH_SDHI_QUIRK_16BIT_BUF) + host->bus_shift = 1; + + return ret; +error: + free(host); + return ret; +} + +#else + +struct sh_sdhi_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +int sh_sdhi_dm_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct sh_sdhi_host *host = dev_get_priv(dev); + + return sh_sdhi_send_cmd_common(host, cmd, data); +} + +int sh_sdhi_dm_set_ios(struct udevice *dev) +{ + struct sh_sdhi_host *host = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + + return sh_sdhi_set_ios_common(host, mmc); +} + +static const struct dm_mmc_ops sh_sdhi_dm_ops = { + .send_cmd = sh_sdhi_dm_send_cmd, + .set_ios = sh_sdhi_dm_set_ios, +}; + +static int sh_sdhi_dm_bind(struct udevice *dev) +{ + struct sh_sdhi_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static int sh_sdhi_dm_probe(struct udevice *dev) +{ + struct sh_sdhi_plat *plat = dev_get_plat(dev); + struct sh_sdhi_host *host = dev_get_priv(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct clk sh_sdhi_clk; + const u32 quirks = dev_get_driver_data(dev); + fdt_addr_t base; + int ret; + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) + return -EINVAL; + + host->addr = devm_ioremap(dev, base, SZ_2K); + if (!host->addr) + return -ENOMEM; + + ret = clk_get_by_index(dev, 0, &sh_sdhi_clk); + if (ret) { + debug("failed to get clock, ret=%d\n", ret); + return ret; + } + + ret = clk_enable(&sh_sdhi_clk); + if (ret) { + debug("failed to enable clock, ret=%d\n", ret); + return ret; + } + + host->quirks = quirks; + + if (host->quirks & SH_SDHI_QUIRK_64BIT_BUF) + host->bus_shift = 2; + else if (host->quirks & SH_SDHI_QUIRK_16BIT_BUF) + host->bus_shift = 1; + + plat->cfg.name = dev->name; + plat->cfg.host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS; + + switch (fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), "bus-width", + 1)) { + case 8: + plat->cfg.host_caps |= MMC_MODE_8BIT; + break; + case 4: + plat->cfg.host_caps |= MMC_MODE_4BIT; + break; + case 1: + break; + default: + dev_err(dev, "Invalid \"bus-width\" value\n"); + return -EINVAL; + } + + sh_sdhi_initialize_common(host); + + plat->cfg.voltages = MMC_VDD_165_195 | MMC_VDD_32_33 | MMC_VDD_33_34; + plat->cfg.f_min = CLKDEV_INIT; + plat->cfg.f_max = CLKDEV_HS_DATA; + plat->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + upriv->mmc = &plat->mmc; + + return 0; +} + +static const struct udevice_id sh_sdhi_sd_match[] = { + { .compatible = "renesas,sdhi-r8a7795", .data = SH_SDHI_QUIRK_64BIT_BUF }, + { .compatible = "renesas,sdhi-r8a7796", .data = SH_SDHI_QUIRK_64BIT_BUF }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(sh_sdhi_mmc) = { + .name = "sh-sdhi-mmc", + .id = UCLASS_MMC, + .of_match = sh_sdhi_sd_match, + .bind = sh_sdhi_dm_bind, + .probe = sh_sdhi_dm_probe, + .priv_auto = sizeof(struct sh_sdhi_host), + .plat_auto = sizeof(struct sh_sdhi_plat), + .ops = &sh_sdhi_dm_ops, +}; +#endif diff --git a/roms/u-boot/drivers/mmc/snps_dw_mmc.c b/roms/u-boot/drivers/mmc/snps_dw_mmc.c new file mode 100644 index 000000000..50a8805e7 --- /dev/null +++ b/roms/u-boot/drivers/mmc/snps_dw_mmc.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Synopsys DesignWare Multimedia Card Interface driver + * extensions used in various Synopsys ARC devboards. + * + * Copyright (C) 2019 Synopsys + * Author: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com> + */ + +#include <common.h> +#include <clk.h> +#include <dm.h> +#include <dwmmc.h> +#include <errno.h> +#include <fdtdec.h> +#include <dm/device_compat.h> +#include <linux/libfdt.h> +#include <linux/err.h> +#include <malloc.h> + +#define CLOCK_MIN 400000 /* 400 kHz */ +#define FIFO_MIN 8 +#define FIFO_MAX 4096 + +struct snps_dwmci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct snps_dwmci_priv_data { + struct dwmci_host host; + u32 f_max; +}; + +static int snps_dwmmc_clk_setup(struct udevice *dev) +{ + struct snps_dwmci_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + + struct clk clk_ciu, clk_biu; + int ret; + + ret = clk_get_by_name(dev, "ciu", &clk_ciu); + if (ret) + goto clk_err; + + ret = clk_enable(&clk_ciu); + if (ret && ret != -ENOSYS && ret != -ENOTSUPP) + goto clk_err_ciu; + + host->bus_hz = clk_get_rate(&clk_ciu); + if (host->bus_hz < CLOCK_MIN) { + ret = -EINVAL; + goto clk_err_ciu_dis; + } + + ret = clk_get_by_name(dev, "biu", &clk_biu); + if (ret) + goto clk_err_ciu_dis; + + ret = clk_enable(&clk_biu); + if (ret && ret != -ENOSYS && ret != -ENOTSUPP) + goto clk_err_biu; + + return 0; + +clk_err_biu: + clk_free(&clk_biu); +clk_err_ciu_dis: + clk_disable(&clk_ciu); +clk_err_ciu: + clk_free(&clk_ciu); +clk_err: + dev_err(dev, "failed to setup clocks, ret %d\n", ret); + + return ret; +} + +static int snps_dwmmc_of_to_plat(struct udevice *dev) +{ + struct snps_dwmci_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + u32 fifo_depth; + int ret; + + host->ioaddr = dev_read_addr_ptr(dev); + + /* + * If fifo-depth is unset don't set fifoth_val - we will try to + * auto detect it. + */ + ret = dev_read_u32(dev, "fifo-depth", &fifo_depth); + if (!ret) { + if (fifo_depth < FIFO_MIN || fifo_depth > FIFO_MAX) + return -EINVAL; + + host->fifoth_val = MSIZE(0x2) | + RX_WMARK(fifo_depth / 2 - 1) | + TX_WMARK(fifo_depth / 2); + } + + host->buswidth = dev_read_u32_default(dev, "bus-width", 4); + if (host->buswidth != 1 && host->buswidth != 4 && host->buswidth != 8) + return -EINVAL; + + /* + * If max-frequency is unset don't set priv->f_max - we will use + * host->bus_hz in probe() instead. + */ + ret = dev_read_u32(dev, "max-frequency", &priv->f_max); + if (!ret && priv->f_max < CLOCK_MIN) + return -EINVAL; + + host->fifo_mode = dev_read_bool(dev, "fifo-mode"); + host->name = dev->name; + host->dev_index = 0; + host->priv = priv; + + return 0; +} + +int snps_dwmmc_getcd(struct udevice *dev) +{ + struct snps_dwmci_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + + return !(dwmci_readl(host, DWMCI_CDETECT) & 1); +} + +struct dm_mmc_ops snps_dwmci_dm_ops; + +static int snps_dwmmc_probe(struct udevice *dev) +{ +#ifdef CONFIG_BLK + struct snps_dwmci_plat *plat = dev_get_plat(dev); +#endif + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct snps_dwmci_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + unsigned int clock_max; + int ret; + + /* Extend generic 'dm_dwmci_ops' with our 'getcd' implementation */ + memcpy(&snps_dwmci_dm_ops, &dm_dwmci_ops, sizeof(struct dm_mmc_ops)); + snps_dwmci_dm_ops.get_cd = snps_dwmmc_getcd; + + ret = snps_dwmmc_clk_setup(dev); + if (ret) + return ret; + + if (!priv->f_max) + clock_max = host->bus_hz; + else + clock_max = min_t(unsigned int, host->bus_hz, priv->f_max); + +#ifdef CONFIG_BLK + dwmci_setup_cfg(&plat->cfg, host, clock_max, CLOCK_MIN); + host->mmc = &plat->mmc; +#else + ret = add_dwmci(host, clock_max, CLOCK_MIN); + if (ret) + return ret; +#endif + host->mmc->priv = &priv->host; + upriv->mmc = host->mmc; + host->mmc->dev = dev; + + return dwmci_probe(dev); +} + +static int snps_dwmmc_bind(struct udevice *dev) +{ +#ifdef CONFIG_BLK + struct snps_dwmci_plat *plat = dev_get_plat(dev); + int ret; + + ret = dwmci_bind(dev, &plat->mmc, &plat->cfg); + if (ret) + return ret; +#endif + + return 0; +} + +static const struct udevice_id snps_dwmmc_ids[] = { + { .compatible = "snps,dw-mshc" }, + { } +}; + +U_BOOT_DRIVER(snps_dwmmc_drv) = { + .name = "snps_dw_mmc", + .id = UCLASS_MMC, + .of_match = snps_dwmmc_ids, + .of_to_plat = snps_dwmmc_of_to_plat, + .ops = &snps_dwmci_dm_ops, + .bind = snps_dwmmc_bind, + .probe = snps_dwmmc_probe, + .priv_auto = sizeof(struct snps_dwmci_priv_data), + .plat_auto = sizeof(struct snps_dwmci_plat), +}; diff --git a/roms/u-boot/drivers/mmc/socfpga_dw_mmc.c b/roms/u-boot/drivers/mmc/socfpga_dw_mmc.c new file mode 100644 index 000000000..be3d8bfb3 --- /dev/null +++ b/roms/u-boot/drivers/mmc/socfpga_dw_mmc.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2013 Altera Corporation <www.altera.com> + */ + +#include <common.h> +#include <log.h> +#include <asm/arch/clock_manager.h> +#include <asm/arch/secure_reg_helper.h> +#include <asm/arch/system_manager.h> +#include <clk.h> +#include <dm.h> +#include <dwmmc.h> +#include <errno.h> +#include <fdtdec.h> +#include <asm/global_data.h> +#include <dm/device_compat.h> +#include <linux/intel-smc.h> +#include <linux/libfdt.h> +#include <linux/err.h> +#include <malloc.h> +#include <reset.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct socfpga_dwmci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +/* socfpga implmentation specific driver private data */ +struct dwmci_socfpga_priv_data { + struct dwmci_host host; + unsigned int drvsel; + unsigned int smplsel; +}; + +static void socfpga_dwmci_reset(struct udevice *dev) +{ + struct reset_ctl_bulk reset_bulk; + int ret; + + ret = reset_get_bulk(dev, &reset_bulk); + if (ret) { + dev_warn(dev, "Can't get reset: %d\n", ret); + return; + } + + reset_deassert_bulk(&reset_bulk); +} + +static int socfpga_dwmci_clksel(struct dwmci_host *host) +{ + struct dwmci_socfpga_priv_data *priv = host->priv; + u32 sdmmc_mask = ((priv->smplsel & 0x7) << SYSMGR_SDMMC_SMPLSEL_SHIFT) | + ((priv->drvsel & 0x7) << SYSMGR_SDMMC_DRVSEL_SHIFT); + + /* Disable SDMMC clock. */ + clrbits_le32(socfpga_get_clkmgr_addr() + CLKMGR_PERPLL_EN, + CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK); + + debug("%s: drvsel %d smplsel %d\n", __func__, + priv->drvsel, priv->smplsel); + +#if !defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_ATF) + int ret; + + ret = socfpga_secure_reg_write32(SOCFPGA_SECURE_REG_SYSMGR_SOC64_SDMMC, + sdmmc_mask); + if (ret) { + printf("DWMMC: Failed to set clksel via SMC call"); + return ret; + } +#else + writel(sdmmc_mask, socfpga_get_sysmgr_addr() + SYSMGR_SDMMC); + + debug("%s: SYSMGR_SDMMCGRP_CTRL_REG = 0x%x\n", __func__, + readl(socfpga_get_sysmgr_addr() + SYSMGR_SDMMC)); +#endif + + /* Enable SDMMC clock */ + setbits_le32(socfpga_get_clkmgr_addr() + CLKMGR_PERPLL_EN, + CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK); + + return 0; +} + +static int socfpga_dwmmc_get_clk_rate(struct udevice *dev) +{ + struct dwmci_socfpga_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; +#if CONFIG_IS_ENABLED(CLK) + struct clk clk; + int ret; + + ret = clk_get_by_index(dev, 1, &clk); + if (ret) + return ret; + + host->bus_hz = clk_get_rate(&clk); + + clk_free(&clk); +#else + /* Fixed clock divide by 4 which due to the SDMMC wrapper */ + host->bus_hz = cm_get_mmc_controller_clk_hz(); +#endif + if (host->bus_hz == 0) { + printf("DWMMC: MMC clock is zero!"); + return -EINVAL; + } + + return 0; +} + +static int socfpga_dwmmc_of_to_plat(struct udevice *dev) +{ + struct dwmci_socfpga_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + int fifo_depth; + + fifo_depth = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), + "fifo-depth", 0); + if (fifo_depth < 0) { + printf("DWMMC: Can't get FIFO depth\n"); + return -EINVAL; + } + + host->name = dev->name; + host->ioaddr = dev_read_addr_ptr(dev); + host->buswidth = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), + "bus-width", 4); + host->clksel = socfpga_dwmci_clksel; + + /* + * TODO(sjg@chromium.org): Remove the need for this hack. + * We only have one dwmmc block on gen5 SoCFPGA. + */ + host->dev_index = 0; + host->fifoth_val = MSIZE(0x2) | + RX_WMARK(fifo_depth / 2 - 1) | TX_WMARK(fifo_depth / 2); + priv->drvsel = fdtdec_get_uint(gd->fdt_blob, dev_of_offset(dev), + "drvsel", 3); + priv->smplsel = fdtdec_get_uint(gd->fdt_blob, dev_of_offset(dev), + "smplsel", 0); + host->priv = priv; + + host->fifo_mode = dev_read_bool(dev, "fifo-mode"); + + return 0; +} + +static int socfpga_dwmmc_probe(struct udevice *dev) +{ +#ifdef CONFIG_BLK + struct socfpga_dwmci_plat *plat = dev_get_plat(dev); +#endif + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct dwmci_socfpga_priv_data *priv = dev_get_priv(dev); + struct dwmci_host *host = &priv->host; + int ret; + + ret = socfpga_dwmmc_get_clk_rate(dev); + if (ret) + return ret; + + socfpga_dwmci_reset(dev); + +#ifdef CONFIG_BLK + dwmci_setup_cfg(&plat->cfg, host, host->bus_hz, 400000); + host->mmc = &plat->mmc; +#else + + ret = add_dwmci(host, host->bus_hz, 400000); + if (ret) + return ret; +#endif + host->mmc->priv = &priv->host; + upriv->mmc = host->mmc; + host->mmc->dev = dev; + + return dwmci_probe(dev); +} + +static int socfpga_dwmmc_bind(struct udevice *dev) +{ +#ifdef CONFIG_BLK + struct socfpga_dwmci_plat *plat = dev_get_plat(dev); + int ret; + + ret = dwmci_bind(dev, &plat->mmc, &plat->cfg); + if (ret) + return ret; +#endif + + return 0; +} + +static const struct udevice_id socfpga_dwmmc_ids[] = { + { .compatible = "altr,socfpga-dw-mshc" }, + { } +}; + +U_BOOT_DRIVER(socfpga_dwmmc_drv) = { + .name = "socfpga_dwmmc", + .id = UCLASS_MMC, + .of_match = socfpga_dwmmc_ids, + .of_to_plat = socfpga_dwmmc_of_to_plat, + .ops = &dm_dwmci_ops, + .bind = socfpga_dwmmc_bind, + .probe = socfpga_dwmmc_probe, + .priv_auto = sizeof(struct dwmci_socfpga_priv_data), + .plat_auto = sizeof(struct socfpga_dwmci_plat), +}; diff --git a/roms/u-boot/drivers/mmc/sti_sdhci.c b/roms/u-boot/drivers/mmc/sti_sdhci.c new file mode 100644 index 000000000..6194768fd --- /dev/null +++ b/roms/u-boot/drivers/mmc/sti_sdhci.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Patrice Chotard, <patrice.chotard@foss.st.com> for STMicroelectronics. + */ + +#include <common.h> +#include <dm.h> +#include <log.h> +#include <mmc.h> +#include <reset-uclass.h> +#include <sdhci.h> +#include <asm/arch/sdhci.h> +#include <asm/global_data.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct sti_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; + struct reset_ctl reset; + int instance; +}; + +/** + * sti_mmc_core_config: configure the Arasan HC + * @dev : udevice + * + * Description: this function is to configure the Arasan MMC HC. + * This should be called when the system starts in case of, on the SoC, + * it is needed to configure the host controller. + * This happens on some SoCs, i.e. StiH410, where the MMC0 inside the flashSS + * needs to be configured as MMC 4.5 to have full capabilities. + * W/o these settings the SDHCI could configure and use the embedded controller + * with limited features. + */ +static int sti_mmc_core_config(struct udevice *dev) +{ + struct sti_sdhci_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + int ret; + + /* only MMC1 has a reset line */ + if (plat->instance) { + ret = reset_deassert(&plat->reset); + if (ret < 0) { + pr_err("MMC1 deassert failed: %d", ret); + return ret; + } + } + + writel(STI_FLASHSS_MMC_CORE_CONFIG_1, + host->ioaddr + FLASHSS_MMC_CORE_CONFIG_1); + + if (plat->instance) { + writel(STI_FLASHSS_MMC_CORE_CONFIG2, + host->ioaddr + FLASHSS_MMC_CORE_CONFIG_2); + writel(STI_FLASHSS_MMC_CORE_CONFIG3, + host->ioaddr + FLASHSS_MMC_CORE_CONFIG_3); + } else { + writel(STI_FLASHSS_SDCARD_CORE_CONFIG2, + host->ioaddr + FLASHSS_MMC_CORE_CONFIG_2); + writel(STI_FLASHSS_SDCARD_CORE_CONFIG3, + host->ioaddr + FLASHSS_MMC_CORE_CONFIG_3); + } + writel(STI_FLASHSS_MMC_CORE_CONFIG4, + host->ioaddr + FLASHSS_MMC_CORE_CONFIG_4); + + return 0; +} + +static int sti_sdhci_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct sti_sdhci_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + int ret; + + /* + * identify current mmc instance, mmc1 has a reset, not mmc0 + * MMC0 is wired to the SD slot, + * MMC1 is wired on the high speed connector + */ + ret = reset_get_by_index(dev, 0, &plat->reset); + if (!ret) + plat->instance = 1; + else + if (ret == -ENOENT) + plat->instance = 0; + else + return ret; + + ret = sti_mmc_core_config(dev); + if (ret) + return ret; + + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | + SDHCI_QUIRK_32BIT_DMA_ADDR | + SDHCI_QUIRK_NO_HISPD_BIT; + + host->host_caps = MMC_MODE_DDR_52MHz; + host->mmc = &plat->mmc; + host->mmc->dev = dev; + host->mmc->priv = host; + + ret = sdhci_setup_cfg(&plat->cfg, host, 50000000, 400000); + if (ret) + return ret; + + upriv->mmc = host->mmc; + + return sdhci_probe(dev); +} + +static int sti_sdhci_of_to_plat(struct udevice *dev) +{ + struct sdhci_host *host = dev_get_priv(dev); + + host->name = strdup(dev->name); + host->ioaddr = dev_read_addr_ptr(dev); + + host->bus_width = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev), + "bus-width", 4); + + return 0; +} + +static int sti_sdhci_bind(struct udevice *dev) +{ + struct sti_sdhci_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id sti_sdhci_ids[] = { + { .compatible = "st,sdhci" }, + { } +}; + +U_BOOT_DRIVER(sti_mmc) = { + .name = "sti_sdhci", + .id = UCLASS_MMC, + .of_match = sti_sdhci_ids, + .bind = sti_sdhci_bind, + .ops = &sdhci_ops, + .of_to_plat = sti_sdhci_of_to_plat, + .probe = sti_sdhci_probe, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct sti_sdhci_plat), +}; diff --git a/roms/u-boot/drivers/mmc/stm32_sdmmc2.c b/roms/u-boot/drivers/mmc/stm32_sdmmc2.c new file mode 100644 index 000000000..a3cdf7bcd --- /dev/null +++ b/roms/u-boot/drivers/mmc/stm32_sdmmc2.c @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Patrice Chotard, <patrice.chotard@foss.st.com> for STMicroelectronics. + */ + +#define LOG_CATEGORY UCLASS_MMC + +#include <common.h> +#include <clk.h> +#include <cpu_func.h> +#include <dm.h> +#include <fdtdec.h> +#include <log.h> +#include <malloc.h> +#include <asm/bitops.h> +#include <asm/cache.h> +#include <dm/device_compat.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/libfdt.h> +#include <mmc.h> +#include <reset.h> +#include <asm/io.h> +#include <asm/gpio.h> +#include <linux/iopoll.h> +#include <watchdog.h> + +struct stm32_sdmmc2_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct stm32_sdmmc2_priv { + fdt_addr_t base; + struct clk clk; + struct reset_ctl reset_ctl; + struct gpio_desc cd_gpio; + u32 clk_reg_msk; + u32 pwr_reg_msk; +}; + +struct stm32_sdmmc2_ctx { + u32 cache_start; + u32 cache_end; + u32 data_length; + bool dpsm_abort; +}; + +/* SDMMC REGISTERS OFFSET */ +#define SDMMC_POWER 0x00 /* SDMMC power control */ +#define SDMMC_CLKCR 0x04 /* SDMMC clock control */ +#define SDMMC_ARG 0x08 /* SDMMC argument */ +#define SDMMC_CMD 0x0C /* SDMMC command */ +#define SDMMC_RESP1 0x14 /* SDMMC response 1 */ +#define SDMMC_RESP2 0x18 /* SDMMC response 2 */ +#define SDMMC_RESP3 0x1C /* SDMMC response 3 */ +#define SDMMC_RESP4 0x20 /* SDMMC response 4 */ +#define SDMMC_DTIMER 0x24 /* SDMMC data timer */ +#define SDMMC_DLEN 0x28 /* SDMMC data length */ +#define SDMMC_DCTRL 0x2C /* SDMMC data control */ +#define SDMMC_DCOUNT 0x30 /* SDMMC data counter */ +#define SDMMC_STA 0x34 /* SDMMC status */ +#define SDMMC_ICR 0x38 /* SDMMC interrupt clear */ +#define SDMMC_MASK 0x3C /* SDMMC mask */ +#define SDMMC_IDMACTRL 0x50 /* SDMMC DMA control */ +#define SDMMC_IDMABASE0 0x58 /* SDMMC DMA buffer 0 base address */ + +/* SDMMC_POWER register */ +#define SDMMC_POWER_PWRCTRL_MASK GENMASK(1, 0) +#define SDMMC_POWER_PWRCTRL_OFF 0 +#define SDMMC_POWER_PWRCTRL_CYCLE 2 +#define SDMMC_POWER_PWRCTRL_ON 3 +#define SDMMC_POWER_VSWITCH BIT(2) +#define SDMMC_POWER_VSWITCHEN BIT(3) +#define SDMMC_POWER_DIRPOL BIT(4) + +/* SDMMC_CLKCR register */ +#define SDMMC_CLKCR_CLKDIV GENMASK(9, 0) +#define SDMMC_CLKCR_CLKDIV_MAX SDMMC_CLKCR_CLKDIV +#define SDMMC_CLKCR_PWRSAV BIT(12) +#define SDMMC_CLKCR_WIDBUS_4 BIT(14) +#define SDMMC_CLKCR_WIDBUS_8 BIT(15) +#define SDMMC_CLKCR_NEGEDGE BIT(16) +#define SDMMC_CLKCR_HWFC_EN BIT(17) +#define SDMMC_CLKCR_DDR BIT(18) +#define SDMMC_CLKCR_BUSSPEED BIT(19) +#define SDMMC_CLKCR_SELCLKRX_MASK GENMASK(21, 20) +#define SDMMC_CLKCR_SELCLKRX_CK 0 +#define SDMMC_CLKCR_SELCLKRX_CKIN BIT(20) +#define SDMMC_CLKCR_SELCLKRX_FBCK BIT(21) + +/* SDMMC_CMD register */ +#define SDMMC_CMD_CMDINDEX GENMASK(5, 0) +#define SDMMC_CMD_CMDTRANS BIT(6) +#define SDMMC_CMD_CMDSTOP BIT(7) +#define SDMMC_CMD_WAITRESP GENMASK(9, 8) +#define SDMMC_CMD_WAITRESP_0 BIT(8) +#define SDMMC_CMD_WAITRESP_1 BIT(9) +#define SDMMC_CMD_WAITINT BIT(10) +#define SDMMC_CMD_WAITPEND BIT(11) +#define SDMMC_CMD_CPSMEN BIT(12) +#define SDMMC_CMD_DTHOLD BIT(13) +#define SDMMC_CMD_BOOTMODE BIT(14) +#define SDMMC_CMD_BOOTEN BIT(15) +#define SDMMC_CMD_CMDSUSPEND BIT(16) + +/* SDMMC_DCTRL register */ +#define SDMMC_DCTRL_DTEN BIT(0) +#define SDMMC_DCTRL_DTDIR BIT(1) +#define SDMMC_DCTRL_DTMODE GENMASK(3, 2) +#define SDMMC_DCTRL_DBLOCKSIZE GENMASK(7, 4) +#define SDMMC_DCTRL_DBLOCKSIZE_SHIFT 4 +#define SDMMC_DCTRL_RWSTART BIT(8) +#define SDMMC_DCTRL_RWSTOP BIT(9) +#define SDMMC_DCTRL_RWMOD BIT(10) +#define SDMMC_DCTRL_SDMMCEN BIT(11) +#define SDMMC_DCTRL_BOOTACKEN BIT(12) +#define SDMMC_DCTRL_FIFORST BIT(13) + +/* SDMMC_STA register */ +#define SDMMC_STA_CCRCFAIL BIT(0) +#define SDMMC_STA_DCRCFAIL BIT(1) +#define SDMMC_STA_CTIMEOUT BIT(2) +#define SDMMC_STA_DTIMEOUT BIT(3) +#define SDMMC_STA_TXUNDERR BIT(4) +#define SDMMC_STA_RXOVERR BIT(5) +#define SDMMC_STA_CMDREND BIT(6) +#define SDMMC_STA_CMDSENT BIT(7) +#define SDMMC_STA_DATAEND BIT(8) +#define SDMMC_STA_DHOLD BIT(9) +#define SDMMC_STA_DBCKEND BIT(10) +#define SDMMC_STA_DABORT BIT(11) +#define SDMMC_STA_DPSMACT BIT(12) +#define SDMMC_STA_CPSMACT BIT(13) +#define SDMMC_STA_TXFIFOHE BIT(14) +#define SDMMC_STA_RXFIFOHF BIT(15) +#define SDMMC_STA_TXFIFOF BIT(16) +#define SDMMC_STA_RXFIFOF BIT(17) +#define SDMMC_STA_TXFIFOE BIT(18) +#define SDMMC_STA_RXFIFOE BIT(19) +#define SDMMC_STA_BUSYD0 BIT(20) +#define SDMMC_STA_BUSYD0END BIT(21) +#define SDMMC_STA_SDMMCIT BIT(22) +#define SDMMC_STA_ACKFAIL BIT(23) +#define SDMMC_STA_ACKTIMEOUT BIT(24) +#define SDMMC_STA_VSWEND BIT(25) +#define SDMMC_STA_CKSTOP BIT(26) +#define SDMMC_STA_IDMATE BIT(27) +#define SDMMC_STA_IDMABTC BIT(28) + +/* SDMMC_ICR register */ +#define SDMMC_ICR_CCRCFAILC BIT(0) +#define SDMMC_ICR_DCRCFAILC BIT(1) +#define SDMMC_ICR_CTIMEOUTC BIT(2) +#define SDMMC_ICR_DTIMEOUTC BIT(3) +#define SDMMC_ICR_TXUNDERRC BIT(4) +#define SDMMC_ICR_RXOVERRC BIT(5) +#define SDMMC_ICR_CMDRENDC BIT(6) +#define SDMMC_ICR_CMDSENTC BIT(7) +#define SDMMC_ICR_DATAENDC BIT(8) +#define SDMMC_ICR_DHOLDC BIT(9) +#define SDMMC_ICR_DBCKENDC BIT(10) +#define SDMMC_ICR_DABORTC BIT(11) +#define SDMMC_ICR_BUSYD0ENDC BIT(21) +#define SDMMC_ICR_SDMMCITC BIT(22) +#define SDMMC_ICR_ACKFAILC BIT(23) +#define SDMMC_ICR_ACKTIMEOUTC BIT(24) +#define SDMMC_ICR_VSWENDC BIT(25) +#define SDMMC_ICR_CKSTOPC BIT(26) +#define SDMMC_ICR_IDMATEC BIT(27) +#define SDMMC_ICR_IDMABTCC BIT(28) +#define SDMMC_ICR_STATIC_FLAGS ((GENMASK(28, 21)) | (GENMASK(11, 0))) + +/* SDMMC_MASK register */ +#define SDMMC_MASK_CCRCFAILIE BIT(0) +#define SDMMC_MASK_DCRCFAILIE BIT(1) +#define SDMMC_MASK_CTIMEOUTIE BIT(2) +#define SDMMC_MASK_DTIMEOUTIE BIT(3) +#define SDMMC_MASK_TXUNDERRIE BIT(4) +#define SDMMC_MASK_RXOVERRIE BIT(5) +#define SDMMC_MASK_CMDRENDIE BIT(6) +#define SDMMC_MASK_CMDSENTIE BIT(7) +#define SDMMC_MASK_DATAENDIE BIT(8) +#define SDMMC_MASK_DHOLDIE BIT(9) +#define SDMMC_MASK_DBCKENDIE BIT(10) +#define SDMMC_MASK_DABORTIE BIT(11) +#define SDMMC_MASK_TXFIFOHEIE BIT(14) +#define SDMMC_MASK_RXFIFOHFIE BIT(15) +#define SDMMC_MASK_RXFIFOFIE BIT(17) +#define SDMMC_MASK_TXFIFOEIE BIT(18) +#define SDMMC_MASK_BUSYD0ENDIE BIT(21) +#define SDMMC_MASK_SDMMCITIE BIT(22) +#define SDMMC_MASK_ACKFAILIE BIT(23) +#define SDMMC_MASK_ACKTIMEOUTIE BIT(24) +#define SDMMC_MASK_VSWENDIE BIT(25) +#define SDMMC_MASK_CKSTOPIE BIT(26) +#define SDMMC_MASK_IDMABTCIE BIT(28) + +/* SDMMC_IDMACTRL register */ +#define SDMMC_IDMACTRL_IDMAEN BIT(0) + +#define SDMMC_CMD_TIMEOUT 0xFFFFFFFF +#define SDMMC_BUSYD0END_TIMEOUT_US 2000000 + +static void stm32_sdmmc2_start_data(struct udevice *dev, + struct mmc_data *data, + struct stm32_sdmmc2_ctx *ctx) +{ + struct stm32_sdmmc2_priv *priv = dev_get_priv(dev); + u32 data_ctrl, idmabase0; + + /* Configure the SDMMC DPSM (Data Path State Machine) */ + data_ctrl = (__ilog2(data->blocksize) << + SDMMC_DCTRL_DBLOCKSIZE_SHIFT) & + SDMMC_DCTRL_DBLOCKSIZE; + + if (data->flags & MMC_DATA_READ) { + data_ctrl |= SDMMC_DCTRL_DTDIR; + idmabase0 = (u32)data->dest; + } else { + idmabase0 = (u32)data->src; + } + + /* Set the SDMMC DataLength value */ + writel(ctx->data_length, priv->base + SDMMC_DLEN); + + /* Write to SDMMC DCTRL */ + writel(data_ctrl, priv->base + SDMMC_DCTRL); + + /* Cache align */ + ctx->cache_start = rounddown(idmabase0, ARCH_DMA_MINALIGN); + ctx->cache_end = roundup(idmabase0 + ctx->data_length, + ARCH_DMA_MINALIGN); + + /* + * Flush data cache before DMA start (clean and invalidate) + * Clean also needed for read + * Avoid issue on buffer not cached-aligned + */ + flush_dcache_range(ctx->cache_start, ctx->cache_end); + + /* Enable internal DMA */ + writel(idmabase0, priv->base + SDMMC_IDMABASE0); + writel(SDMMC_IDMACTRL_IDMAEN, priv->base + SDMMC_IDMACTRL); +} + +static void stm32_sdmmc2_start_cmd(struct udevice *dev, + struct mmc_cmd *cmd, u32 cmd_param, + struct stm32_sdmmc2_ctx *ctx) +{ + struct stm32_sdmmc2_priv *priv = dev_get_priv(dev); + u32 timeout = 0; + + if (readl(priv->base + SDMMC_CMD) & SDMMC_CMD_CPSMEN) + writel(0, priv->base + SDMMC_CMD); + + cmd_param |= cmd->cmdidx | SDMMC_CMD_CPSMEN; + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) + cmd_param |= SDMMC_CMD_WAITRESP; + else if (cmd->resp_type & MMC_RSP_CRC) + cmd_param |= SDMMC_CMD_WAITRESP_0; + else + cmd_param |= SDMMC_CMD_WAITRESP_1; + } + + /* + * SDMMC_DTIME must be set in two case: + * - on data transfert. + * - on busy request. + * If not done or too short, the dtimeout flag occurs and DPSM stays + * enabled/busy and waits for abort (stop transmission cmd). + * Next data command is not possible whereas DPSM is activated. + */ + if (ctx->data_length) { + timeout = SDMMC_CMD_TIMEOUT; + } else { + writel(0, priv->base + SDMMC_DCTRL); + + if (cmd->resp_type & MMC_RSP_BUSY) + timeout = SDMMC_CMD_TIMEOUT; + } + + /* Set the SDMMC Data TimeOut value */ + writel(timeout, priv->base + SDMMC_DTIMER); + + /* Clear flags */ + writel(SDMMC_ICR_STATIC_FLAGS, priv->base + SDMMC_ICR); + + /* Set SDMMC argument value */ + writel(cmd->cmdarg, priv->base + SDMMC_ARG); + + /* Set SDMMC command parameters */ + writel(cmd_param, priv->base + SDMMC_CMD); +} + +static int stm32_sdmmc2_end_cmd(struct udevice *dev, + struct mmc_cmd *cmd, + struct stm32_sdmmc2_ctx *ctx) +{ + struct stm32_sdmmc2_priv *priv = dev_get_priv(dev); + u32 mask = SDMMC_STA_CTIMEOUT; + u32 status; + int ret; + + if (cmd->resp_type & MMC_RSP_PRESENT) { + mask |= SDMMC_STA_CMDREND; + if (cmd->resp_type & MMC_RSP_CRC) + mask |= SDMMC_STA_CCRCFAIL; + } else { + mask |= SDMMC_STA_CMDSENT; + } + + /* Polling status register */ + ret = readl_poll_timeout(priv->base + SDMMC_STA, status, status & mask, + 10000); + + if (ret < 0) { + dev_dbg(dev, "timeout reading SDMMC_STA register\n"); + ctx->dpsm_abort = true; + return ret; + } + + /* Check status */ + if (status & SDMMC_STA_CTIMEOUT) { + dev_dbg(dev, "error SDMMC_STA_CTIMEOUT (0x%x) for cmd %d\n", + status, cmd->cmdidx); + ctx->dpsm_abort = true; + return -ETIMEDOUT; + } + + if (status & SDMMC_STA_CCRCFAIL && cmd->resp_type & MMC_RSP_CRC) { + dev_dbg(dev, "error SDMMC_STA_CCRCFAIL (0x%x) for cmd %d\n", + status, cmd->cmdidx); + ctx->dpsm_abort = true; + return -EILSEQ; + } + + if (status & SDMMC_STA_CMDREND && cmd->resp_type & MMC_RSP_PRESENT) { + cmd->response[0] = readl(priv->base + SDMMC_RESP1); + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[1] = readl(priv->base + SDMMC_RESP2); + cmd->response[2] = readl(priv->base + SDMMC_RESP3); + cmd->response[3] = readl(priv->base + SDMMC_RESP4); + } + + /* Wait for BUSYD0END flag if busy status is detected */ + if (cmd->resp_type & MMC_RSP_BUSY && + status & SDMMC_STA_BUSYD0) { + mask = SDMMC_STA_DTIMEOUT | SDMMC_STA_BUSYD0END; + + /* Polling status register */ + ret = readl_poll_timeout(priv->base + SDMMC_STA, + status, status & mask, + SDMMC_BUSYD0END_TIMEOUT_US); + + if (ret < 0) { + dev_dbg(dev, "timeout reading SDMMC_STA\n"); + ctx->dpsm_abort = true; + return ret; + } + + if (status & SDMMC_STA_DTIMEOUT) { + dev_dbg(dev, + "error SDMMC_STA_DTIMEOUT (0x%x)\n", + status); + ctx->dpsm_abort = true; + return -ETIMEDOUT; + } + } + } + + return 0; +} + +static int stm32_sdmmc2_end_data(struct udevice *dev, + struct mmc_cmd *cmd, + struct mmc_data *data, + struct stm32_sdmmc2_ctx *ctx) +{ + struct stm32_sdmmc2_priv *priv = dev_get_priv(dev); + u32 mask = SDMMC_STA_DCRCFAIL | SDMMC_STA_DTIMEOUT | + SDMMC_STA_IDMATE | SDMMC_STA_DATAEND; + u32 status; + + if (data->flags & MMC_DATA_READ) + mask |= SDMMC_STA_RXOVERR; + else + mask |= SDMMC_STA_TXUNDERR; + + status = readl(priv->base + SDMMC_STA); + while (!(status & mask)) + status = readl(priv->base + SDMMC_STA); + + /* + * Need invalidate the dcache again to avoid any + * cache-refill during the DMA operations (pre-fetching) + */ + if (data->flags & MMC_DATA_READ) + invalidate_dcache_range(ctx->cache_start, ctx->cache_end); + + if (status & SDMMC_STA_DCRCFAIL) { + dev_dbg(dev, "error SDMMC_STA_DCRCFAIL (0x%x) for cmd %d\n", + status, cmd->cmdidx); + if (readl(priv->base + SDMMC_DCOUNT)) + ctx->dpsm_abort = true; + return -EILSEQ; + } + + if (status & SDMMC_STA_DTIMEOUT) { + dev_dbg(dev, "error SDMMC_STA_DTIMEOUT (0x%x) for cmd %d\n", + status, cmd->cmdidx); + ctx->dpsm_abort = true; + return -ETIMEDOUT; + } + + if (status & SDMMC_STA_TXUNDERR) { + dev_dbg(dev, "error SDMMC_STA_TXUNDERR (0x%x) for cmd %d\n", + status, cmd->cmdidx); + ctx->dpsm_abort = true; + return -EIO; + } + + if (status & SDMMC_STA_RXOVERR) { + dev_dbg(dev, "error SDMMC_STA_RXOVERR (0x%x) for cmd %d\n", + status, cmd->cmdidx); + ctx->dpsm_abort = true; + return -EIO; + } + + if (status & SDMMC_STA_IDMATE) { + dev_dbg(dev, "error SDMMC_STA_IDMATE (0x%x) for cmd %d\n", + status, cmd->cmdidx); + ctx->dpsm_abort = true; + return -EIO; + } + + return 0; +} + +static int stm32_sdmmc2_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct stm32_sdmmc2_priv *priv = dev_get_priv(dev); + struct stm32_sdmmc2_ctx ctx; + u32 cmdat = data ? SDMMC_CMD_CMDTRANS : 0; + int ret, retry = 3; + + WATCHDOG_RESET(); + +retry_cmd: + ctx.data_length = 0; + ctx.dpsm_abort = false; + + if (data) { + ctx.data_length = data->blocks * data->blocksize; + stm32_sdmmc2_start_data(dev, data, &ctx); + } + + stm32_sdmmc2_start_cmd(dev, cmd, cmdat, &ctx); + + dev_dbg(dev, "send cmd %d data: 0x%x @ 0x%x\n", + cmd->cmdidx, data ? ctx.data_length : 0, (unsigned int)data); + + ret = stm32_sdmmc2_end_cmd(dev, cmd, &ctx); + + if (data && !ret) + ret = stm32_sdmmc2_end_data(dev, cmd, data, &ctx); + + /* Clear flags */ + writel(SDMMC_ICR_STATIC_FLAGS, priv->base + SDMMC_ICR); + if (data) + writel(0x0, priv->base + SDMMC_IDMACTRL); + + /* + * To stop Data Path State Machine, a stop_transmission command + * shall be send on cmd or data errors. + */ + if (ctx.dpsm_abort && (cmd->cmdidx != MMC_CMD_STOP_TRANSMISSION)) { + struct mmc_cmd stop_cmd; + + stop_cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; + stop_cmd.cmdarg = 0; + stop_cmd.resp_type = MMC_RSP_R1b; + + dev_dbg(dev, "send STOP command to abort dpsm treatments\n"); + + ctx.data_length = 0; + + stm32_sdmmc2_start_cmd(dev, &stop_cmd, + SDMMC_CMD_CMDSTOP, &ctx); + stm32_sdmmc2_end_cmd(dev, &stop_cmd, &ctx); + + writel(SDMMC_ICR_STATIC_FLAGS, priv->base + SDMMC_ICR); + } + + if ((ret != -ETIMEDOUT) && (ret != 0) && retry) { + dev_err(dev, "cmd %d failed, retrying ...\n", cmd->cmdidx); + retry--; + goto retry_cmd; + } + + dev_dbg(dev, "end for CMD %d, ret = %d\n", cmd->cmdidx, ret); + + return ret; +} + +/* + * Reset the SDMMC with the RCC.SDMMCxRST register bit. + * This will reset the SDMMC to the reset state and the CPSM and DPSM + * to the Idle state. SDMMC is disabled, Signals Hiz. + */ +static void stm32_sdmmc2_reset(struct stm32_sdmmc2_priv *priv) +{ + /* Reset */ + reset_assert(&priv->reset_ctl); + udelay(2); + reset_deassert(&priv->reset_ctl); + + /* init the needed SDMMC register after reset */ + writel(priv->pwr_reg_msk, priv->base + SDMMC_POWER); +} + +/* + * Set the SDMMC in power-cycle state. + * This will make that the SDMMC_D[7:0], + * SDMMC_CMD and SDMMC_CK are driven low, to prevent the card from being + * supplied through the signal lines. + */ +static void stm32_sdmmc2_pwrcycle(struct stm32_sdmmc2_priv *priv) +{ + if ((readl(priv->base + SDMMC_POWER) & SDMMC_POWER_PWRCTRL_MASK) == + SDMMC_POWER_PWRCTRL_CYCLE) + return; + + stm32_sdmmc2_reset(priv); +} + +/* + * set the SDMMC state Power-on: the card is clocked + * manage the SDMMC state control: + * Reset => Power-Cycle => Power-Off => Power + * PWRCTRL=10 PWCTRL=00 PWCTRL=11 + */ +static void stm32_sdmmc2_pwron(struct stm32_sdmmc2_priv *priv) +{ + u32 pwrctrl = + readl(priv->base + SDMMC_POWER) & SDMMC_POWER_PWRCTRL_MASK; + + if (pwrctrl == SDMMC_POWER_PWRCTRL_ON) + return; + + /* warning: same PWRCTRL value after reset and for power-off state + * it is the reset state here = the only managed by the driver + */ + if (pwrctrl == SDMMC_POWER_PWRCTRL_OFF) { + writel(SDMMC_POWER_PWRCTRL_CYCLE | priv->pwr_reg_msk, + priv->base + SDMMC_POWER); + } + + /* + * the remaining case is SDMMC_POWER_PWRCTRL_CYCLE + * switch to Power-Off state: SDMCC disable, signals drive 1 + */ + writel(SDMMC_POWER_PWRCTRL_OFF | priv->pwr_reg_msk, + priv->base + SDMMC_POWER); + + /* After the 1ms delay set the SDMMC to power-on */ + mdelay(1); + writel(SDMMC_POWER_PWRCTRL_ON | priv->pwr_reg_msk, + priv->base + SDMMC_POWER); + + /* during the first 74 SDMMC_CK cycles the SDMMC is still disabled. */ +} + +#define IS_RISING_EDGE(reg) (reg & SDMMC_CLKCR_NEGEDGE ? 0 : 1) +static int stm32_sdmmc2_set_ios(struct udevice *dev) +{ + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct stm32_sdmmc2_priv *priv = dev_get_priv(dev); + u32 desired = mmc->clock; + u32 sys_clock = clk_get_rate(&priv->clk); + u32 clk = 0; + + dev_dbg(dev, "bus_with = %d, clock = %d\n", + mmc->bus_width, mmc->clock); + + if (mmc->clk_disable) + stm32_sdmmc2_pwrcycle(priv); + else + stm32_sdmmc2_pwron(priv); + + /* + * clk_div = 0 => command and data generated on SDMMCCLK falling edge + * clk_div > 0 and NEGEDGE = 0 => command and data generated on + * SDMMCCLK rising edge + * clk_div > 0 and NEGEDGE = 1 => command and data generated on + * SDMMCCLK falling edge + */ + if (desired && ((sys_clock > desired) || + IS_RISING_EDGE(priv->clk_reg_msk))) { + clk = DIV_ROUND_UP(sys_clock, 2 * desired); + if (clk > SDMMC_CLKCR_CLKDIV_MAX) + clk = SDMMC_CLKCR_CLKDIV_MAX; + } + + if (mmc->bus_width == 4) + clk |= SDMMC_CLKCR_WIDBUS_4; + if (mmc->bus_width == 8) + clk |= SDMMC_CLKCR_WIDBUS_8; + + writel(clk | priv->clk_reg_msk | SDMMC_CLKCR_HWFC_EN, + priv->base + SDMMC_CLKCR); + + return 0; +} + +static int stm32_sdmmc2_getcd(struct udevice *dev) +{ + struct stm32_sdmmc2_priv *priv = dev_get_priv(dev); + + dev_dbg(dev, "%s called\n", __func__); + + if (dm_gpio_is_valid(&priv->cd_gpio)) + return dm_gpio_get_value(&priv->cd_gpio); + + return 1; +} + +static int stm32_sdmmc2_host_power_cycle(struct udevice *dev) +{ + struct stm32_sdmmc2_priv *priv = dev_get_priv(dev); + + writel(SDMMC_POWER_PWRCTRL_CYCLE | priv->pwr_reg_msk, + priv->base + SDMMC_POWER); + + return 0; +} + +static const struct dm_mmc_ops stm32_sdmmc2_ops = { + .send_cmd = stm32_sdmmc2_send_cmd, + .set_ios = stm32_sdmmc2_set_ios, + .get_cd = stm32_sdmmc2_getcd, + .host_power_cycle = stm32_sdmmc2_host_power_cycle, +}; + +static int stm32_sdmmc2_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct stm32_sdmmc2_plat *plat = dev_get_plat(dev); + struct stm32_sdmmc2_priv *priv = dev_get_priv(dev); + struct mmc_config *cfg = &plat->cfg; + int ret; + + priv->base = dev_read_addr(dev); + if (priv->base == FDT_ADDR_T_NONE) + return -EINVAL; + + if (dev_read_bool(dev, "st,neg-edge")) + priv->clk_reg_msk |= SDMMC_CLKCR_NEGEDGE; + if (dev_read_bool(dev, "st,sig-dir")) + priv->pwr_reg_msk |= SDMMC_POWER_DIRPOL; + if (dev_read_bool(dev, "st,use-ckin")) + priv->clk_reg_msk |= SDMMC_CLKCR_SELCLKRX_CKIN; + + ret = clk_get_by_index(dev, 0, &priv->clk); + if (ret) + return ret; + + ret = clk_enable(&priv->clk); + if (ret) + goto clk_free; + + ret = reset_get_by_index(dev, 0, &priv->reset_ctl); + if (ret) + goto clk_disable; + + gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio, + GPIOD_IS_IN); + + cfg->f_min = 400000; + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + cfg->name = "STM32 SD/MMC"; + + cfg->host_caps = 0; + cfg->f_max = 52000000; + mmc_of_parse(dev, cfg); + + upriv->mmc = &plat->mmc; + + /* SDMMC init */ + stm32_sdmmc2_reset(priv); + return 0; + +clk_disable: + clk_disable(&priv->clk); +clk_free: + clk_free(&priv->clk); + + return ret; +} + +static int stm32_sdmmc_bind(struct udevice *dev) +{ + struct stm32_sdmmc2_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id stm32_sdmmc2_ids[] = { + { .compatible = "st,stm32-sdmmc2" }, + { } +}; + +U_BOOT_DRIVER(stm32_sdmmc2) = { + .name = "stm32_sdmmc2", + .id = UCLASS_MMC, + .of_match = stm32_sdmmc2_ids, + .ops = &stm32_sdmmc2_ops, + .probe = stm32_sdmmc2_probe, + .bind = stm32_sdmmc_bind, + .priv_auto = sizeof(struct stm32_sdmmc2_priv), + .plat_auto = sizeof(struct stm32_sdmmc2_plat), +}; diff --git a/roms/u-boot/drivers/mmc/sunxi_mmc.c b/roms/u-boot/drivers/mmc/sunxi_mmc.c new file mode 100644 index 000000000..3503ccdb2 --- /dev/null +++ b/roms/u-boot/drivers/mmc/sunxi_mmc.c @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2007-2011 + * Allwinner Technology Co., Ltd. <www.allwinnertech.com> + * Aaron <leafy.myeh@allwinnertech.com> + * + * MMC driver for allwinner sunxi platform. + */ + +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <mmc.h> +#include <clk.h> +#include <reset.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/cpu.h> +#include <asm/arch/gpio.h> +#include <asm/arch/mmc.h> +#include <asm-generic/gpio.h> +#include <linux/delay.h> + +struct sunxi_mmc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct sunxi_mmc_priv { + unsigned mmc_no; + uint32_t *mclkreg; + unsigned fatal_err; + struct gpio_desc cd_gpio; /* Change Detect GPIO */ + int cd_inverted; /* Inverted Card Detect */ + struct sunxi_mmc *reg; + struct mmc_config cfg; +}; + +#if !CONFIG_IS_ENABLED(DM_MMC) +/* support 4 mmc hosts */ +struct sunxi_mmc_priv mmc_host[4]; + +static int sunxi_mmc_getcd_gpio(int sdc_no) +{ + switch (sdc_no) { + case 0: return sunxi_name_to_gpio(CONFIG_MMC0_CD_PIN); + case 1: return sunxi_name_to_gpio(CONFIG_MMC1_CD_PIN); + case 2: return sunxi_name_to_gpio(CONFIG_MMC2_CD_PIN); + case 3: return sunxi_name_to_gpio(CONFIG_MMC3_CD_PIN); + } + return -EINVAL; +} + +static int mmc_resource_init(int sdc_no) +{ + struct sunxi_mmc_priv *priv = &mmc_host[sdc_no]; + struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + int cd_pin, ret = 0; + + debug("init mmc %d resource\n", sdc_no); + + switch (sdc_no) { + case 0: + priv->reg = (struct sunxi_mmc *)SUNXI_MMC0_BASE; + priv->mclkreg = &ccm->sd0_clk_cfg; + break; + case 1: + priv->reg = (struct sunxi_mmc *)SUNXI_MMC1_BASE; + priv->mclkreg = &ccm->sd1_clk_cfg; + break; + case 2: + priv->reg = (struct sunxi_mmc *)SUNXI_MMC2_BASE; + priv->mclkreg = &ccm->sd2_clk_cfg; + break; +#ifdef SUNXI_MMC3_BASE + case 3: + priv->reg = (struct sunxi_mmc *)SUNXI_MMC3_BASE; + priv->mclkreg = &ccm->sd3_clk_cfg; + break; +#endif + default: + printf("Wrong mmc number %d\n", sdc_no); + return -1; + } + priv->mmc_no = sdc_no; + + cd_pin = sunxi_mmc_getcd_gpio(sdc_no); + if (cd_pin >= 0) { + ret = gpio_request(cd_pin, "mmc_cd"); + if (!ret) { + sunxi_gpio_set_pull(cd_pin, SUNXI_GPIO_PULL_UP); + ret = gpio_direction_input(cd_pin); + } + } + + return ret; +} +#endif + +static int mmc_set_mod_clk(struct sunxi_mmc_priv *priv, unsigned int hz) +{ + unsigned int pll, pll_hz, div, n, oclk_dly, sclk_dly; + bool new_mode = true; + bool calibrate = false; + u32 val = 0; + + if (!IS_ENABLED(CONFIG_MMC_SUNXI_HAS_NEW_MODE)) + new_mode = false; + + /* A83T support new mode only on eMMC */ + if (IS_ENABLED(CONFIG_MACH_SUN8I_A83T) && priv->mmc_no != 2) + new_mode = false; + +#if defined(CONFIG_MACH_SUN50I) || defined(CONFIG_SUN50I_GEN_H6) + calibrate = true; +#endif + + if (hz <= 24000000) { + pll = CCM_MMC_CTRL_OSCM24; + pll_hz = 24000000; + } else { +#ifdef CONFIG_MACH_SUN9I + pll = CCM_MMC_CTRL_PLL_PERIPH0; + pll_hz = clock_get_pll4_periph0(); +#elif defined(CONFIG_SUN50I_GEN_H6) + pll = CCM_MMC_CTRL_PLL6X2; + pll_hz = clock_get_pll6() * 2; +#else + pll = CCM_MMC_CTRL_PLL6; + pll_hz = clock_get_pll6(); +#endif + } + + div = pll_hz / hz; + if (pll_hz % hz) + div++; + + n = 0; + while (div > 16) { + n++; + div = (div + 1) / 2; + } + + if (n > 3) { + printf("mmc %u error cannot set clock to %u\n", priv->mmc_no, + hz); + return -1; + } + + /* determine delays */ + if (hz <= 400000) { + oclk_dly = 0; + sclk_dly = 0; + } else if (hz <= 25000000) { + oclk_dly = 0; + sclk_dly = 5; +#ifdef CONFIG_MACH_SUN9I + } else if (hz <= 52000000) { + oclk_dly = 5; + sclk_dly = 4; + } else { + /* hz > 52000000 */ + oclk_dly = 2; + sclk_dly = 4; +#else + } else if (hz <= 52000000) { + oclk_dly = 3; + sclk_dly = 4; + } else { + /* hz > 52000000 */ + oclk_dly = 1; + sclk_dly = 4; +#endif + } + + if (new_mode) { +#ifdef CONFIG_MMC_SUNXI_HAS_NEW_MODE +#ifdef CONFIG_MMC_SUNXI_HAS_MODE_SWITCH + val = CCM_MMC_CTRL_MODE_SEL_NEW; +#endif + setbits_le32(&priv->reg->ntsr, SUNXI_MMC_NTSR_MODE_SEL_NEW); +#endif + } else if (!calibrate) { + /* + * Use hardcoded delay values if controller doesn't support + * calibration + */ + val = CCM_MMC_CTRL_OCLK_DLY(oclk_dly) | + CCM_MMC_CTRL_SCLK_DLY(sclk_dly); + } + + writel(CCM_MMC_CTRL_ENABLE| pll | CCM_MMC_CTRL_N(n) | + CCM_MMC_CTRL_M(div) | val, priv->mclkreg); + + debug("mmc %u set mod-clk req %u parent %u n %u m %u rate %u\n", + priv->mmc_no, hz, pll_hz, 1u << n, div, pll_hz / (1u << n) / div); + + return 0; +} + +static int mmc_update_clk(struct sunxi_mmc_priv *priv) +{ + unsigned int cmd; + unsigned timeout_msecs = 2000; + unsigned long start = get_timer(0); + + cmd = SUNXI_MMC_CMD_START | + SUNXI_MMC_CMD_UPCLK_ONLY | + SUNXI_MMC_CMD_WAIT_PRE_OVER; + + writel(cmd, &priv->reg->cmd); + while (readl(&priv->reg->cmd) & SUNXI_MMC_CMD_START) { + if (get_timer(start) > timeout_msecs) + return -1; + } + + /* clock update sets various irq status bits, clear these */ + writel(readl(&priv->reg->rint), &priv->reg->rint); + + return 0; +} + +static int mmc_config_clock(struct sunxi_mmc_priv *priv, struct mmc *mmc) +{ + unsigned rval = readl(&priv->reg->clkcr); + + /* Disable Clock */ + rval &= ~SUNXI_MMC_CLK_ENABLE; + writel(rval, &priv->reg->clkcr); + if (mmc_update_clk(priv)) + return -1; + + /* Set mod_clk to new rate */ + if (mmc_set_mod_clk(priv, mmc->clock)) + return -1; + + /* Clear internal divider */ + rval &= ~SUNXI_MMC_CLK_DIVIDER_MASK; + writel(rval, &priv->reg->clkcr); + +#if defined(CONFIG_MACH_SUN50I) || defined(CONFIG_SUN50I_GEN_H6) + /* A64 supports calibration of delays on MMC controller and we + * have to set delay of zero before starting calibration. + * Allwinner BSP driver sets a delay only in the case of + * using HS400 which is not supported by mainline U-Boot or + * Linux at the moment + */ + writel(SUNXI_MMC_CAL_DL_SW_EN, &priv->reg->samp_dl); +#endif + + /* Re-enable Clock */ + rval |= SUNXI_MMC_CLK_ENABLE; + writel(rval, &priv->reg->clkcr); + if (mmc_update_clk(priv)) + return -1; + + return 0; +} + +static int sunxi_mmc_set_ios_common(struct sunxi_mmc_priv *priv, + struct mmc *mmc) +{ + debug("set ios: bus_width: %x, clock: %d\n", + mmc->bus_width, mmc->clock); + + /* Change clock first */ + if (mmc->clock && mmc_config_clock(priv, mmc) != 0) { + priv->fatal_err = 1; + return -EINVAL; + } + + /* Change bus width */ + if (mmc->bus_width == 8) + writel(0x2, &priv->reg->width); + else if (mmc->bus_width == 4) + writel(0x1, &priv->reg->width); + else + writel(0x0, &priv->reg->width); + + return 0; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +static int sunxi_mmc_core_init(struct mmc *mmc) +{ + struct sunxi_mmc_priv *priv = mmc->priv; + + /* Reset controller */ + writel(SUNXI_MMC_GCTRL_RESET, &priv->reg->gctrl); + udelay(1000); + + return 0; +} +#endif + +static int mmc_trans_data_by_cpu(struct sunxi_mmc_priv *priv, struct mmc *mmc, + struct mmc_data *data) +{ + const int reading = !!(data->flags & MMC_DATA_READ); + const uint32_t status_bit = reading ? SUNXI_MMC_STATUS_FIFO_EMPTY : + SUNXI_MMC_STATUS_FIFO_FULL; + unsigned i; + unsigned *buff = (unsigned int *)(reading ? data->dest : data->src); + unsigned byte_cnt = data->blocksize * data->blocks; + unsigned timeout_msecs = byte_cnt >> 8; + unsigned long start; + + if (timeout_msecs < 2000) + timeout_msecs = 2000; + + /* Always read / write data through the CPU */ + setbits_le32(&priv->reg->gctrl, SUNXI_MMC_GCTRL_ACCESS_BY_AHB); + + start = get_timer(0); + + for (i = 0; i < (byte_cnt >> 2); i++) { + while (readl(&priv->reg->status) & status_bit) { + if (get_timer(start) > timeout_msecs) + return -1; + } + + if (reading) + buff[i] = readl(&priv->reg->fifo); + else + writel(buff[i], &priv->reg->fifo); + } + + return 0; +} + +static int mmc_rint_wait(struct sunxi_mmc_priv *priv, struct mmc *mmc, + uint timeout_msecs, uint done_bit, const char *what) +{ + unsigned int status; + unsigned long start = get_timer(0); + + do { + status = readl(&priv->reg->rint); + if ((get_timer(start) > timeout_msecs) || + (status & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT)) { + debug("%s timeout %x\n", what, + status & SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT); + return -ETIMEDOUT; + } + } while (!(status & done_bit)); + + return 0; +} + +static int sunxi_mmc_send_cmd_common(struct sunxi_mmc_priv *priv, + struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + unsigned int cmdval = SUNXI_MMC_CMD_START; + unsigned int timeout_msecs; + int error = 0; + unsigned int status = 0; + unsigned int bytecnt = 0; + + if (priv->fatal_err) + return -1; + if (cmd->resp_type & MMC_RSP_BUSY) + debug("mmc cmd %d check rsp busy\n", cmd->cmdidx); + if (cmd->cmdidx == 12) + return 0; + + if (!cmd->cmdidx) + cmdval |= SUNXI_MMC_CMD_SEND_INIT_SEQ; + if (cmd->resp_type & MMC_RSP_PRESENT) + cmdval |= SUNXI_MMC_CMD_RESP_EXPIRE; + if (cmd->resp_type & MMC_RSP_136) + cmdval |= SUNXI_MMC_CMD_LONG_RESPONSE; + if (cmd->resp_type & MMC_RSP_CRC) + cmdval |= SUNXI_MMC_CMD_CHK_RESPONSE_CRC; + + if (data) { + if ((u32)(long)data->dest & 0x3) { + error = -1; + goto out; + } + + cmdval |= SUNXI_MMC_CMD_DATA_EXPIRE|SUNXI_MMC_CMD_WAIT_PRE_OVER; + if (data->flags & MMC_DATA_WRITE) + cmdval |= SUNXI_MMC_CMD_WRITE; + if (data->blocks > 1) + cmdval |= SUNXI_MMC_CMD_AUTO_STOP; + writel(data->blocksize, &priv->reg->blksz); + writel(data->blocks * data->blocksize, &priv->reg->bytecnt); + } + + debug("mmc %d, cmd %d(0x%08x), arg 0x%08x\n", priv->mmc_no, + cmd->cmdidx, cmdval | cmd->cmdidx, cmd->cmdarg); + writel(cmd->cmdarg, &priv->reg->arg); + + if (!data) + writel(cmdval | cmd->cmdidx, &priv->reg->cmd); + + /* + * transfer data and check status + * STATREG[2] : FIFO empty + * STATREG[3] : FIFO full + */ + if (data) { + int ret = 0; + + bytecnt = data->blocksize * data->blocks; + debug("trans data %d bytes\n", bytecnt); + writel(cmdval | cmd->cmdidx, &priv->reg->cmd); + ret = mmc_trans_data_by_cpu(priv, mmc, data); + if (ret) { + error = readl(&priv->reg->rint) & + SUNXI_MMC_RINT_INTERRUPT_ERROR_BIT; + error = -ETIMEDOUT; + goto out; + } + } + + error = mmc_rint_wait(priv, mmc, 1000, SUNXI_MMC_RINT_COMMAND_DONE, + "cmd"); + if (error) + goto out; + + if (data) { + timeout_msecs = 120; + debug("cacl timeout %x msec\n", timeout_msecs); + error = mmc_rint_wait(priv, mmc, timeout_msecs, + data->blocks > 1 ? + SUNXI_MMC_RINT_AUTO_COMMAND_DONE : + SUNXI_MMC_RINT_DATA_OVER, + "data"); + if (error) + goto out; + } + + if (cmd->resp_type & MMC_RSP_BUSY) { + unsigned long start = get_timer(0); + timeout_msecs = 2000; + + do { + status = readl(&priv->reg->status); + if (get_timer(start) > timeout_msecs) { + debug("busy timeout\n"); + error = -ETIMEDOUT; + goto out; + } + } while (status & SUNXI_MMC_STATUS_CARD_DATA_BUSY); + } + + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[0] = readl(&priv->reg->resp3); + cmd->response[1] = readl(&priv->reg->resp2); + cmd->response[2] = readl(&priv->reg->resp1); + cmd->response[3] = readl(&priv->reg->resp0); + debug("mmc resp 0x%08x 0x%08x 0x%08x 0x%08x\n", + cmd->response[3], cmd->response[2], + cmd->response[1], cmd->response[0]); + } else { + cmd->response[0] = readl(&priv->reg->resp0); + debug("mmc resp 0x%08x\n", cmd->response[0]); + } +out: + if (error < 0) { + writel(SUNXI_MMC_GCTRL_RESET, &priv->reg->gctrl); + mmc_update_clk(priv); + } + writel(0xffffffff, &priv->reg->rint); + writel(readl(&priv->reg->gctrl) | SUNXI_MMC_GCTRL_FIFO_RESET, + &priv->reg->gctrl); + + return error; +} + +#if !CONFIG_IS_ENABLED(DM_MMC) +static int sunxi_mmc_set_ios_legacy(struct mmc *mmc) +{ + struct sunxi_mmc_priv *priv = mmc->priv; + + return sunxi_mmc_set_ios_common(priv, mmc); +} + +static int sunxi_mmc_send_cmd_legacy(struct mmc *mmc, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct sunxi_mmc_priv *priv = mmc->priv; + + return sunxi_mmc_send_cmd_common(priv, mmc, cmd, data); +} + +static int sunxi_mmc_getcd_legacy(struct mmc *mmc) +{ + struct sunxi_mmc_priv *priv = mmc->priv; + int cd_pin; + + cd_pin = sunxi_mmc_getcd_gpio(priv->mmc_no); + if (cd_pin < 0) + return 1; + + return !gpio_get_value(cd_pin); +} + +static const struct mmc_ops sunxi_mmc_ops = { + .send_cmd = sunxi_mmc_send_cmd_legacy, + .set_ios = sunxi_mmc_set_ios_legacy, + .init = sunxi_mmc_core_init, + .getcd = sunxi_mmc_getcd_legacy, +}; + +struct mmc *sunxi_mmc_init(int sdc_no) +{ + struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE; + struct sunxi_mmc_priv *priv = &mmc_host[sdc_no]; + struct mmc_config *cfg = &priv->cfg; + int ret; + + memset(priv, '\0', sizeof(struct sunxi_mmc_priv)); + + cfg->name = "SUNXI SD/MMC"; + cfg->ops = &sunxi_mmc_ops; + + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + cfg->host_caps = MMC_MODE_4BIT; +#if defined(CONFIG_MACH_SUN50I) || defined(CONFIG_MACH_SUN8I) || defined(CONFIG_SUN50I_GEN_H6) + if (sdc_no == 2) + cfg->host_caps = MMC_MODE_8BIT; +#endif + cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + cfg->f_min = 400000; + cfg->f_max = 52000000; + + if (mmc_resource_init(sdc_no) != 0) + return NULL; + + /* config ahb clock */ + debug("init mmc %d clock and io\n", sdc_no); +#if !defined(CONFIG_SUN50I_GEN_H6) + setbits_le32(&ccm->ahb_gate0, 1 << AHB_GATE_OFFSET_MMC(sdc_no)); + +#ifdef CONFIG_SUNXI_GEN_SUN6I + /* unassert reset */ + setbits_le32(&ccm->ahb_reset0_cfg, 1 << AHB_RESET_OFFSET_MMC(sdc_no)); +#endif +#if defined(CONFIG_MACH_SUN9I) + /* sun9i has a mmc-common module, also set the gate and reset there */ + writel(SUNXI_MMC_COMMON_CLK_GATE | SUNXI_MMC_COMMON_RESET, + SUNXI_MMC_COMMON_BASE + 4 * sdc_no); +#endif +#else /* CONFIG_SUN50I_GEN_H6 */ + setbits_le32(&ccm->sd_gate_reset, 1 << sdc_no); + /* unassert reset */ + setbits_le32(&ccm->sd_gate_reset, 1 << (RESET_SHIFT + sdc_no)); +#endif + ret = mmc_set_mod_clk(priv, 24000000); + if (ret) + return NULL; + + return mmc_create(cfg, priv); +} +#else + +static int sunxi_mmc_set_ios(struct udevice *dev) +{ + struct sunxi_mmc_plat *plat = dev_get_plat(dev); + struct sunxi_mmc_priv *priv = dev_get_priv(dev); + + return sunxi_mmc_set_ios_common(priv, &plat->mmc); +} + +static int sunxi_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct sunxi_mmc_plat *plat = dev_get_plat(dev); + struct sunxi_mmc_priv *priv = dev_get_priv(dev); + + return sunxi_mmc_send_cmd_common(priv, &plat->mmc, cmd, data); +} + +static int sunxi_mmc_getcd(struct udevice *dev) +{ + struct sunxi_mmc_priv *priv = dev_get_priv(dev); + + if (dm_gpio_is_valid(&priv->cd_gpio)) { + int cd_state = dm_gpio_get_value(&priv->cd_gpio); + + return cd_state ^ priv->cd_inverted; + } + return 1; +} + +static const struct dm_mmc_ops sunxi_mmc_ops = { + .send_cmd = sunxi_mmc_send_cmd, + .set_ios = sunxi_mmc_set_ios, + .get_cd = sunxi_mmc_getcd, +}; + +static unsigned get_mclk_offset(void) +{ + if (IS_ENABLED(CONFIG_MACH_SUN9I_A80)) + return 0x410; + + if (IS_ENABLED(CONFIG_SUN50I_GEN_H6)) + return 0x830; + + return 0x88; +}; + +static int sunxi_mmc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct sunxi_mmc_plat *plat = dev_get_plat(dev); + struct sunxi_mmc_priv *priv = dev_get_priv(dev); + struct reset_ctl_bulk reset_bulk; + struct clk gate_clk; + struct mmc_config *cfg = &plat->cfg; + struct ofnode_phandle_args args; + u32 *ccu_reg; + int bus_width, ret; + + cfg->name = dev->name; + bus_width = dev_read_u32_default(dev, "bus-width", 1); + + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34; + cfg->host_caps = 0; + if (bus_width == 8) + cfg->host_caps |= MMC_MODE_8BIT; + if (bus_width >= 4) + cfg->host_caps |= MMC_MODE_4BIT; + cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + cfg->f_min = 400000; + cfg->f_max = 52000000; + + priv->reg = (void *)dev_read_addr(dev); + + /* We don't have a sunxi clock driver so find the clock address here */ + ret = dev_read_phandle_with_args(dev, "clocks", "#clock-cells", 0, + 1, &args); + if (ret) + return ret; + ccu_reg = (u32 *)ofnode_get_addr(args.node); + + priv->mmc_no = ((uintptr_t)priv->reg - SUNXI_MMC0_BASE) / 0x1000; + priv->mclkreg = (void *)ccu_reg + get_mclk_offset() + priv->mmc_no * 4; + + ret = clk_get_by_name(dev, "ahb", &gate_clk); + if (!ret) + clk_enable(&gate_clk); + + ret = reset_get_bulk(dev, &reset_bulk); + if (!ret) + reset_deassert_bulk(&reset_bulk); + + ret = mmc_set_mod_clk(priv, 24000000); + if (ret) + return ret; + + /* This GPIO is optional */ + if (!dev_read_bool(dev, "non-removable") && + !gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio, + GPIOD_IS_IN)) { + int cd_pin = gpio_get_number(&priv->cd_gpio); + + sunxi_gpio_set_pull(cd_pin, SUNXI_GPIO_PULL_UP); + } + + /* Check if card detect is inverted */ + priv->cd_inverted = dev_read_bool(dev, "cd-inverted"); + + upriv->mmc = &plat->mmc; + + /* Reset controller */ + writel(SUNXI_MMC_GCTRL_RESET, &priv->reg->gctrl); + udelay(1000); + + return 0; +} + +static int sunxi_mmc_bind(struct udevice *dev) +{ + struct sunxi_mmc_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id sunxi_mmc_ids[] = { + { .compatible = "allwinner,sun4i-a10-mmc" }, + { .compatible = "allwinner,sun5i-a13-mmc" }, + { .compatible = "allwinner,sun7i-a20-mmc" }, + { .compatible = "allwinner,sun8i-a83t-emmc" }, + { .compatible = "allwinner,sun9i-a80-mmc" }, + { .compatible = "allwinner,sun50i-a64-mmc" }, + { .compatible = "allwinner,sun50i-a64-emmc" }, + { .compatible = "allwinner,sun50i-h6-mmc" }, + { .compatible = "allwinner,sun50i-h6-emmc" }, + { .compatible = "allwinner,sun50i-a100-mmc" }, + { .compatible = "allwinner,sun50i-a100-emmc" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(sunxi_mmc_drv) = { + .name = "sunxi_mmc", + .id = UCLASS_MMC, + .of_match = sunxi_mmc_ids, + .bind = sunxi_mmc_bind, + .probe = sunxi_mmc_probe, + .ops = &sunxi_mmc_ops, + .plat_auto = sizeof(struct sunxi_mmc_plat), + .priv_auto = sizeof(struct sunxi_mmc_priv), +}; +#endif diff --git a/roms/u-boot/drivers/mmc/tangier_sdhci.c b/roms/u-boot/drivers/mmc/tangier_sdhci.c new file mode 100644 index 000000000..115642733 --- /dev/null +++ b/roms/u-boot/drivers/mmc/tangier_sdhci.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2017 Intel Corporation + */ +#include <common.h> +#include <dm.h> +#include <dm/device.h> +#include <linux/io.h> +#include <linux/sizes.h> +#include <malloc.h> +#include <mmc.h> +#include <sdhci.h> + +#define SDHCI_TANGIER_FMAX 200000000 +#define SDHCI_TANGIER_FMIN 400000 + +struct sdhci_tangier_plat { + struct mmc_config cfg; + struct mmc mmc; + void __iomem *ioaddr; +}; + +static int sdhci_tangier_bind(struct udevice *dev) +{ + struct sdhci_tangier_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static int sdhci_tangier_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct sdhci_tangier_plat *plat = dev_get_plat(dev); + struct sdhci_host *host = dev_get_priv(dev); + fdt_addr_t base; + int ret; + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) + return -EINVAL; + + plat->ioaddr = devm_ioremap(dev, base, SZ_1K); + if (!plat->ioaddr) + return -ENOMEM; + + host->name = dev->name; + host->ioaddr = plat->ioaddr; + host->quirks = SDHCI_QUIRK_NO_HISPD_BIT | SDHCI_QUIRK_BROKEN_VOLTAGE | + SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_WAIT_SEND_CMD; + + /* MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195 */ + host->voltages = MMC_VDD_165_195; + + host->mmc = &plat->mmc; + host->mmc->dev = dev; + ret = sdhci_setup_cfg(&plat->cfg, host, SDHCI_TANGIER_FMAX, + SDHCI_TANGIER_FMIN); + if (ret) + return ret; + + upriv->mmc = &plat->mmc; + host->mmc->priv = host; + + return sdhci_probe(dev); +} + +static const struct udevice_id sdhci_tangier_match[] = { + { .compatible = "intel,sdhci-tangier" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(sdhci_tangier) = { + .name = "sdhci-tangier", + .id = UCLASS_MMC, + .of_match = sdhci_tangier_match, + .bind = sdhci_tangier_bind, + .probe = sdhci_tangier_probe, + .ops = &sdhci_ops, + .priv_auto = sizeof(struct sdhci_host), + .plat_auto = sizeof(struct sdhci_tangier_plat), +}; diff --git a/roms/u-boot/drivers/mmc/tegra_mmc.c b/roms/u-boot/drivers/mmc/tegra_mmc.c new file mode 100644 index 000000000..760eca405 --- /dev/null +++ b/roms/u-boot/drivers/mmc/tegra_mmc.c @@ -0,0 +1,776 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2009 SAMSUNG Electronics + * Minkyu Kang <mk7.kang@samsung.com> + * Jaehoon Chung <jh80.chung@samsung.com> + * Portions Copyright 2011-2019 NVIDIA Corporation + */ + +#include <bouncebuf.h> +#include <common.h> +#include <dm.h> +#include <errno.h> +#include <log.h> +#include <mmc.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/arch-tegra/tegra_mmc.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/err.h> +#if defined(CONFIG_TEGRA30) || defined(CONFIG_TEGRA210) +#include <asm/arch/clock.h> +#endif + +struct tegra_mmc_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct tegra_mmc_priv { + struct tegra_mmc *reg; + struct reset_ctl reset_ctl; + struct clk clk; + struct gpio_desc cd_gpio; /* Change Detect GPIO */ + struct gpio_desc pwr_gpio; /* Power GPIO */ + struct gpio_desc wp_gpio; /* Write Protect GPIO */ + unsigned int version; /* SDHCI spec. version */ + unsigned int clock; /* Current clock (MHz) */ + int mmc_id; /* peripheral id */ +}; + +static void tegra_mmc_set_power(struct tegra_mmc_priv *priv, + unsigned short power) +{ + u8 pwr = 0; + debug("%s: power = %x\n", __func__, power); + + if (power != (unsigned short)-1) { + switch (1 << power) { + case MMC_VDD_165_195: + pwr = TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V1_8; + break; + case MMC_VDD_29_30: + case MMC_VDD_30_31: + pwr = TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V3_0; + break; + case MMC_VDD_32_33: + case MMC_VDD_33_34: + pwr = TEGRA_MMC_PWRCTL_SD_BUS_VOLTAGE_V3_3; + break; + } + } + debug("%s: pwr = %X\n", __func__, pwr); + + /* Set the bus voltage first (if any) */ + writeb(pwr, &priv->reg->pwrcon); + if (pwr == 0) + return; + + /* Now enable bus power */ + pwr |= TEGRA_MMC_PWRCTL_SD_BUS_POWER; + writeb(pwr, &priv->reg->pwrcon); +} + +static void tegra_mmc_prepare_data(struct tegra_mmc_priv *priv, + struct mmc_data *data, + struct bounce_buffer *bbstate) +{ + unsigned char ctrl; + + + debug("buf: %p (%p), data->blocks: %u, data->blocksize: %u\n", + bbstate->bounce_buffer, bbstate->user_buffer, data->blocks, + data->blocksize); + + writel((u32)(unsigned long)bbstate->bounce_buffer, &priv->reg->sysad); + /* + * DMASEL[4:3] + * 00 = Selects SDMA + * 01 = Reserved + * 10 = Selects 32-bit Address ADMA2 + * 11 = Selects 64-bit Address ADMA2 + */ + ctrl = readb(&priv->reg->hostctl); + ctrl &= ~TEGRA_MMC_HOSTCTL_DMASEL_MASK; + ctrl |= TEGRA_MMC_HOSTCTL_DMASEL_SDMA; + writeb(ctrl, &priv->reg->hostctl); + + /* We do not handle DMA boundaries, so set it to max (512 KiB) */ + writew((7 << 12) | (data->blocksize & 0xFFF), &priv->reg->blksize); + writew(data->blocks, &priv->reg->blkcnt); +} + +static void tegra_mmc_set_transfer_mode(struct tegra_mmc_priv *priv, + struct mmc_data *data) +{ + unsigned short mode; + debug(" mmc_set_transfer_mode called\n"); + /* + * TRNMOD + * MUL1SIN0[5] : Multi/Single Block Select + * RD1WT0[4] : Data Transfer Direction Select + * 1 = read + * 0 = write + * ENACMD12[2] : Auto CMD12 Enable + * ENBLKCNT[1] : Block Count Enable + * ENDMA[0] : DMA Enable + */ + mode = (TEGRA_MMC_TRNMOD_DMA_ENABLE | + TEGRA_MMC_TRNMOD_BLOCK_COUNT_ENABLE); + + if (data->blocks > 1) + mode |= TEGRA_MMC_TRNMOD_MULTI_BLOCK_SELECT; + + if (data->flags & MMC_DATA_READ) + mode |= TEGRA_MMC_TRNMOD_DATA_XFER_DIR_SEL_READ; + + writew(mode, &priv->reg->trnmod); +} + +static int tegra_mmc_wait_inhibit(struct tegra_mmc_priv *priv, + struct mmc_cmd *cmd, + struct mmc_data *data, + unsigned int timeout) +{ + /* + * PRNSTS + * CMDINHDAT[1] : Command Inhibit (DAT) + * CMDINHCMD[0] : Command Inhibit (CMD) + */ + unsigned int mask = TEGRA_MMC_PRNSTS_CMD_INHIBIT_CMD; + + /* + * We shouldn't wait for data inhibit for stop commands, even + * though they might use busy signaling + */ + if ((data == NULL) && (cmd->resp_type & MMC_RSP_BUSY)) + mask |= TEGRA_MMC_PRNSTS_CMD_INHIBIT_DAT; + + while (readl(&priv->reg->prnsts) & mask) { + if (timeout == 0) { + printf("%s: timeout error\n", __func__); + return -1; + } + timeout--; + udelay(1000); + } + + return 0; +} + +static int tegra_mmc_send_cmd_bounced(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data, + struct bounce_buffer *bbstate) +{ + struct tegra_mmc_priv *priv = dev_get_priv(dev); + int flags, i; + int result; + unsigned int mask = 0; + unsigned int retry = 0x100000; + debug(" mmc_send_cmd called\n"); + + result = tegra_mmc_wait_inhibit(priv, cmd, data, 10 /* ms */); + + if (result < 0) + return result; + + if (data) + tegra_mmc_prepare_data(priv, data, bbstate); + + debug("cmd->arg: %08x\n", cmd->cmdarg); + writel(cmd->cmdarg, &priv->reg->argument); + + if (data) + tegra_mmc_set_transfer_mode(priv, data); + + if ((cmd->resp_type & MMC_RSP_136) && (cmd->resp_type & MMC_RSP_BUSY)) + return -1; + + /* + * CMDREG + * CMDIDX[13:8] : Command index + * DATAPRNT[5] : Data Present Select + * ENCMDIDX[4] : Command Index Check Enable + * ENCMDCRC[3] : Command CRC Check Enable + * RSPTYP[1:0] + * 00 = No Response + * 01 = Length 136 + * 10 = Length 48 + * 11 = Length 48 Check busy after response + */ + if (!(cmd->resp_type & MMC_RSP_PRESENT)) + flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_NO_RESPONSE; + else if (cmd->resp_type & MMC_RSP_136) + flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_136; + else if (cmd->resp_type & MMC_RSP_BUSY) + flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_48_BUSY; + else + flags = TEGRA_MMC_CMDREG_RESP_TYPE_SELECT_LENGTH_48; + + if (cmd->resp_type & MMC_RSP_CRC) + flags |= TEGRA_MMC_TRNMOD_CMD_CRC_CHECK; + if (cmd->resp_type & MMC_RSP_OPCODE) + flags |= TEGRA_MMC_TRNMOD_CMD_INDEX_CHECK; + if (data) + flags |= TEGRA_MMC_TRNMOD_DATA_PRESENT_SELECT_DATA_TRANSFER; + + debug("cmd: %d\n", cmd->cmdidx); + + writew((cmd->cmdidx << 8) | flags, &priv->reg->cmdreg); + + for (i = 0; i < retry; i++) { + mask = readl(&priv->reg->norintsts); + /* Command Complete */ + if (mask & TEGRA_MMC_NORINTSTS_CMD_COMPLETE) { + if (!data) + writel(mask, &priv->reg->norintsts); + break; + } + } + + if (i == retry) { + printf("%s: waiting for status update\n", __func__); + writel(mask, &priv->reg->norintsts); + return -ETIMEDOUT; + } + + if (mask & TEGRA_MMC_NORINTSTS_CMD_TIMEOUT) { + /* Timeout Error */ + debug("timeout: %08x cmd %d\n", mask, cmd->cmdidx); + writel(mask, &priv->reg->norintsts); + return -ETIMEDOUT; + } else if (mask & TEGRA_MMC_NORINTSTS_ERR_INTERRUPT) { + /* Error Interrupt */ + debug("error: %08x cmd %d\n", mask, cmd->cmdidx); + writel(mask, &priv->reg->norintsts); + return -1; + } + + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) { + /* CRC is stripped so we need to do some shifting. */ + for (i = 0; i < 4; i++) { + unsigned long offset = (unsigned long) + (&priv->reg->rspreg3 - i); + cmd->response[i] = readl(offset) << 8; + + if (i != 3) { + cmd->response[i] |= + readb(offset - 1); + } + debug("cmd->resp[%d]: %08x\n", + i, cmd->response[i]); + } + } else if (cmd->resp_type & MMC_RSP_BUSY) { + for (i = 0; i < retry; i++) { + /* PRNTDATA[23:20] : DAT[3:0] Line Signal */ + if (readl(&priv->reg->prnsts) + & (1 << 20)) /* DAT[0] */ + break; + } + + if (i == retry) { + printf("%s: card is still busy\n", __func__); + writel(mask, &priv->reg->norintsts); + return -ETIMEDOUT; + } + + cmd->response[0] = readl(&priv->reg->rspreg0); + debug("cmd->resp[0]: %08x\n", cmd->response[0]); + } else { + cmd->response[0] = readl(&priv->reg->rspreg0); + debug("cmd->resp[0]: %08x\n", cmd->response[0]); + } + } + + if (data) { + unsigned long start = get_timer(0); + + while (1) { + mask = readl(&priv->reg->norintsts); + + if (mask & TEGRA_MMC_NORINTSTS_ERR_INTERRUPT) { + /* Error Interrupt */ + writel(mask, &priv->reg->norintsts); + printf("%s: error during transfer: 0x%08x\n", + __func__, mask); + return -1; + } else if (mask & TEGRA_MMC_NORINTSTS_DMA_INTERRUPT) { + /* + * DMA Interrupt, restart the transfer where + * it was interrupted. + */ + unsigned int address = readl(&priv->reg->sysad); + + debug("DMA end\n"); + writel(TEGRA_MMC_NORINTSTS_DMA_INTERRUPT, + &priv->reg->norintsts); + writel(address, &priv->reg->sysad); + } else if (mask & TEGRA_MMC_NORINTSTS_XFER_COMPLETE) { + /* Transfer Complete */ + debug("r/w is done\n"); + break; + } else if (get_timer(start) > 8000UL) { + writel(mask, &priv->reg->norintsts); + printf("%s: MMC Timeout\n" + " Interrupt status 0x%08x\n" + " Interrupt status enable 0x%08x\n" + " Interrupt signal enable 0x%08x\n" + " Present status 0x%08x\n", + __func__, mask, + readl(&priv->reg->norintstsen), + readl(&priv->reg->norintsigen), + readl(&priv->reg->prnsts)); + return -1; + } + } + writel(mask, &priv->reg->norintsts); + } + + udelay(1000); + return 0; +} + +static int tegra_mmc_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + void *buf; + unsigned int bbflags; + size_t len; + struct bounce_buffer bbstate; + int ret; + + if (data) { + if (data->flags & MMC_DATA_READ) { + buf = data->dest; + bbflags = GEN_BB_WRITE; + } else { + buf = (void *)data->src; + bbflags = GEN_BB_READ; + } + len = data->blocks * data->blocksize; + + bounce_buffer_start(&bbstate, buf, len, bbflags); + } + + ret = tegra_mmc_send_cmd_bounced(dev, cmd, data, &bbstate); + + if (data) + bounce_buffer_stop(&bbstate); + + return ret; +} + +static void tegra_mmc_change_clock(struct tegra_mmc_priv *priv, uint clock) +{ + ulong rate; + int div; + unsigned short clk; + unsigned long timeout; + + debug(" mmc_change_clock called\n"); + + /* + * Change Tegra SDMMCx clock divisor here. Source is PLLP_OUT0 + */ + if (clock == 0) + goto out; + + rate = clk_set_rate(&priv->clk, clock); + div = (rate + clock - 1) / clock; + +#if defined(CONFIG_TEGRA210) + if (priv->mmc_id == PERIPH_ID_SDMMC1 && clock <= 400000) { + /* clock_adjust_periph_pll_div() chooses a 'bad' clock + * on SDMMC1 T210, so skip it here and force a clock + * that's been spec'd in the table in the TRM for + * card-detect (400KHz). + */ + uint effective_rate = clock_adjust_periph_pll_div(priv->mmc_id, + CLOCK_ID_PERIPH, 24727273, NULL); + div = 62; + + debug("%s: WAR: Using SDMMC1 clock of %u, div %d to achieve %dHz card clock ...\n", + __func__, effective_rate, div, clock); + } else { + clock_adjust_periph_pll_div(priv->mmc_id, CLOCK_ID_PERIPH, + clock, &div); + } +#endif + debug("div = %d\n", div); + + writew(0, &priv->reg->clkcon); + + /* + * CLKCON + * SELFREQ[15:8] : base clock divided by value + * ENSDCLK[2] : SD Clock Enable + * STBLINTCLK[1] : Internal Clock Stable + * ENINTCLK[0] : Internal Clock Enable + */ + div >>= 1; + clk = ((div << TEGRA_MMC_CLKCON_SDCLK_FREQ_SEL_SHIFT) | + TEGRA_MMC_CLKCON_INTERNAL_CLOCK_ENABLE); + writew(clk, &priv->reg->clkcon); + + /* Wait max 10 ms */ + timeout = 10; + while (!(readw(&priv->reg->clkcon) & + TEGRA_MMC_CLKCON_INTERNAL_CLOCK_STABLE)) { + if (timeout == 0) { + printf("%s: timeout error\n", __func__); + return; + } + timeout--; + udelay(1000); + } + + clk |= TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE; + writew(clk, &priv->reg->clkcon); + + debug("mmc_change_clock: clkcon = %08X\n", clk); + +out: + priv->clock = clock; +} + +static int tegra_mmc_set_ios(struct udevice *dev) +{ + struct tegra_mmc_priv *priv = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + unsigned char ctrl; + debug(" mmc_set_ios called\n"); + + debug("bus_width: %x, clock: %d\n", mmc->bus_width, mmc->clock); + + /* Change clock first */ + tegra_mmc_change_clock(priv, mmc->clock); + + ctrl = readb(&priv->reg->hostctl); + + /* + * WIDE8[5] + * 0 = Depend on WIDE4 + * 1 = 8-bit mode + * WIDE4[1] + * 1 = 4-bit mode + * 0 = 1-bit mode + */ + if (mmc->bus_width == 8) + ctrl |= (1 << 5); + else if (mmc->bus_width == 4) + ctrl |= (1 << 1); + else + ctrl &= ~(1 << 1 | 1 << 5); + + writeb(ctrl, &priv->reg->hostctl); + debug("mmc_set_ios: hostctl = %08X\n", ctrl); + + return 0; +} + +static void tegra_mmc_pad_init(struct tegra_mmc_priv *priv) +{ +#if defined(CONFIG_TEGRA30) || defined(CONFIG_TEGRA210) + u32 val; + u16 clk_con; + int timeout; + int id = priv->mmc_id; + + debug("%s: sdmmc address = %p, id = %d\n", __func__, + priv->reg, id); + + /* Set the pad drive strength for SDMMC1 or 3 only */ + if (id != PERIPH_ID_SDMMC1 && id != PERIPH_ID_SDMMC3) { + debug("%s: settings are only valid for SDMMC1/SDMMC3!\n", + __func__); + return; + } + + val = readl(&priv->reg->sdmemcmppadctl); + val &= 0xFFFFFFF0; + val |= MEMCOMP_PADCTRL_VREF; + writel(val, &priv->reg->sdmemcmppadctl); + + /* Disable SD Clock Enable before running auto-cal as per TRM */ + clk_con = readw(&priv->reg->clkcon); + debug("%s: CLOCK_CONTROL = 0x%04X\n", __func__, clk_con); + clk_con &= ~TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE; + writew(clk_con, &priv->reg->clkcon); + + val = readl(&priv->reg->autocalcfg); + val &= 0xFFFF0000; + val |= AUTO_CAL_PU_OFFSET | AUTO_CAL_PD_OFFSET; + writel(val, &priv->reg->autocalcfg); + val |= AUTO_CAL_START | AUTO_CAL_ENABLE; + writel(val, &priv->reg->autocalcfg); + debug("%s: AUTO_CAL_CFG = 0x%08X\n", __func__, val); + udelay(1); + timeout = 100; /* 10 mSec max (100*100uS) */ + do { + val = readl(&priv->reg->autocalsts); + udelay(100); + } while ((val & AUTO_CAL_ACTIVE) && --timeout); + val = readl(&priv->reg->autocalsts); + debug("%s: Final AUTO_CAL_STATUS = 0x%08X, timeout = %d\n", + __func__, val, timeout); + + /* Re-enable SD Clock Enable when auto-cal is done */ + clk_con |= TEGRA_MMC_CLKCON_SD_CLOCK_ENABLE; + writew(clk_con, &priv->reg->clkcon); + clk_con = readw(&priv->reg->clkcon); + debug("%s: final CLOCK_CONTROL = 0x%04X\n", __func__, clk_con); + + if (timeout == 0) { + printf("%s: Warning: Autocal timed out!\n", __func__); + /* TBD: Set CFG2TMC_SDMMC1_PAD_CAL_DRV* regs here */ + } + +#if defined(CONFIG_TEGRA210) + u32 tap_value, trim_value; + + /* Set tap/trim values for SDMMC1/3 @ <48MHz here */ + val = readl(&priv->reg->venspictl); /* aka VENDOR_SYS_SW_CNTL */ + val &= IO_TRIM_BYPASS_MASK; + if (id == PERIPH_ID_SDMMC1) { + tap_value = 4; /* default */ + if (val) + tap_value = 3; + trim_value = 2; + } else { /* SDMMC3 */ + tap_value = 3; + trim_value = 3; + } + + val = readl(&priv->reg->venclkctl); + val &= ~TRIM_VAL_MASK; + val |= (trim_value << TRIM_VAL_SHIFT); + val &= ~TAP_VAL_MASK; + val |= (tap_value << TAP_VAL_SHIFT); + writel(val, &priv->reg->venclkctl); + debug("%s: VENDOR_CLOCK_CNTRL = 0x%08X\n", __func__, val); +#endif /* T210 */ +#endif /* T30/T210 */ +} + +static void tegra_mmc_reset(struct tegra_mmc_priv *priv, struct mmc *mmc) +{ + unsigned int timeout; + debug(" mmc_reset called\n"); + + /* + * RSTALL[0] : Software reset for all + * 1 = reset + * 0 = work + */ + writeb(TEGRA_MMC_SWRST_SW_RESET_FOR_ALL, &priv->reg->swrst); + + priv->clock = 0; + + /* Wait max 100 ms */ + timeout = 100; + + /* hw clears the bit when it's done */ + while (readb(&priv->reg->swrst) & TEGRA_MMC_SWRST_SW_RESET_FOR_ALL) { + if (timeout == 0) { + printf("%s: timeout error\n", __func__); + return; + } + timeout--; + udelay(1000); + } + + /* Set SD bus voltage & enable bus power */ + tegra_mmc_set_power(priv, fls(mmc->cfg->voltages) - 1); + debug("%s: power control = %02X, host control = %02X\n", __func__, + readb(&priv->reg->pwrcon), readb(&priv->reg->hostctl)); + + /* Make sure SDIO pads are set up */ + tegra_mmc_pad_init(priv); +} + +static int tegra_mmc_init(struct udevice *dev) +{ + struct tegra_mmc_priv *priv = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + unsigned int mask; + debug(" tegra_mmc_init called\n"); + +#if defined(CONFIG_TEGRA210) + priv->mmc_id = clock_decode_periph_id(dev); + if (priv->mmc_id == PERIPH_ID_NONE) { + printf("%s: Missing/invalid peripheral ID\n", __func__); + return -EINVAL; + } +#endif + tegra_mmc_reset(priv, mmc); + +#if defined(CONFIG_TEGRA124_MMC_DISABLE_EXT_LOOPBACK) + /* + * Disable the external clock loopback and use the internal one on + * SDMMC3 as per the SDMMC_VENDOR_MISC_CNTRL_0 register's SDMMC_SPARE1 + * bits being set to 0xfffd according to the TRM. + * + * TODO(marcel.ziswiler@toradex.com): Move to device tree controlled + * approach once proper kernel integration made it mainline. + */ + if (priv->reg == (void *)0x700b0400) { + mask = readl(&priv->reg->venmiscctl); + mask &= ~TEGRA_MMC_MISCON_ENABLE_EXT_LOOPBACK; + writel(mask, &priv->reg->venmiscctl); + } +#endif + + priv->version = readw(&priv->reg->hcver); + debug("host version = %x\n", priv->version); + + /* mask all */ + writel(0xffffffff, &priv->reg->norintstsen); + writel(0xffffffff, &priv->reg->norintsigen); + + writeb(0xe, &priv->reg->timeoutcon); /* TMCLK * 2^27 */ + /* + * NORMAL Interrupt Status Enable Register init + * [5] ENSTABUFRDRDY : Buffer Read Ready Status Enable + * [4] ENSTABUFWTRDY : Buffer write Ready Status Enable + * [3] ENSTADMAINT : DMA boundary interrupt + * [1] ENSTASTANSCMPLT : Transfre Complete Status Enable + * [0] ENSTACMDCMPLT : Command Complete Status Enable + */ + mask = readl(&priv->reg->norintstsen); + mask &= ~(0xffff); + mask |= (TEGRA_MMC_NORINTSTSEN_CMD_COMPLETE | + TEGRA_MMC_NORINTSTSEN_XFER_COMPLETE | + TEGRA_MMC_NORINTSTSEN_DMA_INTERRUPT | + TEGRA_MMC_NORINTSTSEN_BUFFER_WRITE_READY | + TEGRA_MMC_NORINTSTSEN_BUFFER_READ_READY); + writel(mask, &priv->reg->norintstsen); + + /* + * NORMAL Interrupt Signal Enable Register init + * [1] ENSTACMDCMPLT : Transfer Complete Signal Enable + */ + mask = readl(&priv->reg->norintsigen); + mask &= ~(0xffff); + mask |= TEGRA_MMC_NORINTSIGEN_XFER_COMPLETE; + writel(mask, &priv->reg->norintsigen); + + return 0; +} + +static int tegra_mmc_getcd(struct udevice *dev) +{ + struct tegra_mmc_priv *priv = dev_get_priv(dev); + + debug("tegra_mmc_getcd called\n"); + + if (dm_gpio_is_valid(&priv->cd_gpio)) + return dm_gpio_get_value(&priv->cd_gpio); + + return 1; +} + +static const struct dm_mmc_ops tegra_mmc_ops = { + .send_cmd = tegra_mmc_send_cmd, + .set_ios = tegra_mmc_set_ios, + .get_cd = tegra_mmc_getcd, +}; + +static int tegra_mmc_probe(struct udevice *dev) +{ + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct tegra_mmc_plat *plat = dev_get_plat(dev); + struct tegra_mmc_priv *priv = dev_get_priv(dev); + struct mmc_config *cfg = &plat->cfg; + int bus_width, ret; + + cfg->name = dev->name; + + bus_width = dev_read_u32_default(dev, "bus-width", 1); + + cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + cfg->host_caps = 0; + if (bus_width == 8) + cfg->host_caps |= MMC_MODE_8BIT; + if (bus_width >= 4) + cfg->host_caps |= MMC_MODE_4BIT; + cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + + /* + * min freq is for card identification, and is the highest + * low-speed SDIO card frequency (actually 400KHz) + * max freq is highest HS eMMC clock as per the SD/MMC spec + * (actually 52MHz) + */ + cfg->f_min = 375000; + cfg->f_max = 48000000; + + cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT; + + priv->reg = (void *)dev_read_addr(dev); + + ret = reset_get_by_name(dev, "sdhci", &priv->reset_ctl); + if (ret) { + debug("reset_get_by_name() failed: %d\n", ret); + return ret; + } + ret = clk_get_by_index(dev, 0, &priv->clk); + if (ret) { + debug("clk_get_by_index() failed: %d\n", ret); + return ret; + } + + ret = reset_assert(&priv->reset_ctl); + if (ret) + return ret; + ret = clk_enable(&priv->clk); + if (ret) + return ret; + ret = clk_set_rate(&priv->clk, 20000000); + if (IS_ERR_VALUE(ret)) + return ret; + ret = reset_deassert(&priv->reset_ctl); + if (ret) + return ret; + + /* These GPIOs are optional */ + gpio_request_by_name(dev, "cd-gpios", 0, &priv->cd_gpio, GPIOD_IS_IN); + gpio_request_by_name(dev, "wp-gpios", 0, &priv->wp_gpio, GPIOD_IS_IN); + gpio_request_by_name(dev, "power-gpios", 0, &priv->pwr_gpio, + GPIOD_IS_OUT); + if (dm_gpio_is_valid(&priv->pwr_gpio)) + dm_gpio_set_value(&priv->pwr_gpio, 1); + + upriv->mmc = &plat->mmc; + + return tegra_mmc_init(dev); +} + +static int tegra_mmc_bind(struct udevice *dev) +{ + struct tegra_mmc_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id tegra_mmc_ids[] = { + { .compatible = "nvidia,tegra20-sdhci" }, + { .compatible = "nvidia,tegra30-sdhci" }, + { .compatible = "nvidia,tegra114-sdhci" }, + { .compatible = "nvidia,tegra124-sdhci" }, + { .compatible = "nvidia,tegra210-sdhci" }, + { .compatible = "nvidia,tegra186-sdhci" }, + { } +}; + +U_BOOT_DRIVER(tegra_mmc_drv) = { + .name = "tegra_mmc", + .id = UCLASS_MMC, + .of_match = tegra_mmc_ids, + .bind = tegra_mmc_bind, + .probe = tegra_mmc_probe, + .ops = &tegra_mmc_ops, + .plat_auto = sizeof(struct tegra_mmc_plat), + .priv_auto = sizeof(struct tegra_mmc_priv), +}; diff --git a/roms/u-boot/drivers/mmc/tmio-common.c b/roms/u-boot/drivers/mmc/tmio-common.c new file mode 100644 index 000000000..e9c7d3a2e --- /dev/null +++ b/roms/u-boot/drivers/mmc/tmio-common.c @@ -0,0 +1,783 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Socionext Inc. + * Author: Masahiro Yamada <yamada.masahiro@socionext.com> + */ + +#include <common.h> +#include <clk.h> +#include <cpu_func.h> +#include <fdtdec.h> +#include <mmc.h> +#include <dm.h> +#include <asm/global_data.h> +#include <dm/device_compat.h> +#include <dm/pinctrl.h> +#include <linux/compat.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/sizes.h> +#include <power/regulator.h> +#include <asm/unaligned.h> + +#include "tmio-common.h" + +DECLARE_GLOBAL_DATA_PTR; + +static u64 tmio_sd_readq(struct tmio_sd_priv *priv, unsigned int reg) +{ + return readq(priv->regbase + (reg << 1)); +} + +static void tmio_sd_writeq(struct tmio_sd_priv *priv, + u64 val, unsigned int reg) +{ + writeq(val, priv->regbase + (reg << 1)); +} + +static u16 tmio_sd_readw(struct tmio_sd_priv *priv, unsigned int reg) +{ + return readw(priv->regbase + (reg >> 1)); +} + +static void tmio_sd_writew(struct tmio_sd_priv *priv, + u16 val, unsigned int reg) +{ + writew(val, priv->regbase + (reg >> 1)); +} + +u32 tmio_sd_readl(struct tmio_sd_priv *priv, unsigned int reg) +{ + u32 val; + + if (priv->caps & TMIO_SD_CAP_64BIT) + return readl(priv->regbase + (reg << 1)); + else if (priv->caps & TMIO_SD_CAP_16BIT) { + val = readw(priv->regbase + (reg >> 1)) & 0xffff; + if ((reg == TMIO_SD_RSP10) || (reg == TMIO_SD_RSP32) || + (reg == TMIO_SD_RSP54) || (reg == TMIO_SD_RSP76)) { + val |= readw(priv->regbase + (reg >> 1) + 2) << 16; + } + return val; + } else + return readl(priv->regbase + reg); +} + +void tmio_sd_writel(struct tmio_sd_priv *priv, + u32 val, unsigned int reg) +{ + if (priv->caps & TMIO_SD_CAP_64BIT) + writel(val, priv->regbase + (reg << 1)); + else if (priv->caps & TMIO_SD_CAP_16BIT) { + writew(val & 0xffff, priv->regbase + (reg >> 1)); + if (reg == TMIO_SD_INFO1 || reg == TMIO_SD_INFO1_MASK || + reg == TMIO_SD_INFO2 || reg == TMIO_SD_INFO2_MASK || + reg == TMIO_SD_ARG) + writew(val >> 16, priv->regbase + (reg >> 1) + 2); + } else + writel(val, priv->regbase + reg); +} + +static int tmio_sd_check_error(struct udevice *dev, struct mmc_cmd *cmd) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + u32 info2 = tmio_sd_readl(priv, TMIO_SD_INFO2); + + if (info2 & TMIO_SD_INFO2_ERR_RTO) { + /* + * TIMEOUT must be returned for unsupported command. Do not + * display error log since this might be a part of sequence to + * distinguish between SD and MMC. + */ + return -ETIMEDOUT; + } + + if (info2 & TMIO_SD_INFO2_ERR_TO) { + dev_err(dev, "timeout error\n"); + return -ETIMEDOUT; + } + + if (info2 & (TMIO_SD_INFO2_ERR_END | TMIO_SD_INFO2_ERR_CRC | + TMIO_SD_INFO2_ERR_IDX)) { + if ((cmd->cmdidx != MMC_CMD_SEND_TUNING_BLOCK) && + (cmd->cmdidx != MMC_CMD_SEND_TUNING_BLOCK_HS200)) + dev_err(dev, "communication out of sync\n"); + return -EILSEQ; + } + + if (info2 & (TMIO_SD_INFO2_ERR_ILA | TMIO_SD_INFO2_ERR_ILR | + TMIO_SD_INFO2_ERR_ILW)) { + dev_err(dev, "illegal access\n"); + return -EIO; + } + + return 0; +} + +static int tmio_sd_wait_for_irq(struct udevice *dev, struct mmc_cmd *cmd, + unsigned int reg, u32 flag) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + long wait = 1000000; + int ret; + + while (!(tmio_sd_readl(priv, reg) & flag)) { + if (wait-- < 0) { + dev_err(dev, "timeout\n"); + return -ETIMEDOUT; + } + + ret = tmio_sd_check_error(dev, cmd); + if (ret) + return ret; + + udelay(1); + } + + return 0; +} + +#define tmio_pio_read_fifo(__width, __suffix) \ +static void tmio_pio_read_fifo_##__width(struct tmio_sd_priv *priv, \ + char *pbuf, uint blksz) \ +{ \ + u##__width *buf = (u##__width *)pbuf; \ + int i; \ + \ + if (likely(IS_ALIGNED((uintptr_t)buf, ((__width) / 8)))) { \ + for (i = 0; i < blksz / ((__width) / 8); i++) { \ + *buf++ = tmio_sd_read##__suffix(priv, \ + TMIO_SD_BUF); \ + } \ + } else { \ + for (i = 0; i < blksz / ((__width) / 8); i++) { \ + u##__width data; \ + data = tmio_sd_read##__suffix(priv, \ + TMIO_SD_BUF); \ + put_unaligned(data, buf++); \ + } \ + } \ +} + +tmio_pio_read_fifo(64, q) +tmio_pio_read_fifo(32, l) +tmio_pio_read_fifo(16, w) + +static int tmio_sd_pio_read_one_block(struct udevice *dev, struct mmc_cmd *cmd, + char *pbuf, uint blocksize) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + int ret; + + /* wait until the buffer is filled with data */ + ret = tmio_sd_wait_for_irq(dev, cmd, TMIO_SD_INFO2, + TMIO_SD_INFO2_BRE); + if (ret) + return ret; + + /* + * Clear the status flag _before_ read the buffer out because + * TMIO_SD_INFO2_BRE is edge-triggered, not level-triggered. + */ + tmio_sd_writel(priv, 0, TMIO_SD_INFO2); + + if (priv->caps & TMIO_SD_CAP_64BIT) + tmio_pio_read_fifo_64(priv, pbuf, blocksize); + else if (priv->caps & TMIO_SD_CAP_16BIT) + tmio_pio_read_fifo_16(priv, pbuf, blocksize); + else + tmio_pio_read_fifo_32(priv, pbuf, blocksize); + + return 0; +} + +#define tmio_pio_write_fifo(__width, __suffix) \ +static void tmio_pio_write_fifo_##__width(struct tmio_sd_priv *priv, \ + const char *pbuf, uint blksz)\ +{ \ + const u##__width *buf = (const u##__width *)pbuf; \ + int i; \ + \ + if (likely(IS_ALIGNED((uintptr_t)buf, ((__width) / 8)))) { \ + for (i = 0; i < blksz / ((__width) / 8); i++) { \ + tmio_sd_write##__suffix(priv, *buf++, \ + TMIO_SD_BUF); \ + } \ + } else { \ + for (i = 0; i < blksz / ((__width) / 8); i++) { \ + u##__width data = get_unaligned(buf++); \ + tmio_sd_write##__suffix(priv, data, \ + TMIO_SD_BUF); \ + } \ + } \ +} + +tmio_pio_write_fifo(64, q) +tmio_pio_write_fifo(32, l) +tmio_pio_write_fifo(16, w) + +static int tmio_sd_pio_write_one_block(struct udevice *dev, struct mmc_cmd *cmd, + const char *pbuf, uint blocksize) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + int ret; + + /* wait until the buffer becomes empty */ + ret = tmio_sd_wait_for_irq(dev, cmd, TMIO_SD_INFO2, + TMIO_SD_INFO2_BWE); + if (ret) + return ret; + + tmio_sd_writel(priv, 0, TMIO_SD_INFO2); + + if (priv->caps & TMIO_SD_CAP_64BIT) + tmio_pio_write_fifo_64(priv, pbuf, blocksize); + else if (priv->caps & TMIO_SD_CAP_16BIT) + tmio_pio_write_fifo_16(priv, pbuf, blocksize); + else + tmio_pio_write_fifo_32(priv, pbuf, blocksize); + + return 0; +} + +static int tmio_sd_pio_xfer(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + const char *src = data->src; + char *dest = data->dest; + int i, ret; + + for (i = 0; i < data->blocks; i++) { + if (data->flags & MMC_DATA_READ) + ret = tmio_sd_pio_read_one_block(dev, cmd, dest, + data->blocksize); + else + ret = tmio_sd_pio_write_one_block(dev, cmd, src, + data->blocksize); + if (ret) + return ret; + + if (data->flags & MMC_DATA_READ) + dest += data->blocksize; + else + src += data->blocksize; + } + + return 0; +} + +static void tmio_sd_dma_start(struct tmio_sd_priv *priv, + dma_addr_t dma_addr) +{ + u32 tmp; + + tmio_sd_writel(priv, 0, TMIO_SD_DMA_INFO1); + tmio_sd_writel(priv, 0, TMIO_SD_DMA_INFO2); + + /* enable DMA */ + tmp = tmio_sd_readl(priv, TMIO_SD_EXTMODE); + tmp |= TMIO_SD_EXTMODE_DMA_EN; + tmio_sd_writel(priv, tmp, TMIO_SD_EXTMODE); + + tmio_sd_writel(priv, dma_addr & U32_MAX, TMIO_SD_DMA_ADDR_L); + + /* suppress the warning "right shift count >= width of type" */ + dma_addr >>= min_t(int, 32, 8 * sizeof(dma_addr)); + + tmio_sd_writel(priv, dma_addr & U32_MAX, TMIO_SD_DMA_ADDR_H); + + tmio_sd_writel(priv, TMIO_SD_DMA_CTL_START, TMIO_SD_DMA_CTL); +} + +static int tmio_sd_dma_wait_for_irq(struct udevice *dev, u32 flag, + unsigned int blocks) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + long wait = 1000000 + 10 * blocks; + + while (!(tmio_sd_readl(priv, TMIO_SD_DMA_INFO1) & flag)) { + if (wait-- < 0) { + dev_err(dev, "timeout during DMA\n"); + return -ETIMEDOUT; + } + + udelay(10); + } + + if (tmio_sd_readl(priv, TMIO_SD_DMA_INFO2)) { + dev_err(dev, "error during DMA\n"); + return -EIO; + } + + return 0; +} + +static int tmio_sd_dma_xfer(struct udevice *dev, struct mmc_data *data) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + size_t len = data->blocks * data->blocksize; + void *buf; + enum dma_data_direction dir; + dma_addr_t dma_addr; + u32 poll_flag, tmp; + int ret; + + tmp = tmio_sd_readl(priv, TMIO_SD_DMA_MODE); + + tmp |= priv->idma_bus_width; + + if (data->flags & MMC_DATA_READ) { + buf = data->dest; + dir = DMA_FROM_DEVICE; + /* + * The DMA READ completion flag position differs on Socionext + * and Renesas SoCs. It is bit 20 on Socionext SoCs and using + * bit 17 is a hardware bug and forbidden. It is either bit 17 + * or bit 20 on Renesas SoCs, depending on SoC. + */ + poll_flag = priv->read_poll_flag; + tmp |= TMIO_SD_DMA_MODE_DIR_RD; + } else { + buf = (void *)data->src; + dir = DMA_TO_DEVICE; + poll_flag = TMIO_SD_DMA_INFO1_END_WR; + tmp &= ~TMIO_SD_DMA_MODE_DIR_RD; + } + + tmio_sd_writel(priv, tmp, TMIO_SD_DMA_MODE); + + dma_addr = dma_map_single(buf, len, dir); + + tmio_sd_dma_start(priv, dma_addr); + + ret = tmio_sd_dma_wait_for_irq(dev, poll_flag, data->blocks); + + if (poll_flag == TMIO_SD_DMA_INFO1_END_RD) + udelay(1); + + dma_unmap_single(dma_addr, len, dir); + + return ret; +} + +/* check if the address is DMA'able */ +static bool tmio_sd_addr_is_dmaable(struct mmc_data *data) +{ + uintptr_t addr = (uintptr_t)data->src; + + if (!IS_ALIGNED(addr, TMIO_SD_DMA_MINALIGN)) + return false; + +#if defined(CONFIG_RCAR_GEN3) + if (!(data->flags & MMC_DATA_READ) && !IS_ALIGNED(addr, 128)) + return false; + /* Gen3 DMA has 32bit limit */ + if (addr >> 32) + return false; +#endif + +#if defined(CONFIG_ARCH_UNIPHIER) && !defined(CONFIG_ARM64) && \ + defined(CONFIG_SPL_BUILD) + /* + * For UniPhier ARMv7 SoCs, the stack is allocated in the locked ways + * of L2, which is unreachable from the DMA engine. + */ + if (addr < CONFIG_SPL_STACK) + return false; +#endif + + return true; +} + +int tmio_sd_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + int ret; + u32 tmp; + + if (tmio_sd_readl(priv, TMIO_SD_INFO2) & TMIO_SD_INFO2_CBSY) { + dev_err(dev, "command busy\n"); + return -EBUSY; + } + + /* clear all status flags */ + tmio_sd_writel(priv, 0, TMIO_SD_INFO1); + tmio_sd_writel(priv, 0, TMIO_SD_INFO2); + + /* disable DMA once */ + tmp = tmio_sd_readl(priv, TMIO_SD_EXTMODE); + tmp &= ~TMIO_SD_EXTMODE_DMA_EN; + tmio_sd_writel(priv, tmp, TMIO_SD_EXTMODE); + + tmio_sd_writel(priv, cmd->cmdarg, TMIO_SD_ARG); + + tmp = cmd->cmdidx; + + if (data) { + tmio_sd_writel(priv, data->blocksize, TMIO_SD_SIZE); + tmio_sd_writel(priv, data->blocks, TMIO_SD_SECCNT); + + /* Do not send CMD12 automatically */ + tmp |= TMIO_SD_CMD_NOSTOP | TMIO_SD_CMD_DATA; + + if (data->blocks > 1) + tmp |= TMIO_SD_CMD_MULTI; + + if (data->flags & MMC_DATA_READ) + tmp |= TMIO_SD_CMD_RD; + } + + /* + * Do not use the response type auto-detection on this hardware. + * CMD8, for example, has different response types on SD and eMMC, + * while this controller always assumes the response type for SD. + * Set the response type manually. + */ + switch (cmd->resp_type) { + case MMC_RSP_NONE: + tmp |= TMIO_SD_CMD_RSP_NONE; + break; + case MMC_RSP_R1: + tmp |= TMIO_SD_CMD_RSP_R1; + break; + case MMC_RSP_R1b: + tmp |= TMIO_SD_CMD_RSP_R1B; + break; + case MMC_RSP_R2: + tmp |= TMIO_SD_CMD_RSP_R2; + break; + case MMC_RSP_R3: + tmp |= TMIO_SD_CMD_RSP_R3; + break; + default: + dev_err(dev, "unknown response type\n"); + return -EINVAL; + } + + dev_dbg(dev, "sending CMD%d (SD_CMD=%08x, SD_ARG=%08x)\n", + cmd->cmdidx, tmp, cmd->cmdarg); + tmio_sd_writel(priv, tmp, TMIO_SD_CMD); + + ret = tmio_sd_wait_for_irq(dev, cmd, TMIO_SD_INFO1, + TMIO_SD_INFO1_RSP); + if (ret) + return ret; + + if (cmd->resp_type & MMC_RSP_136) { + u32 rsp_127_104 = tmio_sd_readl(priv, TMIO_SD_RSP76); + u32 rsp_103_72 = tmio_sd_readl(priv, TMIO_SD_RSP54); + u32 rsp_71_40 = tmio_sd_readl(priv, TMIO_SD_RSP32); + u32 rsp_39_8 = tmio_sd_readl(priv, TMIO_SD_RSP10); + + cmd->response[0] = ((rsp_127_104 & 0x00ffffff) << 8) | + ((rsp_103_72 & 0xff000000) >> 24); + cmd->response[1] = ((rsp_103_72 & 0x00ffffff) << 8) | + ((rsp_71_40 & 0xff000000) >> 24); + cmd->response[2] = ((rsp_71_40 & 0x00ffffff) << 8) | + ((rsp_39_8 & 0xff000000) >> 24); + cmd->response[3] = (rsp_39_8 & 0xffffff) << 8; + } else { + /* bit 39-8 */ + cmd->response[0] = tmio_sd_readl(priv, TMIO_SD_RSP10); + } + + if (data) { + /* use DMA if the HW supports it and the buffer is aligned */ + if (priv->caps & TMIO_SD_CAP_DMA_INTERNAL && + tmio_sd_addr_is_dmaable(data)) + ret = tmio_sd_dma_xfer(dev, data); + else + ret = tmio_sd_pio_xfer(dev, cmd, data); + if (ret) + return ret; + + ret = tmio_sd_wait_for_irq(dev, cmd, TMIO_SD_INFO1, + TMIO_SD_INFO1_CMP); + if (ret) + return ret; + } + + return tmio_sd_wait_for_irq(dev, cmd, TMIO_SD_INFO2, + TMIO_SD_INFO2_SCLKDIVEN); +} + +static int tmio_sd_set_bus_width(struct tmio_sd_priv *priv, + struct mmc *mmc) +{ + u32 val, tmp; + + switch (mmc->bus_width) { + case 0: + case 1: + val = TMIO_SD_OPTION_WIDTH_1; + break; + case 4: + val = TMIO_SD_OPTION_WIDTH_4; + break; + case 8: + val = TMIO_SD_OPTION_WIDTH_8; + break; + default: + return -EINVAL; + } + + tmp = tmio_sd_readl(priv, TMIO_SD_OPTION); + tmp &= ~TMIO_SD_OPTION_WIDTH_MASK; + tmp |= val; + tmio_sd_writel(priv, tmp, TMIO_SD_OPTION); + + return 0; +} + +static void tmio_sd_set_ddr_mode(struct tmio_sd_priv *priv, + struct mmc *mmc) +{ + u32 tmp; + + tmp = tmio_sd_readl(priv, TMIO_SD_IF_MODE); + if (mmc->ddr_mode) + tmp |= TMIO_SD_IF_MODE_DDR; + else + tmp &= ~TMIO_SD_IF_MODE_DDR; + tmio_sd_writel(priv, tmp, TMIO_SD_IF_MODE); +} + +static ulong tmio_sd_clk_get_rate(struct tmio_sd_priv *priv) +{ + return priv->clk_get_rate(priv); +} + +static void tmio_sd_set_clk_rate(struct tmio_sd_priv *priv, struct mmc *mmc) +{ + unsigned int divisor; + u32 tmp, val = 0; + ulong mclk; + + if (mmc->clock) { + mclk = tmio_sd_clk_get_rate(priv); + + divisor = DIV_ROUND_UP(mclk, mmc->clock); + + /* Do not set divider to 0xff in DDR mode */ + if (mmc->ddr_mode && (divisor == 1)) + divisor = 2; + + if (divisor <= 1) + val = (priv->caps & TMIO_SD_CAP_RCAR) ? + TMIO_SD_CLKCTL_RCAR_DIV1 : TMIO_SD_CLKCTL_DIV1; + else if (divisor <= 2) + val = TMIO_SD_CLKCTL_DIV2; + else if (divisor <= 4) + val = TMIO_SD_CLKCTL_DIV4; + else if (divisor <= 8) + val = TMIO_SD_CLKCTL_DIV8; + else if (divisor <= 16) + val = TMIO_SD_CLKCTL_DIV16; + else if (divisor <= 32) + val = TMIO_SD_CLKCTL_DIV32; + else if (divisor <= 64) + val = TMIO_SD_CLKCTL_DIV64; + else if (divisor <= 128) + val = TMIO_SD_CLKCTL_DIV128; + else if (divisor <= 256) + val = TMIO_SD_CLKCTL_DIV256; + else if (divisor <= 512 || !(priv->caps & TMIO_SD_CAP_DIV1024)) + val = TMIO_SD_CLKCTL_DIV512; + else + val = TMIO_SD_CLKCTL_DIV1024; + } + + tmp = tmio_sd_readl(priv, TMIO_SD_CLKCTL); + if (mmc->clock && + !((tmp & TMIO_SD_CLKCTL_SCLKEN) && + ((tmp & TMIO_SD_CLKCTL_DIV_MASK) == val))) { + /* + * Stop the clock before changing its rate + * to avoid a glitch signal + */ + tmp &= ~TMIO_SD_CLKCTL_SCLKEN; + tmio_sd_writel(priv, tmp, TMIO_SD_CLKCTL); + + /* Change the clock rate. */ + tmp &= ~TMIO_SD_CLKCTL_DIV_MASK; + tmp |= val; + } + + /* Enable or Disable the clock */ + if (mmc->clk_disable) { + tmp |= TMIO_SD_CLKCTL_OFFEN; + tmp &= ~TMIO_SD_CLKCTL_SCLKEN; + } else { + tmp &= ~TMIO_SD_CLKCTL_OFFEN; + tmp |= TMIO_SD_CLKCTL_SCLKEN; + } + + tmio_sd_writel(priv, tmp, TMIO_SD_CLKCTL); + + udelay(1000); +} + +static void tmio_sd_set_pins(struct udevice *dev) +{ + __maybe_unused struct mmc *mmc = mmc_get_mmc_dev(dev); + +#ifdef CONFIG_DM_REGULATOR + struct tmio_sd_priv *priv = dev_get_priv(dev); + + if (priv->vqmmc_dev) { + if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180) + regulator_set_value(priv->vqmmc_dev, 1800000); + else + regulator_set_value(priv->vqmmc_dev, 3300000); + regulator_set_enable(priv->vqmmc_dev, true); + } +#endif + +#ifdef CONFIG_PINCTRL + if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180) + pinctrl_select_state(dev, "state_uhs"); + else + pinctrl_select_state(dev, "default"); +#endif +} + +int tmio_sd_set_ios(struct udevice *dev) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + struct mmc *mmc = mmc_get_mmc_dev(dev); + int ret; + + dev_dbg(dev, "clock %uHz, DDRmode %d, width %u\n", + mmc->clock, mmc->ddr_mode, mmc->bus_width); + + tmio_sd_set_clk_rate(priv, mmc); + ret = tmio_sd_set_bus_width(priv, mmc); + if (ret) + return ret; + tmio_sd_set_ddr_mode(priv, mmc); + tmio_sd_set_pins(dev); + + return 0; +} + +int tmio_sd_get_cd(struct udevice *dev) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + + if (priv->caps & TMIO_SD_CAP_NONREMOVABLE) + return 1; + + return !!(tmio_sd_readl(priv, TMIO_SD_INFO1) & + TMIO_SD_INFO1_CD); +} + +static void tmio_sd_host_init(struct tmio_sd_priv *priv) +{ + u32 tmp; + + /* soft reset of the host */ + tmp = tmio_sd_readl(priv, TMIO_SD_SOFT_RST); + tmp &= ~TMIO_SD_SOFT_RST_RSTX; + tmio_sd_writel(priv, tmp, TMIO_SD_SOFT_RST); + tmp |= TMIO_SD_SOFT_RST_RSTX; + tmio_sd_writel(priv, tmp, TMIO_SD_SOFT_RST); + + /* FIXME: implement eMMC hw_reset */ + + tmio_sd_writel(priv, TMIO_SD_STOP_SEC, TMIO_SD_STOP); + + /* + * Connected to 32bit AXI. + * This register dropped backward compatibility at version 0x10. + * Write an appropriate value depending on the IP version. + */ + if (priv->version >= 0x10) { + if (priv->caps & TMIO_SD_CAP_64BIT) + tmio_sd_writel(priv, 0x000, TMIO_SD_HOST_MODE); + else + tmio_sd_writel(priv, 0x101, TMIO_SD_HOST_MODE); + } else { + tmio_sd_writel(priv, 0x0, TMIO_SD_HOST_MODE); + } + + if (priv->caps & TMIO_SD_CAP_DMA_INTERNAL) { + tmp = tmio_sd_readl(priv, TMIO_SD_DMA_MODE); + tmp |= TMIO_SD_DMA_MODE_ADDR_INC; + tmp |= priv->idma_bus_width; + tmio_sd_writel(priv, tmp, TMIO_SD_DMA_MODE); + } +} + +int tmio_sd_bind(struct udevice *dev) +{ + struct tmio_sd_plat *plat = dev_get_plat(dev); + + return mmc_bind(dev, &plat->mmc, &plat->cfg); +} + +int tmio_sd_probe(struct udevice *dev, u32 quirks) +{ + struct tmio_sd_plat *plat = dev_get_plat(dev); + struct tmio_sd_priv *priv = dev_get_priv(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + fdt_addr_t base; + ulong mclk; + int ret; + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) + return -EINVAL; + + priv->regbase = devm_ioremap(dev, base, SZ_2K); + if (!priv->regbase) + return -ENOMEM; + +#ifdef CONFIG_DM_REGULATOR + device_get_supply_regulator(dev, "vqmmc-supply", &priv->vqmmc_dev); + if (priv->vqmmc_dev) + regulator_set_value(priv->vqmmc_dev, 3300000); +#endif + + ret = mmc_of_parse(dev, &plat->cfg); + if (ret < 0) { + dev_err(dev, "failed to parse host caps\n"); + return ret; + } + + plat->cfg.name = dev->name; + plat->cfg.host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS; + + if (quirks) + priv->caps = quirks; + + priv->version = tmio_sd_readl(priv, TMIO_SD_VERSION) & + TMIO_SD_VERSION_IP; + dev_dbg(dev, "version %x\n", priv->version); + if (priv->version >= 0x10) { + priv->caps |= TMIO_SD_CAP_DMA_INTERNAL; + priv->caps |= TMIO_SD_CAP_DIV1024; + } + + if (fdt_get_property(gd->fdt_blob, dev_of_offset(dev), "non-removable", + NULL)) + priv->caps |= TMIO_SD_CAP_NONREMOVABLE; + + tmio_sd_host_init(priv); + + mclk = tmio_sd_clk_get_rate(priv); + + plat->cfg.voltages = MMC_VDD_165_195 | MMC_VDD_32_33 | MMC_VDD_33_34; + plat->cfg.f_min = mclk / + (priv->caps & TMIO_SD_CAP_DIV1024 ? 1024 : 512); + plat->cfg.f_max = mclk; + if (quirks & TMIO_SD_CAP_16BIT) + plat->cfg.b_max = U16_MAX; /* max value of TMIO_SD_SECCNT */ + else + plat->cfg.b_max = U32_MAX; /* max value of TMIO_SD_SECCNT */ + + upriv->mmc = &plat->mmc; + + return 0; +} diff --git a/roms/u-boot/drivers/mmc/tmio-common.h b/roms/u-boot/drivers/mmc/tmio-common.h new file mode 100644 index 000000000..59d5a0e22 --- /dev/null +++ b/roms/u-boot/drivers/mmc/tmio-common.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2016 Socionext Inc. + * Author: Masahiro Yamada <yamada.masahiro@socionext.com> + */ + +#ifndef __TMIO_COMMON_H__ +#define __TMIO_COMMON_H__ + +#include <linux/bitops.h> +#define TMIO_SD_CMD 0x000 /* command */ +#define TMIO_SD_CMD_NOSTOP BIT(14) /* No automatic CMD12 issue */ +#define TMIO_SD_CMD_MULTI BIT(13) /* multiple block transfer */ +#define TMIO_SD_CMD_RD BIT(12) /* 1: read, 0: write */ +#define TMIO_SD_CMD_DATA BIT(11) /* data transfer */ +#define TMIO_SD_CMD_APP BIT(6) /* ACMD preceded by CMD55 */ +#define TMIO_SD_CMD_NORMAL (0 << 8)/* auto-detect of resp-type */ +#define TMIO_SD_CMD_RSP_NONE (3 << 8)/* response: none */ +#define TMIO_SD_CMD_RSP_R1 (4 << 8)/* response: R1, R5, R6, R7 */ +#define TMIO_SD_CMD_RSP_R1B (5 << 8)/* response: R1b, R5b */ +#define TMIO_SD_CMD_RSP_R2 (6 << 8)/* response: R2 */ +#define TMIO_SD_CMD_RSP_R3 (7 << 8)/* response: R3, R4 */ +#define TMIO_SD_ARG 0x008 /* command argument */ +#define TMIO_SD_STOP 0x010 /* stop action control */ +#define TMIO_SD_STOP_SEC BIT(8) /* use sector count */ +#define TMIO_SD_STOP_STP BIT(0) /* issue CMD12 */ +#define TMIO_SD_SECCNT 0x014 /* sector counter */ +#define TMIO_SD_RSP10 0x018 /* response[39:8] */ +#define TMIO_SD_RSP32 0x020 /* response[71:40] */ +#define TMIO_SD_RSP54 0x028 /* response[103:72] */ +#define TMIO_SD_RSP76 0x030 /* response[127:104] */ +#define TMIO_SD_INFO1 0x038 /* IRQ status 1 */ +#define TMIO_SD_INFO1_CD BIT(5) /* state of card detect */ +#define TMIO_SD_INFO1_INSERT BIT(4) /* card inserted */ +#define TMIO_SD_INFO1_REMOVE BIT(3) /* card removed */ +#define TMIO_SD_INFO1_CMP BIT(2) /* data complete */ +#define TMIO_SD_INFO1_RSP BIT(0) /* response complete */ +#define TMIO_SD_INFO2 0x03c /* IRQ status 2 */ +#define TMIO_SD_INFO2_ERR_ILA BIT(15) /* illegal access err */ +#define TMIO_SD_INFO2_CBSY BIT(14) /* command busy */ +#define TMIO_SD_INFO2_SCLKDIVEN BIT(13) /* command setting reg ena */ +#define TMIO_SD_INFO2_BWE BIT(9) /* write buffer ready */ +#define TMIO_SD_INFO2_BRE BIT(8) /* read buffer ready */ +#define TMIO_SD_INFO2_DAT0 BIT(7) /* SDDAT0 */ +#define TMIO_SD_INFO2_ERR_RTO BIT(6) /* response time out */ +#define TMIO_SD_INFO2_ERR_ILR BIT(5) /* illegal read err */ +#define TMIO_SD_INFO2_ERR_ILW BIT(4) /* illegal write err */ +#define TMIO_SD_INFO2_ERR_TO BIT(3) /* time out error */ +#define TMIO_SD_INFO2_ERR_END BIT(2) /* END bit error */ +#define TMIO_SD_INFO2_ERR_CRC BIT(1) /* CRC error */ +#define TMIO_SD_INFO2_ERR_IDX BIT(0) /* cmd index error */ +#define TMIO_SD_INFO1_MASK 0x040 +#define TMIO_SD_INFO2_MASK 0x044 +#define TMIO_SD_CLKCTL 0x048 /* clock divisor */ +#define TMIO_SD_CLKCTL_DIV_MASK 0x104ff +#define TMIO_SD_CLKCTL_DIV1024 BIT(16) /* SDCLK = CLK / 1024 */ +#define TMIO_SD_CLKCTL_DIV512 BIT(7) /* SDCLK = CLK / 512 */ +#define TMIO_SD_CLKCTL_DIV256 BIT(6) /* SDCLK = CLK / 256 */ +#define TMIO_SD_CLKCTL_DIV128 BIT(5) /* SDCLK = CLK / 128 */ +#define TMIO_SD_CLKCTL_DIV64 BIT(4) /* SDCLK = CLK / 64 */ +#define TMIO_SD_CLKCTL_DIV32 BIT(3) /* SDCLK = CLK / 32 */ +#define TMIO_SD_CLKCTL_DIV16 BIT(2) /* SDCLK = CLK / 16 */ +#define TMIO_SD_CLKCTL_DIV8 BIT(1) /* SDCLK = CLK / 8 */ +#define TMIO_SD_CLKCTL_DIV4 BIT(0) /* SDCLK = CLK / 4 */ +#define TMIO_SD_CLKCTL_DIV2 0 /* SDCLK = CLK / 2 */ +#define TMIO_SD_CLKCTL_DIV1 BIT(10) /* SDCLK = CLK */ +#define TMIO_SD_CLKCTL_RCAR_DIV1 0xff /* SDCLK = CLK (RCar ver.) */ +#define TMIO_SD_CLKCTL_OFFEN BIT(9) /* stop SDCLK when unused */ +#define TMIO_SD_CLKCTL_SCLKEN BIT(8) /* SDCLK output enable */ +#define TMIO_SD_SIZE 0x04c /* block size */ +#define TMIO_SD_OPTION 0x050 +#define TMIO_SD_OPTION_WIDTH_MASK (5 << 13) +#define TMIO_SD_OPTION_WIDTH_1 (4 << 13) +#define TMIO_SD_OPTION_WIDTH_4 (0 << 13) +#define TMIO_SD_OPTION_WIDTH_8 (1 << 13) +#define TMIO_SD_BUF 0x060 /* read/write buffer */ +#define TMIO_SD_EXTMODE 0x1b0 +#define TMIO_SD_EXTMODE_DMA_EN BIT(1) /* transfer 1: DMA, 0: pio */ +#define TMIO_SD_SOFT_RST 0x1c0 +#define TMIO_SD_SOFT_RST_RSTX BIT(0) /* reset deassert */ +#define TMIO_SD_VERSION 0x1c4 /* version register */ +#define TMIO_SD_VERSION_IP 0xff /* IP version */ +#define TMIO_SD_HOST_MODE 0x1c8 +#define TMIO_SD_IF_MODE 0x1cc +#define TMIO_SD_IF_MODE_DDR BIT(0) /* DDR mode */ +#define TMIO_SD_VOLT 0x1e4 /* voltage switch */ +#define TMIO_SD_VOLT_MASK (3 << 0) +#define TMIO_SD_VOLT_OFF (0 << 0) +#define TMIO_SD_VOLT_330 (1 << 0)/* 3.3V signal */ +#define TMIO_SD_VOLT_180 (2 << 0)/* 1.8V signal */ +#define TMIO_SD_DMA_MODE 0x410 +#define TMIO_SD_DMA_MODE_DIR_RD BIT(16) /* 1: from device, 0: to dev */ +#define TMIO_SD_DMA_MODE_BUS_WIDTH (BIT(5) | BIT(4)) /* RCar, 64bit */ +#define TMIO_SD_DMA_MODE_ADDR_INC BIT(0) /* 1: address inc, 0: fixed */ +#define TMIO_SD_DMA_CTL 0x414 +#define TMIO_SD_DMA_CTL_START BIT(0) /* start DMA (auto cleared) */ +#define TMIO_SD_DMA_RST 0x418 +#define TMIO_SD_DMA_RST_RD BIT(9) +#define TMIO_SD_DMA_RST_WR BIT(8) +#define TMIO_SD_DMA_INFO1 0x420 +#define TMIO_SD_DMA_INFO1_END_RD2 BIT(20) /* DMA from device is complete (uniphier) */ +#define TMIO_SD_DMA_INFO1_END_RD BIT(17) /* DMA from device is complete (renesas) */ +#define TMIO_SD_DMA_INFO1_END_WR BIT(16) /* DMA to device is complete */ +#define TMIO_SD_DMA_INFO1_MASK 0x424 +#define TMIO_SD_DMA_INFO2 0x428 +#define TMIO_SD_DMA_INFO2_ERR_RD BIT(17) +#define TMIO_SD_DMA_INFO2_ERR_WR BIT(16) +#define TMIO_SD_DMA_INFO2_MASK 0x42c +#define TMIO_SD_DMA_ADDR_L 0x440 +#define TMIO_SD_DMA_ADDR_H 0x444 + +/* alignment required by the DMA engine of this controller */ +#define TMIO_SD_DMA_MINALIGN 0x10 + +struct tmio_sd_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct tmio_sd_priv { + void __iomem *regbase; + unsigned int version; + u32 caps; + u32 read_poll_flag; + u32 idma_bus_width; +#define TMIO_SD_CAP_NONREMOVABLE BIT(0) /* Nonremovable e.g. eMMC */ +#define TMIO_SD_CAP_DMA_INTERNAL BIT(1) /* have internal DMA engine */ +#define TMIO_SD_CAP_DIV1024 BIT(2) /* divisor 1024 is available */ +#define TMIO_SD_CAP_64BIT BIT(3) /* Controller is 64bit */ +#define TMIO_SD_CAP_16BIT BIT(4) /* Controller is 16bit */ +#define TMIO_SD_CAP_RCAR_GEN2 BIT(5) /* Renesas RCar version of IP */ +#define TMIO_SD_CAP_RCAR_GEN3 BIT(6) /* Renesas RCar version of IP */ +#define TMIO_SD_CAP_RCAR_UHS BIT(7) /* Renesas RCar UHS/SDR modes */ +#define TMIO_SD_CAP_RCAR \ + (TMIO_SD_CAP_RCAR_GEN2 | TMIO_SD_CAP_RCAR_GEN3) +#ifdef CONFIG_DM_REGULATOR + struct udevice *vqmmc_dev; +#endif +#if CONFIG_IS_ENABLED(CLK) + struct clk clk; +#endif +#if CONFIG_IS_ENABLED(RENESAS_SDHI) + unsigned int smpcmp; + u8 tap_set; + u8 tap_num; + u8 nrtaps; + bool needs_adjust_hs400; + bool adjust_hs400_enable; + u8 adjust_hs400_offset; + u8 adjust_hs400_calibrate; + u8 hs400_bad_tap; + const u8 *adjust_hs400_calib_table; + u32 quirks; +#endif + ulong (*clk_get_rate)(struct tmio_sd_priv *); +}; + +int tmio_sd_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, + struct mmc_data *data); +int tmio_sd_set_ios(struct udevice *dev); +int tmio_sd_get_cd(struct udevice *dev); + +int tmio_sd_bind(struct udevice *dev); +int tmio_sd_probe(struct udevice *dev, u32 quirks); + +u32 tmio_sd_readl(struct tmio_sd_priv *priv, unsigned int reg); +void tmio_sd_writel(struct tmio_sd_priv *priv, + u32 val, unsigned int reg); + +#endif /* __TMIO_COMMON_H__ */ diff --git a/roms/u-boot/drivers/mmc/uniphier-sd.c b/roms/u-boot/drivers/mmc/uniphier-sd.c new file mode 100644 index 000000000..75003a011 --- /dev/null +++ b/roms/u-boot/drivers/mmc/uniphier-sd.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Socionext Inc. + * Author: Masahiro Yamada <yamada.masahiro@socionext.com> + */ + +#include <common.h> +#include <clk.h> +#include <fdtdec.h> +#include <malloc.h> +#include <mmc.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <linux/compat.h> +#include <linux/dma-direction.h> +#include <linux/io.h> +#include <linux/sizes.h> +#include <power/regulator.h> +#include <asm/unaligned.h> + +#include "tmio-common.h" + +static const struct dm_mmc_ops uniphier_sd_ops = { + .send_cmd = tmio_sd_send_cmd, + .set_ios = tmio_sd_set_ios, + .get_cd = tmio_sd_get_cd, +}; + +static const struct udevice_id uniphier_sd_match[] = { + { .compatible = "socionext,uniphier-sd-v2.91" }, + { .compatible = "socionext,uniphier-sd-v3.1" }, + { .compatible = "socionext,uniphier-sd-v3.1.1" }, + { /* sentinel */ } +}; + +static ulong uniphier_sd_clk_get_rate(struct tmio_sd_priv *priv) +{ +#if CONFIG_IS_ENABLED(CLK) + return clk_get_rate(&priv->clk); +#elif CONFIG_SPL_BUILD + return 100000000; +#else + return 0; +#endif +} + +static int uniphier_sd_probe(struct udevice *dev) +{ + struct tmio_sd_priv *priv = dev_get_priv(dev); + + priv->clk_get_rate = uniphier_sd_clk_get_rate; + priv->read_poll_flag = TMIO_SD_DMA_INFO1_END_RD2; + +#ifndef CONFIG_SPL_BUILD + int ret; + + ret = clk_get_by_index(dev, 0, &priv->clk); + if (ret < 0) { + dev_err(dev, "failed to get host clock\n"); + return ret; + } + + /* set to max rate */ + ret = clk_set_rate(&priv->clk, ULONG_MAX); + if (ret < 0) { + dev_err(dev, "failed to set rate for host clock\n"); + clk_free(&priv->clk); + return ret; + } + + ret = clk_enable(&priv->clk); + if (ret) { + dev_err(dev, "failed to enable host clock\n"); + return ret; + } +#endif + + return tmio_sd_probe(dev, 0); +} + +U_BOOT_DRIVER(uniphier_mmc) = { + .name = "uniphier-mmc", + .id = UCLASS_MMC, + .of_match = uniphier_sd_match, + .bind = tmio_sd_bind, + .probe = uniphier_sd_probe, + .priv_auto = sizeof(struct tmio_sd_priv), + .plat_auto = sizeof(struct tmio_sd_plat), + .ops = &uniphier_sd_ops, +}; diff --git a/roms/u-boot/drivers/mmc/xenon_sdhci.c b/roms/u-boot/drivers/mmc/xenon_sdhci.c new file mode 100644 index 000000000..e292f2903 --- /dev/null +++ b/roms/u-boot/drivers/mmc/xenon_sdhci.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Marvell SOC Platform Group Xenon SDHC as a platform device + * + * Copyright (C) 2016 Marvell, All Rights Reserved. + * + * Author: Victor Gu <xigu@marvell.com> + * Date: 2016-8-24 + * + * Included parts of the Linux driver version which was written by: + * Hu Ziji <huziji@marvell.com> + * + * Ported to from Marvell 2015.01 to mainline U-Boot 2017.01: + * Stefan Roese <sr@denx.de> + */ + +#include <common.h> +#include <dm.h> +#include <fdtdec.h> +#include <asm/global_data.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/libfdt.h> +#include <malloc.h> +#include <sdhci.h> +#include <power/regulator.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Register Offset of SD Host Controller SOCP self-defined register */ +#define SDHC_SYS_CFG_INFO 0x0104 +#define SLOT_TYPE_SDIO_SHIFT 24 +#define SLOT_TYPE_EMMC_MASK 0xFF +#define SLOT_TYPE_EMMC_SHIFT 16 +#define SLOT_TYPE_SD_SDIO_MMC_MASK 0xFF +#define SLOT_TYPE_SD_SDIO_MMC_SHIFT 8 +#define NR_SUPPORTED_SLOT_MASK 0x7 + +#define SDHC_SYS_OP_CTRL 0x0108 +#define AUTO_CLKGATE_DISABLE_MASK BIT(20) +#define SDCLK_IDLEOFF_ENABLE_SHIFT 8 +#define SLOT_ENABLE_SHIFT 0 + +#define SDHC_SYS_EXT_OP_CTRL 0x010C +#define MASK_CMD_CONFLICT_ERROR BIT(8) + +#define SDHC_SLOT_EMMC_CTRL 0x0130 +#define ENABLE_DATA_STROBE_SHIFT 24 +#define SET_EMMC_RSTN_SHIFT 16 +#define EMMC_VCCQ_MASK 0x3 +#define EMMC_VCCQ_1_8V 0x1 +#define EMMC_VCCQ_1_2V 0x2 +#define EMMC_VCCQ_3_3V 0x3 + +#define SDHC_SLOT_RETUNING_REQ_CTRL 0x0144 +/* retuning compatible */ +#define RETUNING_COMPATIBLE 0x1 + +/* Xenon specific Mode Select value */ +#define XENON_SDHCI_CTRL_HS200 0x5 +#define XENON_SDHCI_CTRL_HS400 0x6 + +#define EMMC_PHY_REG_BASE 0x170 +#define EMMC_PHY_TIMING_ADJUST EMMC_PHY_REG_BASE +#define OUTPUT_QSN_PHASE_SELECT BIT(17) +#define SAMPL_INV_QSP_PHASE_SELECT BIT(18) +#define SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18 +#define EMMC_PHY_SLOW_MODE BIT(29) +#define PHY_INITIALIZAION BIT(31) +#define WAIT_CYCLE_BEFORE_USING_MASK 0xf +#define WAIT_CYCLE_BEFORE_USING_SHIFT 12 +#define FC_SYNC_EN_DURATION_MASK 0xf +#define FC_SYNC_EN_DURATION_SHIFT 8 +#define FC_SYNC_RST_EN_DURATION_MASK 0xf +#define FC_SYNC_RST_EN_DURATION_SHIFT 4 +#define FC_SYNC_RST_DURATION_MASK 0xf +#define FC_SYNC_RST_DURATION_SHIFT 0 + +#define EMMC_PHY_FUNC_CONTROL (EMMC_PHY_REG_BASE + 0x4) +#define DQ_ASYNC_MODE BIT(4) +#define DQ_DDR_MODE_SHIFT 8 +#define DQ_DDR_MODE_MASK 0xff +#define CMD_DDR_MODE BIT(16) + +#define EMMC_PHY_PAD_CONTROL (EMMC_PHY_REG_BASE + 0x8) +#define REC_EN_SHIFT 24 +#define REC_EN_MASK 0xf +#define FC_DQ_RECEN BIT(24) +#define FC_CMD_RECEN BIT(25) +#define FC_QSP_RECEN BIT(26) +#define FC_QSN_RECEN BIT(27) +#define OEN_QSN BIT(28) +#define AUTO_RECEN_CTRL BIT(30) + +#define EMMC_PHY_PAD_CONTROL1 (EMMC_PHY_REG_BASE + 0xc) +#define EMMC5_1_FC_QSP_PD BIT(9) +#define EMMC5_1_FC_QSP_PU BIT(25) +#define EMMC5_1_FC_CMD_PD BIT(8) +#define EMMC5_1_FC_CMD_PU BIT(24) +#define EMMC5_1_FC_DQ_PD 0xff +#define EMMC5_1_FC_DQ_PU (0xff << 16) + +#define SDHCI_RETUNE_EVT_INTSIG 0x00001000 + +/* Hyperion only have one slot 0 */ +#define XENON_MMC_SLOT_ID_HYPERION 0 + +#define XENON_MMC_MAX_CLK 400000000 +#define XENON_MMC_3V3_UV 3300000 +#define XENON_MMC_1V8_UV 1800000 + +enum soc_pad_ctrl_type { + SOC_PAD_SD, + SOC_PAD_FIXED_1_8V, +}; + +struct xenon_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct xenon_sdhci_priv { + struct sdhci_host host; + + u8 timing; + + unsigned int clock; + + void *pad_ctrl_reg; + int pad_type; + + struct udevice *vqmmc; +}; + +static int xenon_mmc_phy_init(struct sdhci_host *host) +{ + struct xenon_sdhci_priv *priv = host->mmc->priv; + u32 clock = priv->clock; + u32 time; + u32 var; + + /* Enable QSP PHASE SELECT */ + var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST); + var |= SAMPL_INV_QSP_PHASE_SELECT; + if ((priv->timing == MMC_TIMING_UHS_SDR50) || + (priv->timing == MMC_TIMING_UHS_SDR25) || + (priv->timing == MMC_TIMING_UHS_SDR12) || + (priv->timing == MMC_TIMING_SD_HS) || + (priv->timing == MMC_TIMING_LEGACY)) + var |= EMMC_PHY_SLOW_MODE; + sdhci_writel(host, var, EMMC_PHY_TIMING_ADJUST); + + /* Poll for host MMC PHY clock init to be stable */ + /* Wait up to 10ms */ + time = 100; + while (time--) { + var = sdhci_readl(host, SDHCI_CLOCK_CONTROL); + if (var & SDHCI_CLOCK_INT_STABLE) + break; + + udelay(100); + } + + if (time <= 0) { + pr_err("Failed to enable MMC internal clock in time\n"); + return -ETIMEDOUT; + } + + /* Init PHY */ + var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST); + var |= PHY_INITIALIZAION; + sdhci_writel(host, var, EMMC_PHY_TIMING_ADJUST); + + if (clock == 0) { + /* Use the possibly slowest bus frequency value */ + clock = 100000; + } + + /* Poll for host eMMC PHY init to complete */ + /* Wait up to 10ms */ + time = 100; + while (time--) { + var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST); + var &= PHY_INITIALIZAION; + if (!var) + break; + + /* wait for host eMMC PHY init to complete */ + udelay(100); + } + + if (time <= 0) { + pr_err("Failed to init MMC PHY in time\n"); + return -ETIMEDOUT; + } + + return 0; +} + +#define ARMADA_3700_SOC_PAD_1_8V 0x1 +#define ARMADA_3700_SOC_PAD_3_3V 0x0 + +static void armada_3700_soc_pad_voltage_set(struct sdhci_host *host) +{ + struct xenon_sdhci_priv *priv = host->mmc->priv; + + if (priv->pad_type == SOC_PAD_FIXED_1_8V) + writel(ARMADA_3700_SOC_PAD_1_8V, priv->pad_ctrl_reg); + else if (priv->pad_type == SOC_PAD_SD) + writel(ARMADA_3700_SOC_PAD_3_3V, priv->pad_ctrl_reg); +} + +static int xenon_mmc_start_signal_voltage_switch(struct sdhci_host *host) +{ + struct xenon_sdhci_priv *priv = host->mmc->priv; + u8 voltage; + u32 ctrl; + int ret = 0; + + /* If there is no vqmmc regulator, return */ + if (!priv->vqmmc) + return 0; + + if (priv->pad_type == SOC_PAD_FIXED_1_8V) { + /* Switch to 1.8v */ + ret = regulator_set_value(priv->vqmmc, + XENON_MMC_1V8_UV); + } else if (priv->pad_type == SOC_PAD_SD) { + /* Get voltage info */ + voltage = sdhci_readb(host, SDHCI_POWER_CONTROL); + voltage &= ~SDHCI_POWER_ON; + + if (voltage == SDHCI_POWER_330) { + /* Switch to 3.3v */ + ret = regulator_set_value(priv->vqmmc, + XENON_MMC_3V3_UV); + } else { + /* Switch to 1.8v */ + ret = regulator_set_value(priv->vqmmc, + XENON_MMC_1V8_UV); + } + } + + /* Set VCCQ, eMMC mode: 1.8V; SD/SDIO mode: 3.3V */ + ctrl = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL); + if (IS_SD(host->mmc)) + ctrl |= EMMC_VCCQ_3_3V; + else + ctrl |= EMMC_VCCQ_1_8V; + sdhci_writel(host, ctrl, SDHC_SLOT_EMMC_CTRL); + + if (ret) + printf("Signal voltage switch fail\n"); + + return ret; +} + +static void xenon_mmc_phy_set(struct sdhci_host *host) +{ + struct xenon_sdhci_priv *priv = host->mmc->priv; + u32 var; + + /* Setup pad, set bit[30], bit[28] and bits[26:24] */ + var = sdhci_readl(host, EMMC_PHY_PAD_CONTROL); + var |= AUTO_RECEN_CTRL | OEN_QSN | FC_QSP_RECEN | + FC_CMD_RECEN | FC_DQ_RECEN; + sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL); + + /* Set CMD and DQ Pull Up */ + var = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1); + var |= (EMMC5_1_FC_CMD_PU | EMMC5_1_FC_DQ_PU); + var &= ~(EMMC5_1_FC_CMD_PD | EMMC5_1_FC_DQ_PD); + sdhci_writel(host, var, EMMC_PHY_PAD_CONTROL1); + + /* + * If timing belongs to high speed, set bit[17] of + * EMMC_PHY_TIMING_ADJUST register + */ + if ((priv->timing == MMC_TIMING_MMC_HS400) || + (priv->timing == MMC_TIMING_MMC_HS200) || + (priv->timing == MMC_TIMING_UHS_SDR50) || + (priv->timing == MMC_TIMING_UHS_SDR104) || + (priv->timing == MMC_TIMING_UHS_DDR50) || + (priv->timing == MMC_TIMING_UHS_SDR25) || + (priv->timing == MMC_TIMING_MMC_DDR52)) { + var = sdhci_readl(host, EMMC_PHY_TIMING_ADJUST); + var |= OUTPUT_QSN_PHASE_SELECT; + sdhci_writel(host, var, EMMC_PHY_TIMING_ADJUST); + } + + /* + * When setting EMMC_PHY_FUNC_CONTROL register, + * SD clock should be disabled + */ + var = sdhci_readl(host, SDHCI_CLOCK_CONTROL); + var &= ~SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, var, SDHCI_CLOCK_CONTROL); + + var = sdhci_readl(host, EMMC_PHY_FUNC_CONTROL); + if (host->mmc->ddr_mode) { + var |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE; + } else { + var &= ~((DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | + CMD_DDR_MODE); + } + sdhci_writel(host, var, EMMC_PHY_FUNC_CONTROL); + + /* Enable bus clock */ + var = sdhci_readl(host, SDHCI_CLOCK_CONTROL); + var |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, var, SDHCI_CLOCK_CONTROL); + + xenon_mmc_phy_init(host); +} + +/* Enable/Disable the Auto Clock Gating function of this slot */ +static void xenon_mmc_set_acg(struct sdhci_host *host, bool enable) +{ + u32 var; + + var = sdhci_readl(host, SDHC_SYS_OP_CTRL); + if (enable) + var &= ~AUTO_CLKGATE_DISABLE_MASK; + else + var |= AUTO_CLKGATE_DISABLE_MASK; + + sdhci_writel(host, var, SDHC_SYS_OP_CTRL); +} + +#define SLOT_MASK(slot) BIT(slot) + +/* Enable specific slot */ +static void xenon_mmc_enable_slot(struct sdhci_host *host, u8 slot) +{ + u32 var; + + var = sdhci_readl(host, SDHC_SYS_OP_CTRL); + var |= SLOT_MASK(slot) << SLOT_ENABLE_SHIFT; + sdhci_writel(host, var, SDHC_SYS_OP_CTRL); +} + +/* Disable specific slot */ +static void xenon_mmc_disable_slot(struct sdhci_host *host, u8 slot) +{ + u32 var; + + var = sdhci_readl(host, SDHC_SYS_OP_CTRL); + var &= ~(SLOT_MASK(slot) << SLOT_ENABLE_SHIFT); + sdhci_writel(host, var, SDHC_SYS_OP_CTRL); +} + +/* Enable Parallel Transfer Mode */ +static void xenon_mmc_enable_parallel_tran(struct sdhci_host *host, u8 slot) +{ + u32 var; + + var = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL); + var |= SLOT_MASK(slot); + sdhci_writel(host, var, SDHC_SYS_EXT_OP_CTRL); +} + +static void xenon_mmc_disable_tuning(struct sdhci_host *host, u8 slot) +{ + u32 var; + + /* Clear the Re-Tuning Request functionality */ + var = sdhci_readl(host, SDHC_SLOT_RETUNING_REQ_CTRL); + var &= ~RETUNING_COMPATIBLE; + sdhci_writel(host, var, SDHC_SLOT_RETUNING_REQ_CTRL); + + /* Clear the Re-tuning Event Signal Enable */ + var = sdhci_readl(host, SDHCI_SIGNAL_ENABLE); + var &= ~SDHCI_RETUNE_EVT_INTSIG; + sdhci_writel(host, var, SDHCI_SIGNAL_ENABLE); +} + +/* Mask command conflict error */ +static void xenon_mask_cmd_conflict_err(struct sdhci_host *host) +{ + u32 reg; + + reg = sdhci_readl(host, SDHC_SYS_EXT_OP_CTRL); + reg |= MASK_CMD_CONFLICT_ERROR; + sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL); +} + +/* Platform specific function for post set_ios configuration */ +static int xenon_sdhci_set_ios_post(struct sdhci_host *host) +{ + struct xenon_sdhci_priv *priv = host->mmc->priv; + uint speed = host->mmc->tran_speed; + int pwr_18v = 0; + + /* + * Signal Voltage Switching is only applicable for Host Controllers + * v3.00 and above. + */ + if (SDHCI_GET_VERSION(host) >= SDHCI_SPEC_300) + xenon_mmc_start_signal_voltage_switch(host); + + if ((sdhci_readb(host, SDHCI_POWER_CONTROL) & ~SDHCI_POWER_ON) == + SDHCI_POWER_180) + pwr_18v = 1; + + /* Set timing variable according to the configured speed */ + if (IS_SD(host->mmc)) { + /* SD/SDIO */ + if (pwr_18v) { + if (host->mmc->ddr_mode) + priv->timing = MMC_TIMING_UHS_DDR50; + else if (speed <= 25000000) + priv->timing = MMC_TIMING_UHS_SDR25; + else + priv->timing = MMC_TIMING_UHS_SDR50; + } else { + if (speed <= 25000000) + priv->timing = MMC_TIMING_LEGACY; + else + priv->timing = MMC_TIMING_SD_HS; + } + } else { + /* eMMC */ + if (host->mmc->ddr_mode) + priv->timing = MMC_TIMING_MMC_DDR52; + else if (speed <= 26000000) + priv->timing = MMC_TIMING_LEGACY; + else + priv->timing = MMC_TIMING_MMC_HS; + } + + /* Re-init the PHY */ + xenon_mmc_phy_set(host); + + return 0; +} + +/* Install a driver specific handler for post set_ios configuration */ +static const struct sdhci_ops xenon_sdhci_ops = { + .set_ios_post = xenon_sdhci_set_ios_post +}; + +static int xenon_sdhci_probe(struct udevice *dev) +{ + struct xenon_sdhci_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct xenon_sdhci_priv *priv = dev_get_priv(dev); + struct sdhci_host *host = dev_get_priv(dev); + int ret; + + host->mmc = &plat->mmc; + host->mmc->priv = host; + host->mmc->dev = dev; + upriv->mmc = host->mmc; + + /* Set quirks */ + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_32BIT_DMA_ADDR; + + /* Set default timing */ + priv->timing = MMC_TIMING_LEGACY; + + /* Get the vqmmc regulator if there is */ + device_get_supply_regulator(dev, "vqmmc-supply", &priv->vqmmc); + /* Set the initial voltage value to 3.3V if there is regulator */ + if (priv->vqmmc) { + ret = regulator_set_value(priv->vqmmc, + XENON_MMC_3V3_UV); + if (ret) { + printf("Failed to set VQMMC regulator to 3.3V\n"); + return ret; + } + } + + /* Disable auto clock gating during init */ + xenon_mmc_set_acg(host, false); + + /* Enable slot */ + xenon_mmc_enable_slot(host, XENON_MMC_SLOT_ID_HYPERION); + + /* + * Set default power on SoC PHY PAD register (currently only + * available on the Armada 3700) + */ + if (priv->pad_ctrl_reg) + armada_3700_soc_pad_voltage_set(host); + + host->host_caps = MMC_MODE_HS | MMC_MODE_HS_52MHz | MMC_MODE_DDR_52MHz; + + ret = mmc_of_parse(dev, &plat->cfg); + if (ret) + return ret; + + host->ops = &xenon_sdhci_ops; + + host->max_clk = XENON_MMC_MAX_CLK; + ret = sdhci_setup_cfg(&plat->cfg, host, XENON_MMC_MAX_CLK, 0); + if (ret) + return ret; + + ret = sdhci_probe(dev); + if (ret) + return ret; + + /* Enable parallel transfer */ + xenon_mmc_enable_parallel_tran(host, XENON_MMC_SLOT_ID_HYPERION); + + /* Disable tuning functionality of this slot */ + xenon_mmc_disable_tuning(host, XENON_MMC_SLOT_ID_HYPERION); + + /* Enable auto clock gating after init */ + xenon_mmc_set_acg(host, true); + + xenon_mask_cmd_conflict_err(host); + + return ret; +} + +static int xenon_sdhci_remove(struct udevice *dev) +{ + struct sdhci_host *host = dev_get_priv(dev); + + xenon_mmc_disable_slot(host, XENON_MMC_SLOT_ID_HYPERION); + return 0; +} + +static int xenon_sdhci_of_to_plat(struct udevice *dev) +{ + struct sdhci_host *host = dev_get_priv(dev); + struct xenon_sdhci_priv *priv = dev_get_priv(dev); + const char *name; + + host->name = dev->name; + host->ioaddr = dev_read_addr_ptr(dev); + + if (device_is_compatible(dev, "marvell,armada-3700-sdhci")) + priv->pad_ctrl_reg = (void *)devfdt_get_addr_index(dev, 1); + + name = fdt_getprop(gd->fdt_blob, dev_of_offset(dev), "marvell,pad-type", + NULL); + if (name) { + if (0 == strncmp(name, "sd", 2)) { + priv->pad_type = SOC_PAD_SD; + } else if (0 == strncmp(name, "fixed-1-8v", 10)) { + priv->pad_type = SOC_PAD_FIXED_1_8V; + } else { + printf("Unsupported SOC PHY PAD ctrl type %s\n", name); + return -EINVAL; + } + } + + return 0; +} + +static int xenon_sdhci_bind(struct udevice *dev) +{ + struct xenon_sdhci_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id xenon_sdhci_ids[] = { + { .compatible = "marvell,armada-8k-sdhci",}, + { .compatible = "marvell,armada-3700-sdhci",}, + { } +}; + +U_BOOT_DRIVER(xenon_sdhci_drv) = { + .name = "xenon_sdhci", + .id = UCLASS_MMC, + .of_match = xenon_sdhci_ids, + .of_to_plat = xenon_sdhci_of_to_plat, + .ops = &sdhci_ops, + .bind = xenon_sdhci_bind, + .probe = xenon_sdhci_probe, + .remove = xenon_sdhci_remove, + .priv_auto = sizeof(struct xenon_sdhci_priv), + .plat_auto = sizeof(struct xenon_sdhci_plat), +}; diff --git a/roms/u-boot/drivers/mmc/zynq_sdhci.c b/roms/u-boot/drivers/mmc/zynq_sdhci.c new file mode 100644 index 000000000..b79c4021b --- /dev/null +++ b/roms/u-boot/drivers/mmc/zynq_sdhci.c @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2013 - 2015 Xilinx, Inc. + * + * Xilinx Zynq SD Host Controller Interface + */ + +#include <clk.h> +#include <common.h> +#include <dm.h> +#include <fdtdec.h> +#include <linux/delay.h> +#include "mmc_private.h" +#include <log.h> +#include <dm/device_compat.h> +#include <linux/err.h> +#include <linux/libfdt.h> +#include <malloc.h> +#include <sdhci.h> +#include <zynqmp_tap_delay.h> + +#define SDHCI_ARASAN_ITAPDLY_REGISTER 0xF0F8 +#define SDHCI_ARASAN_OTAPDLY_REGISTER 0xF0FC +#define SDHCI_ITAPDLY_CHGWIN 0x200 +#define SDHCI_ITAPDLY_ENABLE 0x100 +#define SDHCI_OTAPDLY_ENABLE 0x40 + +#define SDHCI_TUNING_LOOP_COUNT 40 +#define MMC_BANK2 0x2 + +struct arasan_sdhci_clk_data { + int clk_phase_in[MMC_TIMING_MMC_HS400 + 1]; + int clk_phase_out[MMC_TIMING_MMC_HS400 + 1]; +}; + +struct arasan_sdhci_plat { + struct mmc_config cfg; + struct mmc mmc; +}; + +struct arasan_sdhci_priv { + struct sdhci_host *host; + struct arasan_sdhci_clk_data clk_data; + u8 deviceid; + u8 bank; + u8 no_1p8; +}; + +#if defined(CONFIG_ARCH_ZYNQMP) || defined(CONFIG_ARCH_VERSAL) +/* Default settings for ZynqMP Clock Phases */ +const u32 zynqmp_iclk_phases[] = {0, 63, 63, 0, 63, 0, 0, 183, 54, 0, 0}; +const u32 zynqmp_oclk_phases[] = {0, 72, 60, 0, 60, 72, 135, 48, 72, 135, 0}; + +/* Default settings for Versal Clock Phases */ +const u32 versal_iclk_phases[] = {0, 132, 132, 0, 132, 0, 0, 162, 90, 0, 0}; +const u32 versal_oclk_phases[] = {0, 60, 48, 0, 48, 72, 90, 36, 60, 90, 0}; + +static const u8 mode2timing[] = { + [MMC_LEGACY] = MMC_TIMING_LEGACY, + [MMC_HS] = MMC_TIMING_MMC_HS, + [SD_HS] = MMC_TIMING_SD_HS, + [MMC_HS_52] = MMC_TIMING_UHS_SDR50, + [MMC_DDR_52] = MMC_TIMING_UHS_DDR50, + [UHS_SDR12] = MMC_TIMING_UHS_SDR12, + [UHS_SDR25] = MMC_TIMING_UHS_SDR25, + [UHS_SDR50] = MMC_TIMING_UHS_SDR50, + [UHS_DDR50] = MMC_TIMING_UHS_DDR50, + [UHS_SDR104] = MMC_TIMING_UHS_SDR104, + [MMC_HS_200] = MMC_TIMING_MMC_HS200, +}; + +static void arasan_zynqmp_dll_reset(struct sdhci_host *host, u8 deviceid) +{ + u16 clk; + unsigned long timeout; + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + clk &= ~(SDHCI_CLOCK_CARD_EN); + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + /* Issue DLL Reset */ + zynqmp_dll_reset(deviceid); + + /* Wait max 20 ms */ + timeout = 100; + while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (timeout == 0) { + dev_err(mmc_dev(host->mmc), + ": Internal clock never stabilised.\n"); + return; + } + timeout--; + udelay(1000); + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); +} + +static int arasan_sdhci_execute_tuning(struct mmc *mmc, u8 opcode) +{ + struct mmc_cmd cmd; + struct mmc_data data; + u32 ctrl; + struct sdhci_host *host; + struct arasan_sdhci_priv *priv = dev_get_priv(mmc->dev); + char tuning_loop_counter = SDHCI_TUNING_LOOP_COUNT; + u8 deviceid; + + debug("%s\n", __func__); + + host = priv->host; + deviceid = priv->deviceid; + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl |= SDHCI_CTRL_EXEC_TUNING; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + + mdelay(1); + + arasan_zynqmp_dll_reset(host, deviceid); + + sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_INT_ENABLE); + sdhci_writel(host, SDHCI_INT_DATA_AVAIL, SDHCI_SIGNAL_ENABLE); + + do { + cmd.cmdidx = opcode; + cmd.resp_type = MMC_RSP_R1; + cmd.cmdarg = 0; + + data.blocksize = 64; + data.blocks = 1; + data.flags = MMC_DATA_READ; + + if (tuning_loop_counter-- == 0) + break; + + if (cmd.cmdidx == MMC_CMD_SEND_TUNING_BLOCK_HS200 && + mmc->bus_width == 8) + data.blocksize = 128; + + sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, + data.blocksize), + SDHCI_BLOCK_SIZE); + sdhci_writew(host, data.blocks, SDHCI_BLOCK_COUNT); + sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE); + + mmc_send_cmd(mmc, &cmd, NULL); + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + + if (cmd.cmdidx == MMC_CMD_SEND_TUNING_BLOCK) + udelay(1); + + } while (ctrl & SDHCI_CTRL_EXEC_TUNING); + + if (tuning_loop_counter < 0) { + ctrl &= ~SDHCI_CTRL_TUNED_CLK; + sdhci_writel(host, ctrl, SDHCI_HOST_CONTROL2); + } + + if (!(ctrl & SDHCI_CTRL_TUNED_CLK)) { + printf("%s:Tuning failed\n", __func__); + return -1; + } + + udelay(1); + arasan_zynqmp_dll_reset(host, deviceid); + + /* Enable only interrupts served by the SD controller */ + sdhci_writel(host, SDHCI_INT_DATA_MASK | SDHCI_INT_CMD_MASK, + SDHCI_INT_ENABLE); + /* Mask all sdhci interrupt sources */ + sdhci_writel(host, 0x0, SDHCI_SIGNAL_ENABLE); + + return 0; +} + +/** + * sdhci_zynqmp_sdcardclk_set_phase - Set the SD Output Clock Tap Delays + * + * Set the SD Output Clock Tap Delays for Output path + * + * @host: Pointer to the sdhci_host structure. + * @degrees: The clock phase shift between 0 - 359. + * Return: 0 on success and error value on error + */ +static int sdhci_zynqmp_sdcardclk_set_phase(struct sdhci_host *host, + int degrees) +{ + struct arasan_sdhci_priv *priv = dev_get_priv(host->mmc->dev); + struct mmc *mmc = (struct mmc *)host->mmc; + u8 tap_delay, tap_max = 0; + int ret; + int timing = mode2timing[mmc->selected_mode]; + + /* + * This is applicable for SDHCI_SPEC_300 and above + * ZynqMP does not set phase for <=25MHz clock. + * If degrees is zero, no need to do anything. + */ + if (SDHCI_GET_VERSION(host) < SDHCI_SPEC_300 || + timing == MMC_TIMING_LEGACY || + timing == MMC_TIMING_UHS_SDR12 || !degrees) + return 0; + + switch (timing) { + case MMC_TIMING_MMC_HS: + case MMC_TIMING_SD_HS: + case MMC_TIMING_UHS_SDR25: + case MMC_TIMING_UHS_DDR50: + case MMC_TIMING_MMC_DDR52: + /* For 50MHz clock, 30 Taps are available */ + tap_max = 30; + break; + case MMC_TIMING_UHS_SDR50: + /* For 100MHz clock, 15 Taps are available */ + tap_max = 15; + break; + case MMC_TIMING_UHS_SDR104: + case MMC_TIMING_MMC_HS200: + /* For 200MHz clock, 8 Taps are available */ + tap_max = 8; + default: + break; + } + + tap_delay = (degrees * tap_max) / 360; + + arasan_zynqmp_set_tapdelay(priv->deviceid, 0, tap_delay); + + return ret; +} + +/** + * sdhci_zynqmp_sampleclk_set_phase - Set the SD Input Clock Tap Delays + * + * Set the SD Input Clock Tap Delays for Input path + * + * @host: Pointer to the sdhci_host structure. + * @degrees: The clock phase shift between 0 - 359. + * Return: 0 on success and error value on error + */ +static int sdhci_zynqmp_sampleclk_set_phase(struct sdhci_host *host, + int degrees) +{ + struct arasan_sdhci_priv *priv = dev_get_priv(host->mmc->dev); + struct mmc *mmc = (struct mmc *)host->mmc; + u8 tap_delay, tap_max = 0; + int ret; + int timing = mode2timing[mmc->selected_mode]; + + /* + * This is applicable for SDHCI_SPEC_300 and above + * ZynqMP does not set phase for <=25MHz clock. + * If degrees is zero, no need to do anything. + */ + if (SDHCI_GET_VERSION(host) < SDHCI_SPEC_300 || + timing == MMC_TIMING_LEGACY || + timing == MMC_TIMING_UHS_SDR12 || !degrees) + return 0; + + switch (timing) { + case MMC_TIMING_MMC_HS: + case MMC_TIMING_SD_HS: + case MMC_TIMING_UHS_SDR25: + case MMC_TIMING_UHS_DDR50: + case MMC_TIMING_MMC_DDR52: + /* For 50MHz clock, 120 Taps are available */ + tap_max = 120; + break; + case MMC_TIMING_UHS_SDR50: + /* For 100MHz clock, 60 Taps are available */ + tap_max = 60; + break; + case MMC_TIMING_UHS_SDR104: + case MMC_TIMING_MMC_HS200: + /* For 200MHz clock, 30 Taps are available */ + tap_max = 30; + default: + break; + } + + tap_delay = (degrees * tap_max) / 360; + + arasan_zynqmp_set_tapdelay(priv->deviceid, tap_delay, 0); + + return ret; +} + +/** + * sdhci_versal_sdcardclk_set_phase - Set the SD Output Clock Tap Delays + * + * Set the SD Output Clock Tap Delays for Output path + * + * @host: Pointer to the sdhci_host structure. + * @degrees The clock phase shift between 0 - 359. + * Return: 0 on success and error value on error + */ +static int sdhci_versal_sdcardclk_set_phase(struct sdhci_host *host, + int degrees) +{ + struct mmc *mmc = (struct mmc *)host->mmc; + u8 tap_delay, tap_max = 0; + int ret; + int timing = mode2timing[mmc->selected_mode]; + + /* + * This is applicable for SDHCI_SPEC_300 and above + * Versal does not set phase for <=25MHz clock. + * If degrees is zero, no need to do anything. + */ + if (SDHCI_GET_VERSION(host) < SDHCI_SPEC_300 || + timing == MMC_TIMING_LEGACY || + timing == MMC_TIMING_UHS_SDR12 || !degrees) + return 0; + + switch (timing) { + case MMC_TIMING_MMC_HS: + case MMC_TIMING_SD_HS: + case MMC_TIMING_UHS_SDR25: + case MMC_TIMING_UHS_DDR50: + case MMC_TIMING_MMC_DDR52: + /* For 50MHz clock, 30 Taps are available */ + tap_max = 30; + break; + case MMC_TIMING_UHS_SDR50: + /* For 100MHz clock, 15 Taps are available */ + tap_max = 15; + break; + case MMC_TIMING_UHS_SDR104: + case MMC_TIMING_MMC_HS200: + /* For 200MHz clock, 8 Taps are available */ + tap_max = 8; + default: + break; + } + + tap_delay = (degrees * tap_max) / 360; + + /* Set the Clock Phase */ + if (tap_delay) { + u32 regval; + + regval = sdhci_readl(host, SDHCI_ARASAN_OTAPDLY_REGISTER); + regval |= SDHCI_OTAPDLY_ENABLE; + sdhci_writel(host, regval, SDHCI_ARASAN_OTAPDLY_REGISTER); + regval |= tap_delay; + sdhci_writel(host, regval, SDHCI_ARASAN_OTAPDLY_REGISTER); + } + + return ret; +} + +/** + * sdhci_versal_sampleclk_set_phase - Set the SD Input Clock Tap Delays + * + * Set the SD Input Clock Tap Delays for Input path + * + * @host: Pointer to the sdhci_host structure. + * @degrees The clock phase shift between 0 - 359. + * Return: 0 on success and error value on error + */ +static int sdhci_versal_sampleclk_set_phase(struct sdhci_host *host, + int degrees) +{ + struct mmc *mmc = (struct mmc *)host->mmc; + u8 tap_delay, tap_max = 0; + int ret; + int timing = mode2timing[mmc->selected_mode]; + + /* + * This is applicable for SDHCI_SPEC_300 and above + * Versal does not set phase for <=25MHz clock. + * If degrees is zero, no need to do anything. + */ + if (SDHCI_GET_VERSION(host) < SDHCI_SPEC_300 || + timing == MMC_TIMING_LEGACY || + timing == MMC_TIMING_UHS_SDR12 || !degrees) + return 0; + + switch (timing) { + case MMC_TIMING_MMC_HS: + case MMC_TIMING_SD_HS: + case MMC_TIMING_UHS_SDR25: + case MMC_TIMING_UHS_DDR50: + case MMC_TIMING_MMC_DDR52: + /* For 50MHz clock, 120 Taps are available */ + tap_max = 120; + break; + case MMC_TIMING_UHS_SDR50: + /* For 100MHz clock, 60 Taps are available */ + tap_max = 60; + break; + case MMC_TIMING_UHS_SDR104: + case MMC_TIMING_MMC_HS200: + /* For 200MHz clock, 30 Taps are available */ + tap_max = 30; + default: + break; + } + + tap_delay = (degrees * tap_max) / 360; + + /* Set the Clock Phase */ + if (tap_delay) { + u32 regval; + + regval = sdhci_readl(host, SDHCI_ARASAN_ITAPDLY_REGISTER); + regval |= SDHCI_ITAPDLY_CHGWIN; + sdhci_writel(host, regval, SDHCI_ARASAN_ITAPDLY_REGISTER); + regval |= SDHCI_ITAPDLY_ENABLE; + sdhci_writel(host, regval, SDHCI_ARASAN_ITAPDLY_REGISTER); + regval |= tap_delay; + sdhci_writel(host, regval, SDHCI_ARASAN_ITAPDLY_REGISTER); + regval &= ~SDHCI_ITAPDLY_CHGWIN; + sdhci_writel(host, regval, SDHCI_ARASAN_ITAPDLY_REGISTER); + } + + return ret; +} + +static void arasan_sdhci_set_tapdelay(struct sdhci_host *host) +{ + struct arasan_sdhci_priv *priv = dev_get_priv(host->mmc->dev); + struct arasan_sdhci_clk_data *clk_data = &priv->clk_data; + struct mmc *mmc = (struct mmc *)host->mmc; + struct udevice *dev = mmc->dev; + u8 timing = mode2timing[mmc->selected_mode]; + u32 iclk_phase = clk_data->clk_phase_in[timing]; + u32 oclk_phase = clk_data->clk_phase_out[timing]; + + dev_dbg(dev, "%s, host:%s, mode:%d\n", __func__, host->name, timing); + + if (IS_ENABLED(CONFIG_ARCH_ZYNQMP) && + device_is_compatible(dev, "xlnx,zynqmp-8.9a")) { + sdhci_zynqmp_sampleclk_set_phase(host, iclk_phase); + sdhci_zynqmp_sdcardclk_set_phase(host, oclk_phase); + } else if (IS_ENABLED(CONFIG_ARCH_VERSAL) && + device_is_compatible(dev, "xlnx,versal-8.9a")) { + sdhci_versal_sampleclk_set_phase(host, iclk_phase); + sdhci_versal_sdcardclk_set_phase(host, oclk_phase); + } +} + +static void arasan_dt_read_clk_phase(struct udevice *dev, unsigned char timing, + const char *prop) +{ + struct arasan_sdhci_priv *priv = dev_get_priv(dev); + struct arasan_sdhci_clk_data *clk_data = &priv->clk_data; + u32 clk_phase[2] = {0}; + + /* + * Read Tap Delay values from DT, if the DT does not contain the + * Tap Values then use the pre-defined values + */ + if (dev_read_u32_array(dev, prop, &clk_phase[0], 2)) { + dev_dbg(dev, "Using predefined clock phase for %s = %d %d\n", + prop, clk_data->clk_phase_in[timing], + clk_data->clk_phase_out[timing]); + return; + } + + /* The values read are Input and Output Clock Delays in order */ + clk_data->clk_phase_in[timing] = clk_phase[0]; + clk_data->clk_phase_out[timing] = clk_phase[1]; +} + +/** + * arasan_dt_parse_clk_phases - Read Tap Delay values from DT + * + * Called at initialization to parse the values of Tap Delays. + * + * @dev: Pointer to our struct udevice. + */ +static void arasan_dt_parse_clk_phases(struct udevice *dev) +{ + struct arasan_sdhci_priv *priv = dev_get_priv(dev); + struct arasan_sdhci_clk_data *clk_data = &priv->clk_data; + int i; + + if (IS_ENABLED(CONFIG_ARCH_ZYNQMP) && + device_is_compatible(dev, "xlnx,zynqmp-8.9a")) { + for (i = 0; i <= MMC_TIMING_MMC_HS400; i++) { + clk_data->clk_phase_in[i] = zynqmp_iclk_phases[i]; + clk_data->clk_phase_out[i] = zynqmp_oclk_phases[i]; + } + + if (priv->bank == MMC_BANK2) { + clk_data->clk_phase_out[MMC_TIMING_UHS_SDR104] = 90; + clk_data->clk_phase_out[MMC_TIMING_MMC_HS200] = 90; + } + } + + if (IS_ENABLED(CONFIG_ARCH_VERSAL) && + device_is_compatible(dev, "xlnx,versal-8.9a")) { + for (i = 0; i <= MMC_TIMING_MMC_HS400; i++) { + clk_data->clk_phase_in[i] = versal_iclk_phases[i]; + clk_data->clk_phase_out[i] = versal_oclk_phases[i]; + } + } + + arasan_dt_read_clk_phase(dev, MMC_TIMING_LEGACY, + "clk-phase-legacy"); + arasan_dt_read_clk_phase(dev, MMC_TIMING_MMC_HS, + "clk-phase-mmc-hs"); + arasan_dt_read_clk_phase(dev, MMC_TIMING_SD_HS, + "clk-phase-sd-hs"); + arasan_dt_read_clk_phase(dev, MMC_TIMING_UHS_SDR12, + "clk-phase-uhs-sdr12"); + arasan_dt_read_clk_phase(dev, MMC_TIMING_UHS_SDR25, + "clk-phase-uhs-sdr25"); + arasan_dt_read_clk_phase(dev, MMC_TIMING_UHS_SDR50, + "clk-phase-uhs-sdr50"); + arasan_dt_read_clk_phase(dev, MMC_TIMING_UHS_SDR104, + "clk-phase-uhs-sdr104"); + arasan_dt_read_clk_phase(dev, MMC_TIMING_UHS_DDR50, + "clk-phase-uhs-ddr50"); + arasan_dt_read_clk_phase(dev, MMC_TIMING_MMC_DDR52, + "clk-phase-mmc-ddr52"); + arasan_dt_read_clk_phase(dev, MMC_TIMING_MMC_HS200, + "clk-phase-mmc-hs200"); + arasan_dt_read_clk_phase(dev, MMC_TIMING_MMC_HS400, + "clk-phase-mmc-hs400"); +} + +static void arasan_sdhci_set_control_reg(struct sdhci_host *host) +{ + struct mmc *mmc = (struct mmc *)host->mmc; + u32 reg; + + if (!IS_SD(mmc)) + return; + + if (mmc->signal_voltage == MMC_SIGNAL_VOLTAGE_180) { + reg = sdhci_readw(host, SDHCI_HOST_CONTROL2); + reg |= SDHCI_CTRL_VDD_180; + sdhci_writew(host, reg, SDHCI_HOST_CONTROL2); + } + + if (mmc->selected_mode > SD_HS && + mmc->selected_mode <= MMC_HS_200) + sdhci_set_uhs_timing(host); +} + +const struct sdhci_ops arasan_ops = { + .platform_execute_tuning = &arasan_sdhci_execute_tuning, + .set_delay = &arasan_sdhci_set_tapdelay, + .set_control_reg = &arasan_sdhci_set_control_reg, +}; +#endif + +static int arasan_sdhci_probe(struct udevice *dev) +{ + struct arasan_sdhci_plat *plat = dev_get_plat(dev); + struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev); + struct arasan_sdhci_priv *priv = dev_get_priv(dev); + struct sdhci_host *host; + struct clk clk; + unsigned long clock; + int ret; + + host = priv->host; + + ret = clk_get_by_index(dev, 0, &clk); + if (ret < 0) { + dev_err(dev, "failed to get clock\n"); + return ret; + } + + clock = clk_get_rate(&clk); + if (IS_ERR_VALUE(clock)) { + dev_err(dev, "failed to get rate\n"); + return clock; + } + + debug("%s: CLK %ld\n", __func__, clock); + + ret = clk_enable(&clk); + if (ret) { + dev_err(dev, "failed to enable clock\n"); + return ret; + } + + host->quirks = SDHCI_QUIRK_WAIT_SEND_CMD | + SDHCI_QUIRK_BROKEN_R1B; + +#ifdef CONFIG_ZYNQ_HISPD_BROKEN + host->quirks |= SDHCI_QUIRK_BROKEN_HISPD_MODE; +#endif + + if (priv->no_1p8) + host->quirks |= SDHCI_QUIRK_NO_1_8_V; + + plat->cfg.f_max = CONFIG_ZYNQ_SDHCI_MAX_FREQ; + + ret = mmc_of_parse(dev, &plat->cfg); + if (ret) + return ret; + + host->max_clk = clock; + + host->mmc = &plat->mmc; + host->mmc->dev = dev; + host->mmc->priv = host; + + ret = sdhci_setup_cfg(&plat->cfg, host, plat->cfg.f_max, + CONFIG_ZYNQ_SDHCI_MIN_FREQ); + if (ret) + return ret; + upriv->mmc = host->mmc; + + return sdhci_probe(dev); +} + +static int arasan_sdhci_of_to_plat(struct udevice *dev) +{ + struct arasan_sdhci_priv *priv = dev_get_priv(dev); + + priv->host = calloc(1, sizeof(struct sdhci_host)); + if (!priv->host) + return -1; + + priv->host->name = dev->name; + +#if defined(CONFIG_ARCH_ZYNQMP) || defined(CONFIG_ARCH_VERSAL) + priv->host->ops = &arasan_ops; + arasan_dt_parse_clk_phases(dev); +#endif + + priv->host->ioaddr = (void *)dev_read_addr(dev); + if (IS_ERR(priv->host->ioaddr)) + return PTR_ERR(priv->host->ioaddr); + + priv->deviceid = dev_read_u32_default(dev, "xlnx,device_id", -1); + priv->bank = dev_read_u32_default(dev, "xlnx,mio-bank", 0); + priv->no_1p8 = dev_read_bool(dev, "no-1-8-v"); + + return 0; +} + +static int arasan_sdhci_bind(struct udevice *dev) +{ + struct arasan_sdhci_plat *plat = dev_get_plat(dev); + + return sdhci_bind(dev, &plat->mmc, &plat->cfg); +} + +static const struct udevice_id arasan_sdhci_ids[] = { + { .compatible = "arasan,sdhci-8.9a" }, + { } +}; + +U_BOOT_DRIVER(arasan_sdhci_drv) = { + .name = "arasan_sdhci", + .id = UCLASS_MMC, + .of_match = arasan_sdhci_ids, + .of_to_plat = arasan_sdhci_of_to_plat, + .ops = &sdhci_ops, + .bind = arasan_sdhci_bind, + .probe = arasan_sdhci_probe, + .priv_auto = sizeof(struct arasan_sdhci_priv), + .plat_auto = sizeof(struct arasan_sdhci_plat), +}; |