// SPDX-License-Identifier: GPL-2.0-or-later /* * Efinix SDIO Host Controller Platform Driver * * Copyright (C) 2026 Efinix, Inc. * Author: Khor Swee Aun */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "efx_sdio.h" static const struct mmc_host_ops efx_sdio_ops = { .request = efx_sdio_request, .set_ios = efx_sdio_set_ios, .get_cd = efx_sdio_get_cd, .get_ro = efx_sdio_get_ro, .card_busy = efx_sdio_card_busy_wrapper, .enable_sdio_irq = efx_sdio_enable_sdio_irq, .ack_sdio_irq = efx_sdio_ack_sdio_irq, .execute_tuning = efx_sdio_execute_tuning, .start_signal_voltage_switch = efx_sdio_start_signal_voltage_switch, }; /** * efx_sdio_reset_hw - Reset SDIO IP and device * @host: SDIO host controller instance * * Performs hardware reset sequence according to SDIO specification: * 1. Reset IP core (minimum 1us pulse) * 2. Reset SDIO device (minimum 1us pulse) * 3. Wait for device initialization (200us minimum) */ void efx_sdio_reset_hw(struct efx_sdio_host *host) { u32 reg; /* Reset SDIO IP - minimum 1us pulse width per documentation */ reg = efx_sdio_sys_readl(host, EFX_SYS_RESET_REG); reg |= EFX_SYS_RESET_SDIO_IP; efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG); udelay(EFX_SDIO_RESET_PULSE_WIDTH); /* Release IP reset */ reg &= ~EFX_SYS_RESET_SDIO_IP; efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG); udelay(EFX_SDIO_RESET_PULSE_WIDTH); /* Reset SDIO device - minimum 1us pulse width (tRSTW) */ reg |= EFX_SYS_RESET_SDIO_DEV; efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG); udelay(EFX_SDIO_RESET_PULSE_WIDTH); /* Release device reset */ reg &= ~EFX_SYS_RESET_SDIO_DEV; efx_sdio_sys_writel(host, reg, EFX_SYS_RESET_REG); /* Wait 200us (tRSCA) or 74 clock cycles per documentation */ udelay(EFX_SDIO_POST_RESET_DELAY); } /** * efx_sdio_init_hw - Initialize SDIO hardware * @host: SDIO host controller instance * * Initializes the SDIO controller hardware including: * - Hardware reset * - Capability reading and base clock setup * - Interrupt configuration * - Initial bus width and clock settings * * Return: 0 on success, negative error code on failure */ int efx_sdio_init_hw(struct efx_sdio_host *host) { u32 caps, reg; /* Reset hardware */ efx_sdio_reset_hw(host); /* Read capabilities */ caps = efx_sdio_readl(host, EFX_SDIO_HOST_CAPABILITIES); host->base_clk = (caps & 0x3FF) * 1000000; /* Convert MHz to Hz */ if (host->base_clk == 0) { host->base_clk = EFX_SDIO_BASE_CLK_FREQ_MHZ * 1000000; } host->io_voltage = (caps >> 12) & 0xF; dev_info(&host->pdev->dev, "Base clock: %u Hz, IO Voltage: %sV, Capabilities: 0x%08x\n", host->base_clk, host->io_voltage == EFX_SDIO_IO_VOLTAGE_1_8V ? "1.8" : "3.0", caps); /* Disable all interrupts initially */ efx_sdio_writel(host, 0, EFX_SDIO_INT_SIGNAL_EN); efx_sdio_writel(host, 0, EFX_SDIO_INT_STATUS_EN); /* Clear any pending interrupts */ efx_sdio_writel(host, EFX_SDIO_INT_ALL_MASK, EFX_SDIO_INT_STATUS); /* Set initial bus width to 1-bit */ reg = efx_sdio_readl(host, EFX_SDIO_HOST_CONTROL); reg &= ~EFX_SDIO_DATA_WIDTH_MASK; reg |= (EFX_SDIO_DATA_WIDTH_1BIT << EFX_SDIO_DATA_WIDTH_SHIFT); /*Bit 4 for IB or OOB interrupt * Mask bit 4 for IB interrupt *reg |= 0x10; Set bit 4 for OOB interrupt */ reg &= ~0x10; efx_sdio_writel(host, reg, EFX_SDIO_HOST_CONTROL); /* Set initial clock to identification frequency */ efx_sdio_set_clock(host, EFX_SDIO_MIN_FREQ); /* Wait for hardware to stabilize */ msleep(10); /* Enable interrupts */ efx_sdio_writel(host, EFX_SDIO_INT_ALL_MASK, EFX_SDIO_INT_STATUS_EN); efx_sdio_writel(host, EFX_SDIO_INT_ALL_MASK, EFX_SDIO_INT_SIGNAL_EN); dev_info(&host->pdev->dev, "Hardware initialized successfully\n"); return 0; } int efx_sdio_probe(struct platform_device *pdev) { struct mmc_host *mmc; struct efx_sdio_host *host; struct resource *res; int ret; u32 version, present_state; mmc = mmc_alloc_host(sizeof(struct efx_sdio_host), &pdev->dev); if (!mmc) { return -ENOMEM; } host = mmc_priv(mmc); host->mmc = mmc; host->pdev = pdev; spin_lock_init(&host->lock); /* Initialize tuning-related fields */ host->tuning_done = false; host->tuning_in_progress = false; host->optimal_sample_count = 0; host->optimal_pll_shift = 0; host->optimal_margin = 0; host->prev_timing = MMC_TIMING_LEGACY; host->hs400_retune_pending = false; host->tuned_timing_modes = 0; /* Clear all bits - no modes tuned yet */ /* Get memory resources */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); host->ioaddr = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(host->ioaddr)) { ret = PTR_ERR(host->ioaddr); goto err_free_host; } res = platform_get_resource(pdev, IORESOURCE_MEM, 1); host->sys_ioaddr = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(host->sys_ioaddr)) { ret = PTR_ERR(host->sys_ioaddr); goto err_free_host; } /* Get clock */ host->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(host->clk)) { ret = PTR_ERR(host->clk); dev_err(&pdev->dev, "Failed to get clock: %d\n", ret); goto err_free_host; } ret = clk_prepare_enable(host->clk); if (ret) { dev_err(&pdev->dev, "Failed to enable clock: %d\n", ret); goto err_free_host; } /* Set up DMA mask */ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); if (ret) { ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); if (ret) { dev_err(&pdev->dev, "Failed to set DMA mask\n"); goto err_clk_disable; } host->dma_64bit = false; } else { host->dma_64bit = true; } /* Allocate ADMA descriptor table */ host->adma_desc_sz = EFX_ADMA_TABLE_SZ; host->adma_desc = dma_alloc_coherent(&pdev->dev, host->adma_desc_sz, &host->adma_desc_dma, GFP_KERNEL); if (!host->adma_desc) { dev_err(&pdev->dev, "Failed to allocate ADMA descriptor table\n"); ret = -ENOMEM; goto err_clk_disable; } /* Allocate bounce buffer for unaligned transfers */ host->bounce_buffer_size = 512 * 1024; host->bounce_buffer = dma_alloc_coherent(&pdev->dev, host->bounce_buffer_size, &host->bounce_dma, GFP_KERNEL); if (!host->bounce_buffer) { dev_warn(&pdev->dev, "Failed to allocate bounce buffer, using software alignment\n"); host->bounce_buffer_size = 0; } /* Get IRQ */ host->irq = platform_get_irq(pdev, 0); if (host->irq < 0) { ret = host->irq; goto err_free_dma; } ret = devm_request_irq(&pdev->dev, host->irq, efx_sdio_irq, IRQF_SHARED, mmc_hostname(mmc), host); if (ret) { dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret); goto err_free_dma; } /* Initialize hardware */ ret = efx_sdio_init_hw(host); if (ret) { goto err_free_dma; } /* Read version register to verify hardware is accessible */ version = efx_sdio_readl(host, EFX_SDIO_VERSION); present_state = efx_sdio_readl(host, EFX_SDIO_PRESENT_STATE); dev_info(&pdev->dev, "Version: 0x%08x, Present state: 0x%08x\n", version, present_state); /* Set up MMC host */ mmc->ops = &efx_sdio_ops; mmc->f_min = EFX_SDIO_MIN_FREQ; mmc->f_max = EFX_SDIO_MAX_FREQ; /* SDIO-specific capabilities */ mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ; /* Not support SD and eMMC SDIO IRQ NOTHREAD */ mmc->caps2 = MMC_CAP2_NO_SD | MMC_CAP2_NO_MMC | MMC_CAP2_SDIO_IRQ_NOTHREAD; /* Clear all UHS capability bits first */ mmc->caps &= ~MMC_CAP_UHS; /* Read UHS mode flags directly from DTS */ struct device_node *np = pdev->dev.of_node; bool uhs_set = false; if (np) { if (of_property_read_bool(np, "sd-uhs-sdr25")) { mmc->caps |= MMC_CAP_UHS_SDR25; uhs_set = true; } if (of_property_read_bool(np, "sd-uhs-ddr50")) { mmc->caps |= MMC_CAP_UHS_DDR50; uhs_set = true; } if (of_property_read_bool(np, "sd-uhs-sdr104")) { mmc->caps |= MMC_CAP_UHS_SDR104; uhs_set = true; } } /* Default if no UHS mode specified in DTS */ if (!uhs_set) { mmc->caps |= MMC_CAP_UHS_SDR25; dev_info(&pdev->dev, "No UHS mode in DTS, defaulting to SDR25\n"); } else { dev_info(&pdev->dev, "UHS modes: %s%s%s\n", (mmc->caps & MMC_CAP_UHS_SDR25) ? "SDR25 " : "", (mmc->caps & MMC_CAP_UHS_DDR50) ? "DDR50 " : "", (mmc->caps & MMC_CAP_UHS_SDR104) ? "SDR104 " : ""); } /* Voltage support: 1.7-1.95V and 2.7-3.6V */ mmc->ocr_avail = MMC_VDD_165_195 | 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; // Maximum segment size each scatter-gather descriptor can handle mmc->max_seg_size = 65536; // Maximum number of scatter-gather segments per request mmc->max_segs = 128; // Maximum request size in bytes for all scatter-gather descriptors mmc->max_req_size = mmc->max_seg_size * mmc->max_segs; // Maximum block size mmc->max_blk_size = EFX_SDIO_MAX_BLOCK_LENGTH; // Maximum number of blocks per request mmc->max_blk_count = 65535; platform_set_drvdata(pdev, mmc); ret = mmc_add_host(mmc); if (ret) { dev_err(&pdev->dev, "Failed to add SDIO host: %d\n", ret); goto err_free_dma; } /* Force card detection after a delay */ mmc_detect_change(mmc, msecs_to_jiffies(500)); dev_info(&pdev->dev, "Efinix SDIO Host Controller registered (DMA: %s)\n", host->adma_desc ? "enabled" : "disabled"); dev_info(&pdev->dev, "SDIO caps: 0x%08x, OCR: 0x%08x\n", mmc->caps, mmc->ocr_avail); dev_info(&pdev->dev, "Clock range: %u - %u Hz\n", mmc->f_min, mmc->f_max); dev_info(&pdev->dev, "Max block size: %u, Max segments: %u\n", mmc->max_blk_size, mmc->max_segs); dev_info(&pdev->dev, "ADMA desc table: %zu bytes, Bounce buffer: %u bytes\n", host->adma_desc_sz, host->bounce_buffer_size); return 0; err_free_dma: if (host->bounce_buffer) { dma_free_coherent(&pdev->dev, host->bounce_buffer_size, host->bounce_buffer, host->bounce_dma); } if (host->adma_desc) { dma_free_coherent(&pdev->dev, host->adma_desc_sz, host->adma_desc, host->adma_desc_dma); } err_clk_disable: clk_disable_unprepare(host->clk); err_free_host: mmc_free_host(mmc); return ret; } int efx_sdio_remove(struct platform_device *pdev) { struct mmc_host *mmc; struct efx_sdio_host *host; mmc = platform_get_drvdata(pdev); host = mmc_priv(mmc); mmc_remove_host(mmc); /* Cancel any pending delayed work */ //SA cancel_delayed_work_sync(&host->hs400_retune_work); /* Disable interrupts */ efx_sdio_writel(host, 0, EFX_SDIO_INT_SIGNAL_EN); efx_sdio_writel(host, 0, EFX_SDIO_INT_STATUS_EN); /* Reset hardware */ efx_sdio_reset_hw(host); /* Free DMA resources */ if (host->bounce_buffer) { dma_free_coherent(&pdev->dev, host->bounce_buffer_size, host->bounce_buffer, host->bounce_dma); } if (host->adma_desc) { dma_free_coherent(&pdev->dev, host->adma_desc_sz, host->adma_desc, host->adma_desc_dma); } clk_disable_unprepare(host->clk); mmc_free_host(mmc); dev_info(&pdev->dev, "Efinix SDIO Host Controller removed\n"); return 0; } static const struct of_device_id efx_sdio_of_match[] = { { .compatible = "efinix,sdio-host-controller", }, { } }; MODULE_DEVICE_TABLE(of, efx_sdio_of_match); static struct platform_driver efx_sdio_driver = { .probe = efx_sdio_probe, .remove = efx_sdio_remove, .driver = { .name = "efx-sdio", .of_match_table = efx_sdio_of_match, }, }; module_platform_driver(efx_sdio_driver); MODULE_DESCRIPTION("Efinix SDIO Host Controller Driver with DMA Support"); MODULE_AUTHOR("Khor Swee Aun "); MODULE_LICENSE("GPL v2"); MODULE_VERSION("1.0");