Files
efinix-linux/drivers/mmc/host/efx_sdio_platform.c
Swee Aun Khor 334eb3ba15 Add a new SDIO controller driver for Efinix devices
The driver supports UHS-I bus speed modes: SDR25, DDR50, and SDR104.
By default, the controller operates in SDR25 mode. Higher-speed modes
can be enabled via the Device Tree by adding the following properties
to the SDIO node:

  - sd-uhs-ddr50
  - sd-uhs-sdr104

When these properties are present, the driver negotiates the highest
supported UHS mode with the card and host.

Signed-off-by: Swee Aun Khor <sakhor@efinixinc.com>
2026-08-09 22:44:47 -07:00

427 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Efinix SDIO Host Controller Platform Driver
*
* Copyright (C) 2026 Efinix, Inc.
* Author: Khor Swee Aun <sakhor@efinixinc.com>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#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 <sakhor@efinixinc.com>");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");