/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Efinix eMMC Host Controller Platform Driver * * Copyright (C) 2025 Efinix, Inc. * Author: Teoh Choon Zone */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "efx_emmc.h" static const struct mmc_host_ops efx_emmc_ops = { .request = efx_emmc_request, .set_ios = efx_emmc_set_ios, .get_cd = efx_emmc_get_cd, .get_ro = efx_emmc_get_ro, .card_busy = efx_emmc_card_busy_wrapper, .execute_tuning = efx_emmc_execute_tuning, }; void efx_emmc_hs400_retune_work(struct work_struct *work) { struct efx_emmc_host *host = container_of(work, struct efx_emmc_host, hs400_retune_work.work); dev_info(&host->pdev->dev, "HS400 delayed retune worker started\n"); if (host->hs400_retune_pending && host->mmc && host->mmc->ios.timing == MMC_TIMING_MMC_HS400) { dev_info(&host->pdev->dev, "HS400 conditions met (clock=%u Hz), executing tuning\n", host->mmc->ios.clock); efx_emmc_execute_tuning(host->mmc, MMC_SEND_TUNING_BLOCK_HS200); } else { dev_warn(&host->pdev->dev, "HS400 conditions not met: pending=%s, timing=%u, clock=%u\n", host->hs400_retune_pending ? "true" : "false", host->mmc ? host->mmc->ios.timing : 0, host->mmc ? host->mmc->ios.clock : 0); } dev_info(&host->pdev->dev, "HS400 delayed retune worker completed\n"); } void efx_emmc_reset_hw(struct efx_emmc_host *host) { u32 reg; reg = efx_emmc_sys_readl(host, EFX_SYS_RESET_REG); reg |= EFX_SYS_RESET_EMMC_IP; efx_emmc_sys_writel(host, reg, EFX_SYS_RESET_REG); udelay(EFX_EMMC_RESET_PULSE_WIDTH); reg &= ~EFX_SYS_RESET_EMMC_IP; efx_emmc_sys_writel(host, reg, EFX_SYS_RESET_REG); udelay(EFX_EMMC_RESET_PULSE_WIDTH); reg |= EFX_SYS_RESET_EMMC_DEV; efx_emmc_sys_writel(host, reg, EFX_SYS_RESET_REG); udelay(EFX_EMMC_RESET_PULSE_WIDTH); reg &= ~EFX_SYS_RESET_EMMC_DEV; efx_emmc_sys_writel(host, reg, EFX_SYS_RESET_REG); udelay(EFX_EMMC_POST_RESET_DELAY); } int efx_emmc_init_hw(struct efx_emmc_host *host) { u32 caps, reg; efx_emmc_reset_hw(host); caps = efx_emmc_readl(host, EFX_EMMC_HOST_CAPABILITIES); host->base_clk = (caps & 0x3FF) * 1000000; if (host->base_clk == 0) { host->base_clk = EFX_EMMC_BASE_CLK_FREQ_MHZ * 1000000; } dev_info(&host->pdev->dev, "Base clock: %u Hz, Capabilities: 0x%08x\n", host->base_clk, caps); efx_emmc_writel(host, 0, EFX_EMMC_INT_SIGNAL_EN); efx_emmc_writel(host, 0, EFX_EMMC_INT_STATUS_EN); efx_emmc_writel(host, EFX_EMMC_INT_ALL_MASK, EFX_EMMC_INT_STATUS); reg = efx_emmc_readl(host, EFX_EMMC_HOST_CONTROL); reg &= ~EFX_EMMC_DATA_WIDTH_MASK; reg |= (EFX_EMMC_DATA_WIDTH_1BIT << EFX_EMMC_DATA_WIDTH_SHIFT); efx_emmc_writel(host, reg, EFX_EMMC_HOST_CONTROL); efx_emmc_set_clock(host, EFX_EMMC_MIN_FREQ); msleep(10); efx_emmc_writel(host, EFX_EMMC_INT_ALL_MASK, EFX_EMMC_INT_STATUS_EN); efx_emmc_writel(host, EFX_EMMC_INT_ALL_MASK, EFX_EMMC_INT_SIGNAL_EN); dev_info(&host->pdev->dev, "Hardware initialized successfully\n"); return 0; } int efx_emmc_probe(struct platform_device *pdev) { struct mmc_host *mmc; struct efx_emmc_host *host; struct resource *res; int ret; u32 version, present_state; u32 hs400_defaults[2]; mmc = mmc_alloc_host(sizeof(struct efx_emmc_host), &pdev->dev); if (!mmc) { return -ENOMEM; } host = mmc_priv(mmc); host->mmc = mmc; host->pdev = pdev; spin_lock_init(&host->lock); host->tuning_done = false; host->tuning_in_progress = false; host->hs200_sample_count = 0; host->hs200_pll_shift = 0; host->hs200_margin = 0; host->hs400_sample_count = 0; host->hs400_pll_shift = 0; host->hs400_margin = 0; host->prev_timing = MMC_TIMING_LEGACY; host->hs400_retune_pending = false; host->tuned_timing_modes = 0; INIT_DELAYED_WORK(&host->hs400_retune_work, efx_emmc_hs400_retune_work); 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; } host->hs400_default_sample = 0; host->hs400_default_pll = 6; if (!of_property_read_u32_array(pdev->dev.of_node, "hs400-default-timing", hs400_defaults, 2)) { host->hs400_default_sample = hs400_defaults[0]; host->hs400_default_pll = hs400_defaults[1]; dev_info(&pdev->dev, "HS400 default timing from DT: sample=%u pll=%u\n", host->hs400_default_sample, host->hs400_default_pll); } 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; } 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; 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; } 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; } host->bounce_used = 0; host->bounce_active = false; 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_emmc_irq, IRQF_SHARED, mmc_hostname(mmc), host); if (ret) { dev_err(&pdev->dev, "Failed to request IRQ: %d\n", ret); goto err_free_dma; } ret = efx_emmc_init_hw(host); if (ret) { goto err_free_dma; } version = efx_emmc_readl(host, EFX_EMMC_VERSION); present_state = efx_emmc_readl(host, EFX_EMMC_PRESENT_STATE); dev_info(&pdev->dev, "Version: 0x%08x, Present state: 0x%08x\n", version, present_state); ret = mmc_of_parse(mmc); if (ret) { dev_err(&pdev->dev, "Failed to parse DT: %d\n", ret); goto err_free_dma; } mmc->ops = &efx_emmc_ops; if (!mmc->f_min) mmc->f_min = EFX_EMMC_MIN_FREQ; if (!mmc->f_max) mmc->f_max = EFX_EMMC_MAX_FREQ; if (!(mmc->caps & MMC_CAP_CMD23)) mmc->caps |= MMC_CAP_CMD23; if (!mmc->ocr_avail) 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; mmc->max_seg_size = 65536; mmc->max_segs = 128; mmc->max_req_size = mmc->max_seg_size * mmc->max_segs; mmc->max_blk_size = EFX_EMMC_MAX_BLOCK_LENGTH; mmc->max_blk_count = 65535; platform_set_drvdata(pdev, mmc); ret = mmc_add_host(mmc); if (ret) { dev_err(&pdev->dev, "Failed to add MMC host: %d\n", ret); goto err_free_dma; } mmc_detect_change(mmc, msecs_to_jiffies(500)); dev_info(&pdev->dev, "Efinix eMMC Host Controller registered (DMA: %s)\n", host->adma_desc ? "enabled" : "disabled"); dev_info(&pdev->dev, "MMC 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_emmc_remove(struct platform_device *pdev) { struct mmc_host *mmc; struct efx_emmc_host *host; mmc = platform_get_drvdata(pdev); host = mmc_priv(mmc); mmc_remove_host(mmc); cancel_delayed_work_sync(&host->hs400_retune_work); efx_emmc_writel(host, 0, EFX_EMMC_INT_SIGNAL_EN); efx_emmc_writel(host, 0, EFX_EMMC_INT_STATUS_EN); efx_emmc_reset_hw(host); 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); return 0; } static const struct of_device_id efx_emmc_of_match[] = { { .compatible = "efinix,emmc-host-controller", }, { } }; MODULE_DEVICE_TABLE(of, efx_emmc_of_match); static struct platform_driver efx_emmc_driver = { .probe = efx_emmc_probe, .remove = efx_emmc_remove, .driver = { .name = "efx-emmc", .of_match_table = efx_emmc_of_match, }, }; module_platform_driver(efx_emmc_driver); MODULE_DESCRIPTION("Efinix eMMC Host Controller Driver with DMA Support"); MODULE_AUTHOR("Teoh Choon Zone "); MODULE_LICENSE("GPL v2"); MODULE_VERSION("1.0");