Files
efinix-linux/drivers/mmc/host/efx_emmc_platform.c
2026-08-09 22:42:42 -07:00

371 lines
9.8 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Efinix eMMC Host Controller Platform Driver
*
* Copyright (C) 2025 Efinix, Inc.
* Author: Teoh Choon Zone <czteoh@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_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 <czteoh@efinixinc.com>");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");