371 lines
9.8 KiB
C
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");
|