mmc: add efinix sdhci driver

Signed-off-by: Mohamad Noor Alim Hussin <mnalim@efinixinc.com>
This commit is contained in:
Mohamad Noor Alim Hussin
2023-10-10 14:56:37 +08:00
committed by Byron Lathi
parent 4a4b5d8875
commit 69a1a758d5
3 changed files with 347 additions and 0 deletions

View File

@@ -1099,5 +1099,13 @@ config MMC_OWL
This selects support for the SD/MMC Host Controller on
Actions Semi Owl SoCs.
config MMC_SDHCI_EFX
tristate "EFX SDHCI Controller support"
depends on MMC_SDHCI_PLTFM && OF
select MMC_SDHCI_IO_ACCESSORS
help
This selects the support for SD host controller on
Efinix SoCs.
config MMC_SDHCI_EXTERNAL_DMA
bool

View File

@@ -105,6 +105,7 @@ obj-$(CONFIG_MMC_SDHCI_OMAP) += sdhci-omap.o
obj-$(CONFIG_MMC_SDHCI_SPRD) += sdhci-sprd.o
obj-$(CONFIG_MMC_CQHCI) += cqhci.o
obj-$(CONFIG_MMC_HSQ) += mmc_hsq.o
obj-$(CONFIG_MMC_SDHCI_EFX) += sdhci-efx.o
ifeq ($(CONFIG_CB710_DEBUG),y)
CFLAGS-cb710-mmc += -DDEBUG

View File

@@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Support for SDHIC on Efinix Inc SoC
*
* Copyright (C) 2023 Efinix Inc
*/
#include <linux/module.h>
#include <linux/of.h>
#include <linux/mmc/host.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/swiotlb.h>
#include <linux/dma-mapping.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
#include <linux/mmc/card.h>
#include <linux/delay.h>
#include "sdhci-pltfm.h"
/* Efinix SD Host controller register offset */
#define EFX_SDHCI_VERSION 0x0
#define EFX_SDHCI_BASE_REG_0 0x4
#define EFX_SDHCI_STATUS_REG_0 0x8
#define EFX_SDHCI_REG_OFFSET 0x100
/* Base register 0 */
#define EFX_CLK_EN BIT(16)
#undef pr_fmt
#define pr_fmt(fmt) "%s: " fmt, __func__
struct efx_sdhci_priv {
struct platform_device *pdev;
};
static u32 efx_readl(struct sdhci_host *host, int reg)
{
u32 val;
u32 offset = reg + EFX_SDHCI_REG_OFFSET;
#if defined(CONFIG_32BIT)
struct mmc_command *data_cmd = host->data_cmd;
// clear cache for ACMD51. The clear cache only works for Efinix RV32 SoC.
if (data_cmd) {
if (data_cmd->opcode == 51)
asm(".word(0x500F)");
}
#endif /* CONFIG_32BIT */
val = readl(host->ioaddr + offset);
return val;
}
static u16 efx_readw(struct sdhci_host *host, int reg)
{
u16 val;
reg += EFX_SDHCI_REG_OFFSET;
val = readw(host->ioaddr + reg);
return val;
}
static u8 efx_readb(struct sdhci_host *host, int reg)
{
u8 val;
reg += EFX_SDHCI_REG_OFFSET;
val = readb(host->ioaddr + reg);
return val;
}
static void efx_writel(struct sdhci_host *host, u32 val, int reg)
{
reg += EFX_SDHCI_REG_OFFSET;
writel(val, host->ioaddr + reg);
}
static void efx_writew(struct sdhci_host *host, u16 val, int reg)
{
u16 cmd_index;
reg += EFX_SDHCI_REG_OFFSET;
// clear cache for read and write single/multi blocks
if (reg == SDHCI_COMMAND) {
// check for opcode CMD17, CMD18, CMD24 and CMD25
cmd_index = (val & 0x3F00) >> 8;
switch (cmd_index) {
case 17:
case 18:
case 24:
case 25:
asm(".word(0x500F)");
break;
}
}
writew(val, host->ioaddr + reg);
}
static void efx_writeb(struct sdhci_host *host, u8 val, int reg)
{
reg += EFX_SDHCI_REG_OFFSET;
writeb(val, host->ioaddr + reg);
}
static int efx_sdhci_enable_dma(struct sdhci_host *host)
{
u32 val;
val = efx_readw(host, SDHCI_TRANSFER_MODE);
efx_writew(host, val | SDHCI_TRNS_DMA, SDHCI_TRANSFER_MODE);
sdhci_reset(host, SDHCI_RESET_CMD);
sdhci_reset(host, SDHCI_RESET_DATA);
return 0;
}
static int efx_sdhci_set_dma_mask(struct sdhci_host *host)
{
struct device *dev = mmc_dev(host->mmc);
int ret;
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
if (ret) {
pr_err("Failed to set 32-bit DMA mask.\n");
return ret;
}
return 0;
}
static void efx_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
{
// set clock divider
// enable clock
int div = 0;
int real_div = div;
int actual_clock;
host->mmc->actual_clock = 0;
writel(0, host->ioaddr + EFX_SDHCI_BASE_REG_0);
if (clock == 0) {
writel(0, host->ioaddr + EFX_SDHCI_BASE_REG_0);
return;
}
for (div = 1; div < SDHCI_MAX_DIV_SPEC_200; div *=2) {
if ((host->max_clk / div ) <= clock)
break;
}
real_div = div;
if (real_div) {
actual_clock = host->max_clk / real_div;
host->mmc->actual_clock = actual_clock;
}
writel((EFX_CLK_EN | real_div), host->ioaddr + EFX_SDHCI_BASE_REG_0);
udelay(10);
}
static void efx_sdhci_set_power(struct sdhci_host *host, unsigned char mode,
unsigned short vdd)
{
u8 pwr = 0;
/* Efinix SD host controller does not support power on or power switching. */
if (mode != MMC_POWER_OFF) {
pwr = SDHCI_POWER_330;
host->pwr |= pwr;
}
}
static unsigned int efx_sdhci_get_timeout_clock(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
unsigned int timeout_clock;
timeout_clock = pltfm_host->clock / 1000;
return timeout_clock;
}
static unsigned int efx_sdhci_get_max_clock(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
return pltfm_host->clock;
}
static unsigned int efx_sdhci_get_min_clock(struct sdhci_host *host)
{
unsigned int f_min;
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
f_min = pltfm_host->clock / 4;
return f_min;
}
static int efx_sdhci_get_cd(struct mmc_host *host)
{
// Efinix sd host controller does not have capability to detect
// the card. Thus, set the card always detected.
return 1;
}
static void efx_sdhci_card_event(struct sdhci_host *host)
{
}
static unsigned int efx_sdhci_get_ro(struct sdhci_host *host)
{
/* The card is not write protected. */
return 0;
}
static const struct sdhci_ops efx_sdhci_ops = {
.read_l = efx_readl,
.read_w = efx_readw,
.read_b = efx_readb,
.write_l = efx_writel,
.write_w = efx_writew,
.write_b = efx_writeb,
.enable_dma = efx_sdhci_enable_dma,
.set_dma_mask = efx_sdhci_set_dma_mask,
.reset = sdhci_reset,
.set_clock = efx_sdhci_set_clock,
.set_power = efx_sdhci_set_power,
.set_bus_width = sdhci_set_bus_width,
.get_max_clock = efx_sdhci_get_max_clock,
.get_timeout_clock = efx_sdhci_get_timeout_clock,
.get_min_clock = efx_sdhci_get_min_clock,
.card_event = efx_sdhci_card_event,
.get_ro = efx_sdhci_get_ro,
};
static const struct sdhci_pltfm_data efx_sdhci_pdata = {
.ops = &efx_sdhci_ops,
.quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL
| SDHCI_QUIRK_NO_HISPD_BIT
| SDHCI_QUIRK_32BIT_ADMA_SIZE
| SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC
| SDHCI_QUIRK_NO_LED
| SDHCI_QUIRK_NO_BUSY_IRQ,
.quirks2 = SDHCI_QUIRK2_NO_1_8_V
| SDHCI_QUIRK2_BROKEN_HS200
| SDHCI_QUIRK2_BROKEN_DDR50
| SDHCI_QUIRK2_BROKEN_64_BIT_DMA
| SDHCI_QUIRK2_SUPPORT_SINGLE
| SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
};
static int efx_sdhci_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sdhci_host *host;
struct sdhci_pltfm_host *pltfm_host;
struct efx_sdhci_priv *sdhci_pdata;
int ret;
u32 val;
host = sdhci_pltfm_init(pdev, &efx_sdhci_pdata, sizeof(struct efx_sdhci_priv));
if (IS_ERR(host)) {
ret = PTR_ERR(host);
goto err;
}
pltfm_host = sdhci_priv(host);
sdhci_pdata = sdhci_pltfm_priv(pltfm_host);
device_property_read_u32(dev, "max-frequency", &pltfm_host->clock);
host->mmc_host_ops.get_cd = efx_sdhci_get_cd;
ret = mmc_of_parse(host->mmc);
if (ret)
goto err;
ret = sdhci_add_host(host);
if (ret)
goto err;
dev_info(dev, "Successfully added Efinix SDHCI host controller\n");
return 0;
err:
sdhci_pltfm_free(pdev);
dev_err(&pdev->dev, "Efinix SDHCI host controller probe failed: %d\n", ret);
return ret;
}
static int efx_sdhci_remove(struct platform_device *pdev)
{
struct sdhci_host *host = platform_get_drvdata(pdev);
u32 scratch;
scratch = readl(host->ioaddr + SDHCI_INT_STATUS);
sdhci_remove_host(host, scratch == (u32)~0);
sdhci_pltfm_free(pdev);
return 0;
}
static const struct of_device_id efx_sdhci_match[] = {
{ .compatible = "efx,sdhci" },
{},
};
MODULE_DEVICE_TABLE(of, efx_sdhci_match);
static struct platform_driver efx_sdhci_driver = {
.probe = efx_sdhci_probe,
.remove = efx_sdhci_remove,
.driver = {
.name = "efx-sdhci",
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
.of_match_table = of_match_ptr(efx_sdhci_match),
},
};
module_platform_driver(efx_sdhci_driver);
MODULE_DESCRIPTION("Efinix SDHCI driver");
MODULE_AUTHOR("mnalim@efinixinc.com");
MODULE_LICENSE("GPL v2");