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>
427 lines
12 KiB
C
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");
|