summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichal Wilczynski <m.wilczynski@samsung.com>2024-08-22 10:03:33 +0200
committerMichal Wilczynski <m.wilczynski@samsung.com>2024-08-22 13:53:49 +0200
commit537fabbf2a196e262b45638f1e3d69b3d5f89c27 (patch)
tree4e955736f6168c7273f4c263de91e51576fb87db
parenta58aa9cf4a09a0cfa3416433472d13c541f37771 (diff)
downloadlinux-riscv-537fabbf2a196e262b45638f1e3d69b3d5f89c27.tar.gz
linux-riscv-537fabbf2a196e262b45638f1e3d69b3d5f89c27.tar.bz2
linux-riscv-537fabbf2a196e262b45638f1e3d69b3d5f89c27.zip
mmc: Add SpacemiT K1-X mmc driver
mmc driver enables the ability to load the rootfs from the specified storage, be it eMMC, SD card, or SDIO. Port it from the vendor kernel [1]. [1] - https://github.com/BPI-SINOVOIP/pi-linux.git Change-Id: Ia58f1a574fa25749b58163b70f2818b1f54d6d08 Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
-rw-r--r--drivers/mmc/host/Kconfig13
-rw-r--r--drivers/mmc/host/Makefile1
-rw-r--r--drivers/mmc/host/sdhci-of-k1x.c1871
-rw-r--r--include/dt-bindings/mmc/k1x_sdhci.h53
-rw-r--r--include/linux/mmc/host.h15
-rw-r--r--include/linux/platform_data/k1x_sdhci.h94
6 files changed, 2047 insertions, 0 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index bc7e2ad37002..13c837255dd7 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -251,6 +251,19 @@ config MMC_SDHCI_OF_SPARX5
If unsure, say N.
+config MMC_SDHCI_OF_K1X
+ tristate "SDHCI OF support for the Spacemit K1X SDHCI controllers"
+ depends on MMC_SDHCI_PLTFM
+ depends on OF
+ depends on COMMON_CLK
+ help
+ This selects the Secure Digital Host Controller Interface (SDHCI)
+ found in the Spacemit SoC K1X.
+
+ If you have a controller with this interface, say Y or M here.
+
+ If unsure, say N.
+
config MMC_SDHCI_CADENCE
tristate "SDHCI support for the Cadence SD/SDIO/eMMC controller"
depends on MMC_SDHCI_PLTFM
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index a693fa3d3f1c..3e1c6606e26d 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_DWCMSHC) += sdhci-of-dwcmshc.o
obj-$(CONFIG_MMC_SDHCI_OF_SPARX5) += sdhci-of-sparx5.o
obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o
obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o
+obj-$(CONFIG_MMC_SDHCI_OF_K1X) += sdhci-of-k1x.o
obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o
obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o
obj-$(CONFIG_MMC_SDHCI_MICROCHIP_PIC32) += sdhci-pic32.o
diff --git a/drivers/mmc/host/sdhci-of-k1x.c b/drivers/mmc/host/sdhci-of-k1x.c
new file mode 100644
index 000000000000..3a346283ea85
--- /dev/null
+++ b/drivers/mmc/host/sdhci-of-k1x.c
@@ -0,0 +1,1871 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for Spacemit Mobile Storage Host Controller
+ *
+ * Copyright (C) 2023 Spacemit
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/slot-gpio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_data/k1x_sdhci.h>
+#include <linux/platform_device.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/reset.h>
+
+#include "sdhci.h"
+#include "sdhci-pltfm.h"
+
+#define CONFIG_K1X_MMC_DEBUG 1
+#define BOOTPART_NOACC_DEFAULT 0
+
+/* SDH registers define */
+#define SDHC_OP_EXT_REG 0x108
+#define OVRRD_CLK_OEN 0x0800
+#define FORCE_CLK_ON 0x1000
+
+#define SDHC_LEGACY_CTRL_REG 0x10C
+#define GEN_PAD_CLK_ON 0x0040
+
+#define SDHC_MMC_CTRL_REG 0x114
+#define MISC_INT_EN 0x0002
+#define MISC_INT 0x0004
+#define ENHANCE_STROBE_EN 0x0100
+#define MMC_HS400 0x0200
+#define MMC_HS200 0x0400
+#define MMC_CARD_MODE 0x1000
+
+#define SDHC_TX_CFG_REG 0x11C
+#define TX_INT_CLK_SEL 0x40000000
+#define TX_MUX_SEL 0x80000000
+
+#define SDHC_PHY_CTRL_REG 0x160
+#define PHY_FUNC_EN 0x0001
+#define PHY_PLL_LOCK 0x0002
+#define HOST_LEGACY_MODE 0x80000000
+
+#define SDHC_PHY_FUNC_REG 0x164
+#define PHY_TEST_EN 0x0080
+#define HS200_USE_RFIFO 0x8000
+
+#define SDHC_PHY_DLLCFG 0x168
+#define DLL_PREDLY_NUM 0x04
+#define DLL_FULLDLY_RANGE 0x10
+#define DLL_VREG_CTRL 0x40
+#define DLL_ENABLE 0x80000000
+#define DLL_REFRESH_SWEN_SHIFT 0x1C
+#define DLL_REFRESH_SW_SHIFT 0x1D
+
+#define SDHC_PHY_DLLCFG1 0x16C
+#define DLL_REG2_CTRL 0x0C
+#define DLL_REG3_CTRL_MASK 0xFF
+#define DLL_REG3_CTRL_SHIFT 0x10
+#define DLL_REG2_CTRL_MASK 0xFF
+#define DLL_REG2_CTRL_SHIFT 0x08
+#define DLL_REG1_CTRL 0x92
+#define DLL_REG1_CTRL_MASK 0xFF
+#define DLL_REG1_CTRL_SHIFT 0x00
+
+#define SDHC_PHY_DLLSTS 0x170
+#define DLL_LOCK_STATE 0x01
+
+#define SDHC_PHY_DLLSTS1 0x174
+#define DLL_MASTER_DELAY_MASK 0xFF
+#define DLL_MASTER_DELAY_SHIFT 0x10
+
+#define SDHC_PHY_PADCFG_REG 0x178
+#define RX_BIAS_CTRL_SHIFT 0x5
+#define PHY_DRIVE_SEL_SHIFT 0x0
+#define PHY_DRIVE_SEL_MASK 0x7
+#define PHY_DRIVE_SEL_DEFAULT 0x4
+
+#define RPM_DELAY 50
+#define MAX_74CLK_WAIT_COUNT 100
+
+#define MMC1_IO_V18EN 0x04
+#define AKEY_ASFAR 0xBABA
+#define AKEY_ASSAR 0xEB10
+
+#define SDHC_RX_CFG_REG 0x118
+#define RX_SDCLK_SEL0_MASK 0x03
+#define RX_SDCLK_SEL0_SHIFT 0x00
+#define RX_SDCLK_SEL0 0x02
+#define RX_SDCLK_SEL1_MASK 0x03
+#define RX_SDCLK_SEL1_SHIFT 0x02
+#define RX_SDCLK_SEL1 0x01
+
+#define SDHC_DLINE_CTRL_REG 0x130
+#define DLINE_PU 0x01
+#define RX_DLINE_CODE_MASK 0xFF
+#define RX_DLINE_CODE_SHIFT 0x10
+#define TX_DLINE_CODE_MASK 0xFF
+#define TX_DLINE_CODE_SHIFT 0x18
+
+#define SDHC_DLINE_CFG_REG 0x134
+#define RX_DLINE_REG_MASK 0xFF
+#define RX_DLINE_REG_SHIFT 0x00
+#define RX_DLINE_GAIN_MASK 0x1
+#define RX_DLINE_GAIN_SHIFT 0x8
+#define RX_DLINE_GAIN 0x1
+#define TX_DLINE_REG_MASK 0xFF
+#define TX_DLINE_REG_SHIFT 0x10
+
+#define SDHC_RX_TUNE_DELAY_MIN 0x0
+#define SDHC_RX_TUNE_DELAY_MAX 0xFF
+#define SDHC_RX_TUNE_DELAY_STEP 0x1
+
+static struct sdhci_host* sdio_host;
+
+#define MMC_CAP2_QUIRK_BREAK_SDR104 (1 << 30)
+
+struct sdhci_spacemit {
+ struct clk *clk_core;
+ struct clk *clk_io;
+ struct clk *clk_aib;
+ struct reset_control *reset;
+ unsigned char power_mode;
+ struct pinctrl_state *pin;
+ struct pinctrl *pinctrl;
+};
+
+static int spacemit_reg[] = {
+ 0x100, 0x104, 0x108, 0x10c, 0x110, 0x114, 0x118, 0x11c,
+ 0x120, 0x124, 0x128, 0x12c, 0x130, 0x134, 0x160, 0x164,
+ 0x168, 0x16c, 0x170, 0x174, 0x178, 0x17c, 0x180, 0x184,
+ 0x188, 0x18c, 0x190, 0x1f0, 0x1f4, 0xFFF,
+};
+
+#ifdef CONFIG_K1X_MMC_DEBUG
+static u8 cur_com_reg[960]; /* 8 line, 120 character per line */
+static u8 cur_pri_reg[960];
+static u8 pre_com_reg[960];
+static u8 pre_pri_reg[960];
+#endif
+
+#define spacemit_monitor_cmd(cmd) (((cmd) == MMC_READ_SINGLE_BLOCK) || \
+ ((cmd) == MMC_READ_MULTIPLE_BLOCK) || \
+ ((cmd) == MMC_WRITE_BLOCK) || \
+ ((cmd) == MMC_WRITE_MULTIPLE_BLOCK) || \
+ ((cmd) == MMC_SWITCH) || \
+ ((cmd) == MMC_ERASE))
+
+static const u32 tuning_patten4[16] = {
+ 0x00ff0fff, 0xccc3ccff, 0xffcc3cc3, 0xeffefffe,
+ 0xddffdfff, 0xfbfffbff, 0xff7fffbf, 0xefbdf777,
+ 0xf0fff0ff, 0x3cccfc0f, 0xcfcc33cc, 0xeeffefff,
+ 0xfdfffdff, 0xffbfffdf, 0xfff7ffbb, 0xde7b7ff7,
+};
+
+static const u32 tuning_patten8[32] = {
+ 0xff00ffff, 0x0000ffff, 0xccccffff, 0xcccc33cc,
+ 0xcc3333cc, 0xffffcccc, 0xffffeeff, 0xffeeeeff,
+ 0xffddffff, 0xddddffff, 0xbbffffff, 0xbbffffff,
+ 0xffffffbb, 0xffffff77, 0x77ff7777, 0xffeeddbb,
+ 0x00ffffff, 0x00ffffff, 0xccffff00, 0xcc33cccc,
+ 0x3333cccc, 0xffcccccc, 0xffeeffff, 0xeeeeffff,
+ 0xddffffff, 0xddffffff, 0xffffffdd, 0xffffffbb,
+ 0xffffbbbb, 0xffff77ff, 0xff7777ff, 0xeeddbb77,
+};
+
+static int is_recovery_boot;
+static int __init recovery_boot_mode(char *str)
+{
+ if ((str != NULL) && (str[0] == '1'))
+ is_recovery_boot = 1;
+
+ return 0;
+}
+#ifndef MODULE
+__setup("recovery=", recovery_boot_mode);
+#endif
+
+static void __maybe_unused dump_sdh_regs(struct sdhci_host *host, u8 *com_reg, u8 *pri_reg)
+{
+ int val;
+ int offset;
+ int i;
+ int len;
+ u8 *buf;
+
+ buf = com_reg;
+ len = 0;
+ i = 0;
+ for (offset = 0; offset < 0x70; offset += 4) {
+ val = sdhci_readl(host, offset);
+ if (i % 4 == 0)
+ len += sprintf(buf + len, "\n");
+ len += sprintf(buf + len, "\toffset:0x%03x 0x%08x\t", offset, val);
+ i++;
+ }
+
+ if (i % 4 == 0)
+ len += sprintf(buf + len, "\n");
+ val = sdhci_readl(host, 0xe0);
+ len += sprintf(buf + len, "\toffset:0x%03x 0x%08x\t", 0xe0, val);
+ val = sdhci_readl(host, 0xfc);
+ len += sprintf(buf + len, "\toffset:0x%03x 0x%08x\t\n", 0xfc, val);
+
+ buf = pri_reg;
+ len = 0;
+ i = 0;
+ do {
+ if (((host->mmc->caps2 & MMC_CAP2_NO_MMC) || (host->quirks2 & SDHCI_QUIRK2_BROKEN_PHY_MODULE)) &&
+ (spacemit_reg[i] > 0x134))
+ break;
+ val = sdhci_readl(host, spacemit_reg[i]);
+ if (i % 4 == 0)
+ len += sprintf(buf + len, "\n");
+ len += sprintf(buf + len, "\toffset:0x%03x 0x%08x\t", spacemit_reg[i], val);
+ i++;
+ } while (spacemit_reg[i] != 0xFFF);
+ len += sprintf(buf + len, "\n");
+}
+
+static void spacemit_reset_dllcfg1_reg(struct sdhci_host *host, u32 dllcfg1)
+{
+ u32 reg;
+
+ sdhci_writel(host, dllcfg1, SDHC_PHY_DLLCFG1);
+
+ reg = sdhci_readl(host, SDHC_PHY_DLLCFG);
+ reg |= 1 << DLL_REFRESH_SWEN_SHIFT;
+ sdhci_writel(host, reg, SDHC_PHY_DLLCFG);
+
+ reg = sdhci_readl(host, SDHC_PHY_DLLCFG);
+ reg |= 1 << DLL_REFRESH_SW_SHIFT;
+ sdhci_writel(host, reg, SDHC_PHY_DLLCFG);
+
+ reg = sdhci_readl(host, SDHC_PHY_DLLCFG);
+ reg &= ~(1 << DLL_REFRESH_SW_SHIFT);
+ sdhci_writel(host, reg, SDHC_PHY_DLLCFG);
+
+ reg = sdhci_readl(host, SDHC_PHY_DLLCFG);
+ reg &= ~(1 << DLL_REFRESH_SWEN_SHIFT);
+ sdhci_writel(host, reg, SDHC_PHY_DLLCFG);
+
+ udelay(1);
+}
+
+static __maybe_unused void spacemit_handle_emmc_read_crc(struct sdhci_host *host)
+{
+ struct mmc_host *mmc = host->mmc;
+ struct k1x_sdhci_platdata *pdata = mmc->parent->platform_data;
+
+ u32 data;
+ u32 dllcfg1, dlldelay;
+ u32 dll_reg3_ctrl, dll_master_delay;
+
+ pdata->prev_dllcfg1 = pdata->curr_dllcfg1;
+ pdata->curr_dllcfg1 = sdhci_readl(host, SDHC_PHY_DLLCFG1);
+
+ dllcfg1 = pdata->curr_dllcfg1;
+ dll_reg3_ctrl = (dllcfg1 >> DLL_REG3_CTRL_SHIFT) & DLL_REG3_CTRL_MASK;
+ dlldelay = sdhci_readl(host, SDHC_PHY_DLLSTS1);
+ dll_master_delay = (dlldelay >> DLL_MASTER_DELAY_SHIFT) & DLL_MASTER_DELAY_MASK;
+
+ if (!dll_reg3_ctrl) {
+ /* first time reset */
+ data = dll_master_delay / 3;
+ pdata->dllcfg1_odd_reset = 1;
+ } else if (pdata->dllcfg1_odd_reset & 0x1) {
+ /* odd time */
+ data = 0xFF - (dll_master_delay - dll_reg3_ctrl) / 3;
+ pdata->dllcfg1_odd_reset = 2;
+ } else {
+ /* even time */
+ data = dll_master_delay + 0xFF - dll_reg3_ctrl;
+ data = data / 3;
+ pdata->dllcfg1_odd_reset = 1;
+ }
+
+ /* reset DLL_REG3_CTRL */
+ dllcfg1 &= (~(DLL_REG3_CTRL_MASK << DLL_REG3_CTRL_SHIFT));
+ dllcfg1 |= (data << DLL_REG3_CTRL_SHIFT);
+
+ if (unlikely(dllcfg1 == pdata->prev_dllcfg1)) {
+ /* reset the default: 0x0000000c */
+ pr_warn("%s: maybe fail to fix crc!\n", mmc_hostname(mmc));
+ /* debug purpose */
+ BUG_ON(1);
+ spacemit_reset_dllcfg1_reg(host, 0x0000000c);
+ pdata->new_dllcfg1 = 0x0000000c;
+ pdata->need_reset_dllcfg1 = 0;
+ } else {
+ spacemit_reset_dllcfg1_reg(host, dllcfg1);
+ pdata->new_dllcfg1 = dllcfg1;
+ pdata->need_reset_dllcfg1 = 1;
+ }
+}
+
+static u32 spacemit_handle_interrupt(struct sdhci_host *host, u32 intmask)
+{
+ u32 cmd;
+
+ /* handle sdio SDHCI_INT_CARD_INT */
+ if ((intmask & SDHCI_INT_CARD_INT) && (host->ier & SDHCI_INT_CARD_INT)) {
+ if (!(host->flags & SDHCI_DEVICE_DEAD)) {
+ host->ier &= ~SDHCI_INT_CARD_INT;
+ sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+ sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+ }
+
+ /* wakeup ksdioirqd thread */
+ host->mmc->sdio_irq_pending = true;
+ if (host->mmc->sdio_irq_thread)
+ wake_up_process(host->mmc->sdio_irq_thread);
+ }
+
+ /* handle error interrupts */
+ if (intmask & SDHCI_INT_ERROR) {
+ cmd = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND));
+ if (spacemit_monitor_cmd(cmd)) {
+ printk_ratelimited(KERN_ERR "%s: cmd%d error(INT status:0x%08x).\n",
+ mmc_hostname(host->mmc), cmd, intmask);
+#ifdef CONFIG_K1X_MMC_DEBUG
+ /* dump host register */
+ dump_sdh_regs(host, &cur_com_reg[0], &cur_pri_reg[0]);
+ printk_ratelimited(KERN_INFO "%s", cur_com_reg);
+ printk_ratelimited(KERN_INFO "%s", cur_pri_reg);
+ //pr_err("register before cmd%d trigger %s", cmd, pre_com_reg);
+ //pr_err("%s", pre_pri_reg);
+#endif
+ }
+
+ if (intmask & (SDHCI_INT_CRC | SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_END_BIT | SDHCI_INT_AUTO_CMD_ERR)) {
+ /* handle crc error for sd device */
+ if (host->mmc->caps2 & MMC_CAP2_NO_MMC) {
+ host->mmc->caps2 |= MMC_CAP2_QUIRK_BREAK_SDR104;
+ }
+ }
+ }
+
+ return intmask;
+}
+
+#ifdef CONFIG_K1X_MMC_DEBUG
+void __maybe_unused spacemit_save_sdhci_regs(struct sdhci_host *host, u32 cmd)
+{
+ if (host->mmc->card && spacemit_monitor_cmd(cmd))
+ dump_sdh_regs(host, &pre_com_reg[0], &pre_pri_reg[0]);
+}
+EXPORT_SYMBOL(spacemit_save_sdhci_regs);
+#endif
+
+extern int __mmc_claim_host(struct mmc_host *host, struct mmc_ctx *ctx, atomic_t *abort);
+extern void mmc_release_host(struct mmc_host *host);
+
+void spacemit_sdio_detect_change(int enable_scan)
+{
+#define MMC_CARD_REMOVED (1<<4) /* card has been removed */
+ struct mmc_card *sdio_card;
+
+ if (sdio_host) {
+ if (enable_scan) {
+ sdio_card = sdio_host->mmc->card;
+ if (sdio_card && (sdio_card->sdio_funcs)) {
+ __mmc_claim_host(sdio_host->mmc, NULL, NULL);
+ mmc_sw_reset(sdio_host->mmc->card);
+ mmc_release_host(sdio_host->mmc);
+ } else {
+ /* first insmod */
+ sdio_host->mmc->rescan_entered = 0;
+ mmc_detect_change(sdio_host->mmc, 0);
+ }
+ } else {
+ /* can not directly use the mmc_stop_host helper due to GKI restrictions.
+ * use the detect process to remove the card.
+ */
+ if (!sdio_host->mmc || !sdio_host->mmc->card) {
+ /* sdio card does not exist */
+ return;
+ }
+ sdio_host->mmc->rescan_entered = 0;
+ sdio_host->mmc->card->state |= MMC_CARD_REMOVED;
+ mmc_detect_change(sdio_host->mmc, 0);
+ }
+ }
+}
+EXPORT_SYMBOL(spacemit_sdio_detect_change);
+
+static void spacemit_sdhci_reset(struct sdhci_host *host, u8 mask)
+{
+ struct platform_device *pdev;
+ struct k1x_sdhci_platdata *pdata;
+ unsigned int reg;
+
+ pdev = to_platform_device(mmc_dev(host->mmc));
+ pdata = pdev->dev.platform_data;
+ sdhci_reset(host, mask);
+
+ if (mask != SDHCI_RESET_ALL)
+ return;
+
+ /* sd/sdio only be SDHCI_QUIRK2_BROKEN_PHY_MODULE */
+ if (!(host->quirks2 & SDHCI_QUIRK2_BROKEN_PHY_MODULE)) {
+ if (host->quirks2 & SDHCI_QUIRK2_SUPPORT_PHY_BYPASS) {
+ /* use phy bypass */
+ reg = sdhci_readl(host, SDHC_TX_CFG_REG);
+ reg |= TX_INT_CLK_SEL;
+ sdhci_writel (host, reg, SDHC_TX_CFG_REG);
+
+ reg = sdhci_readl(host, SDHC_PHY_CTRL_REG);
+ reg |= HOST_LEGACY_MODE;
+ sdhci_writel (host, reg, SDHC_PHY_CTRL_REG);
+
+ reg = sdhci_readl(host, SDHC_PHY_FUNC_REG);
+ reg |= PHY_TEST_EN;
+ sdhci_writel (host, reg, SDHC_PHY_FUNC_REG);
+ } else {
+ /* use phy func mode */
+ reg = sdhci_readl(host, SDHC_PHY_CTRL_REG);
+ reg |= (PHY_FUNC_EN | PHY_PLL_LOCK);
+ sdhci_writel(host, reg, SDHC_PHY_CTRL_REG);
+
+ reg = sdhci_readl(host, SDHC_PHY_PADCFG_REG);
+ reg |= (1 << RX_BIAS_CTRL_SHIFT);
+
+ reg &= ~(PHY_DRIVE_SEL_MASK);
+ reg |= (pdata->phy_driver_sel & PHY_DRIVE_SEL_MASK) << PHY_DRIVE_SEL_SHIFT;
+ sdhci_writel(host, reg, SDHC_PHY_PADCFG_REG);
+ }
+ } else {
+ reg = sdhci_readl(host, SDHC_TX_CFG_REG);
+ reg |= TX_INT_CLK_SEL;
+ sdhci_writel (host, reg, SDHC_TX_CFG_REG);
+ }
+
+ /* for emmc */
+ if (!(host->mmc->caps2 & MMC_CAP2_NO_MMC)) {
+ /* mmc card mode */
+ reg = sdhci_readl(host, SDHC_MMC_CTRL_REG);
+ reg |= MMC_CARD_MODE;
+ sdhci_writel(host, reg, SDHC_MMC_CTRL_REG);
+ }
+}
+
+static void spacemit_sdhci_gen_init_74_clocks(struct sdhci_host *host, u8 power_mode)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_spacemit *spacemit = sdhci_pltfm_priv(pltfm_host);
+ unsigned int reg;
+ int count;
+
+ if (!(host->mmc->caps2 & MMC_CAP2_NO_SDIO))
+ return;
+
+ if ((spacemit->power_mode == MMC_POWER_UP) &&
+ (power_mode == MMC_POWER_ON)) {
+ reg = sdhci_readl(host, SDHC_MMC_CTRL_REG);
+ reg |= MISC_INT_EN;
+ sdhci_writel(host, reg, SDHC_MMC_CTRL_REG);
+
+ reg = sdhci_readl(host, SDHC_LEGACY_CTRL_REG);
+ reg |= GEN_PAD_CLK_ON;
+ sdhci_writel(host, reg, SDHC_LEGACY_CTRL_REG);
+
+ count = 0;
+ while (count++ < MAX_74CLK_WAIT_COUNT) {
+ if (sdhci_readl(host, SDHC_MMC_CTRL_REG) & MISC_INT)
+ break;
+ udelay(10);
+ }
+
+ if (count == MAX_74CLK_WAIT_COUNT)
+ pr_warn("%s: gen 74 clock interrupt timeout\n",
+ mmc_hostname(host->mmc));
+
+ reg = sdhci_readl(host, SDHC_MMC_CTRL_REG);
+ reg |= MISC_INT;
+ sdhci_writel(host, reg, SDHC_MMC_CTRL_REG);
+ }
+ spacemit->power_mode = power_mode;
+}
+
+static void __maybe_unused spacemit_sdhci_caps_disable(struct sdhci_host *host)
+{
+ struct platform_device *pdev;
+ struct k1x_sdhci_platdata *pdata;
+
+ pdev = to_platform_device(mmc_dev(host->mmc));
+ pdata = pdev->dev.platform_data;
+
+ if (pdata->host_caps_disable)
+ host->mmc->caps &= ~(pdata->host_caps_disable);
+ if (pdata->host_caps2_disable)
+ host->mmc->caps2 &= ~(pdata->host_caps2_disable);
+}
+
+static void spacemit_sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing)
+{
+ u16 reg;
+
+ if ((timing == MMC_TIMING_MMC_HS200) ||
+ (timing == MMC_TIMING_MMC_HS400)) {
+ reg = sdhci_readw(host, SDHC_MMC_CTRL_REG);
+ reg |= (timing == MMC_TIMING_MMC_HS200) ? MMC_HS200 : MMC_HS400;
+ sdhci_writew(host, reg, SDHC_MMC_CTRL_REG);
+ }
+ sdhci_set_uhs_signaling(host, timing);
+ if (!(host->mmc->caps2 & MMC_CAP2_NO_SDIO)) {
+ reg = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ sdhci_writew(host, reg | SDHCI_CTRL_VDD_180, SDHCI_HOST_CONTROL2);
+ }
+}
+
+static void spacemit_sdhci_set_clk_gate(struct sdhci_host *host, unsigned int auto_gate)
+{
+ unsigned int reg;
+
+ reg = sdhci_readl(host, SDHC_OP_EXT_REG);
+ if (auto_gate)
+ reg &= ~(OVRRD_CLK_OEN | FORCE_CLK_ON);
+ else
+ reg |= (OVRRD_CLK_OEN | FORCE_CLK_ON);
+ sdhci_writel(host, reg, SDHC_OP_EXT_REG);
+}
+
+static int spacemit_sdhci_card_busy(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ u32 present_state;
+ u32 ret;
+ u32 cmd;
+
+ /* Check whether DAT[0] is 0 */
+ present_state = sdhci_readl(host, SDHCI_PRESENT_STATE);
+ ret = !(present_state & SDHCI_DATA_0_LVL_MASK);
+
+ if (host->mmc->caps2 & MMC_CAP2_NO_MMC) {
+ cmd = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND));
+ if ((cmd == SD_SWITCH_VOLTAGE) && (host->mmc->ios.signal_voltage == MMC_SIGNAL_VOLTAGE_180)) {
+ /* recover the auto clock */
+ spacemit_sdhci_set_clk_gate(host, 1);
+ }
+ }
+
+ return ret;
+}
+
+static void spacemit_init_card_quriks(struct mmc_host *mmc, struct mmc_card *card)
+{
+ if (mmc->caps2 & MMC_CAP2_NO_MMC) {
+ /* break sdr104 */
+ if (mmc->caps2 & MMC_CAP2_QUIRK_BREAK_SDR104) {
+ mmc->caps &= ~MMC_CAP_UHS_SDR104;
+ mmc->caps2 &= ~MMC_CAP2_QUIRK_BREAK_SDR104;
+ } else {
+ struct k1x_sdhci_platdata *pdata = mmc->parent->platform_data;
+ struct rx_tuning *rxtuning = &pdata->rxtuning;
+
+ if (rxtuning->tuning_fail) {
+ /* fallback bus speed */
+ mmc->caps &= ~MMC_CAP_UHS_SDR104;
+ rxtuning->tuning_fail = 0;
+ } else {
+ /* recovery sdr104 capability */
+ mmc->caps |= MMC_CAP_UHS_SDR104;
+ }
+ }
+ }
+
+ if (!(mmc->caps2 & MMC_CAP2_NO_SDIO)) {
+ /* disable MMC_CAP2_SDIO_IRQ_NOTHREAD */
+ mmc->caps2 &= ~MMC_CAP2_SDIO_IRQ_NOTHREAD;
+
+ /* use the fake irq pending to avoid to read the SDIO_CCCR_INTx
+ * which sometimes return an abnormal value.
+ */
+ mmc->sdio_irq_pending = true;
+ }
+}
+
+static void spacemit_sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable)
+{
+ if (!(host->flags & SDHCI_DEVICE_DEAD)) {
+ if (enable)
+ host->ier |= SDHCI_INT_CARD_INT;
+ else
+ host->ier &= ~SDHCI_INT_CARD_INT;
+
+ sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+ sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+ }
+}
+
+static void spacemit_sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ spacemit_sdhci_enable_sdio_irq_nolock(host, enable);
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void spacemit_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ unsigned long flags;
+
+ spacemit_sdhci_enable_sdio_irq(mmc, enable);
+
+ /* avoid to read the SDIO_CCCR_INTx */
+ spin_lock_irqsave(&host->lock, flags);
+ mmc->sdio_irq_pending = true;
+ spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void spacemit_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_spacemit *spacemit = sdhci_pltfm_priv(pltfm_host);
+ struct mmc_host *mmc = host->mmc;
+ unsigned int reg;
+ u32 cmd;
+
+ /* according to the SDHC_TX_CFG_REG(0x11c<bit>),
+ * set TX_INT_CLK_SEL to gurantee the hold time
+ * at default speed mode or HS/SDR12/SDR25/SDR50 mode.
+ */
+ reg = sdhci_readl(host, SDHC_TX_CFG_REG);
+ if ((mmc->ios.timing == MMC_TIMING_LEGACY) ||
+ (mmc->ios.timing == MMC_TIMING_SD_HS) ||
+ (mmc->ios.timing == MMC_TIMING_UHS_SDR12) ||
+ (mmc->ios.timing == MMC_TIMING_UHS_SDR25) ||
+ (mmc->ios.timing == MMC_TIMING_UHS_SDR50) ||
+ (mmc->ios.timing == MMC_TIMING_MMC_HS)) {
+ reg |= TX_INT_CLK_SEL;
+ } else {
+ reg &= ~TX_INT_CLK_SEL;
+ }
+ sdhci_writel(host, reg, SDHC_TX_CFG_REG);
+
+ /* set pinctrl state */
+ if (spacemit->pinctrl && !IS_ERR(spacemit->pinctrl)) {
+ if (clock >= 200000000) {
+ spacemit->pin = pinctrl_lookup_state(spacemit->pinctrl, "fast");
+ if (IS_ERR(spacemit->pin))
+ pr_warn("could not get sdhci pinctrl state.\n");
+ else
+ pinctrl_select_state(spacemit->pinctrl, spacemit->pin);
+
+ } else {
+ spacemit->pin = pinctrl_lookup_state(spacemit->pinctrl, "default");
+ if (IS_ERR(spacemit->pin))
+ pr_warn("could not get sdhci pinctrl state.\n");
+ else
+ pinctrl_select_state(spacemit->pinctrl, spacemit->pin);
+ }
+ }
+
+ if (host->mmc->caps2 & MMC_CAP2_NO_MMC) {
+ /*
+ * according to the SD spec, during a signal voltage level switch,
+ * the clock must be closed for 5 ms.
+ * then, the host starts providing clk at 1.8 and the host checks whether
+ * DAT[3:0] is high after 1ms clk.
+ *
+ * for the above goal, temporarily disable the auto clk and keep clk always on for 1ms.
+ */
+ cmd = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND));
+ if ((cmd == SD_SWITCH_VOLTAGE) && (host->mmc->ios.signal_voltage == MMC_SIGNAL_VOLTAGE_180)) {
+ /* disable auto clock */
+ spacemit_sdhci_set_clk_gate(host, 0);
+ }
+ }
+
+ sdhci_set_clock(host, clock);
+};
+
+static void spacemit_sdhci_phy_dll_init(struct sdhci_host *host)
+{
+ u32 reg;
+ int i;
+
+ /* config dll_reg1 & dll_reg2 */
+ reg = sdhci_readl(host, SDHC_PHY_DLLCFG);
+ reg |= (DLL_PREDLY_NUM | DLL_FULLDLY_RANGE | DLL_VREG_CTRL);
+ sdhci_writel(host, reg, SDHC_PHY_DLLCFG);
+
+ reg = sdhci_readl(host, SDHC_PHY_DLLCFG1);
+ reg |= (DLL_REG1_CTRL & DLL_REG1_CTRL_MASK);
+ sdhci_writel(host, reg, SDHC_PHY_DLLCFG1);
+
+ /* dll enable */
+ reg = sdhci_readl(host, SDHC_PHY_DLLCFG);
+ reg |= DLL_ENABLE;
+ sdhci_writel(host, reg, SDHC_PHY_DLLCFG);
+
+ /* wait dll lock */
+ i = 0;
+ while (i++ < 100) {
+ if (sdhci_readl(host, SDHC_PHY_DLLSTS) & DLL_LOCK_STATE)
+ break;
+ udelay(10);
+ }
+ if (i == 100)
+ pr_err("%s: dll lock timeout\n", mmc_hostname(host->mmc));
+}
+
+static void spacemit_sdhci_hs400_enhanced_strobe(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ u32 reg;
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ reg = sdhci_readl(host, SDHC_MMC_CTRL_REG);
+ if (ios->enhanced_strobe)
+ reg |= ENHANCE_STROBE_EN;
+ else
+ reg &= ~ENHANCE_STROBE_EN;
+ sdhci_writel(host, reg, SDHC_MMC_CTRL_REG);
+
+ if (ios->enhanced_strobe)
+ spacemit_sdhci_phy_dll_init(host);
+}
+
+static int spacemit_sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
+ struct mmc_ios *ios)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+ u16 ctrl;
+ int ret;
+
+ /*
+ * Signal Voltage Switching is only applicable for Host Controllers
+ * v3.00 and above.
+ */
+ if (host->version < SDHCI_SPEC_300)
+ return 0;
+
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+
+ switch (ios->signal_voltage) {
+ case MMC_SIGNAL_VOLTAGE_330:
+ if (!(host->flags & SDHCI_SIGNALING_330))
+ return -EINVAL;
+ /* Set 1.8V Signal Enable in the Host Control2 register to 0 */
+ ctrl &= ~SDHCI_CTRL_VDD_180;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+
+ /* Some controller need to do more when switching */
+ if (host->ops->voltage_switch)
+ host->ops->voltage_switch(host);
+
+ if (!IS_ERR(mmc->supply.vqmmc)) {
+ ret = mmc_regulator_set_vqmmc(mmc, ios);
+ if (ret < 0) {
+ pr_warn("%s: Switching to 3.3V signalling voltage failed\n",
+ mmc_hostname(mmc));
+ return -EIO;
+ }
+ }
+ /* Wait for 5ms */
+ usleep_range(5000, 5500);
+
+ /* 3.3V regulator output should be stable within 5 ms */
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (!(ctrl & SDHCI_CTRL_VDD_180))
+ return 0;
+
+ pr_warn("%s: 3.3V regulator output did not become stable\n",
+ mmc_hostname(mmc));
+
+ return -EAGAIN;
+ case MMC_SIGNAL_VOLTAGE_180:
+ if (!(host->flags & SDHCI_SIGNALING_180))
+ return -EINVAL;
+ if (!IS_ERR(mmc->supply.vqmmc)) {
+ ret = mmc_regulator_set_vqmmc(mmc, ios);
+ if (ret < 0) {
+ pr_warn("%s: Switching to 1.8V signalling voltage failed\n",
+ mmc_hostname(mmc));
+ return -EIO;
+ }
+ }
+
+ /*
+ * Enable 1.8V Signal Enable in the Host Control2
+ * register
+ */
+ ctrl |= SDHCI_CTRL_VDD_180;
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+
+ /* Some controller need to do more when switching */
+ if (host->ops->voltage_switch)
+ host->ops->voltage_switch(host);
+
+ /* 1.8V regulator output should be stable within 5 ms */
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ if (ctrl & SDHCI_CTRL_VDD_180)
+ return 0;
+
+ pr_warn("%s: 1.8V regulator output did not become stable\n",
+ mmc_hostname(mmc));
+
+ return -EAGAIN;
+ case MMC_SIGNAL_VOLTAGE_120:
+ if (!(host->flags & SDHCI_SIGNALING_120))
+ return -EINVAL;
+ if (!IS_ERR(mmc->supply.vqmmc)) {
+ ret = mmc_regulator_set_vqmmc(mmc, ios);
+ if (ret < 0) {
+ pr_warn("%s: Switching to 1.2V signalling voltage failed\n",
+ mmc_hostname(mmc));
+ return -EIO;
+ }
+ }
+ return 0;
+ default:
+ /* No signal voltage switch required */
+ return 0;
+ }
+}
+
+static void spacemit_set_aib_mmc1_io(struct sdhci_host *host, int vol)
+{
+ void __iomem *aib_mmc1_io;
+ void __iomem *apbc_asfar;
+ void __iomem *apbc_assar;
+ u32 reg;
+ struct platform_device *pdev;
+ struct k1x_sdhci_platdata *pdata;
+
+ pdev = to_platform_device(mmc_dev(host->mmc));
+ pdata = pdev->dev.platform_data;
+
+ if (!pdata->aib_mmc1_io_reg ||
+ !pdata->apbc_asfar_reg ||
+ !pdata->apbc_assar_reg)
+ return;
+
+ aib_mmc1_io = ioremap(pdata->aib_mmc1_io_reg, 4);
+ apbc_asfar = ioremap(pdata->apbc_asfar_reg, 4);
+ apbc_assar = ioremap(pdata->apbc_assar_reg, 4);
+
+ writel(AKEY_ASFAR, apbc_asfar);
+ writel(AKEY_ASSAR, apbc_assar);
+ reg = readl(aib_mmc1_io);
+
+ switch (vol) {
+ case MMC_SIGNAL_VOLTAGE_180:
+ reg |= MMC1_IO_V18EN;
+ break;
+ default:
+ reg &= ~MMC1_IO_V18EN;
+ break;
+ }
+ writel(AKEY_ASFAR, apbc_asfar);
+ writel(AKEY_ASSAR, apbc_assar);
+ writel(reg, aib_mmc1_io);
+
+ iounmap(apbc_assar);
+ iounmap(apbc_asfar);
+ iounmap(aib_mmc1_io);
+}
+
+static void spacemit_sdhci_voltage_switch(struct sdhci_host *host)
+{
+ struct mmc_host *mmc = host->mmc;
+ struct mmc_ios ios = mmc->ios;
+
+ /*
+ * v18en(MS) bit should meet TSMC's requirement when switch SOC SD
+ * IO voltage from 3.3(3.0)v to 1.8v
+ */
+ if (host->quirks2 & SDHCI_QUIRK2_SET_AIB_MMC)
+ spacemit_set_aib_mmc1_io(host, ios.signal_voltage);
+}
+
+static void spacemit_sw_rx_tuning_prepare(struct sdhci_host *host, u8 dline_reg)
+{
+ struct mmc_host *mmc = host->mmc;
+ struct mmc_ios ios = mmc->ios;
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_DLINE_CFG_REG);
+ reg &= ~(RX_DLINE_REG_MASK << RX_DLINE_REG_SHIFT);
+ reg |= dline_reg << RX_DLINE_REG_SHIFT;
+ reg &= ~(RX_DLINE_GAIN_MASK << RX_DLINE_GAIN_SHIFT);
+ if ((ios.timing == MMC_TIMING_UHS_SDR50) && (reg & 0x40))
+ reg |= RX_DLINE_GAIN << RX_DLINE_GAIN_SHIFT;
+ sdhci_writel(host, reg, SDHC_DLINE_CFG_REG);
+
+ reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
+ reg |= DLINE_PU;
+ sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
+ udelay(5);
+
+ reg = sdhci_readl(host, SDHC_RX_CFG_REG);
+ reg &= ~(RX_SDCLK_SEL1_MASK << RX_SDCLK_SEL1_SHIFT);
+ reg |= RX_SDCLK_SEL1 << RX_SDCLK_SEL1_SHIFT;
+ sdhci_writel(host, reg, SDHC_RX_CFG_REG);
+
+ if ((mmc->ios.timing == MMC_TIMING_MMC_HS200)
+ && !(host->quirks2 & SDHCI_QUIRK2_BROKEN_PHY_MODULE)) {
+ reg = sdhci_readl(host, SDHC_PHY_FUNC_REG);
+ reg |= HS200_USE_RFIFO;
+ sdhci_writel(host, reg, SDHC_PHY_FUNC_REG);
+ }
+}
+
+static void spacemit_sw_rx_set_delaycode(struct sdhci_host *host, u32 delay)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
+ reg &= ~(RX_DLINE_CODE_MASK << RX_DLINE_CODE_SHIFT);
+ reg |= (delay & RX_DLINE_CODE_MASK) << RX_DLINE_CODE_SHIFT;
+ sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
+}
+
+static void spacemit_sw_tx_tuning_prepare(struct sdhci_host *host)
+{
+ u32 reg;
+
+ /* set TX_MUX_SEL */
+ reg = sdhci_readl(host, SDHC_TX_CFG_REG);
+ reg |= TX_MUX_SEL;
+ sdhci_writel(host, reg, SDHC_TX_CFG_REG);
+
+ reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
+ reg |= DLINE_PU;
+ sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
+ udelay(5);
+}
+
+static void spacemit_sw_tx_set_dlinereg(struct sdhci_host *host, u8 dline_reg)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_DLINE_CFG_REG);
+ reg &= ~(TX_DLINE_REG_MASK << TX_DLINE_REG_SHIFT);
+ reg |= dline_reg << TX_DLINE_REG_SHIFT;
+ sdhci_writel(host, reg, SDHC_DLINE_CFG_REG);
+}
+
+static void spacemit_sw_tx_set_delaycode(struct sdhci_host *host, u32 delay)
+{
+ u32 reg;
+
+ reg = sdhci_readl(host, SDHC_DLINE_CTRL_REG);
+ reg &= ~(TX_DLINE_CODE_MASK << TX_DLINE_CODE_SHIFT);
+ reg |= (delay & TX_DLINE_CODE_MASK) << TX_DLINE_CODE_SHIFT;
+ sdhci_writel(host, reg, SDHC_DLINE_CTRL_REG);
+}
+
+static void spacemit_sdhci_clear_set_irqs(struct sdhci_host *host, u32 clr, u32 set)
+{
+ u32 ier;
+
+ ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+ ier &= ~clr;
+ ier |= set;
+ sdhci_writel(host, ier, SDHCI_INT_ENABLE);
+ sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+}
+
+static int spacemit_tuning_patten_check(struct sdhci_host *host, int point)
+{
+ u32 read_patten;
+ unsigned int i;
+ u32 *tuning_patten;
+ int patten_len;
+ int err = 0;
+
+ if (host->mmc->ios.bus_width == MMC_BUS_WIDTH_8) {
+ tuning_patten = (u32 *)tuning_patten8;
+ patten_len = ARRAY_SIZE(tuning_patten8);
+ } else {
+ tuning_patten = (u32 *)tuning_patten4;
+ patten_len = ARRAY_SIZE(tuning_patten4);
+ }
+
+ for (i = 0; i < patten_len; i++) {
+ read_patten = sdhci_readl(host, SDHCI_BUFFER);
+ if (read_patten != tuning_patten[i])
+ err++;
+ }
+
+ return err;
+}
+
+static int spacemit_send_tuning_cmd(struct sdhci_host *host, u32 opcode,
+ int point, unsigned long flags)
+{
+ int err = 0;
+
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ sdhci_send_tuning(host, opcode);
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (!host->tuning_done) {
+ pr_err("%s: Timeout waiting for Buffer Read Ready interrupt "
+ "during tuning procedure, resetting CMD and DATA\n",
+ mmc_hostname(host->mmc));
+ sdhci_reset(host, SDHCI_RESET_CMD|SDHCI_RESET_DATA);
+ /* err = -EIO; */
+ } else
+ err = spacemit_tuning_patten_check(host, point);
+
+ host->tuning_done = 0;
+ return err;
+}
+
+static int spacemit_sw_rx_select_window(struct sdhci_host *host, u32 opcode)
+{
+ int min;
+ int max;
+ u16 ctrl;
+ u32 ier;
+ unsigned long flags = 0;
+ int err = 0;
+ int i, j, len;
+ struct tuning_window tmp;
+ struct mmc_host *mmc = host->mmc;
+ struct k1x_sdhci_platdata *pdata = mmc->parent->platform_data;
+ struct rx_tuning *rxtuning = &pdata->rxtuning;
+
+ /* change to pio mode during the tuning stage */
+ spin_lock_irqsave(&host->lock, flags);
+ ier = sdhci_readl(host, SDHCI_INT_ENABLE);
+ spacemit_sdhci_clear_set_irqs(host, ier, SDHCI_INT_DATA_AVAIL);
+
+ min = SDHC_RX_TUNE_DELAY_MIN;
+ do {
+ /* find the mininum delay first which can pass tuning */
+ while (min < SDHC_RX_TUNE_DELAY_MAX) {
+ spacemit_sw_rx_set_delaycode(host, min);
+ if (!mmc->ops->get_cd(mmc)) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return -ENODEV;
+ }
+ err = spacemit_send_tuning_cmd(host, opcode, min, flags);
+ if (err == -EIO) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return -EIO;
+ }
+ if (!err)
+ break;
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ ctrl &= ~(SDHCI_CTRL_TUNED_CLK | SDHCI_CTRL_EXEC_TUNING);
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+ min += SDHC_RX_TUNE_DELAY_STEP;
+ }
+
+ /* find the maxinum delay which can not pass tuning */
+ max = min + SDHC_RX_TUNE_DELAY_STEP;
+ while (max < SDHC_RX_TUNE_DELAY_MAX) {
+ spacemit_sw_rx_set_delaycode(host, max);
+ if (!mmc->ops->get_cd(mmc)) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return -ENODEV;
+ }
+ err = spacemit_send_tuning_cmd(host, opcode, max, flags);
+ if (err) {
+ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2);
+ ctrl &= ~(SDHCI_CTRL_TUNED_CLK | SDHCI_CTRL_EXEC_TUNING);
+ sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2);
+ if (err == -EIO) {
+ spin_unlock_irqrestore(&host->lock, flags);
+ return -EIO;
+ }
+ break;
+ }
+ max += SDHC_RX_TUNE_DELAY_STEP;
+ }
+
+ pr_notice("%s: pass window [%d %d) \n", mmc_hostname(host->mmc), min, max);
+ /* store the top 3 window */
+ if ((max - min) >= rxtuning->window_limit) {
+ tmp.max_delay = max;
+ tmp.min_delay = min;
+ tmp.type = MIDDLE_WINDOW;
+ for (i = 0; i < CANDIDATE_WIN_NUM; i++) {
+ len = rxtuning->windows[i].max_delay - rxtuning->windows[i].min_delay;
+ if ((tmp.max_delay - tmp.min_delay) > len) {
+ for (j = CANDIDATE_WIN_NUM - 1; j > i; j--) {
+ rxtuning->windows[j] = rxtuning->windows[j-1];
+ }
+ rxtuning->windows[i] = tmp;
+ break;
+ }
+ }
+ }
+ min = max + SDHC_RX_TUNE_DELAY_STEP;
+ } while (min < SDHC_RX_TUNE_DELAY_MAX);
+
+ spacemit_sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier);
+ spin_unlock_irqrestore(&host->lock, flags);
+ return 0;
+}
+
+static int spacemit_sw_rx_select_delay(struct sdhci_host *host)
+{
+ int i;
+ int win_len, min, max, mid;
+ struct tuning_window *window;
+
+ struct mmc_host *mmc = host->mmc;
+ struct k1x_sdhci_platdata *pdata = mmc->parent->platform_data;
+ struct rx_tuning *tuning = &pdata->rxtuning;
+
+ for (i = 0; i < CANDIDATE_WIN_NUM; i++) {
+ window = &tuning->windows[i];
+ min = window->min_delay;
+ max = window->max_delay;
+ mid = (min + max - 1) / 2;
+ win_len = max - min;
+ if (win_len < tuning->window_limit)
+ continue;
+
+ if (window->type == LEFT_WINDOW) {
+ tuning->select_delay[tuning->select_delay_num++] = min + win_len / 3;
+ tuning->select_delay[tuning->select_delay_num++] = min + win_len / 2;
+ } else if (window->type == RIGHT_WINDOW) {
+ tuning->select_delay[tuning->select_delay_num++] = max - win_len / 4;
+ tuning->select_delay[tuning->select_delay_num++] = min - win_len / 3;
+ } else {
+ tuning->select_delay[tuning->select_delay_num++] = mid;
+ tuning->select_delay[tuning->select_delay_num++] = mid + win_len / 4;
+ tuning->select_delay[tuning->select_delay_num++] = mid - win_len / 4;
+ }
+ }
+
+ return tuning->select_delay_num;
+}
+
+static void spacemit_sw_rx_card_store(struct sdhci_host *host, struct rx_tuning *tuning)
+{
+ struct mmc_card *card = host->mmc->card;
+
+ if (card)
+ memcpy(tuning->card_cid, card->raw_cid, sizeof(card->raw_cid));
+}
+
+static int spacemit_sw_rx_card_pretuned(struct sdhci_host *host, struct rx_tuning *tuning)
+{
+ struct mmc_card *card = host->mmc->card;
+
+ if (!card)
+ return 0;
+
+ return !memcmp(tuning->card_cid, card->raw_cid, sizeof(card->raw_cid));
+}
+
+static int spacemit_sdhci_execute_sw_tuning(struct sdhci_host *host, u32 opcode)
+{
+ int ret;
+ int index;
+ struct mmc_host *mmc = host->mmc;
+ struct mmc_ios ios = mmc->ios;
+ struct k1x_sdhci_platdata *pdata = mmc->parent->platform_data;
+ struct rx_tuning *rxtuning = &pdata->rxtuning;
+
+ /*
+ * Tuning is required for SDR50/SDR104, HS200/HS400 cards and
+ * if clock frequency is greater than 100MHz in these modes.
+ */
+ if (host->clock < 100 * 1000 * 1000 ||
+ !((ios.timing == MMC_TIMING_MMC_HS200) ||
+ (ios.timing == MMC_TIMING_UHS_SDR50) ||
+ (ios.timing == MMC_TIMING_UHS_SDR104)))
+ return 0;
+
+ if (!(mmc->caps2 & MMC_CAP2_NO_SD) && !mmc->ops->get_cd(mmc)) {
+ return 0;
+ }
+
+ /* TX tuning config */
+ if ((host->mmc->caps2 & MMC_CAP2_NO_MMC) || (host->quirks2 & SDHCI_QUIRK2_BROKEN_PHY_MODULE)) {
+ spacemit_sw_tx_set_dlinereg(host, pdata->tx_dline_reg);
+ spacemit_sw_tx_set_delaycode(host, pdata->tx_delaycode);
+ pr_info("%s: set tx_delaycode: %d\n", mmc_hostname(mmc), pdata->tx_delaycode);
+ spacemit_sw_tx_tuning_prepare(host);
+ }
+
+ /* step 1: check pretuned card */
+ if (spacemit_sw_rx_card_pretuned(host, rxtuning) &&
+ rxtuning->select_delay_num) {
+ index = rxtuning->current_delay_index;
+ if (mmc->doing_retune)
+ index++;
+ if (index == rxtuning->select_delay_num) {
+ pr_info("%s: all select delay failed, re-init to DDR50\n", mmc_hostname(mmc));
+ rxtuning->select_delay_num = 0;
+ rxtuning->current_delay_index = 0;
+ memset(rxtuning->windows, 0, sizeof(rxtuning->windows));
+ memset(rxtuning->select_delay, 0xFF, sizeof(rxtuning->select_delay));
+ memset(rxtuning->card_cid, 0, sizeof(rxtuning->card_cid));
+ rxtuning->tuning_fail = 1;
+ return -EIO;
+ }
+
+ spacemit_sw_rx_tuning_prepare(host, rxtuning->rx_dline_reg);
+ spacemit_sw_rx_set_delaycode(host, rxtuning->select_delay[index]);
+ pr_info("%s: pretuned card, use select_delay[%d]:%d\n",
+ mmc_hostname(mmc), index, rxtuning->select_delay[index]);
+ rxtuning->current_delay_index = index;
+ return 0;
+ }
+
+ rxtuning->select_delay_num = 0;
+ rxtuning->current_delay_index = 0;
+ memset(rxtuning->windows, 0, sizeof(rxtuning->windows));
+ memset(rxtuning->select_delay, 0xFF, sizeof(rxtuning->select_delay));
+ memset(rxtuning->card_cid, 0, sizeof(rxtuning->card_cid));
+
+ /* step 2: get pass window and caculate the select_delay */
+ spacemit_sw_rx_tuning_prepare(host, rxtuning->rx_dline_reg);
+ ret = spacemit_sw_rx_select_window(host, opcode);
+
+ if (ret) {
+ pr_warn("%s: abort tuning, err:%d\n", mmc_hostname(mmc), ret);
+ rxtuning->tuning_fail = 1;
+ return ret;
+ }
+
+ if (!spacemit_sw_rx_select_delay(host)) {
+ pr_warn("%s: fail to get delaycode\n", mmc_hostname(mmc));
+ rxtuning->tuning_fail = 1;
+ return -EIO;
+ }
+
+ /* step 3: set the delay code and store card cid */
+ spacemit_sw_rx_set_delaycode(host, rxtuning->select_delay[0]);
+ spacemit_sw_rx_card_store(host, rxtuning);
+ rxtuning->tuning_fail = 0;
+ pr_info("%s: tuning done, use the firstly delay_code:%d\n",
+ mmc_hostname(mmc), rxtuning->select_delay[0]);
+ return 0;
+}
+
+static unsigned int spacemit_sdhci_clk_get_max_clock(struct sdhci_host *host)
+{
+ unsigned long rate;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+ rate = clk_get_rate(pltfm_host->clk);
+ return rate;
+}
+
+static unsigned int spacemit_get_max_timeout_count(struct sdhci_host *host)
+{
+ /*
+ * the default sdhci code use the 1 << 27 as the max timeout counter
+ * to calculate the max_busy_timeout.
+ * aquilac sdhci support 1 << 29 as the timeout counter.
+ */
+ return 1 << 29;
+}
+
+static int spacemit_sdhci_pre_select_hs400(struct mmc_host *mmc)
+{
+ u32 reg;
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ reg = sdhci_readl(host, SDHC_MMC_CTRL_REG);
+ reg |= MMC_HS400;
+ sdhci_writel(host, reg, SDHC_MMC_CTRL_REG);
+ host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY;
+
+ return 0;
+}
+
+static void spacemit_sdhci_post_select_hs400(struct mmc_host *mmc)
+{
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ spacemit_sdhci_phy_dll_init(host);
+ host->mmc->caps &= ~MMC_CAP_WAIT_WHILE_BUSY;
+}
+
+static void spacemit_sdhci_pre_hs400_to_hs200(struct mmc_host *mmc)
+{
+ u32 reg;
+ struct sdhci_host *host = mmc_priv(mmc);
+
+ reg = sdhci_readl(host, SDHC_PHY_CTRL_REG);
+ reg &= ~(PHY_FUNC_EN | PHY_PLL_LOCK);
+ sdhci_writel(host, reg, SDHC_PHY_CTRL_REG);
+
+ reg = sdhci_readl(host, SDHC_MMC_CTRL_REG);
+ reg &= ~(MMC_HS400 | MMC_HS200 | ENHANCE_STROBE_EN);
+ sdhci_writel(host, reg, SDHC_MMC_CTRL_REG);
+
+ reg = sdhci_readl(host, SDHC_PHY_FUNC_REG);
+ reg &= ~HS200_USE_RFIFO;
+ sdhci_writel(host, reg, SDHC_PHY_FUNC_REG);
+
+ udelay(5);
+
+ reg = sdhci_readl(host, SDHC_PHY_CTRL_REG);
+ reg |= (PHY_FUNC_EN | PHY_PLL_LOCK);
+ sdhci_writel(host, reg, SDHC_PHY_CTRL_REG);
+}
+
+static void __maybe_unused spacemit_sdhci_reset_dllcfg1(struct sdhci_host *host)
+{
+ struct mmc_host *mmc = host->mmc;
+ struct k1x_sdhci_platdata *pdata = mmc->parent->platform_data;
+
+ if (sdhci_readl(host, SDHC_PHY_DLLCFG1) == pdata->new_dllcfg1)
+ return;
+ if (!pdata->need_reset_dllcfg1)
+ return;
+
+ spacemit_reset_dllcfg1_reg(host, pdata->new_dllcfg1);
+}
+
+static void spacemit_sdhci_set_encrypt(struct sdhci_host *host, unsigned int enc_flag)
+{
+ u32 reg;
+
+ if ((host->quirks2 & SDHCI_QUIRK2_SUPPORT_ENCRYPT)) {
+ reg = sdhci_readl(host, SDHC_DLINE_CFG_REG);
+ if (enc_flag)
+ reg &= ~(0x1 << 8);
+ else
+ reg |= (0x1 << 8);
+ sdhci_writel(host, reg, SDHC_DLINE_CFG_REG);
+ }
+}
+
+static void spacemit_sdhci_dump_vendor_regs(struct sdhci_host *host)
+{
+#ifdef CONFIG_K1X_MMC_DEBUG
+ dump_sdh_regs(host, &cur_com_reg[0], &cur_pri_reg[0]);
+ printk_ratelimited(KERN_INFO "%s", cur_com_reg);
+ printk_ratelimited(KERN_INFO "%s", cur_pri_reg);
+#endif
+}
+
+static const struct sdhci_ops spacemit_sdhci_ops = {
+ .set_clock = spacemit_sdhci_set_clock,
+ .platform_send_init_74_clocks = spacemit_sdhci_gen_init_74_clocks,
+ .get_max_clock = spacemit_sdhci_clk_get_max_clock,
+ .get_max_timeout_count = spacemit_get_max_timeout_count,
+ .set_bus_width = sdhci_set_bus_width,
+ .reset = spacemit_sdhci_reset,
+ .set_uhs_signaling = spacemit_sdhci_set_uhs_signaling,
+ .voltage_switch = spacemit_sdhci_voltage_switch,
+ .platform_execute_tuning = spacemit_sdhci_execute_sw_tuning,
+ .irq = spacemit_handle_interrupt,
+ .set_power = sdhci_set_power_and_bus_voltage,
+ .dump_vendor_regs = spacemit_sdhci_dump_vendor_regs,
+#ifdef CONFIG_ARCH_SPACEMIT_K1X
+ .set_encrypt_feature = spacemit_sdhci_set_encrypt,
+#endif
+};
+
+static struct sdhci_pltfm_data sdhci_k1x_pdata = {
+ .ops = &spacemit_sdhci_ops,
+ .quirks = SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK
+ | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC
+ | SDHCI_QUIRK_32BIT_ADMA_SIZE
+ | SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
+ .quirks2 = SDHCI_QUIRK2_BROKEN_64_BIT_DMA,
+};
+
+static const struct of_device_id sdhci_spacemit_of_match[] = {
+ {
+ .compatible = "spacemit,k1-x-sdhci",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sdhci_spacemit_of_match);
+
+static struct k1x_sdhci_platdata *spacemit_get_mmc_pdata(struct device *dev)
+{
+ struct k1x_sdhci_platdata *pdata;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+ return pdata;
+}
+
+static void spacemit_get_of_property(struct sdhci_host *host,
+ struct device *dev, struct k1x_sdhci_platdata *pdata)
+{
+ struct device_node *np = dev->of_node;
+ u32 property;
+
+ /* sdh io clk */
+ if (!of_property_read_u32(np, "spacemit,sdh-freq", &property))
+ pdata->host_freq = property;
+
+ if (!of_property_read_u32(np, "spacemit,sdh-flags", &property))
+ pdata->flags |= property;
+
+ if (!of_property_read_u32(np, "spacemit,sdh-host-caps", &property))
+ pdata->host_caps |= property;
+ if (!of_property_read_u32(np, "spacemit,sdh-host-caps2", &property))
+ pdata->host_caps2 |= property;
+
+ if (!of_property_read_u32(np, "spacemit,sdh-host-caps-disable", &property))
+ pdata->host_caps_disable |= property;
+ if (!of_property_read_u32(np, "spacemit,sdh-host-caps2-disable", &property))
+ pdata->host_caps2_disable |= property;
+
+ if (!of_property_read_u32(np, "spacemit,sdh-quirks", &property))
+ pdata->quirks |= property;
+ if (!of_property_read_u32(np, "spacemit,sdh-quirks2", &property))
+ pdata->quirks2 |= property;
+
+ pdata->aib_mmc1_io_reg = 0x0;
+ pdata->apbc_asfar_reg = 0x0;
+ pdata->apbc_assar_reg = 0x0;
+ if (!of_property_read_u32(np, "spacemit,aib_mmc1_io_reg", &property))
+ pdata->aib_mmc1_io_reg = property;
+ if (!of_property_read_u32(np, "spacemit,apbc_asfar_reg", &property))
+ pdata->apbc_asfar_reg = property;
+ if (!of_property_read_u32(np, "spacemit,apbc_assar_reg", &property))
+ pdata->apbc_assar_reg = property;
+
+ /* read rx tuning dline_reg */
+ if (!of_property_read_u32(np, "spacemit,rx_dline_reg", &property))
+ pdata->rxtuning.rx_dline_reg = (u8)property;
+ else
+ pdata->rxtuning.rx_dline_reg = RX_TUNING_DLINE_REG;
+
+ /* read rx tuning window limit */
+ if (!of_property_read_u32(np, "spacemit,rx_tuning_limit", &property))
+ pdata->rxtuning.window_limit = (u8)property;
+ else
+ pdata->rxtuning.window_limit = RX_TUNING_WINDOW_THRESHOLD;
+
+ /* tx tuning dline_reg */
+ if (!of_property_read_u32(np, "spacemit,tx_dline_reg", &property))
+ pdata->tx_dline_reg = (u8)property;
+ else
+ pdata->tx_dline_reg = TX_TUNING_DLINE_REG;
+ if (!of_property_read_u32(np, "spacemit,tx_delaycode", &property))
+ pdata->tx_delaycode = (u8)property;
+ else
+ pdata->tx_delaycode = TX_TUNING_DELAYCODE;
+
+ /* phy driver select */
+ if (!of_property_read_u32(np, "spacemit,phy_driver_sel", &property))
+ pdata->phy_driver_sel = (u8)property;
+ else
+ pdata->phy_driver_sel = PHY_DRIVE_SEL_DEFAULT;
+
+ return;
+}
+
+#ifdef CONFIG_SPACEMIT_SW_JTAG
+extern void switch_jtag_tapctl(unsigned int tap_ctl);
+#endif
+#define SD_PMUX_SYSFS "/sys/devices/platform/soc/d4200000.axi/d4280000.sdh/sd_card_pmux"
+ssize_t sdhci_sysfs_pmux_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_spacemit *spacemit = sdhci_pltfm_priv(pltfm_host);
+ char pinname[16];
+
+ if (spacemit == NULL || spacemit->pinctrl == NULL) {
+ pr_err("could not set sd pinctrl.\n");
+ return count;
+ }
+
+ if (!strncmp(buf, "jtag", strlen("jtag"))) {
+ strncpy(pinname, "jtag", strlen("jtag") + 1);
+ spacemit_set_aib_mmc1_io(host, MMC_SIGNAL_VOLTAGE_180);
+#ifdef CONFIG_SPACEMIT_SW_JTAG
+ /* switch tap_ctl as seconod jtag by sw_jtag */
+ switch_jtag_tapctl(0x42);
+#endif
+ } else if (!strncmp(buf, "default", strlen("default"))) {
+ strncpy(pinname, "default", strlen("default") + 1);
+ spacemit_set_aib_mmc1_io(host, MMC_SIGNAL_VOLTAGE_330);
+#ifdef CONFIG_SPACEMIT_SW_JTAG
+ /* switch tap_ctl as primary jtag by sw_jtag */
+ switch_jtag_tapctl(0x0a);
+#endif
+ } else {
+ pr_info("usage: echo [jtag | default] > %s\n", SD_PMUX_SYSFS);
+ return count;
+ }
+
+ spacemit->pin = pinctrl_lookup_state(spacemit->pinctrl, pinname);
+ if (IS_ERR(spacemit->pin)) {
+ pr_err("could not get sdhci pinctrl state.\n");
+ return count;
+ }
+ pinctrl_select_state(spacemit->pinctrl, spacemit->pin);
+ msleep(1);
+
+ return count;
+}
+
+ssize_t sdhci_tx_delaycode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct mmc_host *mmc = host->mmc;
+ struct k1x_sdhci_platdata *pdata = mmc->parent->platform_data;
+
+ return sprintf(buf, "0x%02x\n", pdata->tx_delaycode);
+}
+
+ssize_t sdhci_tx_delaycode_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct mmc_host *mmc = host->mmc;
+ struct k1x_sdhci_platdata *pdata = mmc->parent->platform_data;
+ u8 delaycode;
+
+ if (kstrtou8(buf, 0, &delaycode))
+ return -EINVAL;
+
+ pdata->tx_delaycode = delaycode;
+ return count;
+}
+
+static struct device_attribute sdhci_sysfs_files[] = {
+ __ATTR(sd_card_pmux, S_IWUSR, NULL, sdhci_sysfs_pmux_set),
+ __ATTR(tx_delaycode, S_IRUGO|S_IWUSR, sdhci_tx_delaycode_show, sdhci_tx_delaycode_set),
+};
+
+static int spacemit_sdhci_probe(struct platform_device *pdev)
+{
+ struct sdhci_pltfm_host *pltfm_host;
+ struct device *dev = &pdev->dev;
+ struct sdhci_host *host;
+ const struct of_device_id *match;
+ int i;
+ struct sdhci_spacemit *spacemit;
+ struct k1x_sdhci_platdata *pdata;
+ int ret;
+
+ host = sdhci_pltfm_init(pdev, &sdhci_k1x_pdata, sizeof(*spacemit));
+ if (IS_ERR(host))
+ return PTR_ERR(host);
+
+ pltfm_host = sdhci_priv(host);
+
+ spacemit = sdhci_pltfm_priv(pltfm_host);
+
+ spacemit->clk_io = devm_clk_get(dev, "sdh-io");
+ if (IS_ERR(spacemit->clk_io))
+ spacemit->clk_io = devm_clk_get(dev, NULL);
+ if (IS_ERR(spacemit->clk_io)) {
+ dev_err(dev, "failed to get io clock\n");
+ ret = PTR_ERR(spacemit->clk_io);
+ goto err_clk_get;
+ }
+ pltfm_host->clk = spacemit->clk_io;
+ clk_prepare_enable(spacemit->clk_io);
+
+ spacemit->clk_core = devm_clk_get(dev, "sdh-core");
+ if (!IS_ERR(spacemit->clk_core))
+ clk_prepare_enable(spacemit->clk_core);
+
+ spacemit->clk_aib = devm_clk_get(dev, "aib-clk");
+ if (!IS_ERR(spacemit->clk_aib))
+ clk_prepare_enable(spacemit->clk_aib);
+
+ spacemit->reset = devm_reset_control_array_get_optional_shared(dev);
+ if (IS_ERR(spacemit->reset)) {
+ dev_err(dev, "failed to get reset control\n");
+ ret = PTR_ERR(spacemit->reset);
+ goto err_rst_get;
+ }
+
+ ret = reset_control_deassert(spacemit->reset);
+ if (ret) {
+ goto err_rst_get;
+ }
+
+ match = of_match_device(of_match_ptr(sdhci_spacemit_of_match), &pdev->dev);
+ if (match) {
+ ret = mmc_of_parse(host->mmc);
+ if (ret) {
+ goto err_of_parse;
+ }
+ sdhci_get_of_property(pdev);
+ }
+
+ pdata = pdev->dev.platform_data ? pdev->dev.platform_data : spacemit_get_mmc_pdata(dev);
+ if (IS_ERR_OR_NULL(pdata)) {
+ goto err_of_parse;
+ }
+
+ spacemit_get_of_property(host, dev, pdata);
+ if (pdata->quirks)
+ host->quirks |= pdata->quirks;
+ if (pdata->quirks2)
+ host->quirks2 |= pdata->quirks2;
+ if (pdata->host_caps)
+ host->mmc->caps |= pdata->host_caps;
+ if (pdata->host_caps2)
+ host->mmc->caps2 |= pdata->host_caps2;
+ if (pdata->pm_caps)
+ host->mmc->pm_caps |= pdata->pm_caps;
+ pdev->dev.platform_data = pdata;
+
+ if (host->mmc->pm_caps)
+ host->mmc->pm_flags |= host->mmc->pm_caps;
+
+ if (!(host->mmc->caps2 & MMC_CAP2_NO_MMC)) {
+ host->mmc_host_ops.hs400_prepare_ddr = spacemit_sdhci_pre_select_hs400;
+ host->mmc_host_ops.hs400_complete = spacemit_sdhci_post_select_hs400;
+ host->mmc_host_ops.hs400_downgrade = spacemit_sdhci_pre_hs400_to_hs200;
+ if (host->mmc->caps2 & MMC_CAP2_HS400_ES)
+ host->mmc_host_ops.hs400_enhanced_strobe = spacemit_sdhci_hs400_enhanced_strobe;
+ }
+
+ host->mmc_host_ops.start_signal_voltage_switch = spacemit_sdhci_start_signal_voltage_switch;
+ host->mmc_host_ops.card_busy = spacemit_sdhci_card_busy;
+ host->mmc_host_ops.init_card = spacemit_init_card_quriks;
+ host->mmc_host_ops.enable_sdio_irq = spacemit_enable_sdio_irq;
+
+ if (!(host->mmc->caps2 & MMC_CAP2_NO_SDIO)) {
+ /* skip auto rescan */
+ host->mmc->rescan_entered = 1;
+ }
+#if BOOTPART_NOACC_DEFAULT
+ if (!(host->mmc->caps2 & MMC_CAP2_NO_MMC) && !is_recovery_boot)
+ host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC;
+#endif
+ host->mmc->caps |= MMC_CAP_NEED_RSP_BUSY;
+
+ pm_runtime_get_noresume(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, RPM_DELAY);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ pm_suspend_ignore_children(&pdev->dev, 1);
+ pm_runtime_get_sync(&pdev->dev);
+
+ /* set io clock rate */
+ if (pdata->host_freq) {
+ ret = clk_set_rate(spacemit->clk_io, pdata->host_freq);
+ if (ret) {
+ dev_err(dev, "failed to set io clock freq\n");
+ goto err_add_host;
+ }
+ } else {
+ dev_err(dev, "failed to get io clock freq\n");
+ goto err_add_host;
+ }
+
+ ret = sdhci_add_host(host);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add spacemit sdhc.\n");
+ goto err_add_host;
+ } else {
+ if (!(host->mmc->caps2 & MMC_CAP2_NO_SDIO)) {
+ pr_notice("sdio: save sdio_host <- %p\n", host);
+ sdio_host = host;
+ }
+ }
+
+ spacemit_sdhci_caps_disable(host);
+
+ if ((host->mmc->caps2 & MMC_CAP2_NO_MMC) || (host->quirks2 & SDHCI_QUIRK2_BROKEN_PHY_MODULE)) {
+ pr_debug("%s: get card pinctrl\n", mmc_hostname(host->mmc));
+ spacemit->pinctrl = devm_pinctrl_get(&pdev->dev);
+ }
+ if (host->mmc->caps2 & MMC_CAP2_NO_MMC) {
+#ifdef CONFIG_SYSFS
+ for (i = 0; i < ARRAY_SIZE(sdhci_sysfs_files); i++) {
+ device_create_file(dev, &sdhci_sysfs_files[i]);
+ }
+#endif
+ }
+
+ if (host->mmc->pm_caps & MMC_PM_WAKE_SDIO_IRQ)
+ device_init_wakeup(&pdev->dev, 1);
+ pm_runtime_put_autosuspend(&pdev->dev);
+
+ return 0;
+
+err_add_host:
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+err_of_parse:
+ reset_control_assert(spacemit->reset);
+err_rst_get:
+ if (!IS_ERR(spacemit->clk_aib))
+ clk_disable_unprepare(spacemit->clk_aib);
+ clk_disable_unprepare(spacemit->clk_io);
+ clk_disable_unprepare(spacemit->clk_core);
+err_clk_get:
+ sdhci_pltfm_free(pdev);
+ return ret;
+}
+
+static int spacemit_sdhci_remove(struct platform_device *pdev)
+{
+ struct sdhci_host *host = platform_get_drvdata(pdev);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_spacemit *spacemit = sdhci_pltfm_priv(pltfm_host);
+ int i;
+
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ sdhci_remove_host(host, 1);
+
+ reset_control_assert(spacemit->reset);
+ if (!IS_ERR(spacemit->clk_aib))
+ clk_disable_unprepare(spacemit->clk_aib);
+ clk_disable_unprepare(spacemit->clk_io);
+ clk_disable_unprepare(spacemit->clk_core);
+
+ if (!(host->mmc->caps2 & MMC_CAP2_NO_SD)) {
+#ifdef CONFIG_SYSFS
+ for (i = 0; i < ARRAY_SIZE(sdhci_sysfs_files); i++) {
+ device_remove_file(&pdev->dev, &sdhci_sysfs_files[i]);
+ }
+#endif
+ }
+
+ sdhci_pltfm_free(pdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int spacemit_sdhci_suspend(struct device *dev)
+{
+ int ret;
+ struct sdhci_host *host = dev_get_drvdata(dev);
+
+ pm_runtime_get_sync(dev);
+ ret = sdhci_suspend_host(host);
+ if (ret)
+ return ret;
+ ret = pm_runtime_force_suspend(dev);
+ return ret;
+}
+
+static int spacemit_sdhci_resume(struct device *dev)
+{
+ int ret;
+ struct sdhci_host *host = dev_get_drvdata(dev);
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret) {
+ dev_err(dev, "failed to resume pm_runtime (%d)\n", ret);
+ return ret;
+ }
+ ret = sdhci_resume_host(host);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ if (ret) {
+ dev_err(dev, "failed to resume (%d)\n", ret);
+ return ret;
+ }
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int spacemit_sdhci_runtime_suspend(struct device *dev)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_spacemit *spacemit = sdhci_pltfm_priv(pltfm_host);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&host->lock, flags);
+ host->runtime_suspended = true;
+ if (!(host->mmc->caps2 & MMC_CAP2_NO_MMC)
+ && !(host->quirks2 & SDHCI_QUIRK2_BROKEN_PHY_MODULE)
+ && !(host->quirks2 & SDHCI_QUIRK2_SUPPORT_PHY_BYPASS)) {
+ reg = sdhci_readl(host, SDHC_PHY_CTRL_REG);
+ reg &= ~PHY_FUNC_EN;
+ sdhci_writel(host, reg, SDHC_PHY_CTRL_REG);
+ }
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ clk_disable_unprepare(spacemit->clk_io);
+ if (!IS_ERR(spacemit->clk_aib))
+ clk_disable_unprepare(spacemit->clk_aib);
+ if (!IS_ERR(spacemit->clk_core))
+ clk_disable_unprepare(spacemit->clk_core);
+
+ return 0;
+}
+
+static int spacemit_sdhci_runtime_resume(struct device *dev)
+{
+ struct sdhci_host *host = dev_get_drvdata(dev);
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_spacemit *spacemit = sdhci_pltfm_priv(pltfm_host);
+ unsigned long flags;
+ u32 reg;
+
+ clk_prepare_enable(spacemit->clk_io);
+ if (!IS_ERR(spacemit->clk_aib))
+ clk_prepare_enable(spacemit->clk_aib);
+ if (!IS_ERR(spacemit->clk_core))
+ clk_prepare_enable(spacemit->clk_core);
+
+ spin_lock_irqsave(&host->lock, flags);
+ if (!(host->mmc->caps2 & MMC_CAP2_NO_MMC)
+ && !(host->quirks2 & SDHCI_QUIRK2_BROKEN_PHY_MODULE)
+ && !(host->quirks2 & SDHCI_QUIRK2_SUPPORT_PHY_BYPASS)) {
+ reg = sdhci_readl(host, SDHC_PHY_CTRL_REG);
+ reg |= PHY_FUNC_EN;
+ sdhci_writel(host, reg, SDHC_PHY_CTRL_REG);
+ }
+ host->runtime_suspended = false;
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ return 0;
+}
+
+static const struct dev_pm_ops sdhci_spacemit_pmops = {
+ SET_SYSTEM_SLEEP_PM_OPS(spacemit_sdhci_suspend, spacemit_sdhci_resume)
+ SET_RUNTIME_PM_OPS(spacemit_sdhci_runtime_suspend,
+ spacemit_sdhci_runtime_resume, NULL)
+};
+
+#define SDHCI_SPACEMIT_PMOPS (&sdhci_spacemit_pmops)
+
+#else
+#define SDHCI_SPACEMIT_PMOPS NULL
+#endif
+
+static struct platform_driver spacemit_sdhci_driver = {
+ .driver = {
+ .name = "sdhci-spacemit",
+ .of_match_table = of_match_ptr(sdhci_spacemit_of_match),
+ .pm = SDHCI_SPACEMIT_PMOPS,
+ },
+ .probe = spacemit_sdhci_probe,
+ .remove = spacemit_sdhci_remove,
+};
+
+module_platform_driver(spacemit_sdhci_driver);
+
+MODULE_DESCRIPTION("SDHCI platform driver for Spacemit");
+MODULE_LICENSE("GPL v2");
diff --git a/include/dt-bindings/mmc/k1x_sdhci.h b/include/dt-bindings/mmc/k1x_sdhci.h
new file mode 100644
index 000000000000..8f93a718e9ce
--- /dev/null
+++ b/include/dt-bindings/mmc/k1x_sdhci.h
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * include/linux/dt-bindings/mmc/k1x_sdhci.h
+ *
+ * SDH driver for SPACEMIT K1X SDCHI
+ * Copyright (C) 2023 Spacemit
+ */
+
+#ifndef K1X_DT_BINDINGS_MMC_SDHCI_H
+#define K1X_DT_BINDINGS_MMC_SDHCI_H
+
+/* K1x specific flag */
+
+/* MMC Quirks */
+/* Controller has an unusable ADMA engine */
+#define SDHCI_QUIRK_BROKEN_ADMA (1<<6)
+#define SDHCI_QUIRK2_PRESET_VALUE_BROKEN (1<<3)
+/* Controller does not support HS200 */
+#define SDHCI_QUIRK2_BROKEN_HS200 (1<<6)
+/* Support SDH controller on FPGA */
+#define SDHCI_QUIRK2_SUPPORT_PHY_BYPASS (1<<25)
+/* Disable scan card at probe phase */
+#define SDHCI_QUIRK2_DISABLE_PROBE_CDSCAN (1<<26)
+/* Need to set IO capability by SOC part register */
+#define SDHCI_QUIRK2_SET_AIB_MMC (1<<27)
+/* Controller not support phy module */
+#define SDHCI_QUIRK2_BROKEN_PHY_MODULE (1<<28)
+/* Controller support encrypt module */
+#define SDHCI_QUIRK2_SUPPORT_ENCRYPT (1<<29)
+
+/* Common flag */
+/* Controller provides an incorrect timeout value for transfers */
+#define SDHCI_QUIRK_BROKEN_TIMEOUT_VAL (1<<12)
+/* Controller has unreliable card detection */
+#define SDHCI_QUIRK_BROKEN_CARD_DETECTION (1<<15)
+
+/* Controller reports inverted write-protect state */
+#define SDHCI_QUIRK_INVERTED_WRITE_PROTECT (1<<16)
+
+/* MMC caps */
+#define MMC_CAP2_CRC_SW_RETRY (1 << 30)
+
+/* for SDIO */
+#define MMC_CAP_NEEDS_POLL (1 << 5) /* Needs polling for card-detection */
+
+/* for SD card */
+#define MMC_CAP_UHS_SDR12 (1 << 16) /* Host supports UHS SDR12 mode */
+#define MMC_CAP_UHS_SDR25 (1 << 17) /* Host supports UHS SDR25 mode */
+#define MMC_CAP_UHS_SDR50 (1 << 18) /* Host supports UHS SDR50 mode */
+#define MMC_CAP_UHS_SDR104 (1 << 19) /* Host supports UHS SDR104 mode */
+#define MMC_CAP_UHS_DDR50 (1 << 20) /* Host supports UHS DDR50 mode */
+#endif /* K1X_DT_BINDINGS_MMC_SDHCI_H */
+
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 62a6847a3b6f..65ef62ef88fe 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -218,6 +218,16 @@ struct mmc_host_ops {
/* Initialize an SD express card, mandatory for MMC_CAP2_SD_EXP. */
int (*init_sd_express)(struct mmc_host *host, struct mmc_ios *ios);
+
+#ifdef CONFIG_ARCH_SPACEMIT_K1X
+ void (*auto_clk_gate)(struct mmc_host *host, int auto_gate);
+ void (*pre_select_hs400)(struct mmc_host *host);
+ void (*post_select_hs400)(struct mmc_host *host);
+ void (*pre_hs400_to_hs200)(struct mmc_host *host);
+ void (*dump_host_register)(struct mmc_host *host);
+ void (*encrypt_config)(struct mmc_host *host, unsigned int enc_flag);
+#endif
+
};
struct mmc_cqe_ops {
@@ -427,6 +437,11 @@ struct mmc_host {
#define MMC_CAP2_CRYPTO 0
#endif
#define MMC_CAP2_ALT_GPT_TEGRA (1 << 28) /* Host with eMMC that has GPT entry at a non-standard location */
+#ifdef CONFIG_ARCH_SPACEMIT_K1X
+#define MMC_CAP2_DISABLE_PROBE_SCAN (1 << 29)
+#define MMC_CAP2_CRC_SW_RETRY (1 << 30)
+#endif
+
int fixed_drv_type; /* fixed driver type for non-removable media */
diff --git a/include/linux/platform_data/k1x_sdhci.h b/include/linux/platform_data/k1x_sdhci.h
new file mode 100644
index 000000000000..f6de96801279
--- /dev/null
+++ b/include/linux/platform_data/k1x_sdhci.h
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * include/linux/platform_data/k1x_sdhci.h
+ *
+ * Copyright (C) 2023 Spacemit
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _K1X_SDHCI_H_
+#define _K1X_SDHCI_H_
+
+#define CANDIDATE_WIN_NUM 3
+#define SELECT_DELAY_NUM 9
+#define WINDOW_1ST 0
+#define WINDOW_2ND 1
+#define WINDOW_3RD 2
+
+#define RX_TUNING_WINDOW_THRESHOLD 80
+#define RX_TUNING_DLINE_REG 0x09
+#define TX_TUNING_DLINE_REG 0x00
+#define TX_TUNING_DELAYCODE 127
+
+enum window_type {
+ LEFT_WINDOW = 0,
+ MIDDLE_WINDOW = 1,
+ RIGHT_WINDOW = 2,
+};
+
+struct tuning_window {
+ u8 type;
+ u8 min_delay;
+ u8 max_delay;
+};
+
+struct rx_tuning {
+ u8 rx_dline_reg;
+ u8 select_delay_num;
+ u8 current_delay_index;
+ /* 0: biggest window, 1: bigger, 2: small */
+ struct tuning_window windows[CANDIDATE_WIN_NUM];
+ u8 select_delay[SELECT_DELAY_NUM];
+
+ u32 card_cid[4];
+ u8 window_limit;
+ u8 tuning_fail;
+};
+
+/*
+ * struct k1x_sdhci_platdata() - Platform device data for Spacemit K1x SDHCI
+ * @flags: flags for platform requirement
+ * @host_caps: Standard MMC host capabilities bit field
+ * @host_caps2: Standard MMC host capabilities bit field
+ * @host_caps_disable: Aquila MMC host capabilities disable bit field
+ * @host_caps2_disable: Aquila MMC host capabilities disable bit field
+ * @quirks: quirks of platform
+ * @quirks2: quirks2 of platform
+ * @pm_caps: pm_caps of platform
+ */
+struct k1x_sdhci_platdata {
+ u32 host_freq;
+ u32 flags;
+ u32 host_caps;
+ u32 host_caps2;
+ u32 host_caps_disable;
+ u32 host_caps2_disable;
+ u32 quirks;
+ u32 quirks2;
+ u32 pm_caps;
+
+ u32 aib_mmc1_io_reg;
+ u32 apbc_asfar_reg;
+ u32 apbc_assar_reg;
+
+ u8 tx_dline_reg;
+ u8 tx_delaycode;
+ u8 phy_driver_sel;
+ struct rx_tuning rxtuning;
+ u8 need_reset_dllcfg1;
+ u32 prev_dllcfg1;
+ u32 curr_dllcfg1;
+ u32 new_dllcfg1;
+ u8 dllcfg1_odd_reset;
+};
+
+#endif /* _K1X_SDHCI_H_ */