// 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");