All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC
@ 2021-10-29  5:57 LH.Kuo
  2021-10-29  5:57 ` [PATCH 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021 LH.Kuo
                   ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: LH.Kuo @ 2021-10-29  5:57 UTC (permalink / raw)
  To: ulf.hansson, p.zabel, robh+dt, linux-kernel, linux-mmc, devicetree
  Cc: dvorkin, qinjian, wells.lu, LH.Kuo

Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates
many peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and
etc.) into a single chip. It is designed for industrial control.

Refer to:														
https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
https://tibbo.com/store/plus1.html

LH.Kuo (2):
  mmc: Add SD/SDIO driver for Sunplus SP7021
  devicetree bindings mmc Add bindings doc for Sunplus SP7021

 .../devicetree/bindings/mmc/sunplus-sd2.yaml       |   82 ++
 MAINTAINERS                                        |   41 +-
 drivers/mmc/host/Kconfig                           |   10 +
 drivers/mmc/host/Makefile                          |    1 +
 drivers/mmc/host/sunplus_sd2.c                     | 1069 ++++++++++++++++++++
 drivers/mmc/host/sunplus_sd2.h                     |  216 ++++
 6 files changed, 1403 insertions(+), 16 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
 create mode 100644 drivers/mmc/host/sunplus_sd2.c
 create mode 100644 drivers/mmc/host/sunplus_sd2.h

-- 
2.7.4


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021
  2021-10-29  5:57 [PATCH 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC LH.Kuo
@ 2021-10-29  5:57 ` LH.Kuo
  2021-10-30  0:00   ` Randy Dunlap
  2021-11-02 14:34   ` Philipp Zabel
  2021-10-29  5:57 ` [PATCH 2/2] devicetree bindings mmc Add bindings doc " LH.Kuo
  2021-11-09  7:58 ` [PATCH v2 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC LH.Kuo
  2 siblings, 2 replies; 13+ messages in thread
From: LH.Kuo @ 2021-10-29  5:57 UTC (permalink / raw)
  To: ulf.hansson, p.zabel, robh+dt, linux-kernel, linux-mmc, devicetree
  Cc: dvorkin, qinjian, wells.lu, LH.Kuo

Add SD/SDIO driver for Sunplus SP7021.

Signed-off-by: LH.Kuo <lh.kuo@sunplus.com>
---
 MAINTAINERS                    |   40 +-
 drivers/mmc/host/Kconfig       |   10 +
 drivers/mmc/host/Makefile      |    1 +
 drivers/mmc/host/sunplus_sd2.c | 1069 ++++++++++++++++++++++++++++++++++++++++
 drivers/mmc/host/sunplus_sd2.h |  216 ++++++++
 5 files changed, 1320 insertions(+), 16 deletions(-)
 create mode 100644 drivers/mmc/host/sunplus_sd2.c
 create mode 100644 drivers/mmc/host/sunplus_sd2.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 80eebc1..0fb096d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3553,7 +3553,7 @@ N:	kona
 
 BROADCOM BCM47XX MIPS ARCHITECTURE
 M:	Hauke Mehrtens <hauke@hauke-m.de>
-M:	Rafał Miłecki <zajec5@gmail.com>
+M:	Rafa? Mi?ecki <zajec5@gmail.com>
 L:	linux-mips@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/mips/brcm/
@@ -3561,7 +3561,7 @@ F:	arch/mips/bcm47xx/*
 F:	arch/mips/include/asm/mach-bcm47xx/*
 
 BROADCOM BCM4908 ETHERNET DRIVER
-M:	Rafał Miłecki <rafal@milecki.pl>
+M:	Rafa? Mi?ecki <rafal@milecki.pl>
 M:	bcm-kernel-feedback-list@broadcom.com
 L:	netdev@vger.kernel.org
 S:	Maintained
@@ -3571,7 +3571,7 @@ F:	drivers/net/ethernet/broadcom/unimac.h
 
 BROADCOM BCM5301X ARM ARCHITECTURE
 M:	Hauke Mehrtens <hauke@hauke-m.de>
-M:	Rafał Miłecki <zajec5@gmail.com>
+M:	Rafa? Mi?ecki <zajec5@gmail.com>
 M:	bcm-kernel-feedback-list@broadcom.com
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
 S:	Maintained
@@ -3581,7 +3581,7 @@ F:	arch/arm/boot/dts/bcm953012*
 F:	arch/arm/mach-bcm/bcm_5301x.c
 
 BROADCOM BCM53573 ARM ARCHITECTURE
-M:	Rafał Miłecki <rafal@milecki.pl>
+M:	Rafa? Mi?ecki <rafal@milecki.pl>
 L:	bcm-kernel-feedback-list@broadcom.com
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
 S:	Maintained
@@ -3800,7 +3800,7 @@ N:	hr2
 N:	stingray
 
 BROADCOM IPROC GBIT ETHERNET DRIVER
-M:	Rafał Miłecki <rafal@milecki.pl>
+M:	Rafa? Mi?ecki <rafal@milecki.pl>
 M:	bcm-kernel-feedback-list@broadcom.com
 L:	netdev@vger.kernel.org
 S:	Maintained
@@ -3835,13 +3835,13 @@ F:	drivers/infiniband/hw/bnxt_re/
 F:	include/uapi/rdma/bnxt_re-abi.h
 
 BROADCOM NVRAM DRIVER
-M:	Rafał Miłecki <zajec5@gmail.com>
+M:	Rafa? Mi?ecki <zajec5@gmail.com>
 L:	linux-mips@vger.kernel.org
 S:	Maintained
 F:	drivers/firmware/broadcom/*
 
 BROADCOM PMB (POWER MANAGEMENT BUS) DRIVER
-M:	Rafał Miłecki <rafal@milecki.pl>
+M:	Rafa? Mi?ecki <rafal@milecki.pl>
 M:	Florian Fainelli <f.fainelli@gmail.com>
 M:	bcm-kernel-feedback-list@broadcom.com
 L:	linux-pm@vger.kernel.org
@@ -3851,7 +3851,7 @@ F:	drivers/soc/bcm/bcm63xx/bcm-pmb.c
 F:	include/dt-bindings/soc/bcm-pmb.h
 
 BROADCOM SPECIFIC AMBA DRIVER (BCMA)
-M:	Rafał Miłecki <zajec5@gmail.com>
+M:	Rafa? Mi?ecki <zajec5@gmail.com>
 L:	linux-wireless@vger.kernel.org
 S:	Maintained
 F:	drivers/bcma/
@@ -6947,7 +6947,7 @@ W:	http://www.broadcom.com
 F:	drivers/scsi/elx/
 
 ENE CB710 FLASH CARD READER DRIVER
-M:	Michał Mirosław <mirq-linux@rere.qmqm.pl>
+M:	Micha? Miros?aw <mirq-linux@rere.qmqm.pl>
 S:	Maintained
 F:	drivers/misc/cb710/
 F:	drivers/mmc/host/cb710-mmc.*
@@ -7389,7 +7389,7 @@ F:	include/uapi/video/
 F:	include/video/
 
 FREESCALE CAAM (Cryptographic Acceleration and Assurance Module) DRIVER
-M:	Horia Geantă <horia.geanta@nxp.com>
+M:	Horia Geant? <horia.geanta@nxp.com>
 M:	Pankaj Gupta <pankaj.gupta@nxp.com>
 L:	linux-crypto@vger.kernel.org
 S:	Maintained
@@ -7926,7 +7926,7 @@ F:	fs/gfs2/
 F:	include/uapi/linux/gfs2_ondisk.h
 
 GIGABYTE WMI DRIVER
-M:	Thomas Weißschuh <thomas@weissschuh.net>
+M:	Thomas Wei?schuh <thomas@weissschuh.net>
 L:	platform-driver-x86@vger.kernel.org
 S:	Maintained
 F:	drivers/platform/x86/gigabyte-wmi.c
@@ -8871,7 +8871,7 @@ S:	Maintained
 F:	drivers/i2c/i2c-stub.c
 
 I3C DRIVER FOR CADENCE I3C MASTER IP
-M:	Przemysław Gaj <pgaj@cadence.com>
+M:	Przemys?aw Gaj <pgaj@cadence.com>
 S:	Maintained
 F:	Documentation/devicetree/bindings/i3c/cdns,i3c-master.txt
 F:	drivers/i3c/master/i3c-master-cdns.c
@@ -14456,7 +14456,7 @@ F:	drivers/pci/controller/pci-v3-semi.c
 PCI ENDPOINT SUBSYSTEM
 M:	Kishon Vijay Abraham I <kishon@ti.com>
 M:	Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
-R:	Krzysztof Wilczyński <kw@linux.com>
+R:	Krzysztof Wilczy?ski <kw@linux.com>
 L:	linux-pci@vger.kernel.org
 S:	Supported
 F:	Documentation/PCI/endpoint/*
@@ -14504,7 +14504,7 @@ F:	drivers/pci/controller/pci-xgene-msi.c
 PCI NATIVE HOST BRIDGE AND ENDPOINT DRIVERS
 M:	Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
 R:	Rob Herring <robh@kernel.org>
-R:	Krzysztof Wilczyński <kw@linux.com>
+R:	Krzysztof Wilczy?ski <kw@linux.com>
 L:	linux-pci@vger.kernel.org
 S:	Supported
 Q:	http://patchwork.ozlabs.org/project/linux-pci/list/
@@ -16490,7 +16490,7 @@ F:	Documentation/devicetree/bindings/rng/samsung,exynos4-rng.yaml
 F:	drivers/crypto/exynos-rng.c
 
 SAMSUNG EXYNOS TRUE RANDOM NUMBER GENERATOR (TRNG) DRIVER
-M:	Łukasz Stelmach <l.stelmach@samsung.com>
+M:	?ukasz Stelmach <l.stelmach@samsung.com>
 L:	linux-samsung-soc@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/rng/samsung,exynos5250-trng.yaml
@@ -16504,7 +16504,7 @@ F:	drivers/video/fbdev/s3c-fb.c
 
 SAMSUNG INTERCONNECT DRIVERS
 M:	Sylwester Nawrocki <s.nawrocki@samsung.com>
-M:	Artur Świgoń <a.swigon@samsung.com>
+M:	Artur ?wigo? <a.swigon@samsung.com>
 L:	linux-pm@vger.kernel.org
 L:	linux-samsung-soc@vger.kernel.org
 S:	Supported
@@ -17947,6 +17947,14 @@ L:	netdev@vger.kernel.org
 S:	Maintained
 F:	drivers/net/ethernet/dlink/sundance.c
 
+SUNPLUS SD/SDIO HOST CONTROLLER INTERFACE DRIVER
+M:	LH Kuo <lh.kuo@sunplus.com>
+L:	sdricohcs-devel@lists.sourceforge.net (subscribers-only)
+S:	Maintained
+F:	drivers/mmc/host/Kconfig
+F:	drivers/mmc/host/Makefile
+F:	drivers/mmc/host/sunplus_sd2.*
+
 SUPERH
 M:	Yoshinori Sato <ysato@users.sourceforge.jp>
 M:	Rich Felker <dalias@libc.org>
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 95b3511..6f8bcd6 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -14,6 +14,16 @@ config MMC_DEBUG
 	  added host drivers please don't invent their private macro for
 	  debugging.
 
+config SP_SDV2
+	tristate "Sunplus SP7021 SD/SDIO Controller"
+	depends on SOC_SP7021
+	default m
+	help
+	  This selects the Sunplus SP7021 SD/SDIO controller.
+	  If you have a controller with this interface, say Y or M here..
+
+	  If unsure, say N.
+
 config MMC_ARMMMCI
 	tristate "ARM AMBA Multimedia Card Interface support"
 	depends on ARM_AMBA
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 14004cc..1db7913 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -98,6 +98,7 @@ obj-$(CONFIG_MMC_SDHCI_MICROCHIP_PIC32)	+= sdhci-pic32.o
 obj-$(CONFIG_MMC_SDHCI_BRCMSTB)		+= sdhci-brcmstb.o
 obj-$(CONFIG_MMC_SDHCI_OMAP)		+= sdhci-omap.o
 obj-$(CONFIG_MMC_SDHCI_SPRD)		+= sdhci-sprd.o
+obj-$(CONFIG_SP_SDV2)			+= sunplus_sd2.o
 obj-$(CONFIG_MMC_CQHCI)			+= cqhci.o
 cqhci-y					+= cqhci-core.o
 cqhci-$(CONFIG_MMC_CRYPTO)		+= cqhci-crypto.o
diff --git a/drivers/mmc/host/sunplus_sd2.c b/drivers/mmc/host/sunplus_sd2.c
new file mode 100644
index 0000000..22679de
--- /dev/null
+++ b/drivers/mmc/host/sunplus_sd2.c
@@ -0,0 +1,1069 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/**
+ * (C) Copyright 2019 Sunplus Technology. <http://www.sunplus.com/>
+ *
+ * Sunplus SD host controller v2.0
+ *
+ */
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_device.h>
+#include <linux/reset.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/slot-gpio.h>
+#include <linux/clk.h>
+#include <linux/bitops.h>
+#include <linux/uaccess.h>
+#include "sunplus_sd2.h"
+
+enum loglevel {
+	SPSDC_LOG_OFF,
+	SPSDC_LOG_ERROR,
+	SPSDC_LOG_WARNING,
+	SPSDC_LOG_INFO,
+	SPSDC_LOG_DEBUG,
+	SPSDC_LOG_VERBOSE,
+	SPSDC_LOG_MAX
+};
+static int loglevel = SPSDC_LOG_WARNING;
+
+/**
+ * we do not need `SPSDC_LOG_' prefix here, when specify @level.
+ */
+#define spsdc_pr(level, fmt, ...)	\
+	do {	\
+		if (unlikely(SPSDC_LOG_##level <= loglevel))	\
+			pr_info("SPSDC [" #level "] " fmt, ##__VA_ARGS__);	\
+	} while (0)
+
+/* Produces a mask of set bits covering a range of a 32-bit value */
+static inline u32 bitfield_mask(u32 shift, u32 width)
+{
+	return ((1 << width) - 1) << shift;
+}
+
+/* Extract the value of a bitfield found within a given register value */
+static inline u32 bitfield_extract(u32 reg_val, u32 shift, u32 width)
+{
+	return (reg_val & bitfield_mask(shift, width)) >> shift;
+}
+
+/* Replace the value of a bitfield found within a given register value */
+static inline u32 bitfield_replace(u32 reg_val, u32 shift, u32 width, u32 val)
+{
+	u32 mask = bitfield_mask(shift, width);
+
+	return (reg_val & ~mask) | (val << shift);
+}
+/* for register value with mask bits */
+#define __bitfield_replace(value, shift, width, new_value)		\
+	(bitfield_replace(value, shift, width, new_value)|bitfield_mask(shift+16, width))
+
+/**
+ * wait for transaction done, return -1 if error.
+ */
+static inline int spsdc_wait_finish(struct spsdc_host *host)
+{
+	/* Wait for transaction finish */
+	unsigned long timeout = jiffies + msecs_to_jiffies(5000);
+
+	while (!time_after(jiffies, timeout)) {
+		if (readl(&host->base->sd_state) & SPSDC_SDSTATE_FINISH)
+			return 0;
+		if (readl(&host->base->sd_state) & SPSDC_SDSTATE_ERROR)
+			return -1;
+	}
+	return -1;
+}
+
+static inline int spsdc_wait_sdstatus(struct spsdc_host *host, unsigned int status_bit)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(5000);
+
+	while (!time_after(jiffies, timeout)) {
+		if (readl(&host->base->sd_status) & status_bit)
+			return 0;
+		if (readl(&host->base->sd_state) & SPSDC_SDSTATE_ERROR)
+			return -1;
+	}
+	return -1;
+}
+
+#define spsdc_wait_rspbuf_full(host) spsdc_wait_sdstatus(host, SPSDC_SDSTATUS_RSP_BUF_FULL)
+#define spsdc_wait_rxbuf_full(host) spsdc_wait_sdstatus(host, SPSDC_SDSTATUS_RX_DATA_BUF_FULL)
+#define spsdc_wait_txbuf_empty(host) spsdc_wait_sdstatus(host, SPSDC_SDSTATUS_TX_DATA_BUF_EMPTY)
+
+static void spsdc_get_rsp(struct spsdc_host *host, struct mmc_command *cmd)
+{
+	u32 value0_3, value4_5;
+
+	if (unlikely(!(cmd->flags & MMC_RSP_PRESENT)))
+		return;
+	if (unlikely(cmd->flags & MMC_RSP_136)) {
+		if (spsdc_wait_rspbuf_full(host))
+			return;
+		value0_3 = readl(&host->base->sd_rspbuf0_3);
+		value4_5 = readl(&host->base->sd_rspbuf4_5) & 0xffff;
+		cmd->resp[0] = (value0_3 << 8) | (value4_5 >> 8);
+		cmd->resp[1] = value4_5 << 24;
+		if (spsdc_wait_rspbuf_full(host))
+			return;
+		value0_3 = readl(&host->base->sd_rspbuf0_3);
+		value4_5 = readl(&host->base->sd_rspbuf4_5) & 0xffff;
+		cmd->resp[1] |= value0_3 >> 8;
+		cmd->resp[2] = value0_3 << 24;
+		cmd->resp[2] |= value4_5 << 8;
+		if (spsdc_wait_rspbuf_full(host))
+			return;
+		value0_3 = readl(&host->base->sd_rspbuf0_3);
+		value4_5 = readl(&host->base->sd_rspbuf4_5) & 0xffff;
+		cmd->resp[2] |= value0_3 >> 24;
+		cmd->resp[3] = value0_3 << 8;
+		cmd->resp[3] |= value4_5 >> 8;
+	} else {
+		if (spsdc_wait_rspbuf_full(host))
+			return;
+		value0_3 = readl(&host->base->sd_rspbuf0_3);
+		value4_5 = readl(&host->base->sd_rspbuf4_5) & 0xffff;
+		cmd->resp[0] = (value0_3 << 8) | (value4_5 >> 8);
+		cmd->resp[1] = value4_5 << 24;
+	}
+}
+
+static void spsdc_set_bus_clk(struct spsdc_host *host, int clk)
+{
+	unsigned int clkdiv;
+	int f_min = host->mmc->f_min;
+	int f_max = host->mmc->f_max;
+	u32 value = readl(&host->base->sd_config);
+
+	if (clk < f_min)
+		clk = f_min;
+	if (clk > f_max)
+		clk = f_max;
+
+	// SD 2.0 only max set to 50Mhz CLK
+	if (clk >= SPSDC_50M_CLK)
+		clk = f_max;
+
+	spsdc_pr(INFO, "set bus clock to %d\n", clk);
+	clkdiv = (clk_get_rate(host->clk)+clk)/clk-1;
+	if (clkdiv > 0xfff) {
+		spsdc_pr(WARNING, "clock %d is too low to be set!\n", clk);
+		clkdiv = 0xfff;
+	}
+	value = bitfield_replace(value, 0, 12, clkdiv);
+	writel(value, &host->base->sd_config);
+
+	/* In order to reduce the frequency of context switch,
+	 * if it is high speed or upper, we do not use interrupt
+	 * when send a command that without data.
+	 */
+	if (clk > 25000000)
+		host->use_int = 0;
+	else
+		host->use_int = 1;
+}
+
+static void spsdc_set_bus_timing(struct spsdc_host *host, unsigned int timing)
+{
+	u32 value = readl(&host->base->sd_timing_config0);
+	int clkdiv = readl(&host->base->sd_config) & 0xfff;
+	int delay = (clkdiv/2 < 7) ? clkdiv/2 : 7;
+	char *timing_name;
+
+	switch (timing) {
+	case MMC_TIMING_LEGACY:
+		value = bitfield_replace(value, 11, 1, 0); /* sd_high_speed_en */
+		timing_name = "legacy";
+		break;
+	case MMC_TIMING_SD_HS:
+	case MMC_TIMING_MMC_HS:
+		value = bitfield_replace(value, 11, 1, 1);
+		value = bitfield_replace(value, 12, 3, delay); /* sd_wr_dly_sel */
+		timing_name = "hs";
+		break;
+	}
+	spsdc_pr(INFO, "set bus timing to %s\n", timing_name);
+	writel(value, &host->base->sd_timing_config0);
+}
+
+static void spsdc_set_bus_width(struct spsdc_host *host, int width)
+{
+	u32 value = readl(&host->base->sd_config);
+	int bus_width;
+
+	switch (width) {
+	case MMC_BUS_WIDTH_8:
+		value = bitfield_replace(value, 12, 1, 0);
+		value = bitfield_replace(value, 18, 1, 1);
+		bus_width = 8;
+		break;
+	case MMC_BUS_WIDTH_4:
+		value = bitfield_replace(value, 12, 1, 1);
+		value = bitfield_replace(value, 18, 1, 0);
+		bus_width = 4;
+		break;
+	default:
+		value = bitfield_replace(value, 12, 1, 0);
+		value = bitfield_replace(value, 18, 1, 0);
+		bus_width = 1;
+		break;
+	};
+	spsdc_pr(INFO, "set bus width to %d bit(s)\n", bus_width);
+	writel(value, &host->base->sd_config);
+}
+/**
+ * select the working mode of controller: sd/sdio/emmc
+ */
+static void spsdc_select_mode(struct spsdc_host *host, int mode)
+{
+	u32 value = readl(&host->base->sd_config);
+
+	host->mode = mode;
+	/* set `sdmmcmode', as it will sample data at fall edge
+	 * of SD bus clock if `sdmmcmode' is not set when
+	 * `sd_high_speed_en' is not set, which is not compliant
+	 * with SD specification
+	 */
+	value = bitfield_replace(value, 16, 1, 1);
+	switch (mode) {
+	case SPSDC_MODE_EMMC:
+		value = bitfield_replace(value, 20, 1, 0);
+		writel(value, &host->base->sd_config);
+		break;
+	case SPSDC_MODE_SDIO:
+		value = bitfield_replace(value, 20, 1, 1);
+		writel(value, &host->base->sd_config);
+		value = readl(&host->base->sdio_ctrl);
+		value = bitfield_replace(value, 6, 1, 1); /* int_multi_trig */
+		writel(value, &host->base->sdio_ctrl);
+		break;
+	case SPSDC_MODE_SD:
+	default:
+		value = bitfield_replace(value, 20, 1, 0);
+		host->mode = SPSDC_MODE_SD;
+		writel(value, &host->base->sd_config);
+		break;
+	}
+}
+
+static void spsdc_sw_reset(struct spsdc_host *host)
+{
+	spsdc_pr(DEBUG, "sw reset\n");
+	writel(0x7, &host->base->sd_rst);
+	writel(0x6, &host->base->dma_hw_stop_rst);
+	while (readl(&host->base->dma_hw_stop_rst) & BIT(2))
+		;
+	/* reset dma operation */
+	writel(0x0, &host->base->dma_ctrl);
+	writel(0x1, &host->base->dma_ctrl);
+	writel(0x0, &host->base->dma_ctrl);
+	spsdc_pr(DEBUG, "sw reset done\n");
+}
+
+static void spsdc_prepare_cmd(struct spsdc_host *host, struct mmc_command *cmd)
+{
+
+	u32 value;
+
+	/* add start bit, according to spec, command format */
+	writeb((u8)(cmd->opcode | 0x40), &host->base->sd_cmdbuf0);
+	writeb((u8)((cmd->arg >> 24) & 0x000000ff), &host->base->sd_cmdbuf1);
+	writeb((u8)((cmd->arg >> 16) & 0x000000ff), &host->base->sd_cmdbuf2);
+	writeb((u8)((cmd->arg >> 8) & 0x000000ff), &host->base->sd_cmdbuf3);
+	writeb((u8)((cmd->arg >> 0) & 0x000000ff), &host->base->sd_cmdbuf4);
+
+	/* disable interrupt if needed */
+	value = readl(&host->base->sd_int);
+	value = bitfield_replace(value, 2, 1, 1); /* sd_cmp_clr */
+	if (likely(!host->use_int || cmd->flags & MMC_RSP_136))
+		value = bitfield_replace(value, 0, 1, 0); /* sdcmpen */
+	else
+		value = bitfield_replace(value, 0, 1, 1);
+	writel(value, &host->base->sd_int);
+
+	value = readl(&host->base->sd_config0);
+	value = bitfield_replace(value, 4, 2, 0); /* sd_trans_mode */
+	value = bitfield_replace(value, 7, 1, 1); /* sdcmddummy */
+	if (likely(cmd->flags & MMC_RSP_PRESENT)) {
+		value = bitfield_replace(value, 6, 1, 1); /* sdautorsp */
+	} else {
+		value = bitfield_replace(value, 6, 1, 0);
+		writel(value, &host->base->sd_config0);
+		return;
+	}
+	/*
+	 * Currently, host is not capable of checking R2's CRC7,
+	 * thus, enable crc7 check only for 48 bit response commands
+	 */
+	if (likely(cmd->flags & MMC_RSP_CRC && !(cmd->flags & MMC_RSP_136)))
+		value = bitfield_replace(value, 8, 1, 1); /* sdrspchk_en */
+	else
+		value = bitfield_replace(value, 8, 1, 0);
+	writel(value, &host->base->sd_config0);
+
+	value = readl(&host->base->sd_config);
+	if (unlikely(cmd->flags & MMC_RSP_136))
+		value = bitfield_replace(value, 13, 1, 1); /* sdrsptype */
+	else
+		value = bitfield_replace(value, 13, 1, 0);
+	writel(value, &host->base->sd_config);
+
+}
+
+static void spsdc_prepare_data(struct spsdc_host *host, struct mmc_data *data)
+{
+	u32 value;
+
+	writel(data->blocks - 1, &host->base->sd_page_num);
+	writel(data->blksz - 1, &host->base->sd_blocksize);
+	value = readl(&host->base->sd_config0);
+	if (data->flags & MMC_DATA_READ) {
+		value = bitfield_replace(value, 4, 2, 2); /* sd_trans_mode */
+		value = bitfield_replace(value, 6, 1, 0); /* sdautorsp */
+		value = bitfield_replace(value, 7, 1, 0); /* sdcmddummy */
+		writel(0x12, &host->base->dma_srcdst);
+	} else {
+		value = bitfield_replace(value, 4, 2, 1);
+		writel(0x21, &host->base->dma_srcdst);
+	}
+	/* to prevent of the responses of CMD18/25 being over by CMD12's,
+	 * send CMD12 by ourself instead of by controller automatically
+	 *
+	 *	#if 0
+	 *	if ((cmd->opcode == MMC_READ_MULTIPLE_BLOCK)
+	 *	|| (cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK))
+	 *		value = bitfield_replace(value, 2, 1, 0); //sd_len_mode
+	 *	else
+	 *		value = bitfield_replace(value, 2, 1, 1);
+	 *	#endif
+	 */
+	value = bitfield_replace(value, 2, 1, 1);
+
+	if (likely(host->dmapio_mode == SPSDC_DMA_MODE)) {
+		struct scatterlist *sg;
+		dma_addr_t dma_addr;
+		unsigned int dma_size;
+		u32 *reg_addr;
+		int dma_direction = data->flags & MMC_DATA_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+		int i, count = dma_map_sg(host->mmc->parent, data->sg, data->sg_len, dma_direction);
+
+		if (unlikely(!count || count > SPSDC_MAX_DMA_MEMORY_SECTORS)) {
+			spsdc_pr(ERROR, "error at dma_mapp_sg: count = %d\n", count);
+			data->error = -EINVAL;
+			return;
+		}
+		for_each_sg(data->sg, sg, count, i) {
+			dma_addr = sg_dma_address(sg);
+			dma_size = sg_dma_len(sg) / data->blksz - 1;
+			//dma_size = sg_dma_len(sg) / 512 - 1;
+			if (i == 0) {
+				writel(dma_addr, &host->base->dma_base_addr15_0);
+				writel(dma_addr >> 16, &host->base->dma_base_addr31_16);
+				writel(dma_size, &host->base->sdram_sector_0_size);
+			} else {
+				reg_addr = &host->base->sdram_sector_1_addr + (i - 1) * 2;
+				writel(dma_addr, reg_addr);
+				writel(dma_size, reg_addr + 1);
+			}
+		}
+		value = bitfield_replace(value, 0, 1, 0); /* sdpiomode */
+		writel(value, &host->base->sd_config0);
+		writel(data->blksz - 1, &host->base->dma_size);
+		/* enable interrupt if needed */
+		if (!host->use_int && data->blksz * data->blocks > host->dma_int_threshold) {
+			host->dma_use_int = 1;
+			value = readl(&host->base->sd_int);
+			value = bitfield_replace(value, 0, 1, 1); /* sdcmpen */
+			writel(value, &host->base->sd_int);
+		}
+	} else {
+		value = bitfield_replace(value, 0, 1, 1);
+		writel(value, &host->base->sd_config0);
+	}
+}
+
+static inline void spsdc_trigger_transaction(struct spsdc_host *host)
+{
+	u32 value = readl(&host->base->sd_ctrl);
+
+	value = bitfield_replace(value, 0, 1, 1); /* trigger transaction */
+	writel(value, &host->base->sd_ctrl);
+}
+
+static int __send_stop_cmd(struct spsdc_host *host, struct mmc_command *stop)
+{
+	u32 value;
+
+	spsdc_prepare_cmd(host, stop);
+	value = readl(&host->base->sd_int);
+	value = bitfield_replace(value, 0, 1, 0); /* sdcmpen */
+	writel(value, &host->base->sd_int);
+	spsdc_trigger_transaction(host);
+	if (spsdc_wait_finish(host)) {
+		value = readl(&host->base->sd_status);
+		if (value & SPSDC_SDSTATUS_RSP_CRC7_ERROR)
+			stop->error = -EILSEQ;
+		else
+			stop->error = -ETIMEDOUT;
+		return -1;
+	}
+	spsdc_get_rsp(host, stop);
+	return 0;
+}
+
+#ifdef SPSDC_WIDTH_SWITCH
+
+static int __switch_sdio_bus_width(struct spsdc_host *host, int width)
+{
+	struct mmc_command cmd = {0};
+	u8 ctrl;
+	u32 value;
+	int ret = 0;
+
+	cmd.opcode = SD_IO_RW_DIRECT;
+	cmd.arg |= SDIO_CCCR_IF << 9;
+	cmd.flags = MMC_RSP_R5;
+	spsdc_prepare_cmd(host, &cmd);
+	value = readl(&host->base->sd_int);
+	value = bitfield_replace(value, 0, 1, 0); /* sdcmpen */
+	writel(value, &host->base->sd_int);
+	spsdc_trigger_transaction(host);
+	ret = spsdc_wait_finish(host);
+	if (ret) {
+		spsdc_sw_reset(host);
+		return ret;
+	}
+	spsdc_get_rsp(host, &cmd);
+	ctrl = cmd.resp[0] & 0xff;
+
+	ctrl &= ~SDIO_BUS_WIDTH_MASK;
+	if (width == MMC_BUS_WIDTH_4) {
+		/* set to 4-bit bus width */
+		ctrl |= SDIO_BUS_WIDTH_4BIT;
+	}
+
+	cmd.arg |= 0x80000000;
+	cmd.arg |= ctrl;
+	spsdc_prepare_cmd(host, &cmd);
+	value = readl(&host->base->sd_int);
+	value = bitfield_replace(value, 0, 1, 0); /* sdcmpen */
+	writel(value, &host->base->sd_int);
+	spsdc_trigger_transaction(host);
+	ret = spsdc_wait_finish(host);
+	if (ret) {
+		spsdc_sw_reset(host);
+		return ret;
+	}
+	spsdc_get_rsp(host, &cmd);
+	spsdc_set_bus_width(host, width);
+
+	return ret;
+}
+
+#endif
+
+/**
+ * check if error during transaction.
+ * @host -  host
+ * @mrq - the mrq
+ * @return 0 if no error otherwise the error number.
+ */
+static int spsdc_check_error(struct spsdc_host *host, struct mmc_request *mrq)
+{
+	int ret = 0;
+	struct mmc_command *cmd = mrq->cmd;
+	struct mmc_data *data = mrq->data;
+	u32 value = readl(&host->base->sd_state);
+
+	if (unlikely(value & SPSDC_SDSTATE_ERROR)) {
+		u32 timing_cfg0, timing_cfg1;
+
+		spsdc_pr(DEBUG, "%s cmd %d with data %p error!\n", __func__, cmd->opcode, data);
+		spsdc_pr(VERBOSE, "%s sd_state: 0x%08x\n", __func__, value);
+		value = readl(&host->base->sd_status);
+		spsdc_pr(VERBOSE, "%s sd_status: 0x%08x\n", __func__, value);
+		timing_cfg0 = readl(&host->base->sd_timing_config0);
+		host->tuning_info.wr_dly = bitfield_extract(timing_cfg0, 12, 3);
+		timing_cfg1 = readl(&host->base->sd_timing_config1);
+		host->tuning_info.rd_dly = bitfield_extract(timing_cfg1, 13, 3);
+		if (value & SPSDC_SDSTATUS_RSP_TIMEOUT) {
+			ret = -ETIMEDOUT;
+			host->tuning_info.wr_dly++;
+		} else if (value & SPSDC_SDSTATUS_RSP_CRC7_ERROR) {
+			ret = -EILSEQ;
+			host->tuning_info.rd_dly++;
+		}
+		if (data) {
+			if ((value & SPSDC_SDSTATUS_STB_TIMEOUT) ||
+				(value & SPSDC_SDSTATUS_CARD_CRC_CHECK_TIMEOUT)) {
+				ret = -ETIMEDOUT;
+				host->tuning_info.rd_dly++;
+			} else if (value & SPSDC_SDSTATUS_CRC_TOKEN_CHECK_ERROR) {
+				ret = -EILSEQ;
+				host->tuning_info.wr_dly++;
+			} else if (value & SPSDC_SDSTATUS_RDATA_CRC16_ERROR) {
+				ret = -EILSEQ;
+				host->tuning_info.rd_dly++;
+			}
+			data->error = ret;
+			data->bytes_xfered = 0;
+		}
+		cmd->error = ret;
+		if (!host->tuning_info.need_tuning)
+			cmd->retries = SPSDC_MAX_RETRIES; /* retry it */
+		spsdc_sw_reset(host);
+		timing_cfg0 = bitfield_replace(timing_cfg0, 12, 3, host->tuning_info.wr_dly);
+		writel(timing_cfg0, &host->base->sd_timing_config0);
+		timing_cfg1 = bitfield_replace(timing_cfg0, 13, 3, host->tuning_info.rd_dly);
+		writel(timing_cfg1, &host->base->sd_timing_config1);
+
+	} else if (data) {
+		data->bytes_xfered = data->blocks * data->blksz;
+	}
+	host->tuning_info.need_tuning = ret;
+	return ret;
+}
+
+
+static inline __maybe_unused void spsdc_txdummy(struct spsdc_host *host)
+{
+	u32 value;
+
+	value = readl(&host->base->sd_ctrl);
+	value = bitfield_replace(value, 1, 1, 1); /* trigger tx dummy */
+	writel(value, &host->base->sd_ctrl);
+}
+
+static void spsdc_xfer_data_pio(struct spsdc_host *host, struct mmc_data *data)
+{
+	u16 *buf; /* tx/rx 2 bytes one time in pio mode */
+	int data_left = data->blocks * data->blksz;
+	int consumed, remain;
+	struct sg_mapping_iter *sg_miter = &host->sg_miter;
+	unsigned int flags = 0;
+
+	if (data->flags & MMC_DATA_WRITE)
+		flags |= SG_MITER_FROM_SG;
+	else
+		flags |= SG_MITER_TO_SG;
+	sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
+	while (data_left > 0) {
+		consumed = 0;
+		if (!sg_miter_next(sg_miter))
+			break;
+		buf = sg_miter->addr;
+		remain = sg_miter->length;
+		do {
+			if (data->flags & MMC_DATA_WRITE) {
+				if (spsdc_wait_txbuf_empty(host))
+					goto done;
+				writel(*buf, &host->base->sd_piodatatx);
+			} else {
+				if (spsdc_wait_rxbuf_full(host))
+					goto done;
+				*buf = readl(&host->base->sd_piodatarx);
+			}
+			buf++;
+			consumed += 2;
+			remain -= 2;
+		} while (remain);
+		sg_miter->consumed = consumed;
+		data_left -= consumed;
+	}
+done:
+	sg_miter_stop(sg_miter);
+}
+
+static void spsdc_controller_init(struct spsdc_host *host)
+{
+	u32 value;
+	int ret = reset_control_assert(host->rstc);
+
+	if (!ret) {
+		mdelay(1);
+		ret = reset_control_deassert(host->rstc);
+	}
+	if (ret)
+		spsdc_pr(WARNING, "Failed to reset SD controller!\n");
+	value = readl(&host->base->card_mediatype);
+	value = bitfield_replace(value, 0, 3, SPSDC_MEDIA_SD);
+	writel(value, &host->base->card_mediatype);
+}
+
+static void spsdc_set_power_mode(struct spsdc_host *host, struct mmc_ios *ios)
+{
+	if (host->power_state == ios->power_mode)
+		return;
+
+	switch (ios->power_mode) {
+		/* power off->up->on */
+	case MMC_POWER_ON:
+		spsdc_pr(DEBUG, "set MMC_POWER_ON\n");
+		spsdc_controller_init(host);
+		pm_runtime_get_sync(host->mmc->parent);
+		break;
+	case MMC_POWER_UP:
+		spsdc_pr(DEBUG, "set MMC_POWER_UP\n");
+		break;
+	case MMC_POWER_OFF:
+		spsdc_pr(DEBUG, "set MMC_POWER_OFF\n");
+		pm_runtime_put(host->mmc->parent);
+		break;
+	}
+	host->power_state = ios->power_mode;
+}
+
+/**
+ * 1. unmap scatterlist if needed;
+ * 2. get response & check error conditions;
+ * 3. unlock host->mrq_lock
+ * 4. notify mmc layer the request is done
+ */
+static void spsdc_finish_request(struct spsdc_host *host, struct mmc_request *mrq)
+{
+	struct mmc_command *cmd;
+	struct mmc_data *data;
+
+	if (!mrq)
+		return;
+
+	cmd = mrq->cmd;
+	data = mrq->data;
+
+	if (data && SPSDC_DMA_MODE == host->dmapio_mode) {
+		int dma_direction = data->flags & MMC_DATA_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+
+		dma_unmap_sg(host->mmc->parent, data->sg, data->sg_len, dma_direction);
+		host->dma_use_int = 0;
+	}
+	spsdc_get_rsp(host, cmd);
+	spsdc_check_error(host, mrq);
+	if (mrq->stop) {
+		if (__send_stop_cmd(host, mrq->stop))
+			spsdc_sw_reset(host);
+	}
+	host->mrq = NULL;
+
+#ifdef SPSDC_WIDTH_SWITCH
+	if (host->restore_4bit_sdio_bus) {
+		__switch_sdio_bus_width(host, MMC_BUS_WIDTH_4);
+		host->restore_4bit_sdio_bus = 0;
+	}
+#endif
+
+	mutex_unlock(&host->mrq_lock);
+	spsdc_pr(VERBOSE, "request done > error:%d, cmd:%d, resp:0x%08x\n",
+		cmd->error, cmd->opcode, cmd->resp[0]);
+	mmc_request_done(host->mmc, mrq);
+}
+
+/* Interrupt Service Routine */
+irqreturn_t spsdc_irq(int irq, void *dev_id)
+{
+	struct spsdc_host *host = dev_id;
+	u32 value = readl(&host->base->sd_int);
+
+	spin_lock(&host->lock);
+	if ((value & SPSDC_SDINT_SDCMP) &&
+		(value & SPSDC_SDINT_SDCMPEN)) {
+		value = bitfield_replace(value, 0, 1, 0); /* disable sdcmp */
+		value = bitfield_replace(value, 2, 1, 1); /* sd_cmp_clr */
+		writel(value, &host->base->sd_int);
+		/* we may need send stop command to stop data transaction,
+		 * which is time consuming, so make use of tasklet to handle this.
+		 */
+		if (host->mrq && host->mrq->stop)
+			tasklet_schedule(&host->tsklet_finish_req);
+		else
+			spsdc_finish_request(host, host->mrq);
+
+	}
+	if ((value & SPSDC_SDINT_SDIO) &&
+		(value & SPSDC_SDINT_SDIOEN)) {
+		mmc_signal_sdio_irq(host->mmc);
+	}
+	spin_unlock(&host->lock);
+	return IRQ_HANDLED;
+}
+
+static void spsdc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct spsdc_host *host = mmc_priv(mmc);
+	struct mmc_data *data;
+	struct mmc_command *cmd;
+
+#ifdef SPSDC_WIDTH_SWITCH
+	int bus_width = mmc->ios.bus_width;
+#endif
+
+	mutex_lock_interruptible(&host->mrq_lock);
+	host->mrq = mrq;
+	data = mrq->data;
+	cmd = mrq->cmd;
+	spsdc_pr(VERBOSE, "%s > cmd:%d, arg:0x%08x, data len:%d\n", __func__,
+		 cmd->opcode, cmd->arg, data ? (data->blocks*data->blksz) : 0);
+
+#ifdef SPSDC_WIDTH_SWITCH
+	if (SD_IO_RW_EXTENDED == cmd->opcode && MMC_BUS_WIDTH_4 == bus_width
+		&& data->blocks*data->blksz <= 4) {
+		if (__switch_sdio_bus_width(host, MMC_BUS_WIDTH_1)) {
+			cmd->error = -1;
+			host->mrq = NULL;
+			mutex_unlock(&host->mrq_lock);
+			mmc_request_done(host->mmc, mrq);
+			return;
+		}
+		host->restore_4bit_sdio_bus = 1;
+	}
+#endif
+
+	spsdc_prepare_cmd(host, cmd);
+	/* we need manually read response R2. */
+	if (unlikely(cmd->flags & MMC_RSP_136)) {
+		spsdc_trigger_transaction(host);
+		spsdc_get_rsp(host, cmd);
+		spsdc_wait_finish(host);
+		spsdc_check_error(host, mrq);
+		host->mrq = NULL;
+		spsdc_pr(VERBOSE, "request done > error:%d, cmd:%d, resp:%08x %08x %08x %08x\n",
+			 cmd->error, cmd->opcode, cmd->resp[0],
+				cmd->resp[1], cmd->resp[2], cmd->resp[3]);
+		mutex_unlock(&host->mrq_lock);
+		mmc_request_done(host->mmc, mrq);
+	} else {
+		if (data)
+			spsdc_prepare_data(host, data);
+
+		if ((host->dmapio_mode && data) ==  SPSDC_PIO_MODE) {
+			u32 value;
+			/* pio data transfer do not use interrupt */
+			value = readl(&host->base->sd_int);
+			value = bitfield_replace(value, 0, 1, 0); /* sdcmpen */
+			writel(value, &host->base->sd_int);
+			spsdc_trigger_transaction(host);
+			spsdc_xfer_data_pio(host, data);
+			spsdc_wait_finish(host);
+			spsdc_finish_request(host, mrq);
+		} else {
+			if (!(host->use_int || host->dma_use_int)) {
+				spsdc_trigger_transaction(host);
+				spsdc_wait_finish(host);
+				spsdc_finish_request(host, mrq);
+			} else {
+				spsdc_trigger_transaction(host);
+			}
+		}
+	}
+}
+
+static void spsdc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct spsdc_host *host = (struct spsdc_host *)mmc_priv(mmc);
+
+	mutex_lock(&host->mrq_lock);
+	spsdc_set_power_mode(host, ios);
+	spsdc_set_bus_clk(host, ios->clock);
+	spsdc_set_bus_timing(host, ios->timing);
+	spsdc_set_bus_width(host, ios->bus_width);
+	/* ensure mode is correct, because we might have hw reset the controller */
+	spsdc_select_mode(host, host->mode);
+	mutex_unlock(&host->mrq_lock);
+
+}
+
+/**
+ * Return values for the get_cd callback should be:
+ *   0 for a absent card
+ *   1 for a present card
+ *   -ENOSYS when not supported (equal to NULL callback)
+ *   or a negative errno value when something bad happened
+ */
+int spsdc_get_cd(struct mmc_host *mmc)
+{
+	int ret = 0;
+
+	if (mmc_can_gpio_cd(mmc))
+		ret = mmc_gpio_get_cd(mmc);
+	else
+		spsdc_pr(WARNING, "no gpio assigned for card detection\n");
+
+	if (ret < 0) {
+		spsdc_pr(ERROR, "Failed to get card presence status\n");
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static void spsdc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+	struct spsdc_host *host = mmc_priv(mmc);
+	u32 value = readl(&host->base->sd_int);
+
+	value = bitfield_replace(value, 6, 1, 1); /* sdio_int_clr */
+	if (enable)
+		value = bitfield_replace(value, 4, 1, 1);
+	else
+		value = bitfield_replace(value, 4, 1, 0);
+	writel(value, &host->base->sd_int);
+}
+
+static const struct mmc_host_ops spsdc_ops = {
+	.request = spsdc_request,
+	.set_ios = spsdc_set_ios,
+	.get_cd = spsdc_get_cd,
+	.enable_sdio_irq = spsdc_enable_sdio_irq,
+};
+
+static void tsklet_func_finish_req(unsigned long data)
+{
+	struct spsdc_host *host = (struct spsdc_host *)data;
+
+	spin_lock(&host->lock);
+	spsdc_finish_request(host, host->mrq);
+	spin_unlock(&host->lock);
+}
+
+static int spsdc_drv_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct mmc_host *mmc;
+	struct resource *resource;
+	struct spsdc_host *host;
+	unsigned int mode;
+
+	mmc = mmc_alloc_host(sizeof(*host), &pdev->dev);
+	if (!mmc) {
+		ret = -ENOMEM;
+		goto probe_free_host;
+	}
+
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->power_state = MMC_POWER_OFF;
+	host->dma_int_threshold = 1024;
+	host->dmapio_mode = SPSDC_DMA_MODE;
+
+	host->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(host->clk)) {
+		spsdc_pr(ERROR, "Can not find clock source\n");
+		ret = PTR_ERR(host->clk);
+		goto probe_free_host;
+	}
+
+	host->rstc = devm_reset_control_get(&pdev->dev, NULL);
+	if (IS_ERR(host->rstc)) {
+		spsdc_pr(ERROR, "Can not find reset controller\n");
+		ret = PTR_ERR(host->rstc);
+		goto probe_free_host;
+	}
+
+	resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (IS_ERR(resource)) {
+		spsdc_pr(ERROR, "get sd register resource fail\n");
+		ret = PTR_ERR(resource);
+		goto probe_free_host;
+	}
+
+	host->base = devm_ioremap_resource(&pdev->dev, resource);
+	if (IS_ERR((void *)host->base)) {
+		spsdc_pr(ERROR, "devm_ioremap_resource fail\n");
+		ret = PTR_ERR((void *)host->base);
+		goto probe_free_host;
+	}
+
+	host->irq = platform_get_irq(pdev, 0);
+	if (host->irq <= 0) {
+		spsdc_pr(ERROR, "get sd irq resource fail\n");
+		ret = -EINVAL;
+		goto probe_free_host;
+	}
+	if (devm_request_irq(&pdev->dev, host->irq, spsdc_irq,
+		IRQF_SHARED, dev_name(&pdev->dev), host)) {
+		spsdc_pr(ERROR, "Failed to request sd card interrupt.\n");
+		ret = -ENOENT;
+		goto probe_free_host;
+	}
+	spsdc_pr(INFO, "spsdc driver probe, reg base:0x%p, irq:%d\n", host->base, host->irq);
+
+
+	ret = mmc_of_parse(mmc);
+	if (ret)
+		goto probe_free_host;
+
+	ret = clk_prepare(host->clk);
+	if (ret)
+		goto probe_free_host;
+
+	ret = clk_enable(host->clk);
+	if (ret)
+		goto probe_clk_unprepare;
+
+	spin_lock_init(&host->lock);
+	mutex_init(&host->mrq_lock);
+	tasklet_init(&host->tsklet_finish_req, tsklet_func_finish_req, (unsigned long)host);
+	mmc->ops = &spsdc_ops;
+	mmc->f_min = SPSDC_MIN_CLK;
+	if (mmc->f_max > SPSDC_MAX_CLK) {
+		spsdc_pr(DEBUG, "max-frequency is too high, set it to %d\n", SPSDC_MAX_CLK);
+		mmc->f_max = SPSDC_MAX_CLK;
+	}
+	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+	mmc->max_seg_size = SPSDC_MAX_BLK_COUNT * 512;
+	/* Host controller supports up to "SPSDC_MAX_DMA_MEMORY_SECTORS",
+	 * a.k.a. max scattered memory segments per request
+	 */
+	/* Limited by the max value of dma_size & data_length, set it to 512 bytes for now */
+	mmc->max_segs = SPSDC_MAX_DMA_MEMORY_SECTORS;
+	mmc->max_req_size = SPSDC_MAX_BLK_COUNT * 512;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count = SPSDC_MAX_BLK_COUNT; /* Limited by sd_page_num */
+
+	dev_set_drvdata(&pdev->dev, host);
+	spsdc_controller_init(host);
+	mode = (int)of_device_get_match_data(&pdev->dev);
+	spsdc_select_mode(host, mode);
+	mmc_add_host(mmc);
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
+	return 0;
+
+probe_clk_unprepare:
+	spsdc_pr(ERROR, "unable to enable controller clock\n");
+	clk_unprepare(host->clk);
+probe_free_host:
+	if (mmc)
+		mmc_free_host(mmc);
+
+	return ret;
+}
+
+static int spsdc_drv_remove(struct platform_device *dev)
+{
+	struct spsdc_host *host = platform_get_drvdata(dev);
+
+	mmc_remove_host(host->mmc);
+	clk_disable(host->clk);
+	clk_unprepare(host->clk);
+	pm_runtime_disable(&dev->dev);
+	platform_set_drvdata(dev, NULL);
+	mmc_free_host(host->mmc);
+
+	return 0;
+}
+
+
+static int spsdc_drv_suspend(struct platform_device *dev, pm_message_t state)
+{
+	struct spsdc_host *host;
+
+	host = platform_get_drvdata(dev);
+	mutex_lock(&host->mrq_lock); /* Make sure that no one is holding the controller */
+	mutex_unlock(&host->mrq_lock);
+	clk_disable(host->clk);
+	return 0;
+}
+
+static int spsdc_drv_resume(struct platform_device *dev)
+{
+	struct spsdc_host *host;
+
+	host = platform_get_drvdata(dev);
+	return clk_enable(host->clk);
+}
+
+#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
+static int spsdc_pm_suspend(struct device *dev)
+{
+	pm_runtime_force_suspend(dev);
+	return 0;
+}
+
+static int spsdc_pm_resume(struct device *dev)
+{
+	pm_runtime_force_resume(dev);
+	return 0;
+}
+#endif /* ifdef CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME_SD
+static int spsdc_pm_runtime_suspend(struct device *dev)
+{
+	struct spsdc_host *host;
+
+	host = dev_get_drvdata(dev);
+	if (__clk_is_enabled(host->clk))
+		clk_disable(host->clk);
+	return 0;
+}
+
+static int spsdc_pm_runtime_resume(struct device *dev)
+{
+	struct spsdc_host *host;
+	int ret = 0;
+
+	host = dev_get_drvdata(dev);
+	if (!host->mmc)
+		return -EINVAL;
+	if (mmc_can_gpio_cd(host->mmc)) {
+		ret = mmc_gpio_get_cd(host->mmc);
+		if (!ret) {
+			spsdc_pr(DEBUG, "No card insert\n");
+			return 0;
+		}
+	}
+	return clk_enable(host->clk);
+}
+#endif /* ifdef CONFIG_PM_RUNTIME_SD */
+
+static const struct dev_pm_ops spsdc_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(spsdc_pm_suspend, spsdc_pm_resume)
+#ifdef CONFIG_PM_RUNTIME_SD
+	SET_RUNTIME_PM_OPS(spsdc_pm_runtime_suspend, spsdc_pm_runtime_resume, NULL)
+#endif
+};
+#endif /* ifdef CONFIG_PM */
+
+static const struct of_device_id spsdc_of_table[] = {
+	{
+		.compatible = "sunplus,sp7021-card1",
+		.data = (void *)SPSDC_MODE_SD,
+	},
+	{
+		.compatible = "sunplus,sp7021-sdio",
+		.data = (void *)SPSDC_MODE_SDIO,
+	},
+	{/* sentinel */}
+
+};
+MODULE_DEVICE_TABLE(of, spsdc_of_table);
+
+static struct platform_driver spsdc_driver = {
+	.probe = spsdc_drv_probe,
+	.remove = spsdc_drv_remove,
+	.suspend = spsdc_drv_suspend,
+	.resume = spsdc_drv_resume,
+	.driver = {
+		.name = "spsdc",
+		.owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm = &spsdc_pm_ops,
+#endif
+		.of_match_table = spsdc_of_table,
+	},
+};
+module_platform_driver(spsdc_driver);
+
+MODULE_AUTHOR("lh.kuo <lh.kuo@sunplus.com>");
+MODULE_DESCRIPTION("Sunplus SD/SDIO host controller v2.0 driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sunplus_sd2.h b/drivers/mmc/host/sunplus_sd2.h
new file mode 100644
index 0000000..822679a
--- /dev/null
+++ b/drivers/mmc/host/sunplus_sd2.h
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ * (C) Copyright 2019 Sunplus Technology. <http://www.sunplus.com/>
+ *
+ * Sunplus SD host controller v2.0
+ */
+#ifndef __SPSDC_H__
+#define __SPSDC_H__
+
+#include <linux/types.h>
+#include <linux/spinlock_types.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+
+
+#define SPSDC_WIDTH_SWITCH
+
+#define SPSDC_MIN_CLK	400000
+#define SPSDC_MAX_CLK	52000000
+#define SPSDC_50M_CLK   50000000
+
+#define SPSDC_MAX_BLK_COUNT 65536
+
+#define __rsvd_regs(l) __append_suffix(l, __COUNTER__)
+#define __append_suffix(l, s) _append_suffix(l, s)
+#define _append_suffix(l, s) (reserved##s[l])
+
+struct spsdc_regs {
+#define SPSDC_MEDIA_NONE 0
+#define SPSDC_MEDIA_SD 6
+#define SPSDC_MEDIA_MS 7
+	u32 card_mediatype;
+
+	u32 __rsvd_regs(1);
+	u32 card_cpu_page_cnt;
+	u32 card_ctl_page_cnt;
+	u32 sdram_sector_0_size;
+	u32 ring_buffer_on;
+	u32 card_gclk_disable;
+#define SPSDC_MAX_DMA_MEMORY_SECTORS 8 /* support up to 8 fragmented memory blocks */
+	u32 sdram_sector_1_addr;
+	u32 sdram_sector_1_size;
+	u32 sdram_sector_2_addr;
+	u32 sdram_sector_2_size;
+	u32 sdram_sector_3_addr;
+	u32 sdram_sector_3_size;
+	u32 sdram_sector_4_addr;
+	u32 sdram_sector_4_size;
+	u32 sdram_sector_5_addr;
+	u32 sdram_sector_5_size;
+	u32 sdram_sector_6_addr;
+	u32 sdram_sector_6_size;
+	u32 sdram_sector_7_addr;
+	u32 sdram_sector_7_size;
+	u32 sdram_sector_cnt;
+
+	u32 __rsvd_regs(10);
+
+	u32 __rsvd_regs(11);
+	u32 sd_vol_ctrl;
+#define SPSDC_SDINT_SDCMPEN	BIT(0)
+#define SPSDC_SDINT_SDCMP	BIT(1)
+#define SPSDC_SDINT_SDIOEN	BIT(4)
+#define SPSDC_SDINT_SDIO	BIT(5)
+	u32 sd_int;
+	u32 sd_page_num;
+	u32 sd_config0;
+	u32 sdio_ctrl;
+	u32 sd_rst;
+#define SPSDC_MODE_SDIO	2
+#define SPSDC_MODE_EMMC	1
+#define SPSDC_MODE_SD	0
+	u32 sd_config;
+	u32 sd_ctrl;
+#define SPSDC_SDSTATUS_DUMMY_READY			BIT(0)
+#define SPSDC_SDSTATUS_RSP_BUF_FULL			BIT(1)
+#define SPSDC_SDSTATUS_TX_DATA_BUF_EMPTY		BIT(2)
+#define SPSDC_SDSTATUS_RX_DATA_BUF_FULL			BIT(3)
+#define SPSDC_SDSTATUS_CMD_PIN_STATUS			BIT(4)
+#define SPSDC_SDSTATUS_DAT0_PIN_STATUS			BIT(5)
+#define SPSDC_SDSTATUS_RSP_TIMEOUT			BIT(6)
+#define SPSDC_SDSTATUS_CARD_CRC_CHECK_TIMEOUT		BIT(7)
+#define SPSDC_SDSTATUS_STB_TIMEOUT			BIT(8)
+#define SPSDC_SDSTATUS_RSP_CRC7_ERROR			BIT(9)
+#define SPSDC_SDSTATUS_CRC_TOKEN_CHECK_ERROR		BIT(10)
+#define SPSDC_SDSTATUS_RDATA_CRC16_ERROR		BIT(11)
+#define SPSDC_SDSTATUS_SUSPEND_STATE_READY		BIT(12)
+#define SPSDC_SDSTATUS_BUSY_CYCLE			BIT(13)
+	u32 sd_status;
+#define SPSDC_SDSTATE_IDLE	(0x0)
+#define SPSDC_SDSTATE_TXDUMMY	(0x1)
+#define SPSDC_SDSTATE_TXCMD	(0x2)
+#define SPSDC_SDSTATE_RXRSP	(0x3)
+#define SPSDC_SDSTATE_TXDATA	(0x4)
+#define SPSDC_SDSTATE_RXCRC	(0x5)
+#define SPSDC_SDSTATE_RXDATA	(0x5)
+#define SPSDC_SDSTATE_MASK	(0x7)
+#define SPSDC_SDSTATE_BADCRC	(0x5)
+#define SPSDC_SDSTATE_ERROR	BIT(13)
+#define SPSDC_SDSTATE_FINISH	BIT(14)
+	u32 sd_state;
+	u32 sd_blocksize;
+	u32 sd_hwdma_config;
+	u32 sd_timing_config0;
+	u32 sd_timing_config1;
+	u32 sd_piodatatx;
+	u32 sd_piodatarx;
+	u32 sd_cmdbuf0;
+	u32 sd_cmdbuf1;
+	u32 sd_cmdbuf2;
+	u32 sd_cmdbuf3;
+	u32 sd_cmdbuf4;
+
+	u32 sd_rspbuf0_3;
+	u32 sd_rspbuf4_5;
+	u32 sd_crc16even0;
+	u32 sd_crc16even1;
+	u32 sd_crc16even2;
+	u32 sd_crc16even3;
+	u32 sd_crc7buf;
+	u32 sd_crc16buf0;
+	u32 sd_hw_state;
+	u32 sd_crc16buf1;
+	u32 sd_hw_cmd13_rca;
+	u32 sd_crc16buf2;
+	u32 sd_tx_dummy_num;
+	u32 sd_crc16buf3;
+	u32 sd_clk_dly;
+
+	u32 __rsvd_regs(17);
+
+	u32 __rsvd_regs(32);
+
+	u32 dma_data;
+	u32 dma_srcdst;
+	u32 dma_size;
+	u32 dma_hw_stop_rst;
+	u32 dma_ctrl;
+	u32 dma_base_addr15_0;
+	u32 dma_base_addr31_16;
+	u32 dma_hw_en;
+	u32 dma_hw_page_addr_0_15_0;
+	u32 dma_hw_page_addr_0_31_16;
+	u32 dma_hw_page_addr_1_15_0;
+	u32 dma_hw_page_addr_1_31_16;
+	u32 dma_hw_page_addr_2_15_0;
+	u32 dma_hw_page_addr_2_31_16;
+	u32 dma_hw_page_addr_3_15_0;
+	u32 dma_hw_page_addr_3_31_16;
+	u32 dma_hw_page_num0;
+	u32 dma_hw_page_num1;
+	u32 dma_hw_page_num2;
+	u32 dma_hw_page_num3;
+	u32 dma_hw_block_num;
+	u32 dma_start;
+	u32 dma_hw_page_cnt;
+	u32 dma_cmp;
+	u32 dma_int_en;
+
+	u32 __rsvd_regs(1);
+	u32 dma_hw_wait_num15_0;
+	u32 dma_hw_wait_num31_16;
+	u32 dma_hw_delay_num;
+	u32 dma_debug;
+
+	u32 __rsvd_regs(2);
+};
+
+struct spsdc_tuning_info {
+	int need_tuning;
+#define SPSDC_MAX_RETRIES (8 * 8)
+	int retried; /* how many times has been retried */
+	u32 wr_dly:3;
+	u32 rd_dly:3;
+	u32 clk_dly:3;
+};
+
+struct spsdc_host {
+	struct spsdc_regs *base;
+	struct clk *clk;
+	struct reset_control *rstc;
+	int mode; /* SD/SDIO/eMMC */
+	spinlock_t lock; /* controller lock */
+	struct mutex mrq_lock;
+	/* tasklet used to handle error then finish the request */
+	struct tasklet_struct tsklet_finish_req;
+	struct mmc_host *mmc;
+	struct mmc_request *mrq; /* current mrq */
+
+	int irq;
+	int use_int; /* should raise irq when done */
+	int power_state; /* current power state: off/up/on */
+
+
+#ifdef SPSDC_WIDTH_SWITCH
+	int restore_4bit_sdio_bus;
+#endif
+
+#define SPSDC_DMA_MODE 0
+#define SPSDC_PIO_MODE 1
+	int dmapio_mode;
+	/* for purpose of reducing context switch, only when transfer data that
+	 *  length is greater than `dma_int_threshold' should use interrupt
+	 */
+	int dma_int_threshold;
+	int dma_use_int; /* should raise irq when dma done */
+	struct sg_mapping_iter sg_miter; /* for pio mode to access sglist */
+	struct spsdc_tuning_info tuning_info;
+};
+
+#endif /* #ifndef __SPSDC_H__ */
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 2/2] devicetree bindings mmc Add bindings doc for Sunplus SP7021
  2021-10-29  5:57 [PATCH 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC LH.Kuo
  2021-10-29  5:57 ` [PATCH 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021 LH.Kuo
@ 2021-10-29  5:57 ` LH.Kuo
  2021-11-09  7:58 ` [PATCH v2 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC LH.Kuo
  2 siblings, 0 replies; 13+ messages in thread
From: LH.Kuo @ 2021-10-29  5:57 UTC (permalink / raw)
  To: ulf.hansson, p.zabel, robh+dt, linux-kernel, linux-mmc, devicetree
  Cc: dvorkin, qinjian, wells.lu, LH.Kuo

Add devicetree bindings mmc Add bindings doc for Sunplus SP7021

Signed-off-by: LH.Kuo <lh.kuo@sunplus.com>
---
 .../devicetree/bindings/mmc/sunplus-sd2.yaml       | 82 ++++++++++++++++++++++
 MAINTAINERS                                        |  1 +
 2 files changed, 83 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml

diff --git a/Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml b/Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
new file mode 100644
index 0000000..02f0479
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
@@ -0,0 +1,82 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mmc/sunplus-sd2.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SD/SDIO controller
+
+maintainers:
+  - lh.kuo <lh.kuo@sunplus.com>
+
+properties:
+  compatible:
+    enum:
+      - sunplus,sp7021-card1
+      - sunplus,sp7021-sdio
+
+  reg:
+    items:
+      - description: Base address and length of the SD/SDIO registers
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  resets:
+    maxItems: 1
+
+  pinctrl-names:
+    description:
+      A pinctrl state named "default" must be defined.
+    const: default
+
+  pinctrl-0:
+    description:
+      A phandle to the default pinctrl state.
+
+  max-frequency: true
+
+allOf:
+  - $ref: "mmc-controller.yaml"
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - resets
+  - pinctrl-names
+  - pinctrl-0
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/sp-sp7021.h>
+    #include <dt-bindings/reset/sp-sp7021.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    mmc1: mmc@9C003e80 {
+       compatible = "sunplus,sp7021-card1";
+       reg = <0x9c003e80 0x280>;
+       interrupts = <21 IRQ_TYPE_LEVEL_HIGH>;
+       clocks = <&clkc CARD_CTL1>;
+       resets = <&rstc RST_CARD_CTL1>;
+       pinctrl-names = "default";
+       pinctrl-0 = <&mmc1_mux &mmc1_mux_cd>;
+       max-frequency = <52000000>;
+    };
+    sdio: mmc@9C008400 {
+       compatible = "sunplus,sp7021-sdio";
+       reg = <0x9c008400 0x280>;
+       interrupts = <21 IRQ_TYPE_LEVEL_HIGH>;
+       clocks = <&clkc CARD_CTL1>;
+       resets = <&rstc RST_CARD_CTL1>;
+       pinctrl-names = "default";
+       pinctrl-0 = <&pins_sdio>;
+       max-frequency = <52000000>;
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 0fb096d..83464c4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17951,6 +17951,7 @@ SUNPLUS SD/SDIO HOST CONTROLLER INTERFACE DRIVER
 M:	LH Kuo <lh.kuo@sunplus.com>
 L:	sdricohcs-devel@lists.sourceforge.net (subscribers-only)
 S:	Maintained
+F:	Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
 F:	drivers/mmc/host/Kconfig
 F:	drivers/mmc/host/Makefile
 F:	drivers/mmc/host/sunplus_sd2.*
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021
  2021-10-29  5:57 ` [PATCH 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021 LH.Kuo
@ 2021-10-30  0:00   ` Randy Dunlap
  2021-11-02 14:34   ` Philipp Zabel
  1 sibling, 0 replies; 13+ messages in thread
From: Randy Dunlap @ 2021-10-30  0:00 UTC (permalink / raw)
  To: LH.Kuo, ulf.hansson, p.zabel, robh+dt, linux-kernel, linux-mmc,
	devicetree
  Cc: dvorkin, qinjian, wells.lu, LH.Kuo

On 10/28/21 10:57 PM, LH.Kuo wrote:
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 95b3511..6f8bcd6 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -14,6 +14,16 @@ config MMC_DEBUG
>   	  added host drivers please don't invent their private macro for
>   	  debugging.
>   
> +config SP_SDV2
> +	tristate "Sunplus SP7021 SD/SDIO Controller"
> +	depends on SOC_SP7021
> +	default m
> +	help
> +	  This selects the Sunplus SP7021 SD/SDIO controller.
> +	  If you have a controller with this interface, say Y or M here..
> +
> +	  If unsure, say N.

Hi,
Does SOC_SP7021 require this driver to be built in order
to boot?
If not, please remove the "default m" line.

Also, several of your patches seem to have lots of line
changes to the MAINTAINERS file, but it looks like they
should not be part of your patch submission.

thanks.
-- 
~Randy

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021
  2021-10-29  5:57 ` [PATCH 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021 LH.Kuo
  2021-10-30  0:00   ` Randy Dunlap
@ 2021-11-02 14:34   ` Philipp Zabel
  1 sibling, 0 replies; 13+ messages in thread
From: Philipp Zabel @ 2021-11-02 14:34 UTC (permalink / raw)
  To: LH.Kuo, ulf.hansson, robh+dt, linux-kernel, linux-mmc, devicetree
  Cc: dvorkin, qinjian, wells.lu, LH.Kuo

On Fri, 2021-10-29 at 13:57 +0800, LH.Kuo wrote:
[...]
> --- /dev/null
> +++ b/drivers/mmc/host/sunplus_sd2.c
> @@ -0,0 +1,1069 @@
[...]
> +static void spsdc_controller_init(struct spsdc_host *host)
> +{
> +	u32 value;
> +	int ret = reset_control_assert(host->rstc);
> +
> +	if (!ret) {
> +		mdelay(1);

Consider using usleep_range(), see Documentation/timers/timers-howto.rst

[...]
> +static int spsdc_drv_probe(struct platform_device *pdev)
> +{
> +	int ret = 0;
> +	struct mmc_host *mmc;
> +	struct resource *resource;
> +	struct spsdc_host *host;
> +	unsigned int mode;
> +
> +	mmc = mmc_alloc_host(sizeof(*host), &pdev->dev);
> +	if (!mmc) {
> +		ret = -ENOMEM;
> +		goto probe_free_host;
> +	}
> +
> +	host = mmc_priv(mmc);
> +	host->mmc = mmc;
> +	host->power_state = MMC_POWER_OFF;
> +	host->dma_int_threshold = 1024;
> +	host->dmapio_mode = SPSDC_DMA_MODE;
> +
> +	host->clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(host->clk)) {
> +		spsdc_pr(ERROR, "Can not find clock source\n");
> +		ret = PTR_ERR(host->clk);
> +		goto probe_free_host;
> +	}
> +
> +	host->rstc = devm_reset_control_get(&pdev->dev, NULL);

Please use devm_reset_control_get_exclusive() instead.

regards
Philipp

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC
  2021-10-29  5:57 [PATCH 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC LH.Kuo
  2021-10-29  5:57 ` [PATCH 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021 LH.Kuo
  2021-10-29  5:57 ` [PATCH 2/2] devicetree bindings mmc Add bindings doc " LH.Kuo
@ 2021-11-09  7:58 ` LH.Kuo
  2021-11-09  7:58   ` [PATCH v2 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021 LH.Kuo
  2021-11-09  7:58   ` [PATCH v2 2/2] devicetree bindings mmc Add bindings doc " LH.Kuo
  2 siblings, 2 replies; 13+ messages in thread
From: LH.Kuo @ 2021-11-09  7:58 UTC (permalink / raw)
  To: p.zabel, daniel.thompson, lee.jones, u.kleine-koenig,
	ulf.hansson, robh+dt, linux-kernel, linux-mmc, devicetree
  Cc: qinjian, wells.lu, LH.Kuo

	This is a patch series for SD/SDIO driver for Sunplus SP7021 SoC.										
											
	Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates										
	many peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and										
	etc.) into a single chip. It is designed for industrial control.										
											
	Refer to:										
	https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview										
	https://tibbo.com/store/plus1.html	

LH.Kuo (2):
  mmc: Add SD/SDIO driver for Sunplus SP7021
  devicetree bindings mmc Add bindings doc for Sunplus SP7021

 .../devicetree/bindings/mmc/sunplus-sd2.yaml       |   82 ++
 MAINTAINERS                                        |    7 +
 drivers/mmc/host/Kconfig                           |   10 +
 drivers/mmc/host/Makefile                          |    1 +
 drivers/mmc/host/sunplus_sd2.c                     | 1068 ++++++++++++++++++++
 drivers/mmc/host/sunplus_sd2.h                     |  155 +++
 6 files changed, 1323 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
 create mode 100644 drivers/mmc/host/sunplus_sd2.c
 create mode 100644 drivers/mmc/host/sunplus_sd2.h

-- 
2.7.4


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021
  2021-11-09  7:58 ` [PATCH v2 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC LH.Kuo
@ 2021-11-09  7:58   ` LH.Kuo
  2021-11-10  2:33     ` Randy Dunlap
  2021-11-09  7:58   ` [PATCH v2 2/2] devicetree bindings mmc Add bindings doc " LH.Kuo
  1 sibling, 1 reply; 13+ messages in thread
From: LH.Kuo @ 2021-11-09  7:58 UTC (permalink / raw)
  To: p.zabel, daniel.thompson, lee.jones, u.kleine-koenig,
	ulf.hansson, robh+dt, linux-kernel, linux-mmc, devicetree
  Cc: qinjian, wells.lu, LH.Kuo

Add SD/SDIO driver for Sunplus SP7021.

Signed-off-by: LH.Kuo <lh.kuo@sunplus.com>
---
Changes in v2:
 - Addressed all comments from Mr. Philipp Zabel
 - Modified the structure and register access method.
 - Modifiedthe path about MAINTAINERS. ( wrong messages PATH in v1).

 MAINTAINERS                    |    6 +
 drivers/mmc/host/Kconfig       |   10 +
 drivers/mmc/host/Makefile      |    1 +
 drivers/mmc/host/sunplus_sd2.c | 1068 ++++++++++++++++++++++++++++++++++++++++
 drivers/mmc/host/sunplus_sd2.h |  155 ++++++
 5 files changed, 1240 insertions(+)
 create mode 100644 drivers/mmc/host/sunplus_sd2.c
 create mode 100644 drivers/mmc/host/sunplus_sd2.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 170bbbe..2746084 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18189,6 +18189,12 @@ L:	netdev@vger.kernel.org
 S:	Maintained
 F:	drivers/net/ethernet/dlink/sundance.c
 
+SUNPLUS SD/SDIO HOST CONTROLLER INTERFACE DRIVER
+M:	LH Kuo <lh.kuo@sunplus.com>
+L:	linux-mmc@vger.kernel.org
+S:	Maintained
+F:	drivers/mmc/host/sunplus_sd2.*
+
 SUPERH
 M:	Yoshinori Sato <ysato@users.sourceforge.jp>
 M:	Rich Felker <dalias@libc.org>
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 5af8494..2aba9eb 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -1091,5 +1091,15 @@ config MMC_OWL
 	  This selects support for the SD/MMC Host Controller on
 	  Actions Semi Owl SoCs.
 
+config MMC_SP_SDV2
+	tristate "Sunplus SP7021 SD/SDIO Controller"
+	depends on SOC_SP7021
+	help
+		If you say yes here, you will get support for SD/SDIO host interface
+		on sunplus Socs.
+		If you have a controller with this interface, say Y or M here.
+		If unsure, say N.
+                Sunplus  SD/SDIO Host Controller support"
+
 config MMC_SDHCI_EXTERNAL_DMA
 	bool
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index ea36d37..167aa5c 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -101,6 +101,7 @@ obj-$(CONFIG_MMC_CQHCI)			+= cqhci.o
 cqhci-y					+= cqhci-core.o
 cqhci-$(CONFIG_MMC_CRYPTO)		+= cqhci-crypto.o
 obj-$(CONFIG_MMC_HSQ)			+= mmc_hsq.o
+obj-$(CONFIG_MMC_SP_SDV2)		+= sunplus_sd2.o
 
 ifeq ($(CONFIG_CB710_DEBUG),y)
 	CFLAGS-cb710-mmc	+= -DDEBUG
diff --git a/drivers/mmc/host/sunplus_sd2.c b/drivers/mmc/host/sunplus_sd2.c
new file mode 100644
index 0000000..756c3d4
--- /dev/null
+++ b/drivers/mmc/host/sunplus_sd2.c
@@ -0,0 +1,1068 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/**
+ * (C) Copyright 2019 Sunplus Technology. <http://www.sunplus.com/>
+ *
+ * Sunplus SD host controller v2.0
+ *
+ */
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_device.h>
+#include <linux/reset.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/slot-gpio.h>
+#include <linux/clk.h>
+#include <linux/bitops.h>
+#include <linux/uaccess.h>
+#include "sunplus_sd2.h"
+
+enum loglevel {
+	SPSDC_LOG_OFF,
+	SPSDC_LOG_ERROR,
+	SPSDC_LOG_WARNING,
+	SPSDC_LOG_INFO,
+	SPSDC_LOG_DEBUG,
+	SPSDC_LOG_VERBOSE,
+	SPSDC_LOG_MAX
+};
+static int loglevel = SPSDC_LOG_WARNING;
+
+/**
+ * we do not need `SPSDC_LOG_' prefix here, when specify @level.
+ */
+#define spsdc_pr(level, fmt, ...)	\
+	do {	\
+		if (unlikely(SPSDC_LOG_##level <= loglevel))	\
+			pr_info("SPSDC [" #level "] " fmt, ##__VA_ARGS__);	\
+	} while (0)
+
+/* Produces a mask of set bits covering a range of a 32-bit value */
+static inline u32 bitfield_mask(u32 shift, u32 width)
+{
+	return ((1 << width) - 1) << shift;
+}
+
+/* Extract the value of a bitfield found within a given register value */
+static inline u32 bitfield_extract(u32 reg_val, u32 shift, u32 width)
+{
+	return (reg_val & bitfield_mask(shift, width)) >> shift;
+}
+
+/* Replace the value of a bitfield found within a given register value */
+static inline u32 bitfield_replace(u32 reg_val, u32 shift, u32 width, u32 val)
+{
+	u32 mask = bitfield_mask(shift, width);
+
+	return (reg_val & ~mask) | (val << shift);
+}
+/* for register value with mask bits */
+#define __bitfield_replace(value, shift, width, new_value)		\
+	(bitfield_replace(value, shift, width, new_value)|bitfield_mask(shift+16, width))
+
+/**
+ * wait for transaction done, return -1 if error.
+ */
+static inline int spsdc_wait_finish(struct spsdc_host *host)
+{
+	/* Wait for transaction finish */
+	unsigned long timeout = jiffies + msecs_to_jiffies(5000);
+
+	while (!time_after(jiffies, timeout)) {
+		if (readl(host->base + SPSD2_SD_STATE_REG) & SPSDC_SDSTATE_FINISH)
+			return 0;
+		if (readl(host->base + SPSD2_SD_STATE_REG) & SPSDC_SDSTATE_ERROR)
+			return -1;
+	}
+	return -1;
+}
+
+static inline int spsdc_wait_sdstatus(struct spsdc_host *host, unsigned int status_bit)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(5000);
+
+	while (!time_after(jiffies, timeout)) {
+		if (readl(host->base + SPSD2_SD_STATUS_REG) & status_bit)
+			return 0;
+		if (readl(host->base + SPSD2_SD_STATE_REG) & SPSDC_SDSTATE_ERROR)
+			return -1;
+	}
+	return -1;
+}
+
+#define spsdc_wait_rspbuf_full(host) spsdc_wait_sdstatus(host, SPSDC_SDSTATUS_RSP_BUF_FULL)
+#define spsdc_wait_rxbuf_full(host) spsdc_wait_sdstatus(host, SPSDC_SDSTATUS_RX_DATA_BUF_FULL)
+#define spsdc_wait_txbuf_empty(host) spsdc_wait_sdstatus(host, SPSDC_SDSTATUS_TX_DATA_BUF_EMPTY)
+
+static void spsdc_get_rsp(struct spsdc_host *host, struct mmc_command *cmd)
+{
+	u32 value0_3, value4_5;
+
+	if (unlikely(!(cmd->flags & MMC_RSP_PRESENT)))
+		return;
+	if (unlikely(cmd->flags & MMC_RSP_136)) {
+		if (spsdc_wait_rspbuf_full(host))
+			return;
+		value0_3 = readl(host->base + SPSD2_SD_RSP_BUF0_3_REG);
+		value4_5 = readl(host->base + SPSD2_SD_RSP_BUF4_5_REG) & 0xffff;
+		cmd->resp[0] = (value0_3 << 8) | (value4_5 >> 8);
+		cmd->resp[1] = value4_5 << 24;
+		if (spsdc_wait_rspbuf_full(host))
+			return;
+		value0_3 = readl(host->base + SPSD2_SD_RSP_BUF0_3_REG);
+		value4_5 = readl(host->base + SPSD2_SD_RSP_BUF4_5_REG) & 0xffff;
+		cmd->resp[1] |= value0_3 >> 8;
+		cmd->resp[2] = value0_3 << 24;
+		cmd->resp[2] |= value4_5 << 8;
+		if (spsdc_wait_rspbuf_full(host))
+			return;
+		value0_3 = readl(host->base + SPSD2_SD_RSP_BUF0_3_REG);
+		value4_5 = readl(host->base + SPSD2_SD_RSP_BUF4_5_REG) & 0xffff;
+		cmd->resp[2] |= value0_3 >> 24;
+		cmd->resp[3] = value0_3 << 8;
+		cmd->resp[3] |= value4_5 >> 8;
+	} else {
+		if (spsdc_wait_rspbuf_full(host))
+			return;
+		value0_3 = readl(host->base + SPSD2_SD_RSP_BUF0_3_REG);
+		value4_5 = readl(host->base + SPSD2_SD_RSP_BUF4_5_REG) & 0xffff;
+		cmd->resp[0] = (value0_3 << 8) | (value4_5 >> 8);
+		cmd->resp[1] = value4_5 << 24;
+	}
+}
+
+static void spsdc_set_bus_clk(struct spsdc_host *host, int clk)
+{
+	unsigned int clkdiv;
+	int f_min = host->mmc->f_min;
+	int f_max = host->mmc->f_max;
+	u32 value = readl(host->base + SPSD2_SD_CONF_REG);
+
+	if (clk < f_min)
+		clk = f_min;
+	if (clk > f_max)
+		clk = f_max;
+
+	// SD 2.0 only max set to 50Mhz CLK
+	if (clk >= SPSDC_50M_CLK)
+		clk = f_max;
+
+	spsdc_pr(INFO, "set bus clock to %d\n", clk);
+	clkdiv = (clk_get_rate(host->clk)+clk)/clk-1;
+	if (clkdiv > 0xfff) {
+		spsdc_pr(WARNING, "clock %d is too low to be set!\n", clk);
+		clkdiv = 0xfff;
+	}
+	value = bitfield_replace(value, 0, 12, clkdiv);
+	writel(value, host->base + SPSD2_SD_CONF_REG);
+
+	/* In order to reduce the frequency of context switch,
+	 * if it is high speed or upper, we do not use interrupt
+	 * when send a command that without data.
+	 */
+	if (clk > 25000000)
+		host->use_int = 0;
+	else
+		host->use_int = 1;
+}
+
+static void spsdc_set_bus_timing(struct spsdc_host *host, unsigned int timing)
+{
+	u32 value = readl(host->base + SPSD2_SD_TIMING_CONF0_REG);
+	int clkdiv = readl(host->base + SPSD2_SD_CONF_REG) & 0xfff;
+	int delay = (clkdiv/2 < 7) ? clkdiv/2 : 7;
+	char *timing_name;
+
+	switch (timing) {
+	case MMC_TIMING_LEGACY:
+		value = bitfield_replace(value, 11, 1, 0); /* sd_high_speed_en */
+		timing_name = "legacy";
+		break;
+	case MMC_TIMING_SD_HS:
+	case MMC_TIMING_MMC_HS:
+		value = bitfield_replace(value, 11, 1, 1);
+		value = bitfield_replace(value, 12, 3, delay); /* sd_wr_dly_sel */
+		timing_name = "hs";
+		break;
+	}
+	spsdc_pr(INFO, "set bus timing to %s\n", timing_name);
+	writel(value, host->base + SPSD2_SD_TIMING_CONF0_REG);
+}
+
+static void spsdc_set_bus_width(struct spsdc_host *host, int width)
+{
+	u32 value = readl(host->base + SPSD2_SD_CONF_REG);
+	int bus_width;
+
+	switch (width) {
+	case MMC_BUS_WIDTH_8:
+		value = bitfield_replace(value, 12, 1, 0);
+		value = bitfield_replace(value, 18, 1, 1);
+		bus_width = 8;
+		break;
+	case MMC_BUS_WIDTH_4:
+		value = bitfield_replace(value, 12, 1, 1);
+		value = bitfield_replace(value, 18, 1, 0);
+		bus_width = 4;
+		break;
+	default:
+		value = bitfield_replace(value, 12, 1, 0);
+		value = bitfield_replace(value, 18, 1, 0);
+		bus_width = 1;
+		break;
+	};
+	spsdc_pr(INFO, "set bus width to %d bit(s)\n", bus_width);
+	writel(value, host->base + SPSD2_SD_CONF_REG);
+}
+/**
+ * select the working mode of controller: sd/sdio/emmc
+ */
+static void spsdc_select_mode(struct spsdc_host *host, int mode)
+{
+	u32 value = readl(host->base + SPSD2_SD_CONF_REG);
+
+	host->mode = mode;
+	/* set `sdmmcmode', as it will sample data at fall edge
+	 * of SD bus clock if `sdmmcmode' is not set when
+	 * `sd_high_speed_en' is not set, which is not compliant
+	 * with SD specification
+	 */
+	value = bitfield_replace(value, 16, 1, 1);
+	switch (mode) {
+	case SPSDC_MODE_EMMC:
+		value = bitfield_replace(value, 20, 1, 0);
+		writel(value, host->base + SPSD2_SD_CONF_REG);
+		break;
+	case SPSDC_MODE_SDIO:
+		value = bitfield_replace(value, 20, 1, 1);
+		writel(value, host->base + SPSD2_SD_CONF_REG);
+		value = readl(host->base + SPSD2_SDIO_CTRL_REG);
+		value = bitfield_replace(value, 6, 1, 1); /* int_multi_trig */
+		writel(value, host->base + SPSD2_SDIO_CTRL_REG);
+		break;
+	case SPSDC_MODE_SD:
+	default:
+		value = bitfield_replace(value, 20, 1, 0);
+		host->mode = SPSDC_MODE_SD;
+		writel(value, host->base + SPSD2_SD_CONF_REG);
+		break;
+	}
+}
+
+static void spsdc_sw_reset(struct spsdc_host *host)
+{
+	spsdc_pr(DEBUG, "sw reset\n");
+	writel(0x7, host->base + SPSD2_SD_RST_REG);
+	writel(0x6, host->base + SPSD2_DMA_STOP_RST_REG);
+	while (readl(host->base + SPSD2_DMA_STOP_RST_REG) & BIT(2))
+		;
+	/* reset dma operation */
+	writel(0x0, host->base + SPSD2_DMA_CTRL_REG);
+	writel(0x1, host->base + SPSD2_DMA_CTRL_REG);
+	writel(0x0, host->base + SPSD2_DMA_CTRL_REG);
+	spsdc_pr(DEBUG, "sw reset done\n");
+}
+
+static void spsdc_prepare_cmd(struct spsdc_host *host, struct mmc_command *cmd)
+{
+
+	u32 value;
+
+	writeb((u8)(cmd->opcode | 0x40), host->base + SPSD2_SD_CMD_BUF0_REG);
+	writeb((u8)((cmd->arg >> 24) & 0x000000ff), host->base + SPSD2_SD_CMD_BUF1_REG);
+	writeb((u8)((cmd->arg >> 16) & 0x000000ff), host->base + SPSD2_SD_CMD_BUF2_REG);
+	writeb((u8)((cmd->arg >> 8) & 0x000000ff), host->base + SPSD2_SD_CMD_BUF3_REG);
+	writeb((u8)((cmd->arg >> 0) & 0x000000ff), host->base + SPSD2_SD_CMD_BUF4_REG);
+
+	/* disable interrupt if needed */
+	value = readl(host->base + SPSD2_SD_INT_REG);
+	value = bitfield_replace(value, 2, 1, 1); /* sd_cmp_clr */
+	if (likely(!host->use_int || cmd->flags & MMC_RSP_136))
+		value = bitfield_replace(value, 0, 1, 0); /* sdcmpen */
+	else
+		value = bitfield_replace(value, 0, 1, 1);
+	writel(value, host->base + SPSD2_SD_INT_REG);
+
+	value = readl(host->base + SPSD2_SD_CONF0_REG);
+	value = bitfield_replace(value, 4, 2, 0); /* sd_trans_mode */
+	value = bitfield_replace(value, 7, 1, 1); /* sdcmddummy */
+	if (likely(cmd->flags & MMC_RSP_PRESENT)) {
+		value = bitfield_replace(value, 6, 1, 1); /* sdautorsp */
+	} else {
+		value = bitfield_replace(value, 6, 1, 0);
+		writel(value, host->base + SPSD2_SD_CONF0_REG);
+		return;
+	}
+	/*
+	 * Currently, host is not capable of checking R2's CRC7,
+	 * thus, enable crc7 check only for 48 bit response commands
+	 */
+	if (likely(cmd->flags & MMC_RSP_CRC && !(cmd->flags & MMC_RSP_136)))
+		value = bitfield_replace(value, 8, 1, 1); /* sdrspchk_en */
+	else
+		value = bitfield_replace(value, 8, 1, 0);
+	writel(value, host->base + SPSD2_SD_CONF0_REG);
+
+	value = readl(host->base + SPSD2_SD_CONF_REG);
+	if (unlikely(cmd->flags & MMC_RSP_136))
+		value = bitfield_replace(value, 13, 1, 1); /* sdrsptype */
+	else
+		value = bitfield_replace(value, 13, 1, 0);
+	writel(value, host->base + SPSD2_SD_CONF_REG);
+
+}
+
+static void spsdc_prepare_data(struct spsdc_host *host, struct mmc_data *data)
+{
+	u32 value;
+
+	writel(data->blocks - 1, host->base + SPSD2_SD_PAGE_NUM_REG);
+	writel(data->blksz - 1, host->base + SPSD2_BLOCKSIZE_REG);
+	value = readl(host->base + SPSD2_SD_CONF0_REG);
+	if (data->flags & MMC_DATA_READ) {
+		value = bitfield_replace(value, 4, 2, 2); /* sd_trans_mode */
+		value = bitfield_replace(value, 6, 1, 0); /* sdautorsp */
+		value = bitfield_replace(value, 7, 1, 0); /* sdcmddummy */
+		writel(0x12, host->base + SPSD2_DMA_SRCDST_REG);
+	} else {
+		value = bitfield_replace(value, 4, 2, 1);
+		writel(0x21, host->base + SPSD2_DMA_SRCDST_REG);
+	}
+	/* to prevent of the responses of CMD18/25 being by CMD12's,
+	 * send CMD12 by ourself instead of by controller automatically
+	 *
+	 *	#if 0
+	 *	if ((cmd->opcode == MMC_READ_MULTIPLE_BLOCK)
+	 *	 || (cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK))
+	 *		value = bitfield_replace(value, 2, 1, 0); //sd_len_mode
+	 *	else
+	 *		value = bitfield_replace(value, 2, 1, 1);
+	 *	#endif
+	 */
+	value = bitfield_replace(value, 2, 1, 1);
+
+	if (likely(host->dmapio_mode == SPSDC_DMA_MODE)) {
+		struct scatterlist *sg;
+		dma_addr_t dma_addr;
+		unsigned int dma_size;
+		void __iomem *reg_addr;
+		int dma_direction = data->flags & MMC_DATA_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+		int i, count = dma_map_sg(host->mmc->parent, data->sg, data->sg_len, dma_direction);
+
+		if (unlikely(!count || count > SPSDC_MAX_DMA_MEMORY_SECTORS)) {
+			spsdc_pr(ERROR, "error at dma_mapp_sg: count = %d\n", count);
+			data->error = -EINVAL;
+			return;
+		}
+		for_each_sg(data->sg, sg, count, i) {
+			dma_addr = sg_dma_address(sg);
+			dma_size = sg_dma_len(sg) / data->blksz - 1;
+			//dma_size = sg_dma_len(sg) / 512 - 1;
+			if (i == 0) {
+				writel(dma_addr, host->base + SPSD2_DMA_BASE_ADDR0_REG);
+				writel(dma_addr >> 16, host->base + SPSD2_DMA_BASE_ADDR16_REG);
+				writel(dma_size, host->base + SPSD2_SDRAM_SECTOR_SIZE_REG);
+			} else {
+				reg_addr = host->base + (SPSD2_SDRAM_SECTOR_ADDR_REG + (i - 1) * 8);
+				writel(dma_addr, reg_addr);
+				writel(dma_size, reg_addr + 4);
+			}
+		}
+		value = bitfield_replace(value, 0, 1, 0); /* sdpiomode */
+		writel(value, host->base + SPSD2_SD_CONF0_REG);
+		writel(data->blksz - 1, host->base + SPSD2_DMA_SIZE_REG);
+		/* enable interrupt if needed */
+		if (!host->use_int && data->blksz * data->blocks > host->dma_int_threshold) {
+			host->dma_use_int = 1;
+			value = readl(host->base + SPSD2_SD_INT_REG);
+			value = bitfield_replace(value, 0, 1, 1); /* sdcmpen */
+			writel(value, host->base + SPSD2_SD_INT_REG);
+		}
+	} else {
+		value = bitfield_replace(value, 0, 1, 1);
+		writel(value, host->base + SPSD2_SD_CONF0_REG);
+	}
+}
+
+static inline void spsdc_trigger_transaction(struct spsdc_host *host)
+{
+	u32 value = readl(host->base + SPSD2_SD_CTRL_REG);
+
+	value = bitfield_replace(value, 0, 1, 1); /* trigger transaction */
+	writel(value, host->base + SPSD2_SD_CTRL_REG);
+}
+
+static int __send_stop_cmd(struct spsdc_host *host, struct mmc_command *stop)
+{
+	u32 value;
+
+	spsdc_prepare_cmd(host, stop);
+	value = readl(host->base + SPSD2_SD_INT_REG);
+	value = bitfield_replace(value, 0, 1, 0); /* sdcmpen */
+	writel(value, host->base + SPSD2_SD_INT_REG);
+	spsdc_trigger_transaction(host);
+	if (spsdc_wait_finish(host)) {
+		value = readl(host->base + SPSD2_SD_STATUS_REG);
+		if (value & SPSDC_SDSTATUS_RSP_CRC7_ERROR)
+			stop->error = -EILSEQ;
+		else
+			stop->error = -ETIMEDOUT;
+		return -1;
+	}
+	spsdc_get_rsp(host, stop);
+	return 0;
+}
+
+#ifdef SPSDC_WIDTH_SWITCH
+
+static int __switch_sdio_bus_width(struct spsdc_host *host, int width)
+{
+	struct mmc_command cmd = {0};
+	u8 ctrl;
+	u32 value;
+	int ret = 0;
+
+	cmd.opcode = SD_IO_RW_DIRECT;
+	cmd.arg |= SDIO_CCCR_IF << 9;
+	cmd.flags = MMC_RSP_R5;
+	spsdc_prepare_cmd(host, &cmd);
+	value = readl(host->base + SPSD2_SD_INT_REG);
+	value = bitfield_replace(value, 0, 1, 0); /* sdcmpen */
+	writel(value, host->base + SPSD2_SD_INT_REG);
+	spsdc_trigger_transaction(host);
+	ret = spsdc_wait_finish(host);
+	if (ret) {
+		spsdc_sw_reset(host);
+		return ret;
+	}
+	spsdc_get_rsp(host, &cmd);
+	ctrl = cmd.resp[0] & 0xff;
+
+	ctrl &= ~SDIO_BUS_WIDTH_MASK;
+	if (width == MMC_BUS_WIDTH_4) {
+		/* set to 4-bit bus width */
+		ctrl |= SDIO_BUS_WIDTH_4BIT;
+	}
+
+	cmd.arg |= 0x80000000;
+	cmd.arg |= ctrl;
+	spsdc_prepare_cmd(host, &cmd);
+	value = readl(host->base + SPSD2_SD_INT_REG);
+	value = bitfield_replace(value, 0, 1, 0); /* sdcmpen */
+	writel(value, host->base + SPSD2_SD_INT_REG);
+	spsdc_trigger_transaction(host);
+	ret = spsdc_wait_finish(host);
+	if (ret) {
+		spsdc_sw_reset(host);
+		return ret;
+	}
+	spsdc_get_rsp(host, &cmd);
+	spsdc_set_bus_width(host, width);
+
+	return ret;
+}
+
+#endif
+
+/**
+ * check if error during transaction.
+ * @host -  host
+ * @mrq - the mrq
+ * @return 0 if no error otherwise the error number.
+ */
+static int spsdc_check_error(struct spsdc_host *host, struct mmc_request *mrq)
+{
+	int ret = 0;
+	struct mmc_command *cmd = mrq->cmd;
+	struct mmc_data *data = mrq->data;
+	u32 value = readl(host->base + SPSD2_SD_STATE_REG);
+
+	if (unlikely(value & SPSDC_SDSTATE_ERROR)) {
+		u32 timing_cfg0, timing_cfg1;
+
+		spsdc_pr(DEBUG, "%s cmd %d with data %p error!\n", __func__, cmd->opcode, data);
+		spsdc_pr(VERBOSE, "%s sd_state: 0x%08x\n", __func__, value);
+		value = readl(host->base + SPSD2_SD_STATUS_REG);
+		spsdc_pr(VERBOSE, "%s sd_status: 0x%08x\n", __func__, value);
+		timing_cfg0 = readl(host->base + SPSD2_SD_TIMING_CONF0_REG);
+		host->tuning_info.wr_dly = bitfield_extract(timing_cfg0, 12, 3);
+		timing_cfg1 = readl(host->base + SPSD2_SD_TIMING_CONF1_REG);
+		host->tuning_info.rd_dly = bitfield_extract(timing_cfg1, 13, 3);
+		if (value & SPSDC_SDSTATUS_RSP_TIMEOUT) {
+			ret = -ETIMEDOUT;
+			host->tuning_info.wr_dly++;
+		} else if (value & SPSDC_SDSTATUS_RSP_CRC7_ERROR) {
+			ret = -EILSEQ;
+			host->tuning_info.rd_dly++;
+		}
+		if (data) {
+			if ((value & SPSDC_SDSTATUS_STB_TIMEOUT) ||
+				(value & SPSDC_SDSTATUS_CARD_CRC_CHECK_TIMEOUT)) {
+				ret = -ETIMEDOUT;
+				host->tuning_info.rd_dly++;
+			} else if (value & SPSDC_SDSTATUS_CRC_TOKEN_CHECK_ERROR) {
+				ret = -EILSEQ;
+				host->tuning_info.wr_dly++;
+			} else if (value & SPSDC_SDSTATUS_RDATA_CRC16_ERROR) {
+				ret = -EILSEQ;
+				host->tuning_info.rd_dly++;
+			}
+			data->error = ret;
+			data->bytes_xfered = 0;
+		}
+		cmd->error = ret;
+		if (!host->tuning_info.need_tuning)
+			cmd->retries = SPSDC_MAX_RETRIES; /* retry it */
+		spsdc_sw_reset(host);
+		timing_cfg0 = bitfield_replace(timing_cfg0, 12, 3, host->tuning_info.wr_dly);
+		writel(timing_cfg0, host->base + SPSD2_SD_TIMING_CONF0_REG);
+		timing_cfg1 = bitfield_replace(timing_cfg0, 13, 3, host->tuning_info.rd_dly);
+		writel(timing_cfg1, host->base + SPSD2_SD_TIMING_CONF1_REG);
+
+	} else if (data) {
+		data->bytes_xfered = data->blocks * data->blksz;
+	}
+	host->tuning_info.need_tuning = ret;
+	return ret;
+}
+
+
+static inline __maybe_unused void spsdc_txdummy(struct spsdc_host *host)
+{
+	u32 value;
+
+	value = readl(host->base + SPSD2_SD_CTRL_REG);
+	value = bitfield_replace(value, 1, 1, 1); /* trigger tx dummy */
+	writel(value, host->base + SPSD2_SD_CTRL_REG);
+}
+
+static void spsdc_xfer_data_pio(struct spsdc_host *host, struct mmc_data *data)
+{
+	u16 *buf; /* tx/rx 2 bytes one time in pio mode */
+	int data_left = data->blocks * data->blksz;
+	int consumed, remain;
+	struct sg_mapping_iter *sg_miter = &host->sg_miter;
+	unsigned int flags = 0;
+
+	if (data->flags & MMC_DATA_WRITE)
+		flags |= SG_MITER_FROM_SG;
+	else
+		flags |= SG_MITER_TO_SG;
+	sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
+	while (data_left > 0) {
+		consumed = 0;
+		if (!sg_miter_next(sg_miter))
+			break;
+		buf = sg_miter->addr;
+		remain = sg_miter->length;
+		do {
+			if (data->flags & MMC_DATA_WRITE) {
+				if (spsdc_wait_txbuf_empty(host))
+					goto done;
+				writel(*buf, host->base + SPSD2_SD_PIO_TX_REG);
+			} else {
+				if (spsdc_wait_rxbuf_full(host))
+					goto done;
+				*buf = readl(host->base + SPSD2_SD_PIO_RX_REG);
+			}
+			buf++;
+			consumed += 2;
+			remain -= 2;
+		} while (remain);
+		sg_miter->consumed = consumed;
+		data_left -= consumed;
+	}
+done:
+	sg_miter_stop(sg_miter);
+}
+
+static void spsdc_controller_init(struct spsdc_host *host)
+{
+	u32 value;
+	int ret = reset_control_assert(host->rstc);
+
+	if (!ret) {
+		usleep_range(1000, 1250);
+		ret = reset_control_deassert(host->rstc);
+	}
+	if (ret)
+		spsdc_pr(WARNING, "Failed to reset SD controller!\n");
+	value = readl(host->base + SPSD2_MEDIA_TYPE_REG);
+	value = bitfield_replace(value, 0, 3, SPSDC_MEDIA_SD);
+	writel(value, host->base + SPSD2_MEDIA_TYPE_REG);
+}
+
+static void spsdc_set_power_mode(struct spsdc_host *host, struct mmc_ios *ios)
+{
+	if (host->power_state == ios->power_mode)
+		return;
+
+	switch (ios->power_mode) {
+		/* power off->up->on */
+	case MMC_POWER_ON:
+		spsdc_pr(DEBUG, "set MMC_POWER_ON\n");
+		spsdc_controller_init(host);
+		pm_runtime_get_sync(host->mmc->parent);
+		break;
+	case MMC_POWER_UP:
+		spsdc_pr(DEBUG, "set MMC_POWER_UP\n");
+		break;
+	case MMC_POWER_OFF:
+		spsdc_pr(DEBUG, "set MMC_POWER_OFF\n");
+		pm_runtime_put(host->mmc->parent);
+		break;
+	}
+	host->power_state = ios->power_mode;
+}
+
+/**
+ * 1. unmap scatterlist if needed;
+ * 2. get response & check error conditions;
+ * 3. unlock host->mrq_lock
+ * 4. notify mmc layer the request is done
+ */
+static void spsdc_finish_request(struct spsdc_host *host, struct mmc_request *mrq)
+{
+	struct mmc_command *cmd;
+	struct mmc_data *data;
+
+	if (!mrq)
+		return;
+
+	cmd = mrq->cmd;
+	data = mrq->data;
+
+	if (data && SPSDC_DMA_MODE == host->dmapio_mode) {
+		int dma_direction = data->flags & MMC_DATA_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
+
+		dma_unmap_sg(host->mmc->parent, data->sg, data->sg_len, dma_direction);
+		host->dma_use_int = 0;
+	}
+	spsdc_get_rsp(host, cmd);
+	spsdc_check_error(host, mrq);
+	if (mrq->stop) {
+		if (__send_stop_cmd(host, mrq->stop))
+			spsdc_sw_reset(host);
+	}
+	host->mrq = NULL;
+
+#ifdef SPSDC_WIDTH_SWITCH
+	if (host->restore_4bit_sdio_bus) {
+		__switch_sdio_bus_width(host, MMC_BUS_WIDTH_4);
+		host->restore_4bit_sdio_bus = 0;
+	}
+#endif
+
+	mutex_unlock(&host->mrq_lock);
+	spsdc_pr(VERBOSE, "request done > error:%d, cmd:%d, resp:0x%08x\n",
+				cmd->error, cmd->opcode, cmd->resp[0]);
+	mmc_request_done(host->mmc, mrq);
+}
+
+/* Interrupt Service Routine */
+irqreturn_t spsdc_irq(int irq, void *dev_id)
+{
+	struct spsdc_host *host = dev_id;
+	u32 value = readl(host->base + SPSD2_SD_INT_REG);
+
+	spin_lock(&host->lock);
+	if ((value & SPSDC_SDINT_SDCMP) &&
+		(value & SPSDC_SDINT_SDCMPEN)) {
+		value = bitfield_replace(value, 0, 1, 0); /* disable sdcmp */
+		value = bitfield_replace(value, 2, 1, 1); /* sd_cmp_clr */
+		writel(value, host->base + SPSD2_SD_INT_REG);
+		/* we may need send stop command to stop data transaction,
+		 * which is time consuming, so make use of tasklet to handle this.
+		 */
+		if (host->mrq && host->mrq->stop)
+			tasklet_schedule(&host->tsklet_finish_req);
+		else
+			spsdc_finish_request(host, host->mrq);
+
+	}
+	if ((value & SPSDC_SDINT_SDIO) &&
+		(value & SPSDC_SDINT_SDIOEN)) {
+		mmc_signal_sdio_irq(host->mmc);
+	}
+	spin_unlock(&host->lock);
+	return IRQ_HANDLED;
+}
+
+static void spsdc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct spsdc_host *host = mmc_priv(mmc);
+	struct mmc_data *data;
+	struct mmc_command *cmd;
+
+#ifdef SPSDC_WIDTH_SWITCH
+	int bus_width = mmc->ios.bus_width;
+#endif
+
+	mutex_lock_interruptible(&host->mrq_lock);
+	host->mrq = mrq;
+	data = mrq->data;
+	cmd = mrq->cmd;
+	spsdc_pr(VERBOSE, "%s > cmd:%d, arg:0x%08x, data len:%d\n", __func__,
+		 cmd->opcode, cmd->arg, data ? (data->blocks*data->blksz) : 0);
+
+#ifdef SPSDC_WIDTH_SWITCH
+	if (cmd->opcode == SD_IO_RW_EXTENDED && bus_width ==
+			MMC_BUS_WIDTH_4 && data->blocks*data->blksz <= 4) {
+		if (__switch_sdio_bus_width(host, MMC_BUS_WIDTH_1)) {
+			cmd->error = -1;
+			host->mrq = NULL;
+			mutex_unlock(&host->mrq_lock);
+			mmc_request_done(host->mmc, mrq);
+			return;
+		}
+		host->restore_4bit_sdio_bus = 1;
+	}
+#endif
+
+	spsdc_prepare_cmd(host, cmd);
+	/* we need manually read response R2. */
+	if (unlikely(cmd->flags & MMC_RSP_136)) {
+		spsdc_trigger_transaction(host);
+		spsdc_get_rsp(host, cmd);
+		spsdc_wait_finish(host);
+		spsdc_check_error(host, mrq);
+		host->mrq = NULL;
+		spsdc_pr(VERBOSE, "request done > error:%d, cmd:%d, resp:%08x %08x %08x %08x\n",
+			 cmd->error, cmd->opcode, cmd->resp[0],
+			 cmd->resp[1], cmd->resp[2], cmd->resp[3]);
+		mutex_unlock(&host->mrq_lock);
+		mmc_request_done(host->mmc, mrq);
+	} else {
+		if (data)
+			spsdc_prepare_data(host, data);
+
+		if ((host->dmapio_mode && data) ==  SPSDC_PIO_MODE) {
+			u32 value;
+			/* pio data transfer do not use interrupt */
+			value = readl(host->base + SPSD2_SD_INT_REG);
+			value = bitfield_replace(value, 0, 1, 0); /* sdcmpen */
+			writel(value, host->base + SPSD2_SD_INT_REG);
+			spsdc_trigger_transaction(host);
+			spsdc_xfer_data_pio(host, data);
+			spsdc_wait_finish(host);
+			spsdc_finish_request(host, mrq);
+		} else {
+			if (!(host->use_int || host->dma_use_int)) {
+				spsdc_trigger_transaction(host);
+				spsdc_wait_finish(host);
+				spsdc_finish_request(host, mrq);
+			} else {
+				spsdc_trigger_transaction(host);
+			}
+		}
+	}
+}
+
+static void spsdc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct spsdc_host *host = (struct spsdc_host *)mmc_priv(mmc);
+
+	mutex_lock(&host->mrq_lock);
+	spsdc_set_power_mode(host, ios);
+	spsdc_set_bus_clk(host, ios->clock);
+	spsdc_set_bus_timing(host, ios->timing);
+	spsdc_set_bus_width(host, ios->bus_width);
+	/* ensure mode is correct, because we might have hw reset the controller */
+	spsdc_select_mode(host, host->mode);
+	mutex_unlock(&host->mrq_lock);
+
+}
+
+/**
+ * Return values for the get_cd callback should be:
+ *   0 for a absent card
+ *   1 for a present card
+ *   -ENOSYS when not supported (equal to NULL callback)
+ *   or a negative errno value when something bad happened
+ */
+int spsdc_get_cd(struct mmc_host *mmc)
+{
+	int ret = 0;
+
+	if (mmc_can_gpio_cd(mmc))
+		ret = mmc_gpio_get_cd(mmc);
+	else
+		spsdc_pr(WARNING, "no gpio assigned for card detection\n");
+
+	if (ret < 0) {
+		spsdc_pr(ERROR, "Failed to get card presence status\n");
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static void spsdc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+	struct spsdc_host *host = mmc_priv(mmc);
+	u32 value = readl(host->base + SPSD2_SD_INT_REG);
+
+	value = bitfield_replace(value, 6, 1, 1); /* sdio_int_clr */
+	if (enable)
+		value = bitfield_replace(value, 4, 1, 1);
+	else
+		value = bitfield_replace(value, 4, 1, 0);
+	writel(value, host->base + SPSD2_SD_INT_REG);
+}
+
+static const struct mmc_host_ops spsdc_ops = {
+	.request = spsdc_request,
+	.set_ios = spsdc_set_ios,
+	.get_cd = spsdc_get_cd,
+	.enable_sdio_irq = spsdc_enable_sdio_irq,
+};
+
+static void tsklet_func_finish_req(unsigned long data)
+{
+	struct spsdc_host *host = (struct spsdc_host *)data;
+
+	spin_lock(&host->lock);
+	spsdc_finish_request(host, host->mrq);
+	spin_unlock(&host->lock);
+}
+
+static int spsdc_drv_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct mmc_host *mmc;
+	struct resource *resource;
+	struct spsdc_host *host;
+	unsigned int mode;
+
+	mmc = mmc_alloc_host(sizeof(*host), &pdev->dev);
+	if (!mmc) {
+		ret = -ENOMEM;
+		goto probe_free_host;
+	}
+
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->power_state = MMC_POWER_OFF;
+	host->dma_int_threshold = 1024;
+	host->dmapio_mode = SPSDC_DMA_MODE;
+
+	host->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(host->clk)) {
+		spsdc_pr(ERROR, "Can not find clock source\n");
+		ret = PTR_ERR(host->clk);
+		goto probe_free_host;
+	}
+
+	host->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+	if (IS_ERR(host->rstc)) {
+		spsdc_pr(ERROR, "Can not find reset controller\n");
+		ret = PTR_ERR(host->rstc);
+		goto probe_free_host;
+	}
+
+	resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (IS_ERR(resource)) {
+		spsdc_pr(ERROR, "get sd register resource fail\n");
+		ret = PTR_ERR(resource);
+		goto probe_free_host;
+	}
+
+	host->base = devm_ioremap_resource(&pdev->dev, resource);
+	if (IS_ERR((void *)host->base)) {
+		spsdc_pr(ERROR, "devm_ioremap_resource fail\n");
+		ret = PTR_ERR((void *)host->base);
+		goto probe_free_host;
+	}
+
+	host->irq = platform_get_irq(pdev, 0);
+	if (host->irq <= 0) {
+		spsdc_pr(ERROR, "get sd irq resource fail\n");
+		ret = -EINVAL;
+		goto probe_free_host;
+	}
+	if (devm_request_irq(&pdev->dev, host->irq, spsdc_irq,
+		IRQF_SHARED, dev_name(&pdev->dev), host)) {
+		spsdc_pr(ERROR, "Failed to request sd card interrupt.\n");
+		ret = -ENOENT;
+		goto probe_free_host;
+	}
+	spsdc_pr(INFO, "spsdc driver probe, reg base:0x%p, irq:%d\n", host->base, host->irq);
+
+
+	ret = mmc_of_parse(mmc);
+	if (ret)
+		goto probe_free_host;
+
+	ret = clk_prepare(host->clk);
+	if (ret)
+		goto probe_free_host;
+
+	ret = clk_enable(host->clk);
+	if (ret)
+		goto probe_clk_unprepare;
+
+	spin_lock_init(&host->lock);
+	mutex_init(&host->mrq_lock);
+	tasklet_init(&host->tsklet_finish_req, tsklet_func_finish_req, (unsigned long)host);
+	mmc->ops = &spsdc_ops;
+	mmc->f_min = SPSDC_MIN_CLK;
+	if (mmc->f_max > SPSDC_MAX_CLK) {
+		spsdc_pr(DEBUG, "max-frequency is too high, set it to %d\n", SPSDC_MAX_CLK);
+		mmc->f_max = SPSDC_MAX_CLK;
+	}
+	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+	mmc->max_seg_size = SPSDC_MAX_BLK_COUNT * 512;
+	/* Host controller supports up to "SPSDC_MAX_DMA_MEMORY_SECTORS",
+	 * a.k.a. max scattered memory segments per request
+	 */
+	/* Limited by the max value of dma_size & data_length, set it to 512 bytes for now */
+	mmc->max_segs = SPSDC_MAX_DMA_MEMORY_SECTORS;
+	mmc->max_req_size = SPSDC_MAX_BLK_COUNT * 512;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count = SPSDC_MAX_BLK_COUNT; /* Limited by sd_page_num */
+
+	dev_set_drvdata(&pdev->dev, host);
+	spsdc_controller_init(host);
+	mode = (int)of_device_get_match_data(&pdev->dev);
+	spsdc_select_mode(host, mode);
+	mmc_add_host(mmc);
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
+	return 0;
+
+probe_clk_unprepare:
+	spsdc_pr(ERROR, "unable to enable controller clock\n");
+	clk_unprepare(host->clk);
+probe_free_host:
+	if (mmc)
+		mmc_free_host(mmc);
+
+	return ret;
+}
+
+static int spsdc_drv_remove(struct platform_device *dev)
+{
+	struct spsdc_host *host = platform_get_drvdata(dev);
+
+	mmc_remove_host(host->mmc);
+	clk_disable(host->clk);
+	clk_unprepare(host->clk);
+	pm_runtime_disable(&dev->dev);
+	platform_set_drvdata(dev, NULL);
+	mmc_free_host(host->mmc);
+
+	return 0;
+}
+
+
+static int spsdc_drv_suspend(struct platform_device *dev, pm_message_t state)
+{
+	struct spsdc_host *host;
+
+	host = platform_get_drvdata(dev);
+	mutex_lock(&host->mrq_lock); /* Make sure that no one is holding the controller */
+	mutex_unlock(&host->mrq_lock);
+	clk_disable(host->clk);
+	return 0;
+}
+
+static int spsdc_drv_resume(struct platform_device *dev)
+{
+	struct spsdc_host *host;
+
+	host = platform_get_drvdata(dev);
+	return clk_enable(host->clk);
+}
+
+#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
+static int spsdc_pm_suspend(struct device *dev)
+{
+	pm_runtime_force_suspend(dev);
+	return 0;
+}
+
+static int spsdc_pm_resume(struct device *dev)
+{
+	pm_runtime_force_resume(dev);
+	return 0;
+}
+#endif /* ifdef CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME_SD
+static int spsdc_pm_runtime_suspend(struct device *dev)
+{
+	struct spsdc_host *host;
+
+	host = dev_get_drvdata(dev);
+	if (__clk_is_enabled(host->clk))
+		clk_disable(host->clk);
+	return 0;
+}
+
+static int spsdc_pm_runtime_resume(struct device *dev)
+{
+	struct spsdc_host *host;
+	int ret = 0;
+
+	host = dev_get_drvdata(dev);
+	if (!host->mmc)
+		return -EINVAL;
+	if (mmc_can_gpio_cd(host->mmc)) {
+		ret = mmc_gpio_get_cd(host->mmc);
+		if (!ret) {
+			spsdc_pr(DEBUG, "No card insert\n");
+			return 0;
+		}
+	}
+	return clk_enable(host->clk);
+}
+#endif /* ifdef CONFIG_PM_RUNTIME_SD */
+
+static const struct dev_pm_ops spsdc_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(spsdc_pm_suspend, spsdc_pm_resume)
+#ifdef CONFIG_PM_RUNTIME_SD
+	SET_RUNTIME_PM_OPS(spsdc_pm_runtime_suspend, spsdc_pm_runtime_resume, NULL)
+#endif
+};
+#endif /* ifdef CONFIG_PM */
+
+static const struct of_device_id spsdc_of_table[] = {
+	{
+		.compatible = "sunplus,sp7021-card1",
+		.data = (void *)SPSDC_MODE_SD,
+	},
+	{
+		.compatible = "sunplus,sp7021-sdio",
+		.data = (void *)SPSDC_MODE_SDIO,
+	},
+	{/* sentinel */}
+
+};
+MODULE_DEVICE_TABLE(of, spsdc_of_table);
+
+static struct platform_driver spsdc_driver = {
+	.probe = spsdc_drv_probe,
+	.remove = spsdc_drv_remove,
+	.suspend = spsdc_drv_suspend,
+	.resume = spsdc_drv_resume,
+	.driver = {
+		.name = "spsdc",
+		.owner = THIS_MODULE,
+#ifdef CONFIG_PM
+		.pm = &spsdc_pm_ops,
+#endif
+		.of_match_table = spsdc_of_table,
+	},
+};
+module_platform_driver(spsdc_driver);
+
+MODULE_AUTHOR("lh.kuo <lh.kuo@sunplus.com>");
+MODULE_DESCRIPTION("Sunplus SD/SDIO host controller v2.0 driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/mmc/host/sunplus_sd2.h b/drivers/mmc/host/sunplus_sd2.h
new file mode 100644
index 0000000..65760ec
--- /dev/null
+++ b/drivers/mmc/host/sunplus_sd2.h
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ * (C) Copyright 2019 Sunplus Technology. <http://www.sunplus.com/>
+ *
+ * Sunplus SD host controller v2.0
+ */
+#ifndef __SPSDC_H__
+#define __SPSDC_H__
+
+#include <linux/types.h>
+#include <linux/spinlock_types.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/clk.h>
+#include <linux/of.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+
+
+#define SPSDC_WIDTH_SWITCH
+
+#define SPSDC_MIN_CLK	400000
+#define SPSDC_MAX_CLK	52000000
+#define SPSDC_50M_CLK   50000000
+
+#define SPSDC_MAX_BLK_COUNT 65536
+
+#define __rsvd_regs(l) __append_suffix(l, __COUNTER__)
+#define __append_suffix(l, s) _append_suffix(l, s)
+#define _append_suffix(l, s) (reserved##s[l])
+
+#define SPSD2_MEDIA_TYPE_REG 0x0000
+#define SPSDC_MEDIA_NONE 0
+#define SPSDC_MEDIA_SD 6
+#define SPSDC_MEDIA_MS 7
+
+
+#define SPSD2_SDRAM_SECTOR_SIZE_REG 0x0010
+
+#define SPSDC_MAX_DMA_MEMORY_SECTORS 8 /* support up to 8 fragmented memory blocks */
+
+#define SPSD2_SDRAM_SECTOR_ADDR_REG 0x001C
+
+#define SPSD2_SD_INT_REG            0x00B0
+#define SPSDC_SDINT_SDCMPEN	BIT(0)
+#define SPSDC_SDINT_SDCMP	BIT(1)
+#define SPSDC_SDINT_SDIOEN	BIT(4)
+#define SPSDC_SDINT_SDIO	BIT(5)
+
+#define SPSD2_SD_PAGE_NUM_REG       0x00B4
+#define SPSD2_SD_CONF0_REG          0x00B8
+#define SPSD2_SDIO_CTRL_REG         0x00Bc
+#define SPSD2_SD_RST_REG            0x00C0
+
+#define SPSD2_SD_CONF_REG           0x00C4
+#define SPSDC_MODE_SDIO	2
+#define SPSDC_MODE_EMMC	1
+#define SPSDC_MODE_SD	0
+
+#define SPSD2_SD_CTRL_REG           0x00C8
+#define SPSDC_SDSTATUS_DUMMY_READY			BIT(0)
+#define SPSDC_SDSTATUS_RSP_BUF_FULL			BIT(1)
+#define SPSDC_SDSTATUS_TX_DATA_BUF_EMPTY		BIT(2)
+#define SPSDC_SDSTATUS_RX_DATA_BUF_FULL			BIT(3)
+#define SPSDC_SDSTATUS_CMD_PIN_STATUS			BIT(4)
+#define SPSDC_SDSTATUS_DAT0_PIN_STATUS			BIT(5)
+#define SPSDC_SDSTATUS_RSP_TIMEOUT			BIT(6)
+#define SPSDC_SDSTATUS_CARD_CRC_CHECK_TIMEOUT		BIT(7)
+#define SPSDC_SDSTATUS_STB_TIMEOUT			BIT(8)
+#define SPSDC_SDSTATUS_RSP_CRC7_ERROR			BIT(9)
+#define SPSDC_SDSTATUS_CRC_TOKEN_CHECK_ERROR		BIT(10)
+#define SPSDC_SDSTATUS_RDATA_CRC16_ERROR		BIT(11)
+#define SPSDC_SDSTATUS_SUSPEND_STATE_READY		BIT(12)
+#define SPSDC_SDSTATUS_BUSY_CYCLE			BIT(13)
+
+#define SPSD2_SD_STATUS_REG         0x00CC
+
+#define SPSD2_SD_STATE_REG          0x00D0
+#define SPSDC_SDSTATE_IDLE	(0x0)
+#define SPSDC_SDSTATE_TXDUMMY	(0x1)
+#define SPSDC_SDSTATE_TXCMD	(0x2)
+#define SPSDC_SDSTATE_RXRSP	(0x3)
+#define SPSDC_SDSTATE_TXDATA	(0x4)
+#define SPSDC_SDSTATE_RXCRC	(0x5)
+#define SPSDC_SDSTATE_RXDATA	(0x5)
+#define SPSDC_SDSTATE_MASK	(0x7)
+#define SPSDC_SDSTATE_BADCRC	(0x5)
+#define SPSDC_SDSTATE_ERROR	BIT(13)
+#define SPSDC_SDSTATE_FINISH	BIT(14)
+
+#define SPSD2_BLOCKSIZE_REG         0x00D4
+#define SPSD2_SD_TIMING_CONF0_REG   0x00Dc
+#define SPSD2_SD_TIMING_CONF1_REG   0x00E0
+#define SPSD2_SD_PIO_TX_REG         0x00E4
+#define SPSD2_SD_PIO_RX_REG         0x00E8
+#define SPSD2_SD_CMD_BUF0_REG       0x00Ec
+#define SPSD2_SD_CMD_BUF1_REG       0x00F0
+#define SPSD2_SD_CMD_BUF2_REG       0x00F4
+#define SPSD2_SD_CMD_BUF3_REG       0x00F8
+#define SPSD2_SD_CMD_BUF4_REG       0x00Fc
+
+#define SPSD2_SD_RSP_BUF0_3_REG     0x0100
+#define SPSD2_SD_RSP_BUF4_5_REG     0x0104
+
+#define SPSD2_DMA_SRCDST_REG        0x0204
+#define SPSD2_DMA_SIZE_REG          0x0208
+#define SPSD2_DMA_STOP_RST_REG      0x020c
+#define SPSD2_DMA_CTRL_REG          0x0210
+#define SPSD2_DMA_BASE_ADDR0_REG    0x0214
+#define SPSD2_DMA_BASE_ADDR16_REG   0x0218
+
+struct spsdc_tuning_info {
+	int need_tuning;
+#define SPSDC_MAX_RETRIES (8 * 8)
+	int retried; /* how many times has been retried */
+	u32 wr_dly:3;
+	u32 rd_dly:3;
+	u32 clk_dly:3;
+};
+
+struct spsdc_host {
+	void __iomem *base;
+	struct clk *clk;
+	struct reset_control *rstc;
+	int mode; /* SD/SDIO/eMMC */
+	spinlock_t lock; /* controller lock */
+	struct mutex mrq_lock;
+	/* tasklet used to handle error then finish the request */
+	struct tasklet_struct tsklet_finish_req;
+	struct mmc_host *mmc;
+	struct mmc_request *mrq; /* current mrq */
+
+	int irq;
+	int use_int; /* should raise irq when done */
+	int power_state; /* current power state: off/up/on */
+
+
+#ifdef SPSDC_WIDTH_SWITCH
+	int restore_4bit_sdio_bus;
+#endif
+
+#define SPSDC_DMA_MODE 0
+#define SPSDC_PIO_MODE 1
+	int dmapio_mode;
+	/* for purpose of reducing context switch, only when transfer data that
+	 *  length is greater than `dma_int_threshold' should use interrupt
+	 */
+	int dma_int_threshold;
+	int dma_use_int; /* should raise irq when dma done */
+	struct sg_mapping_iter sg_miter; /* for pio mode to access sglist */
+	struct spsdc_tuning_info tuning_info;
+};
+
+#endif /* #ifndef __SPSDC_H__ */
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 2/2] devicetree bindings mmc Add bindings doc for Sunplus SP7021
  2021-11-09  7:58 ` [PATCH v2 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC LH.Kuo
  2021-11-09  7:58   ` [PATCH v2 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021 LH.Kuo
@ 2021-11-09  7:58   ` LH.Kuo
  2021-11-29  1:37     ` Rob Herring
  1 sibling, 1 reply; 13+ messages in thread
From: LH.Kuo @ 2021-11-09  7:58 UTC (permalink / raw)
  To: p.zabel, daniel.thompson, lee.jones, u.kleine-koenig,
	ulf.hansson, robh+dt, linux-kernel, linux-mmc, devicetree
  Cc: qinjian, wells.lu, LH.Kuo

Add devicetree bindings mmc Add bindings doc for Sunplus SP7021

Signed-off-by: LH.Kuo <lh.kuo@sunplus.com>
---
Changes in v2:
 - Addressed all comments from Mr. Philipp Zabel
 - Modified the structure and register access method.
 - Modifiedthe path about MAINTAINERS. ( wrong messages PATH in v1).

 .../devicetree/bindings/mmc/sunplus-sd2.yaml       | 82 ++++++++++++++++++++++
 MAINTAINERS                                        |  1 +
 2 files changed, 83 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml

diff --git a/Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml b/Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
new file mode 100644
index 0000000..95dc0bb
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
@@ -0,0 +1,82 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+# Copyright (C) Sunplus Co., Ltd. 2021
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mmc/sunplus-sd2.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sunplus SD/SDIO controller
+
+maintainers:
+  - lh.kuo <lh.kuo@sunplus.com>
+
+properties:
+  compatible:
+    enum:
+      - sunplus,sp7021-card1
+      - sunplus,sp7021-sdio
+
+  reg:
+    items:
+      - description: Base address and length of the SD/SDIO registers
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  resets:
+    maxItems: 1
+
+  pinctrl-names:
+    description:
+      A pinctrl state named "default" must be defined.
+    const: default
+
+  pinctrl-0:
+    description:
+      A phandle to the default pinctrl state.
+
+  max-frequency: true
+
+allOf:
+  - $ref: "mmc-controller.yaml"
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - resets
+  - pinctrl-names
+  - pinctrl-0
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/sp-sp7021.h>
+    #include <dt-bindings/reset/sp-sp7021.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    mmc1: mmc@9C003e80 {
+       compatible = "sunplus,sp7021-card1";
+       reg = <0x9c003e80 0x280>;
+       interrupts = <21 IRQ_TYPE_LEVEL_HIGH>;
+       clocks = <&clkc CARD_CTL1>;
+       resets = <&rstc RST_CARD_CTL1>;
+       pinctrl-names = "default";
+       pinctrl-0 = <&mmc1_mux &mmc1_mux_cd>;
+       max-frequency = <52000000>;
+    };
+    sdio: mmc@9C008400 {
+       compatible = "sunplus,sp7021-sdio";
+       reg = <0x9c008400 0x280>;
+       interrupts = <21 IRQ_TYPE_LEVEL_HIGH>;
+       clocks = <&clkc CARD_CTL1>;
+       resets = <&rstc RST_CARD_CTL1>;
+       pinctrl-names = "default";
+       pinctrl-0 = <&pins_sdio>;
+       max-frequency = <52000000>;
+    };   
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 2746084..e653a1d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18193,6 +18193,7 @@ SUNPLUS SD/SDIO HOST CONTROLLER INTERFACE DRIVER
 M:	LH Kuo <lh.kuo@sunplus.com>
 L:	linux-mmc@vger.kernel.org
 S:	Maintained
+F:	Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
 F:	drivers/mmc/host/sunplus_sd2.*
 
 SUPERH
-- 
2.7.4


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021
  2021-11-09  7:58   ` [PATCH v2 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021 LH.Kuo
@ 2021-11-10  2:33     ` Randy Dunlap
       [not found]       ` <d25f2bff024b40fd9691960221c0bebe@sphcmbx02.sunplus.com.tw>
  0 siblings, 1 reply; 13+ messages in thread
From: Randy Dunlap @ 2021-11-10  2:33 UTC (permalink / raw)
  To: LH.Kuo, p.zabel, daniel.thompson, lee.jones, u.kleine-koenig,
	ulf.hansson, robh+dt, linux-kernel, linux-mmc, devicetree
  Cc: qinjian, wells.lu, LH.Kuo

Hi--

On 11/8/21 11:58 PM, LH.Kuo wrote:
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 5af8494..2aba9eb 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -1091,5 +1091,15 @@ config MMC_OWL
>   	  This selects support for the SD/MMC Host Controller on
>   	  Actions Semi Owl SoCs.
>   
> +config MMC_SP_SDV2
> +	tristate "Sunplus SP7021 SD/SDIO Controller"
> +	depends on SOC_SP7021
> +	help
> +		If you say yes here, you will get support for SD/SDIO host interface
> +		on sunplus Socs.

		   Sunplus SoCs.

> +		If you have a controller with this interface, say Y or M here.
> +		If unsure, say N.

All 4 lines of help text should be indented only with one tab + 2 spaces,
not 2 tabs, per coding-style.rst.


> +                Sunplus  SD/SDIO Host Controller support"

I am thinking that this last line should not be here at all... ???


thanks.
-- 
~Randy

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021
       [not found]       ` <d25f2bff024b40fd9691960221c0bebe@sphcmbx02.sunplus.com.tw>
@ 2021-11-10  5:44         ` Randy Dunlap
  0 siblings, 0 replies; 13+ messages in thread
From: Randy Dunlap @ 2021-11-10  5:44 UTC (permalink / raw)
  To: Lh Kuo 郭力豪,
	LH.Kuo, p.zabel, daniel.thompson, lee.jones, u.kleine-koenig,
	ulf.hansson, robh+dt, linux-kernel, linux-mmc, devicetree
  Cc: qinjian, Wells Lu 呂芳騰

Hi--

On 11/9/21 9:40 PM, Lh Kuo 郭力豪 wrote:
> Hi
> 
>> -----Original Message-----
>> From: Randy Dunlap <rdunlap@infradead.org>
>> Sent: Wednesday, November 10, 2021 10:33 AM
>> To: LH.Kuo <lhjeff911@gmail.com>; p.zabel@pengutronix.de;
>> daniel.thompson@linaro.org; lee.jones@linaro.org;
>> u.kleine-koenig@pengutronix.de; ulf.hansson@linaro.org; robh+dt@kernel.org;
>> linux-kernel@vger.kernel.org; linux-mmc@vger.kernel.org;
>> devicetree@vger.kernel.org
>> Cc: qinjian@cqplus1.com; Wells Lu 呂芳騰 <wells.lu@sunplus.com>; Lh Kuo
>> 郭力豪 <lh.Kuo@sunplus.com>
>> Subject: Re: [PATCH v2 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021
>>
>> Hi--
>>
>> On 11/8/21 11:58 PM, LH.Kuo wrote:
>>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
>>> index 5af8494..2aba9eb 100644
>>> --- a/drivers/mmc/host/Kconfig
>>> +++ b/drivers/mmc/host/Kconfig
>>> @@ -1091,5 +1091,15 @@ config MMC_OWL
>>>    	  This selects support for the SD/MMC Host Controller on
>>>    	  Actions Semi Owl SoCs.
>>>
>>> +config MMC_SP_SDV2
>>> +	tristate "Sunplus SP7021 SD/SDIO Controller"
>>> +	depends on SOC_SP7021
>>> +	help
>>> +		If you say yes here, you will get support for SD/SDIO host interface
>>> +		on sunplus Socs.
>>
>> 		   Sunplus SoCs.
>>
>>> +		If you have a controller with this interface, say Y or M here.
>>> +		If unsure, say N.
>>
>> All 4 lines of help text should be indented only with one tab + 2 spaces,
>> not 2 tabs, per coding-style.rst.
>>
>>
>>> +                Sunplus  SD/SDIO Host Controller support"
>>
>> I am thinking that this last line should not be here at all... ???
>>
>>
>> thanks.
>> --
>> ~Randy
> 
> 
> I will make change as below  is it OK ?
> 
> config MMC_SP_SDV2
> 	tristate "Sunplus SP7021 SD/SDIO Controller"
> 	depends on SOC_SP7021
> 	help
> 	  This selects the Sunplus Host Controller Interface
> 	  support present in Sunplus SP7021 SOCs. The controller supports
> 	  SD/SDIO devices.
> 		
> 		If you have a controller with this interface, say Y or M here.
> 		
> 		If unsure, say N.

Too much indentation on the last 2 (non-blank) help text lines.
Should just be one tab + 2 spaces, not 2 tabs.


-- 
~Randy

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/2] devicetree bindings mmc Add bindings doc for Sunplus SP7021
  2021-11-09  7:58   ` [PATCH v2 2/2] devicetree bindings mmc Add bindings doc " LH.Kuo
@ 2021-11-29  1:37     ` Rob Herring
       [not found]       ` <f5607fa7ad9c49a7bfcce02eac834838@sphcmbx02.sunplus.com.tw>
  0 siblings, 1 reply; 13+ messages in thread
From: Rob Herring @ 2021-11-29  1:37 UTC (permalink / raw)
  To: LH.Kuo
  Cc: p.zabel, daniel.thompson, lee.jones, u.kleine-koenig,
	ulf.hansson, linux-kernel, linux-mmc, devicetree, qinjian,
	wells.lu, LH.Kuo

On Tue, Nov 09, 2021 at 03:58:25PM +0800, LH.Kuo wrote:
> Add devicetree bindings mmc Add bindings doc for Sunplus SP7021
> 
> Signed-off-by: LH.Kuo <lh.kuo@sunplus.com>
> ---
> Changes in v2:
>  - Addressed all comments from Mr. Philipp Zabel
>  - Modified the structure and register access method.
>  - Modifiedthe path about MAINTAINERS. ( wrong messages PATH in v1).
> 
>  .../devicetree/bindings/mmc/sunplus-sd2.yaml       | 82 ++++++++++++++++++++++
>  MAINTAINERS                                        |  1 +
>  2 files changed, 83 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
> 
> diff --git a/Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml b/Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
> new file mode 100644
> index 0000000..95dc0bb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
> @@ -0,0 +1,82 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +# Copyright (C) Sunplus Co., Ltd. 2021
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/mmc/sunplus-sd2.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Sunplus SD/SDIO controller
> +
> +maintainers:
> +  - lh.kuo <lh.kuo@sunplus.com>
> +
> +properties:
> +  compatible:
> +    enum:
> +      - sunplus,sp7021-card1
> +      - sunplus,sp7021-sdio

What's the difference between these 2 blocks?

> +
> +  reg:
> +    items:
> +      - description: Base address and length of the SD/SDIO registers

Just 'maxItems: 1' is sufficient.

> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +
> +  resets:
> +    maxItems: 1
> +
> +  pinctrl-names:
> +    description:
> +      A pinctrl state named "default" must be defined.
> +    const: default
> +
> +  pinctrl-0:
> +    description:
> +      A phandle to the default pinctrl state.
> +
> +  max-frequency: true
> +
> +allOf:
> +  - $ref: "mmc-controller.yaml"
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - clocks
> +  - resets
> +  - pinctrl-names
> +  - pinctrl-0
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/sp-sp7021.h>
> +    #include <dt-bindings/reset/sp-sp7021.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    mmc1: mmc@9C003e80 {

Use lower case hex.

> +       compatible = "sunplus,sp7021-card1";
> +       reg = <0x9c003e80 0x280>;
> +       interrupts = <21 IRQ_TYPE_LEVEL_HIGH>;
> +       clocks = <&clkc CARD_CTL1>;
> +       resets = <&rstc RST_CARD_CTL1>;
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&mmc1_mux &mmc1_mux_cd>;
> +       max-frequency = <52000000>;
> +    };
> +    sdio: mmc@9C008400 {

Use lower case hex.

> +       compatible = "sunplus,sp7021-sdio";
> +       reg = <0x9c008400 0x280>;
> +       interrupts = <21 IRQ_TYPE_LEVEL_HIGH>;
> +       clocks = <&clkc CARD_CTL1>;
> +       resets = <&rstc RST_CARD_CTL1>;
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&pins_sdio>;
> +       max-frequency = <52000000>;
> +    };   
> +...
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2746084..e653a1d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -18193,6 +18193,7 @@ SUNPLUS SD/SDIO HOST CONTROLLER INTERFACE DRIVER
>  M:	LH Kuo <lh.kuo@sunplus.com>
>  L:	linux-mmc@vger.kernel.org
>  S:	Maintained
> +F:	Documentation/devicetree/bindings/mmc/sunplus-sd2.yaml
>  F:	drivers/mmc/host/sunplus_sd2.*
>  
>  SUPERH
> -- 
> 2.7.4
> 
> 

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/2] devicetree bindings mmc Add bindings doc for Sunplus SP7021
       [not found]       ` <f5607fa7ad9c49a7bfcce02eac834838@sphcmbx02.sunplus.com.tw>
@ 2021-11-29 16:29         ` Rob Herring
       [not found]           ` <1eb97e1aca9c4c8d8f1e17c51f2792ac@sphcmbx02.sunplus.com.tw>
  0 siblings, 1 reply; 13+ messages in thread
From: Rob Herring @ 2021-11-29 16:29 UTC (permalink / raw)
  To: Lh Kuo 郭力豪
  Cc: LH.Kuo, p.zabel, daniel.thompson, lee.jones, u.kleine-koenig,
	ulf.hansson, linux-kernel, linux-mmc, devicetree, qinjian,
	Wells Lu 呂芳騰

On Sun, Nov 28, 2021 at 11:30 PM Lh Kuo 郭力豪 <lh.Kuo@sunplus.com> wrote:
>
> > > +
> > > +properties:
> > > +  compatible:
> > > +    enum:
> > > +      - sunplus,sp7021-card1
> > > +      - sunplus,sp7021-sdio
> >
> > What's the difference between these 2 blocks?
> >
>
> One for SD card One for SDIO

If the programming model is the same, then it should be the same
compatible string. We have various properties to handle differences
like bus width, card detect or not, etc.

> > > +  reg:
> > > +    items:
> > > +      - description: Base address and length of the SD/SDIO registers
> >
> > Just 'maxItems: 1' is sufficient.
> >
> > > +
> > > +  interrupts:
> > > +    maxItems: 1
> > > +
> > > +  clocks:
> > > +    maxItems: 1
> > > +
> > > +  resets:
> > > +    maxItems: 1
> > > +
> > > +  pinctrl-names:
> > > +    description:
> > > +      A pinctrl state named "default" must be defined.
> > > +    const: default
> > > +
> > > +  pinctrl-0:
> > > +    description:
> > > +      A phandle to the default pinctrl state.
> > > +
> > > +  max-frequency: true
> > > +
> > > +allOf:
> > > +  - $ref: "mmc-controller.yaml"
> > > +
> > > +required:
> > > +  - compatible
> > > +  - reg
> > > +  - interrupts
> > > +  - clocks
> > > +  - resets
> > > +  - pinctrl-names
> > > +  - pinctrl-0
> > > +
> > > +additionalProperties: false
> > > +
> > > +examples:
> > > +  - |
> > > +    #include <dt-bindings/clock/sp-sp7021.h>
> > > +    #include <dt-bindings/reset/sp-sp7021.h>
> > > +    #include <dt-bindings/interrupt-controller/irq.h>
> > > +    mmc1: mmc@9C003e80 {
> >
> > Use lower case hex.
>
> Do you mean as follows? ?
>
> mmc1: mmc@3e80 {

No, 'mmc@9c003e80 {'

You also don't need 'mmc1'.

> > > +       compatible = "sunplus,sp7021-card1";
> > > +       reg = <0x9c003e80 0x280>;
> > > +       interrupts = <21 IRQ_TYPE_LEVEL_HIGH>;
> > > +       clocks = <&clkc CARD_CTL1>;
> > > +       resets = <&rstc RST_CARD_CTL1>;
> > > +       pinctrl-names = "default";
> > > +       pinctrl-0 = <&mmc1_mux &mmc1_mux_cd>;
> > > +       max-frequency = <52000000>;
> > > +    };
> > > +    sdio: mmc@9C008400 {
> >
>
> Do you mean as follows? ?
>
> mmc1: mmc@8400 {
>
>
> > Use lower case hex.
> >
> > > +       compatible = "sunplus,sp7021-sdio";
>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/2] devicetree bindings mmc Add bindings doc for Sunplus SP7021
       [not found]           ` <1eb97e1aca9c4c8d8f1e17c51f2792ac@sphcmbx02.sunplus.com.tw>
@ 2022-01-04 20:31             ` Rob Herring
  0 siblings, 0 replies; 13+ messages in thread
From: Rob Herring @ 2022-01-04 20:31 UTC (permalink / raw)
  To: Lh Kuo 郭力豪
  Cc: LH.Kuo, p.zabel, daniel.thompson, lee.jones, u.kleine-koenig,
	ulf.hansson, linux-kernel, linux-mmc, devicetree, qinjian,
	Wells Lu 呂芳騰

On Tue, Nov 30, 2021 at 7:59 PM Lh Kuo 郭力豪 <lh.Kuo@sunplus.com> wrote:
>
> > > > > +properties:
> > > > > +  compatible:
> > > > > +    enum:
> > > > > +      - sunplus,sp7021-card1
> > > > > +      - sunplus,sp7021-sdio
> > > >
> > > > What's the difference between these 2 blocks?
> > > >
> > >
> > > One for SD card One for SDIO
> >
> > If the programming model is the same, then it should be the same compatible string. We have various
> > properties to handle differences like bus width, card detect or not, etc.
> >
>
> SDIO and SDCARD still need to set the date and CMD decoding differences.

I still don't understand. A host controller should be able to
initialize a card enough to tell what kind it is. And we have things
defined in DT like 'no-sd' and 'no-mmc'.

Looking at the driver, the difference appears to be just setting a
register to the mode (eMMC/SD/SDIO). That's not a difference in the
h/w block which is when different compatibles would be appropriate. A
property, if anything, is the right thing to do here.

Rob

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2022-01-04 20:31 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-10-29  5:57 [PATCH 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC LH.Kuo
2021-10-29  5:57 ` [PATCH 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021 LH.Kuo
2021-10-30  0:00   ` Randy Dunlap
2021-11-02 14:34   ` Philipp Zabel
2021-10-29  5:57 ` [PATCH 2/2] devicetree bindings mmc Add bindings doc " LH.Kuo
2021-11-09  7:58 ` [PATCH v2 0/2] Add SD/SDIO control driver for Sunplus SP7021 SoC LH.Kuo
2021-11-09  7:58   ` [PATCH v2 1/2] mmc: Add SD/SDIO driver for Sunplus SP7021 LH.Kuo
2021-11-10  2:33     ` Randy Dunlap
     [not found]       ` <d25f2bff024b40fd9691960221c0bebe@sphcmbx02.sunplus.com.tw>
2021-11-10  5:44         ` Randy Dunlap
2021-11-09  7:58   ` [PATCH v2 2/2] devicetree bindings mmc Add bindings doc " LH.Kuo
2021-11-29  1:37     ` Rob Herring
     [not found]       ` <f5607fa7ad9c49a7bfcce02eac834838@sphcmbx02.sunplus.com.tw>
2021-11-29 16:29         ` Rob Herring
     [not found]           ` <1eb97e1aca9c4c8d8f1e17c51f2792ac@sphcmbx02.sunplus.com.tw>
2022-01-04 20:31             ` Rob Herring

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.