diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 30ff42fd1..9321cf6dc 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -1099,5 +1099,13 @@ config MMC_OWL This selects support for the SD/MMC Host Controller on Actions Semi Owl SoCs. +config MMC_SDHCI_EFX + tristate "EFX SDHCI Controller support" + depends on MMC_SDHCI_PLTFM && OF + select MMC_SDHCI_IO_ACCESSORS + help + This selects the support for SD host controller on + Efinix SoCs. + config MMC_SDHCI_EXTERNAL_DMA bool diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 451c25fc2..c6a7ed2ab 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -105,6 +105,7 @@ obj-$(CONFIG_MMC_SDHCI_OMAP) += sdhci-omap.o obj-$(CONFIG_MMC_SDHCI_SPRD) += sdhci-sprd.o obj-$(CONFIG_MMC_CQHCI) += cqhci.o obj-$(CONFIG_MMC_HSQ) += mmc_hsq.o +obj-$(CONFIG_MMC_SDHCI_EFX) += sdhci-efx.o ifeq ($(CONFIG_CB710_DEBUG),y) CFLAGS-cb710-mmc += -DDEBUG diff --git a/drivers/mmc/host/sdhci-efx.c b/drivers/mmc/host/sdhci-efx.c new file mode 100644 index 000000000..2bcbce1f5 --- /dev/null +++ b/drivers/mmc/host/sdhci-efx.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for SDHIC on Efinix Inc SoC + * + * Copyright (C) 2023 Efinix Inc + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "sdhci-pltfm.h" + +/* Efinix SD Host controller register offset */ +#define EFX_SDHCI_VERSION 0x0 +#define EFX_SDHCI_BASE_REG_0 0x4 +#define EFX_SDHCI_STATUS_REG_0 0x8 +#define EFX_SDHCI_REG_OFFSET 0x100 + +/* Base register 0 */ +#define EFX_CLK_EN BIT(16) + +#undef pr_fmt +#define pr_fmt(fmt) "%s: " fmt, __func__ + +struct efx_sdhci_priv { + struct platform_device *pdev; +}; + +static u32 efx_readl(struct sdhci_host *host, int reg) +{ + u32 val; + u32 offset = reg + EFX_SDHCI_REG_OFFSET; +#if defined(CONFIG_32BIT) + struct mmc_command *data_cmd = host->data_cmd; + + // clear cache for ACMD51. The clear cache only works for Efinix RV32 SoC. + if (data_cmd) { + if (data_cmd->opcode == 51) + asm(".word(0x500F)"); + } +#endif /* CONFIG_32BIT */ + + val = readl(host->ioaddr + offset); + + return val; +} + +static u16 efx_readw(struct sdhci_host *host, int reg) +{ + u16 val; + + reg += EFX_SDHCI_REG_OFFSET; + val = readw(host->ioaddr + reg); + + return val; +} + +static u8 efx_readb(struct sdhci_host *host, int reg) +{ + u8 val; + reg += EFX_SDHCI_REG_OFFSET; + val = readb(host->ioaddr + reg); + + return val; +} + +static void efx_writel(struct sdhci_host *host, u32 val, int reg) +{ + reg += EFX_SDHCI_REG_OFFSET; + writel(val, host->ioaddr + reg); +} + +static void efx_writew(struct sdhci_host *host, u16 val, int reg) +{ + u16 cmd_index; + reg += EFX_SDHCI_REG_OFFSET; + + // clear cache for read and write single/multi blocks + if (reg == SDHCI_COMMAND) { + // check for opcode CMD17, CMD18, CMD24 and CMD25 + cmd_index = (val & 0x3F00) >> 8; + switch (cmd_index) { + case 17: + case 18: + case 24: + case 25: + asm(".word(0x500F)"); + break; + } + } + + writew(val, host->ioaddr + reg); +} + +static void efx_writeb(struct sdhci_host *host, u8 val, int reg) +{ + reg += EFX_SDHCI_REG_OFFSET; + writeb(val, host->ioaddr + reg); +} + +static int efx_sdhci_enable_dma(struct sdhci_host *host) +{ + u32 val; + + val = efx_readw(host, SDHCI_TRANSFER_MODE); + efx_writew(host, val | SDHCI_TRNS_DMA, SDHCI_TRANSFER_MODE); + + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); + + return 0; +} + +static int efx_sdhci_set_dma_mask(struct sdhci_host *host) +{ + struct device *dev = mmc_dev(host->mmc); + int ret; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) { + pr_err("Failed to set 32-bit DMA mask.\n"); + return ret; + } + + return 0; +} + +static void efx_sdhci_set_clock(struct sdhci_host *host, unsigned int clock) +{ + // set clock divider + // enable clock + int div = 0; + int real_div = div; + int actual_clock; + + host->mmc->actual_clock = 0; + writel(0, host->ioaddr + EFX_SDHCI_BASE_REG_0); + + if (clock == 0) { + writel(0, host->ioaddr + EFX_SDHCI_BASE_REG_0); + + return; + } + + for (div = 1; div < SDHCI_MAX_DIV_SPEC_200; div *=2) { + if ((host->max_clk / div ) <= clock) + break; + } + real_div = div; + + if (real_div) { + actual_clock = host->max_clk / real_div; + host->mmc->actual_clock = actual_clock; + } + + writel((EFX_CLK_EN | real_div), host->ioaddr + EFX_SDHCI_BASE_REG_0); + udelay(10); + +} + +static void efx_sdhci_set_power(struct sdhci_host *host, unsigned char mode, + unsigned short vdd) +{ + u8 pwr = 0; + + /* Efinix SD host controller does not support power on or power switching. */ + if (mode != MMC_POWER_OFF) { + pwr = SDHCI_POWER_330; + host->pwr |= pwr; + } + +} + +static unsigned int efx_sdhci_get_timeout_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + + unsigned int timeout_clock; + timeout_clock = pltfm_host->clock / 1000; + + return timeout_clock; +} +static unsigned int efx_sdhci_get_max_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + + return pltfm_host->clock; +} + +static unsigned int efx_sdhci_get_min_clock(struct sdhci_host *host) +{ + unsigned int f_min; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + + f_min = pltfm_host->clock / 4; + + return f_min; +} + +static int efx_sdhci_get_cd(struct mmc_host *host) +{ + // Efinix sd host controller does not have capability to detect + // the card. Thus, set the card always detected. + return 1; +} + +static void efx_sdhci_card_event(struct sdhci_host *host) +{ + +} + +static unsigned int efx_sdhci_get_ro(struct sdhci_host *host) +{ + /* The card is not write protected. */ + return 0; +} + +static const struct sdhci_ops efx_sdhci_ops = { + .read_l = efx_readl, + .read_w = efx_readw, + .read_b = efx_readb, + .write_l = efx_writel, + .write_w = efx_writew, + .write_b = efx_writeb, + .enable_dma = efx_sdhci_enable_dma, + .set_dma_mask = efx_sdhci_set_dma_mask, + .reset = sdhci_reset, + .set_clock = efx_sdhci_set_clock, + .set_power = efx_sdhci_set_power, + .set_bus_width = sdhci_set_bus_width, + .get_max_clock = efx_sdhci_get_max_clock, + .get_timeout_clock = efx_sdhci_get_timeout_clock, + .get_min_clock = efx_sdhci_get_min_clock, + .card_event = efx_sdhci_card_event, + .get_ro = efx_sdhci_get_ro, +}; + +static const struct sdhci_pltfm_data efx_sdhci_pdata = { + .ops = &efx_sdhci_ops, + .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL + | SDHCI_QUIRK_NO_HISPD_BIT + | SDHCI_QUIRK_32BIT_ADMA_SIZE + | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC + | SDHCI_QUIRK_NO_LED + | SDHCI_QUIRK_NO_BUSY_IRQ, + .quirks2 = SDHCI_QUIRK2_NO_1_8_V + | SDHCI_QUIRK2_BROKEN_HS200 + | SDHCI_QUIRK2_BROKEN_DDR50 + | SDHCI_QUIRK2_BROKEN_64_BIT_DMA + | SDHCI_QUIRK2_SUPPORT_SINGLE + | SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, +}; + +static int efx_sdhci_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host; + struct efx_sdhci_priv *sdhci_pdata; + int ret; + u32 val; + + host = sdhci_pltfm_init(pdev, &efx_sdhci_pdata, sizeof(struct efx_sdhci_priv)); + if (IS_ERR(host)) { + ret = PTR_ERR(host); + goto err; + } + + pltfm_host = sdhci_priv(host); + sdhci_pdata = sdhci_pltfm_priv(pltfm_host); + + device_property_read_u32(dev, "max-frequency", &pltfm_host->clock); + + host->mmc_host_ops.get_cd = efx_sdhci_get_cd; + + ret = mmc_of_parse(host->mmc); + if (ret) + goto err; + + ret = sdhci_add_host(host); + if (ret) + goto err; + + dev_info(dev, "Successfully added Efinix SDHCI host controller\n"); + + return 0; + +err: + sdhci_pltfm_free(pdev); + dev_err(&pdev->dev, "Efinix SDHCI host controller probe failed: %d\n", ret); + return ret; +} + +static int efx_sdhci_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + u32 scratch; + + scratch = readl(host->ioaddr + SDHCI_INT_STATUS); + sdhci_remove_host(host, scratch == (u32)~0); + sdhci_pltfm_free(pdev); + + return 0; +} + +static const struct of_device_id efx_sdhci_match[] = { + { .compatible = "efx,sdhci" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, efx_sdhci_match); + +static struct platform_driver efx_sdhci_driver = { + .probe = efx_sdhci_probe, + .remove = efx_sdhci_remove, + .driver = { + .name = "efx-sdhci", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = of_match_ptr(efx_sdhci_match), + }, +}; + +module_platform_driver(efx_sdhci_driver); + +MODULE_DESCRIPTION("Efinix SDHCI driver"); +MODULE_AUTHOR("mnalim@efinixinc.com"); +MODULE_LICENSE("GPL v2");