Linux MultiMedia Card development
 help / color / mirror / Atom feed
* [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver
@ 2025-06-18  8:07 Binbin Zhou
  2025-06-18  8:07 ` [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
                   ` (3 more replies)
  0 siblings, 4 replies; 11+ messages in thread
From: Binbin Zhou @ 2025-06-18  8:07 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Ulf Hansson
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang,
	Binbin Zhou

Hi all:

This patchset introduce the MMC host controller on Loongson-2K series
CPUs.

They are similar, except for the interface characteristics and the use of
DMA engine, specifically, the Loongson-2K0500/Loongson-2K1000 use an
externally shared APBDMA engine, while the Loongson-2K2000 uses an
internally exclusive DMA.

Based on this, I'm splitting the driver into two patches.

List of the patchset:
Patch1: bindings for Loongson-2K0500/Loongson-2K1000;
Patch2: driver for MMC controller using externally shared APBDMA engine;
Patch3: bindings for Loongson-2K2000;
Patch4: driver for MMC controller using internally exclusive DMA.

Thanks.

-------
V3:
- Rebase on linux-mmc/next branch.
patch(1/4):
 - Rename dt-binding file as loongson,ls2k0500-mmc.yaml.
patch(2/4):
 - Fix lkp error;
    https://lore.kernel.org/all/202505081845.0NQYX2nS-lkp@intel.com/
    https://lore.kernel.org/all/202505130918.uanOGxju-lkp@intel.com/
 - Add regulators support for ios ops;
 - Add ack_sdio_irq() callback;
 - Add MMC_CAP2_SDIO_IRQ_NOTHREAD flag;
patch(3/4):
 - Add Ack-by tag.
patch(4/4):
 - Update commit for fix_data_timeout().

Link to V2:
https://lore.kernel.org/all/cover.1746581751.git.zhoubinbin@loongson.cn/

V2:
patch(1/4):
 - Add reg define for each reg entry.

patch(2/4):
 - Put all code in the c-file;
 - Use mmc_from_priv() instead of host->mmc;
 - Use sdio_signal_irq() instead of mmc_signal_sdio_irq();
 - Use devm_mmc_alloc_host() instead of mmc_alloc_host();
 - Use mmc_regulator_get_supply();

patch(4/4):
 - Add fix_cmd_interrupt function which is needed by Loongson-2K2000.

Link to V1:
https://lore.kernel.org/linux-mmc/cover.1744273956.git.zhoubinbin@loongson.cn/

Binbin Zhou (4):
  dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding
  mmc: loongson2: Add Loongson-2K SD/SDIO controller driver
  dt-bindings: mmc: loongson,ls2k0500-mmc: Add compatible for
    Loongson-2K2000
  mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver

 .../bindings/mmc/loongson,ls2k0500-mmc.yaml   |  112 ++
 MAINTAINERS                                   |    7 +
 drivers/mmc/host/Kconfig                      |   13 +
 drivers/mmc/host/Makefile                     |    1 +
 drivers/mmc/host/loongson2-mmc.c              | 1032 +++++++++++++++++
 5 files changed, 1165 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml
 create mode 100644 drivers/mmc/host/loongson2-mmc.c


base-commit: 187715cfd12932a528ff3a3952648e2b55381d4c
-- 
2.47.1


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

* [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding
  2025-06-18  8:07 [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou
@ 2025-06-18  8:07 ` Binbin Zhou
  2025-06-18 13:44   ` Krzysztof Kozlowski
  2025-06-18  8:07 ` [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 11+ messages in thread
From: Binbin Zhou @ 2025-06-18  8:07 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Ulf Hansson
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang,
	Binbin Zhou

Add the Loongson-2K SoC's SD/SDIO/eMMC controller binding with DT schema
format using json-schema.

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 .../bindings/mmc/loongson,ls2k0500-mmc.yaml   | 69 +++++++++++++++++++
 MAINTAINERS                                   |  6 ++
 2 files changed, 75 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml

diff --git a/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml b/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml
new file mode 100644
index 000000000000..f2f10b2c618c
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mmc/loongson,ls2k0500-mmc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: The SD/SDIO/eMMC host controller for Loongson-2K family SoCs
+
+description:
+  The MMC host controller on the Loongson-2K0500/2K1000 (using an externally
+  shared apbdma controller) provides the SD and SDIO device interfaces.
+
+maintainers:
+  - Binbin Zhou <zhoubinbin@loongson.cn>
+
+allOf:
+  - $ref: mmc-controller.yaml#
+
+properties:
+  compatible:
+    enum:
+      - loongson,ls2k0500-mmc
+      - loongson,ls2k1000-mmc
+
+  reg:
+    items:
+      - description: Loongson-2K MMC controller registers.
+      - description: APB DMA config register for Loongson-2K MMC controller.
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  dmas:
+    maxItems: 1
+
+  dma-names:
+    const: rx-tx
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - dmas
+  - dma-names
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/clock/loongson,ls2k-clk.h>
+
+    mmc@1fe2c000 {
+        compatible = "loongson,ls2k1000-mmc";
+        reg = <0x1fe2c000 0x68>,
+              <0x1fe00438 0x8>;
+        interrupt-parent = <&liointc0>;
+        interrupts = <31 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&clk LOONGSON2_APB_CLK>;
+        dmas = <&apbdma1 0>;
+        dma-names = "rx-tx";
+        bus-width = <4>;
+        cd-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index a92290fffa16..2d1cdd2cfc2b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14175,6 +14175,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/hwinfo/loongson,ls2k-chipid.yaml
 F:	drivers/soc/loongson/loongson2_guts.c
 
+LOONGSON-2 SOC SERIES MMC/SD/SDIO CONTROLLER DRIVER
+M:	Binbin Zhou <zhoubinbin@loongson.cn>
+L:	linux-mmc@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml
+
 LOONGSON-2 SOC SERIES PM DRIVER
 M:	Yinbo Zhu <zhuyinbo@loongson.cn>
 L:	linux-pm@vger.kernel.org
-- 
2.47.1


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

* [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver
  2025-06-18  8:07 [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou
  2025-06-18  8:07 ` [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
@ 2025-06-18  8:07 ` Binbin Zhou
  2025-06-19  4:02   ` Huacai Chen
  2025-06-20 13:20   ` kernel test robot
  2025-06-18  8:07 ` [PATCH v3 3/4] dt-bindings: mmc: loongson,ls2k0500-mmc: Add compatible for Loongson-2K2000 Binbin Zhou
  2025-06-18  8:08 ` [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver Binbin Zhou
  3 siblings, 2 replies; 11+ messages in thread
From: Binbin Zhou @ 2025-06-18  8:07 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Ulf Hansson
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang,
	Binbin Zhou

The MMC controllers on the Loongson-2K series CPUs are similar,
except for the interface characteristics and the use of DMA controllers.

This patch describes the MMC controllers on the Loongson-2K0500/2K1000,
with the distinguishing feature being the use of an externally shared
APBDMA engine.

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 MAINTAINERS                      |   1 +
 drivers/mmc/host/Kconfig         |  13 +
 drivers/mmc/host/Makefile        |   1 +
 drivers/mmc/host/loongson2-mmc.c | 820 +++++++++++++++++++++++++++++++
 4 files changed, 835 insertions(+)
 create mode 100644 drivers/mmc/host/loongson2-mmc.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 2d1cdd2cfc2b..4a4892613c66 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14180,6 +14180,7 @@ M:	Binbin Zhou <zhoubinbin@loongson.cn>
 L:	linux-mmc@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml
+F:	drivers/mmc/host/loongson2-mmc.c
 
 LOONGSON-2 SOC SERIES PM DRIVER
 M:	Yinbo Zhu <zhuyinbo@loongson.cn>
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index c3f0f41a426d..7232de1c0688 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -1111,6 +1111,19 @@ config MMC_OWL
 	  This selects support for the SD/MMC Host Controller on
 	  Actions Semi Owl SoCs.
 
+config MMC_LOONGSON2
+	tristate "Loongson-2K SD/SDIO/eMMC Host Interface support"
+	depends on LOONGARCH || COMPILE_TEST
+	depends on HAS_DMA
+	help
+	  This selects support for the SD/SDIO/eMMC Host Controller on
+	  Loongson-2K series CPUs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mmc_loongson2.
+
+	  If unsure, say N.
+
 config MMC_SDHCI_EXTERNAL_DMA
 	bool
 
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 75bafc7b162b..5057fea8afb6 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_MMC_USDHI6ROL0)	+= usdhi6rol0.o
 obj-$(CONFIG_MMC_TOSHIBA_PCI)	+= toshsd.o
 obj-$(CONFIG_MMC_BCM2835)	+= bcm2835.o
 obj-$(CONFIG_MMC_OWL)		+= owl-mmc.o
+obj-$(CONFIG_MMC_LOONGSON2)	+= loongson2-mmc.o
 
 obj-$(CONFIG_MMC_REALTEK_PCI)	+= rtsx_pci_sdmmc.o
 obj-$(CONFIG_MMC_REALTEK_USB)	+= rtsx_usb_sdmmc.o
diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c
new file mode 100644
index 000000000000..872f5dc21b21
--- /dev/null
+++ b/drivers/mmc/host/loongson2-mmc.c
@@ -0,0 +1,820 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Loongson-2K MMC/SDIO controller driver
+ *
+ * Copyright (C) 2018-2025 Loongson Technology Corporation Limited.
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitrev.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mmc/core.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/slot-gpio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define LOONGSON2_MMC_REG_CTL		0x00 /* Control Register */
+#define LOONGSON2_MMC_REG_PRE		0x04 /* Prescaler Register */
+#define LOONGSON2_MMC_REG_CARG		0x08 /* Command Register */
+#define LOONGSON2_MMC_REG_CCTL		0x0c /* Command Control Register */
+#define LOONGSON2_MMC_REG_CSTS		0x10 /* Command Status Register */
+#define LOONGSON2_MMC_REG_RSP0		0x14 /* Command Response Register 0 */
+#define LOONGSON2_MMC_REG_RSP1		0x18 /* Command Response Register 1 */
+#define LOONGSON2_MMC_REG_RSP2		0x1c /* Command Response Register 2 */
+#define LOONGSON2_MMC_REG_RSP3		0x20 /* Command Response Register 3 */
+#define LOONGSON2_MMC_REG_TIMER		0x24 /* Data Timeout Register */
+#define LOONGSON2_MMC_REG_BSIZE		0x28 /* Block Size Register */
+#define LOONGSON2_MMC_REG_DCTL		0x2c /* Data Control Register */
+#define LOONGSON2_MMC_REG_DCNT		0x30 /* Data Counter Register */
+#define LOONGSON2_MMC_REG_DSTS		0x34 /* Data Status Register */
+#define LOONGSON2_MMC_REG_FSTS		0x38 /* FIFO Status Register */
+#define LOONGSON2_MMC_REG_INT		0x3c /* Interrupt Register */
+#define LOONGSON2_MMC_REG_DATA		0x40 /* Data Register */
+#define LOONGSON2_MMC_REG_IEN		0x64 /* Interrupt Enable Register */
+
+/* Bitfields of control register */
+#define LOONGSON2_MMC_CTL_ENCLK		BIT(0)
+#define LOONGSON2_MMC_CTL_EXTCLK	BIT(1)
+#define LOONGSON2_MMC_CTL_RESET		BIT(8)
+
+/* Bitfields of prescaler register */
+#define LOONGSON2_MMC_PRE		GENMASK(9, 0)
+#define LOONGSON2_MMC_PRE_EN		BIT(31)
+
+/* Bitfields of command control register */
+#define LOONGSON2_MMC_CCTL_INDEX	GENMASK(5, 0)
+#define LOONGSON2_MMC_CCTL_HOST		BIT(6)
+#define LOONGSON2_MMC_CCTL_START	BIT(8)
+#define LOONGSON2_MMC_CCTL_WAIT_RSP	BIT(9)
+#define LOONGSON2_MMC_CCTL_LONG_RSP	BIT(10)
+#define LOONGSON2_MMC_CCTL_ABORT	BIT(12)
+#define LOONGSON2_MMC_CCTL_CHECK	BIT(13)
+#define LOONGSON2_MMC_CCTL_SDIO		BIT(14)
+#define LOONGSON2_MMC_CCTL_CMD6		BIT(18)
+
+/* Bitfields of command status register */
+#define LOONGSON2_MMC_CSTS_INDEX	GENMASK(7, 0)
+#define LOONGSON2_MMC_CSTS_ON		BIT(8)
+#define LOONGSON2_MMC_CSTS_RSP		BIT(9)
+#define LOONGSON2_MMC_CSTS_TIMEOUT	BIT(10)
+#define LOONGSON2_MMC_CSTS_END		BIT(11)
+#define LOONGSON2_MMC_CSTS_CRC_ERR	BIT(12)
+#define LOONGSON2_MMC_CSTS_AUTO_STOP	BIT(13)
+#define LOONGSON2_MMC_CSTS_FIN		BIT(14)
+
+/* Bitfields of data timeout register */
+#define LOONGSON2_MMC_DTIMR		GENMASK(23, 0)
+
+/* Bitfields of block size register */
+#define LOONGSON2_MMC_BSIZE		GENMASK(11, 0)
+
+/* Bitfields of data control register */
+#define LOONGSON2_MMC_DCTL_BNUM		GENMASK(11, 0)
+#define LOONGSON2_MMC_DCTL_START	BIT(14)
+#define LOONGSON2_MMC_DCTL_ENDMA	BIT(15)
+#define LOONGSON2_MMC_DCTL_WIDE		BIT(16)
+#define LOONGSON2_MMC_DCTL_RWAIT	BIT(17)
+#define LOONGSON2_MMC_DCTL_IO_SUSPEND	BIT(18)
+#define LOONGSON2_MMC_DCTL_IO_RESUME	BIT(19)
+#define LOONGSON2_MMC_DCTL_RW_RESUME	BIT(20)
+#define LOONGSON2_MMC_DCTL_8BIT_BUS	BIT(26)
+
+/* Bitfields of sata counter register */
+#define LOONGSON2_MMC_DCNT_BNUM		GENMASK(11, 0)
+#define LOONGSON2_MMC_DCNT_BYTE		GENMASK(23, 12)
+
+/* Bitfields of command status register */
+#define LOONGSON2_MMC_DSTS_RXON		BIT(0)
+#define LOONGSON2_MMC_DSTS_TXON		BIT(1)
+#define LOONGSON2_MMC_DSTS_SBITERR	BIT(2)
+#define LOONGSON2_MMC_DSTS_BUSYFIN	BIT(3)
+#define LOONGSON2_MMC_DSTS_XFERFIN	BIT(4)
+#define LOONGSON2_MMC_DSTS_DTIMEOUT	BIT(5)
+#define LOONGSON2_MMC_DSTS_RXCRC	BIT(6)
+#define LOONGSON2_MMC_DSTS_TXCRC	BIT(7)
+#define LOONGSON2_MMC_DSTS_IRQ		BIT(8)
+#define LOONGSON2_MMC_DSTS_START	BIT(13)
+#define LOONGSON2_MMC_DSTS_RESUME	BIT(15)
+#define LOONGSON2_MMC_DSTS_SUSPEND	BIT(16)
+
+/* Bitfields of interrupt register */
+#define LOONGSON2_MMC_INT_DFIN		BIT(0)
+#define LOONGSON2_MMC_INT_DTIMEOUT	BIT(1)
+#define LOONGSON2_MMC_INT_RXCRC		BIT(2)
+#define LOONGSON2_MMC_INT_TXCRC		BIT(3)
+#define LOONGSON2_MMC_INT_PROGERR	BIT(4)
+#define LOONGSON2_MMC_INT_SDIOIRQ	BIT(5)
+#define LOONGSON2_MMC_INT_CSENT		BIT(6)
+#define LOONGSON2_MMC_INT_CTIMEOUT	BIT(7)
+#define LOONGSON2_MMC_INT_RESPCRC	BIT(8)
+#define LOONGSON2_MMC_INT_BUSYEND	BIT(9)
+
+/* Bitfields of interrupt enable register */
+#define LOONGSON2_MMC_IEN_DFIN		BIT(0)
+#define LOONGSON2_MMC_IEN_DTIMEOUT	BIT(1)
+#define LOONGSON2_MMC_IEN_RXCRC		BIT(2)
+#define LOONGSON2_MMC_IEN_TXCRC		BIT(3)
+#define LOONGSON2_MMC_IEN_PROGERR	BIT(4)
+#define LOONGSON2_MMC_IEN_SDIOIRQ	BIT(5)
+#define LOONGSON2_MMC_IEN_CSENT		BIT(6)
+#define LOONGSON2_MMC_IEN_CTIMEOUT	BIT(7)
+#define LOONGSON2_MMC_IEN_RESPCRC	BIT(8)
+#define LOONGSON2_MMC_IEN_BUSYEND	BIT(9)
+
+#define LOONGSON2_MMC_IEN_ALL		GENMASK(9, 0)
+#define LOONGSON2_MMC_INT_CLEAR		GENMASK(9, 0)
+
+/* Loongson-2K1000 SDIO2 DMA routing register */
+#define LS2K1000_SDIO_DMA_MASK		GENMASK(17, 15)
+#define LS2K1000_DMA0_CONF		0x0
+#define LS2K1000_DMA1_CONF		0x1
+#define LS2K1000_DMA2_CONF		0x2
+#define LS2K1000_DMA3_CONF		0x3
+#define LS2K1000_DMA4_CONF		0x4
+
+/* Loongson-2K0500 SDIO2 DMA routing register */
+#define LS2K0500_SDIO_DMA_MASK		GENMASK(15, 14)
+#define LS2K0500_DMA0_CONF		0x1
+#define LS2K0500_DMA1_CONF		0x2
+#define LS2K0500_DMA2_CONF		0x3
+
+enum loongson2_mmc_state {
+	STATE_NONE,
+	STATE_FINALIZE,
+	STATE_CMDSENT,
+	STATE_RSPFIN,
+	STATE_XFERFINISH,
+	STATE_XFERFINISH_RSPFIN,
+};
+
+struct loongson2_dma_desc {
+	u32 ndesc_addr;
+	u32 mem_addr;
+	u32 apb_addr;
+	u32 len;
+	u32 step_len;
+	u32 step_times;
+	u32 cmd;
+	u32 stats;
+	u32 high_ndesc_addr;
+	u32 high_mem_addr;
+	u32 reserved[2];
+} __packed;
+
+struct loongson2_mmc_host {
+	struct device *dev;
+	struct mmc_request *mrq;
+	struct regmap *regmap;
+	struct resource *res;
+	struct clk *clk;
+	u64 rate;
+	int dma_complete;
+	struct dma_chan *chan;
+	int cmd_is_stop;
+	int bus_width;
+	spinlock_t lock; /* Prevent races with irq handler */
+	enum loongson2_mmc_state state;
+	const struct loongson2_mmc_pdata *pdata;
+};
+
+struct loongson2_mmc_pdata {
+	const struct regmap_config *regmap_config;
+	void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
+	int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev);
+	int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data);
+	void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev);
+};
+
+static void loongson2_mmc_send_command(struct loongson2_mmc_host *host,
+				       struct mmc_command *cmd)
+{
+	u32 cctrl;
+
+	if (cmd->data)
+		host->state = STATE_XFERFINISH_RSPFIN;
+	else if (cmd->flags & MMC_RSP_PRESENT)
+		host->state = STATE_RSPFIN;
+	else
+		host->state = STATE_CMDSENT;
+
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_CARG, cmd->arg);
+
+	cctrl = FIELD_PREP(LOONGSON2_MMC_CCTL_INDEX, cmd->opcode);
+	cctrl |= LOONGSON2_MMC_CCTL_HOST | LOONGSON2_MMC_CCTL_START;
+
+	if (cmd->opcode == SD_SWITCH && cmd->data)
+		cctrl |= LOONGSON2_MMC_CCTL_CMD6;
+
+	if (cmd->flags & MMC_RSP_PRESENT)
+		cctrl |= LOONGSON2_MMC_CCTL_WAIT_RSP;
+
+	if (cmd->flags & MMC_RSP_136)
+		cctrl |= LOONGSON2_MMC_CCTL_LONG_RSP;
+
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_CCTL, cctrl);
+}
+
+static int loongson2_mmc_setup_data(struct loongson2_mmc_host *host,
+				    struct mmc_data *data)
+{
+	u32 dctrl;
+
+	if ((data->blksz & 3) != 0)
+		return -EINVAL;
+
+	dctrl = FIELD_PREP(LOONGSON2_MMC_DCTL_BNUM, data->blocks);
+	dctrl |= LOONGSON2_MMC_DCTL_START | LOONGSON2_MMC_DCTL_ENDMA;
+
+	if (host->bus_width == MMC_BUS_WIDTH_4)
+		dctrl |= LOONGSON2_MMC_DCTL_WIDE;
+	else if (host->bus_width == MMC_BUS_WIDTH_8)
+		dctrl |= LOONGSON2_MMC_DCTL_8BIT_BUS;
+
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_DCTL, dctrl);
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_BSIZE, data->blksz);
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_TIMER, U32_MAX);
+
+	return 0;
+}
+
+static int loongson2_mmc_prepare_dma(struct loongson2_mmc_host *host,
+				     struct mmc_data *data)
+{
+	int ret;
+
+	if (!data)
+		return 0;
+
+	ret = loongson2_mmc_setup_data(host, data);
+	if (ret)
+		return ret;
+
+	host->dma_complete = 0;
+
+	return host->pdata->prepare_dma(host, data);
+}
+
+static void loongson2_mmc_send_request(struct mmc_host *mmc)
+{
+	int ret;
+	struct loongson2_mmc_host *host = mmc_priv(mmc);
+	struct mmc_request *mrq = host->mrq;
+	struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
+
+	ret = loongson2_mmc_prepare_dma(host, cmd->data);
+	if (ret) {
+		dev_err(host->dev, "DMA data prepared failed with %d\n", ret);
+		cmd->error = ret;
+		cmd->data->error = ret;
+		mmc_request_done(mmc, mrq);
+		return;
+	}
+
+	loongson2_mmc_send_command(host, cmd);
+
+	/* Fix deselect card */
+	if (cmd->opcode == MMC_SELECT_CARD && cmd->arg == 0) {
+		cmd->error = 0;
+		mmc_request_done(mmc, mrq);
+	}
+}
+
+static irqreturn_t loongson2_mmc_irq_worker(int irq, void *devid)
+{
+	struct loongson2_mmc_host *host = (struct loongson2_mmc_host *)devid;
+	struct mmc_host *mmc = mmc_from_priv(host);
+	struct mmc_request *mrq = host->mrq;
+	struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
+
+	if (cmd->data)
+		dma_unmap_sg(mmc_dev(mmc), cmd->data->sg, cmd->data->sg_len,
+			     mmc_get_dma_dir(cmd->data));
+
+	if (cmd->data && !cmd->error &&
+	    !cmd->data->error && !host->dma_complete)
+		return IRQ_HANDLED;
+
+	/* Read response from controller. */
+	regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP0, &cmd->resp[0]);
+	regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP1, &cmd->resp[1]);
+	regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP2, &cmd->resp[2]);
+	regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP3, &cmd->resp[3]);
+
+	/* Cleanup controller */
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_CARG, 0);
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_CCTL, 0);
+
+	if (cmd->data && cmd->error)
+		cmd->data->error = cmd->error;
+
+	if (cmd->data && cmd->data->stop && !host->cmd_is_stop) {
+		host->cmd_is_stop = 1;
+		loongson2_mmc_send_request(mmc);
+		return IRQ_HANDLED;
+	}
+
+	/* If we have no data transfer we are finished here */
+	if (!mrq->data)
+		goto request_done;
+
+	/* Calculate the amount of bytes transfer if there was no error */
+	if (mrq->data->error == 0) {
+		mrq->data->bytes_xfered =
+			(mrq->data->blocks * mrq->data->blksz);
+	} else {
+		mrq->data->bytes_xfered = 0;
+	}
+
+request_done:
+	host->state = STATE_NONE;
+	host->mrq = NULL;
+	mmc_request_done(mmc, mrq);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t loongson2_mmc_irq(int irq, void *devid)
+{
+	struct loongson2_mmc_host *host = (struct loongson2_mmc_host *)devid;
+	struct mmc_host *mmc = mmc_from_priv(host);
+	struct mmc_command *cmd;
+	unsigned long iflags;
+	u32 dsts, imsk;
+
+	regmap_read(host->regmap, LOONGSON2_MMC_REG_INT, &imsk);
+	regmap_read(host->regmap, LOONGSON2_MMC_REG_DSTS, &dsts);
+
+	if ((dsts & LOONGSON2_MMC_DSTS_IRQ) &&
+	    (imsk & LOONGSON2_MMC_INT_SDIOIRQ)) {
+		regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_INT,
+				   LOONGSON2_MMC_INT_SDIOIRQ,
+				   LOONGSON2_MMC_INT_SDIOIRQ);
+
+		sdio_signal_irq(mmc);
+		return IRQ_HANDLED;
+	}
+
+	spin_lock_irqsave(&host->lock, iflags);
+
+	if (host->state == STATE_NONE || host->state == STATE_FINALIZE ||
+	    !host->mrq)
+		goto irq_out;
+
+	cmd = host->cmd_is_stop ? host->mrq->stop : host->mrq->cmd;
+	if (!cmd)
+		goto irq_out;
+
+	cmd->error = 0;
+
+	if (imsk & LOONGSON2_MMC_INT_CTIMEOUT) {
+		cmd->error = -ETIMEDOUT;
+		goto close_transfer;
+	}
+
+	if (imsk & LOONGSON2_MMC_INT_CSENT) {
+		if (host->state == STATE_RSPFIN || host->state == STATE_CMDSENT)
+			goto close_transfer;
+
+		if (host->state == STATE_XFERFINISH_RSPFIN)
+			host->state = STATE_XFERFINISH;
+	}
+
+	if (!cmd->data)
+		goto irq_out;
+
+	if (imsk & (LOONGSON2_MMC_INT_RXCRC | LOONGSON2_MMC_INT_TXCRC)) {
+		cmd->data->error = -EILSEQ;
+		goto close_transfer;
+	}
+
+	if (imsk & LOONGSON2_MMC_INT_DTIMEOUT) {
+		cmd->data->error = -ETIMEDOUT;
+		goto close_transfer;
+	}
+
+	if (imsk & LOONGSON2_MMC_INT_DFIN) {
+		if (host->state == STATE_XFERFINISH) {
+			host->dma_complete = 1;
+			goto close_transfer;
+		}
+
+		if (host->state == STATE_XFERFINISH_RSPFIN)
+			host->state = STATE_RSPFIN;
+	}
+
+irq_out:
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, imsk);
+	spin_unlock_irqrestore(&host->lock, iflags);
+	return IRQ_HANDLED;
+
+close_transfer:
+	host->state = STATE_FINALIZE;
+	host->pdata->reorder_cmd_data(host, cmd);
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, imsk);
+	spin_unlock_irqrestore(&host->lock, iflags);
+	return IRQ_WAKE_THREAD;
+}
+
+static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_ios *ios)
+{
+	u32 pre;
+
+	pre = DIV_ROUND_UP(host->rate, ios->clock);
+	if (pre > 255)
+		pre = 255;
+
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_PRE, pre | LOONGSON2_MMC_PRE_EN);
+
+	regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL,
+			   LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK);
+}
+
+static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct loongson2_mmc_host *host = mmc_priv(mmc);
+	int ret;
+
+	if (ios->power_mode == MMC_POWER_UP) {
+		if (!IS_ERR(mmc->supply.vmmc)) {
+			ret = mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd);
+			if (ret) {
+				dev_err(host->dev, "failed to enable vmmc regulator\n");
+				/*return, if failed turn on vmmc*/
+				return;
+			}
+		}
+		regmap_write(host->regmap, LOONGSON2_MMC_REG_CTL, LOONGSON2_MMC_CTL_RESET);
+		mdelay(10);
+		regmap_write(host->regmap, LOONGSON2_MMC_REG_CTL, LOONGSON2_MMC_CTL_EXTCLK);
+		regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, LOONGSON2_MMC_IEN_ALL);
+		regmap_write(host->regmap, LOONGSON2_MMC_REG_IEN, LOONGSON2_MMC_INT_CLEAR);
+	} else if (ios->power_mode == MMC_POWER_OFF) {
+		regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL,
+				   LOONGSON2_MMC_CTL_RESET, LOONGSON2_MMC_CTL_RESET);
+		if (!IS_ERR(mmc->supply.vmmc))
+			mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
+		return;
+	}
+
+	loongson2_mmc_set_clk(host, ios);
+
+	host->bus_width = ios->bus_width;
+}
+
+static void loongson2_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct loongson2_mmc_host *host = mmc_priv(mmc);
+
+	host->cmd_is_stop = 0;
+	host->mrq = mrq;
+	loongson2_mmc_send_request(mmc);
+}
+
+static void loongson2_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+	struct loongson2_mmc_host *host = mmc_priv(mmc);
+
+	regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_IEN,
+			   LOONGSON2_MMC_INT_SDIOIRQ, enable);
+}
+
+static void loongson2_mmc_ack_sdio_irq(struct mmc_host *mmc)
+{
+	loongson2_mmc_enable_sdio_irq(mmc, 1);
+}
+
+static struct mmc_host_ops loongson2_mmc_ops = {
+	.request	= loongson2_mmc_request,
+	.set_ios	= loongson2_mmc_set_ios,
+	.get_ro		= mmc_gpio_get_ro,
+	.get_cd		= mmc_gpio_get_cd,
+	.enable_sdio_irq = loongson2_mmc_enable_sdio_irq,
+	.ack_sdio_irq	= loongson2_mmc_ack_sdio_irq,
+};
+
+static const struct regmap_config ls2k1000_mmc_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.max_register = LOONGSON2_MMC_REG_IEN,
+};
+
+static int loongson2_reorder_cmd_list[] = { SD_APP_SEND_SCR, SD_APP_SEND_NUM_WR_BLKS,
+					    SD_APP_SD_STATUS, MMC_SEND_WRITE_PROT,
+					    SD_SWITCH };
+
+/*
+ * According to SD spec, ACMD13, ACMD22, ACMD51 and CMD30
+ * response datas has different byte order with usual data packets.
+ * However sdio controller will send these datas in usual data format,
+ * so we need to adjust these datas to a protocol consistent byte order.
+ */
+static void loongson2_mmc_reorder_cmd_data(struct loongson2_mmc_host *host,
+					   struct mmc_command *cmd)
+{
+	struct scatterlist *sg;
+	u32 *data;
+	int i, j;
+
+	if (mmc_cmd_type(cmd) != MMC_CMD_ADTC)
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(loongson2_reorder_cmd_list); i++)
+		if (cmd->opcode == loongson2_reorder_cmd_list[i])
+			break;
+
+	if (i == ARRAY_SIZE(loongson2_reorder_cmd_list))
+		return;
+
+	for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) {
+		data = sg_virt(&sg[i]);
+		for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++)
+			if (cmd->opcode == SD_SWITCH)
+				data[j] = bitrev8x4(data[j]);
+			else
+				data[j] = (__force u32)cpu_to_be32(data[j]);
+	}
+}
+
+static int loongson2_mmc_prepare_external_dma(struct loongson2_mmc_host *host,
+					      struct mmc_data *data)
+{
+	struct mmc_host *mmc = mmc_from_priv(host);
+	struct dma_slave_config dma_conf = { };
+	struct dma_async_tx_descriptor *desc;
+	int ret;
+
+	ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len,
+			 mmc_get_dma_dir(data));
+	if (!ret)
+		return -ENOMEM;
+
+	dma_conf.src_addr = host->res->start + LOONGSON2_MMC_REG_DATA,
+	dma_conf.dst_addr = host->res->start + LOONGSON2_MMC_REG_DATA,
+	dma_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+	dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+	dma_conf.direction = !(data->flags & MMC_DATA_WRITE) ?
+			     DMA_DEV_TO_MEM : DMA_MEM_TO_DEV;
+
+	dmaengine_slave_config(host->chan, &dma_conf);
+	desc = dmaengine_prep_slave_sg(host->chan, data->sg, data->sg_len,
+				       dma_conf.direction,
+				       DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
+	if (!desc)
+		goto unmap_exit;
+
+	dmaengine_submit(desc);
+	dma_async_issue_pending(host->chan);
+
+	return 0;
+
+unmap_exit:
+	dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len,
+		     mmc_get_dma_dir(data));
+	return -ENOMEM;
+}
+
+static void loongson2_mmc_release_external_dma(struct loongson2_mmc_host *host,
+					       struct device *dev)
+{
+	dma_release_channel(host->chan);
+}
+
+static int ls2k0500_mmc_set_external_dma(struct loongson2_mmc_host *host,
+					 struct platform_device *pdev)
+{
+	int ret, val;
+	void __iomem *regs;
+
+	regs = devm_platform_ioremap_resource(pdev, 1);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	val = readl(regs);
+	val |= FIELD_PREP(LS2K0500_SDIO_DMA_MASK, LS2K0500_DMA2_CONF);
+	writel(val, regs);
+
+	host->chan = dma_request_chan(&pdev->dev, "rx-tx");
+	ret = PTR_ERR_OR_ZERO(host->chan);
+	if (ret) {
+		dev_err(&pdev->dev, "cannot get DMA channel.\n");
+		return  ret;
+	}
+
+	return 0;
+}
+
+static struct loongson2_mmc_pdata ls2k0500_mmc_pdata = {
+	.regmap_config		= &ls2k1000_mmc_regmap_config,
+	.reorder_cmd_data	= loongson2_mmc_reorder_cmd_data,
+	.setting_dma		= ls2k0500_mmc_set_external_dma,
+	.prepare_dma		= loongson2_mmc_prepare_external_dma,
+	.release_dma		= loongson2_mmc_release_external_dma,
+};
+
+static int ls2k1000_mmc_set_external_dma(struct loongson2_mmc_host *host,
+					 struct platform_device *pdev)
+{
+	int ret, val;
+	void __iomem *regs;
+
+	regs = devm_platform_ioremap_resource(pdev, 1);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	val = readl(regs);
+	val |= FIELD_PREP(LS2K1000_SDIO_DMA_MASK, LS2K1000_DMA1_CONF);
+	writel(val, regs);
+
+	host->chan = dma_request_chan(&pdev->dev, "rx-tx");
+	ret = PTR_ERR_OR_ZERO(host->chan);
+	if (ret) {
+		dev_err(&pdev->dev, "cannot get DMA channel.\n");
+		return  ret;
+	}
+
+	return 0;
+}
+
+static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = {
+	.regmap_config		= &ls2k1000_mmc_regmap_config,
+	.reorder_cmd_data	= loongson2_mmc_reorder_cmd_data,
+	.setting_dma		= ls2k1000_mmc_set_external_dma,
+	.prepare_dma		= loongson2_mmc_prepare_external_dma,
+	.release_dma		= loongson2_mmc_release_external_dma,
+};
+
+static int loongson2_mmc_resource_request(struct platform_device *pdev,
+					  struct loongson2_mmc_host *host)
+{
+	struct device *dev = &pdev->dev;
+	void __iomem *base;
+	int ret, irq;
+
+	base = devm_platform_get_and_ioremap_resource(pdev, 0, &host->res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	host->regmap = devm_regmap_init_mmio(dev, base, host->pdata->regmap_config);
+	if (IS_ERR(host->regmap))
+		return PTR_ERR(host->regmap);
+
+	host->clk = devm_clk_get_optional_enabled(dev, NULL);
+	if (IS_ERR(host->clk))
+		return PTR_ERR(host->clk);
+
+	if (host->clk) {
+		ret = devm_clk_rate_exclusive_get(dev, host->clk);
+		if (ret)
+			return PTR_ERR(host->clk);
+
+		host->rate = clk_get_rate(host->clk);
+	} else {
+		/* For ACPI, we get rate through clock-frequency attribute */
+		device_property_read_u64(dev, "clock-frequency", &host->rate);
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	ret = devm_request_threaded_irq(dev, irq, loongson2_mmc_irq,
+					loongson2_mmc_irq_worker,
+					IRQF_ONESHOT, "loongson2-mmc", host);
+	if (ret)
+		return ret;
+
+	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	if (ret)
+		return ret;
+
+	return host->pdata->setting_dma(host, pdev);
+}
+
+static int loongson2_mmc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct loongson2_mmc_host *host;
+	struct mmc_host	*mmc;
+	int ret;
+
+	mmc = devm_mmc_alloc_host(dev, sizeof(*host));
+	if (!mmc)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, mmc);
+
+	host = mmc_priv(mmc);
+	host->state = STATE_NONE;
+	spin_lock_init(&host->lock);
+
+	host->pdata = device_get_match_data(dev);
+	if (!host->pdata)
+		return dev_err_probe(dev, -EINVAL, "Failed to get match data\n");
+
+	ret = loongson2_mmc_resource_request(pdev, host);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to request resource\n");
+
+	mmc->ops = &loongson2_mmc_ops;
+	mmc->f_min = DIV_ROUND_UP(host->rate, 256);
+	mmc->f_max = host->rate;
+	mmc->max_blk_count = 4095;
+	mmc->max_blk_size = 4095;
+	mmc->max_req_size = mmc->max_blk_count * mmc->max_blk_size;
+	mmc->max_segs = 1;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	/* Process SDIO IRQs through the sdio_irq_work. */
+	if (mmc->caps & MMC_CAP_SDIO_IRQ)
+		mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD;
+
+	ret = mmc_regulator_get_supply(mmc);
+	if (ret || mmc->ocr_avail == 0) {
+		dev_warn(dev, "Can't get voltage, defaulting to 3.3V\n");
+		mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+	}
+
+	ret = mmc_of_parse(mmc);
+	if (ret) {
+		dev_err(dev, "Failed to parse device node\n");
+		goto free_dma;
+	}
+
+	ret = mmc_add_host(mmc);
+	if (ret) {
+		dev_err(dev, "Failed to add mmc host\n");
+		goto free_dma;
+	}
+
+	return 0;
+
+free_dma:
+	host->pdata->release_dma(host, dev);
+	return ret;
+}
+
+static void loongson2_mmc_remove(struct platform_device *pdev)
+{
+	struct mmc_host *mmc  = platform_get_drvdata(pdev);
+	struct loongson2_mmc_host *host = mmc_priv(mmc);
+
+	mmc_remove_host(mmc);
+	host->pdata->release_dma(host, &pdev->dev);
+}
+
+static const struct of_device_id loongson2_mmc_of_ids[] = {
+	{ .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata },
+	{ .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids);
+
+static int loongson2_mmc_suspend(struct device *dev)
+{
+	struct mmc_host *mmc  = dev_get_drvdata(dev);
+	struct loongson2_mmc_host *host = mmc_priv(mmc);
+
+	clk_disable_unprepare(host->clk);
+
+	return 0;
+}
+
+static int loongson2_mmc_resume(struct device *dev)
+{
+	struct mmc_host *mmc  = dev_get_drvdata(dev);
+	struct loongson2_mmc_host *host = mmc_priv(mmc);
+
+	return clk_prepare_enable(host->clk);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(loongson2_mmc_pm_ops, loongson2_mmc_suspend,
+				loongson2_mmc_resume);
+
+static struct platform_driver loongson2_mmc_driver = {
+	.driver	= {
+		.name = "loongson2-mmc",
+		.of_match_table = loongson2_mmc_of_ids,
+		.pm = pm_ptr(&loongson2_mmc_pm_ops),
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.probe = loongson2_mmc_probe,
+	.remove = loongson2_mmc_remove,
+};
+
+module_platform_driver(loongson2_mmc_driver);
+
+MODULE_DESCRIPTION("Loongson-2K SD/SDIO/eMMC Interface driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_LICENSE("GPL");
-- 
2.47.1


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

* [PATCH v3 3/4] dt-bindings: mmc: loongson,ls2k0500-mmc: Add compatible for Loongson-2K2000
  2025-06-18  8:07 [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou
  2025-06-18  8:07 ` [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
  2025-06-18  8:07 ` [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou
@ 2025-06-18  8:07 ` Binbin Zhou
  2025-06-18  8:08 ` [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver Binbin Zhou
  3 siblings, 0 replies; 11+ messages in thread
From: Binbin Zhou @ 2025-06-18  8:07 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Ulf Hansson
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang,
	Binbin Zhou, Conor Dooley

Add the devicetree compatible for Loongson-2K2000 EMMC/SD/SDIO controller.

Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 .../bindings/mmc/loongson,ls2k0500-mmc.yaml   | 47 ++++++++++++++++++-
 1 file changed, 45 insertions(+), 2 deletions(-)

diff --git a/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml b/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml
index f2f10b2c618c..c142421bc723 100644
--- a/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml
+++ b/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml
@@ -9,6 +9,9 @@ title: The SD/SDIO/eMMC host controller for Loongson-2K family SoCs
 description:
   The MMC host controller on the Loongson-2K0500/2K1000 (using an externally
   shared apbdma controller) provides the SD and SDIO device interfaces.
+  The two MMC host controllers on the Loongson-2K2000 are similar,
+  except that they use internal exclusive DMA. one controller provides
+  the eMMC interface and the other provides the SD/SDIO interface.
 
 maintainers:
   - Binbin Zhou <zhoubinbin@loongson.cn>
@@ -21,8 +24,10 @@ properties:
     enum:
       - loongson,ls2k0500-mmc
       - loongson,ls2k1000-mmc
+      - loongson,ls2k2000-mmc
 
   reg:
+    minItems: 1
     items:
       - description: Loongson-2K MMC controller registers.
       - description: APB DMA config register for Loongson-2K MMC controller.
@@ -44,11 +49,31 @@ required:
   - reg
   - interrupts
   - clocks
-  - dmas
-  - dma-names
 
 unevaluatedProperties: false
 
+if:
+  properties:
+    compatible:
+      contains:
+        enum:
+          - loongson,ls2k0500-mmc
+          - loongson,ls2k1000-mmc
+
+then:
+  properties:
+    reg:
+      minItems: 2
+
+  required:
+    - dmas
+    - dma-names
+
+else:
+  properties:
+    reg:
+      maxItems: 1
+
 examples:
   - |
     #include <dt-bindings/gpio/gpio.h>
@@ -67,3 +92,21 @@ examples:
         bus-width = <4>;
         cd-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
     };
+
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/clock/loongson,ls2k-clk.h>
+
+    mmc@79990000 {
+        compatible = "loongson,ls2k2000-mmc";
+        reg = <0x79990000 0x1000>;
+        interrupt-parent = <&pic>;
+        interrupts = <51 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&clk LOONGSON2_EMMC_CLK>;
+        bus-width = <8>;
+        non-removable;
+        cap-mmc-highspeed;
+        mmc-hs200-1_8v;
+        no-sd;
+        no-sdio;
+    };
-- 
2.47.1


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

* [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver
  2025-06-18  8:07 [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou
                   ` (2 preceding siblings ...)
  2025-06-18  8:07 ` [PATCH v3 3/4] dt-bindings: mmc: loongson,ls2k0500-mmc: Add compatible for Loongson-2K2000 Binbin Zhou
@ 2025-06-18  8:08 ` Binbin Zhou
  2025-06-19  4:02   ` Huacai Chen
  2025-06-23 11:24   ` Ulf Hansson
  3 siblings, 2 replies; 11+ messages in thread
From: Binbin Zhou @ 2025-06-18  8:08 UTC (permalink / raw)
  To: Binbin Zhou, Huacai Chen, Ulf Hansson
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang,
	Binbin Zhou

This patch describes the two MMC controllers of the Loongson-2K2000 SoC,
one providing an eMMC interface and the other exporting an SD/SDIO
interface.

Compared to the Loongson-2K1000's MMC controllers, their internals are
similar, except that we use an internally exclusive DMA engine instead of
an externally shared APBDMA engine.

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
 drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++
 1 file changed, 212 insertions(+)

diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c
index 872f5dc21b21..75144221a821 100644
--- a/drivers/mmc/host/loongson2-mmc.c
+++ b/drivers/mmc/host/loongson2-mmc.c
@@ -44,6 +44,18 @@
 #define LOONGSON2_MMC_REG_DATA		0x40 /* Data Register */
 #define LOONGSON2_MMC_REG_IEN		0x64 /* Interrupt Enable Register */
 
+/* EMMC DLL Mode Registers */
+#define LOONGSON2_MMC_REG_DLLVAL	0xf0 /* DLL Master Lock-value Register */
+#define LOONGSON2_MMC_REG_DLLCTL	0xf4 /* DLL Control Register */
+#define LOONGSON2_MMC_REG_DELAY		0xf8 /* DLL Delayed Parameter Register */
+#define LOONGSON2_MMC_REG_SEL		0xfc /* Bus Mode Selection Register */
+
+/* Exclusive DMA R/W Registers */
+#define LOONGSON2_MMC_REG_WDMA_LO	0x400
+#define LOONGSON2_MMC_REG_WDMA_HI	0x404
+#define LOONGSON2_MMC_REG_RDMA_LO	0x800
+#define LOONGSON2_MMC_REG_RDMA_HI	0x804
+
 /* Bitfields of control register */
 #define LOONGSON2_MMC_CTL_ENCLK		BIT(0)
 #define LOONGSON2_MMC_CTL_EXTCLK	BIT(1)
@@ -109,6 +121,9 @@
 #define LOONGSON2_MMC_DSTS_RESUME	BIT(15)
 #define LOONGSON2_MMC_DSTS_SUSPEND	BIT(16)
 
+/* Bitfields of FIFO Status Register */
+#define LOONGSON2_MMC_FSTS_TXFULL	BIT(11)
+
 /* Bitfields of interrupt register */
 #define LOONGSON2_MMC_INT_DFIN		BIT(0)
 #define LOONGSON2_MMC_INT_DTIMEOUT	BIT(1)
@@ -136,6 +151,41 @@
 #define LOONGSON2_MMC_IEN_ALL		GENMASK(9, 0)
 #define LOONGSON2_MMC_INT_CLEAR		GENMASK(9, 0)
 
+/* Bitfields of DLL master lock-value register */
+#define LOONGSON2_MMC_DLLVAL_DONE	BIT(8)
+
+/* Bitfields of DLL control register */
+#define LOONGSON2_MMC_DLLCTL_TIME	GENMASK(7, 0)
+#define LOONGSON2_MMC_DLLCTL_INCRE	GENMASK(15, 8)
+#define LOONGSON2_MMC_DLLCTL_START	GENMASK(23, 16)
+#define LOONGSON2_MMC_DLLCTL_CLK_MODE	BIT(24)
+#define LOONGSON2_MMC_DLLCTL_START_BIT	BIT(25)
+#define LOONGSON2_MMC_DLLCTL_TIME_BPASS	GENMASK(29, 26)
+
+#define LOONGSON2_MMC_DELAY_PAD		GENMASK(7, 0)
+#define LOONGSON2_MMC_DELAY_RD		GENMASK(15, 8)
+
+#define LOONGSON2_MMC_SEL_DATA		BIT(0)	/* 0: SDR, 1: DDR */
+#define LOONGSON2_MMC_SEL_BUS		BIT(0)	/* 0: EMMC, 1: SDIO */
+
+/* Internal dma controller registers */
+
+/* Bitfields of Global Configuration Register */
+#define LOONGSON2_MMC_DMA_64BIT_EN	BIT(0) /* 1: 64 bit support */
+#define LOONGSON2_MMC_DMA_UNCOHERENT_EN	BIT(1) /* 0: cache, 1: uncache */
+#define LOONGSON2_MMC_DMA_ASK_VALID	BIT(2)
+#define LOONGSON2_MMC_DMA_START		BIT(3) /* DMA start operation */
+#define LOONGSON2_MMC_DMA_STOP		BIT(4) /* DMA stop operation */
+#define LOONGSON2_MMC_DMA_CONFIG_MASK	GENMASK_ULL(4, 0) /* DMA controller config bits mask */
+
+/* Bitfields of ndesc_addr field of HW descriptor */
+#define LOONGSON2_MMC_DMA_DESC_EN	BIT(0) /*1: The next descriptor is valid */
+#define LOONGSON2_MMC_DMA_DESC_ADDR_LOW	GENMASK(31, 1)
+
+/* Bitfields of cmd field of HW descriptor */
+#define LOONGSON2_MMC_DMA_INT		BIT(1)	/* Enable DMA interrupts */
+#define LOONGSON2_MMC_DMA_DATA_DIR	BIT(12) /* 1: write to device, 0: read from device */
+
 /* Loongson-2K1000 SDIO2 DMA routing register */
 #define LS2K1000_SDIO_DMA_MASK		GENMASK(17, 15)
 #define LS2K1000_DMA0_CONF		0x0
@@ -180,6 +230,8 @@ struct loongson2_mmc_host {
 	struct resource *res;
 	struct clk *clk;
 	u64 rate;
+	void *sg_cpu;
+	dma_addr_t sg_dma;
 	int dma_complete;
 	struct dma_chan *chan;
 	int cmd_is_stop;
@@ -192,6 +244,7 @@ struct loongson2_mmc_host {
 struct loongson2_mmc_pdata {
 	const struct regmap_config *regmap_config;
 	void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
+	void (*fix_data_timeout)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
 	int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev);
 	int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data);
 	void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev);
@@ -282,6 +335,9 @@ static void loongson2_mmc_send_request(struct mmc_host *mmc)
 		return;
 	}
 
+	if (host->pdata->fix_data_timeout)
+		host->pdata->fix_data_timeout(host, cmd);
+
 	loongson2_mmc_send_command(host, cmd);
 
 	/* Fix deselect card */
@@ -426,6 +482,36 @@ static irqreturn_t loongson2_mmc_irq(int irq, void *devid)
 	return IRQ_WAKE_THREAD;
 }
 
+static void loongson2_mmc_dll_mode_init(struct loongson2_mmc_host *host)
+{
+	u32 val, pad_delay, delay, ret;
+
+	regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_SEL,
+			   LOONGSON2_MMC_SEL_DATA, LOONGSON2_MMC_SEL_DATA);
+
+	val = FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME, 0xc8)
+	    | FIELD_PREP(LOONGSON2_MMC_DLLCTL_INCRE, 0x1)
+	    | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START, 0x1)
+	    | FIELD_PREP(LOONGSON2_MMC_DLLCTL_CLK_MODE, 0x1)
+	    | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START_BIT, 0x1)
+	    | FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME_BPASS, 0xf);
+
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_DLLCTL, val);
+
+	ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_DLLVAL, val,
+				       (val & LOONGSON2_MMC_DLLVAL_DONE), 0, 4000);
+	if (ret < 0)
+		return;
+
+	regmap_read(host->regmap, LOONGSON2_MMC_REG_DLLVAL, &val);
+	pad_delay = FIELD_GET(GENMASK(7, 1), val);
+
+	delay = FIELD_PREP(LOONGSON2_MMC_DELAY_PAD, pad_delay)
+	      | FIELD_PREP(LOONGSON2_MMC_DELAY_RD, pad_delay + 1);
+
+	regmap_write(host->regmap, LOONGSON2_MMC_REG_DELAY, delay);
+}
+
 static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_ios *ios)
 {
 	u32 pre;
@@ -438,6 +524,10 @@ static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_io
 
 	regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL,
 			   LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK);
+
+	/* EMMC DLL mode setting */
+	if (ios->timing == MMC_TIMING_UHS_DDR50 || ios->timing == MMC_TIMING_MMC_DDR52)
+		loongson2_mmc_dll_mode_init(host);
 }
 
 static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
@@ -655,6 +745,127 @@ static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = {
 	.release_dma		= loongson2_mmc_release_external_dma,
 };
 
+static const struct regmap_config ls2k2000_mmc_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.max_register = LOONGSON2_MMC_REG_RDMA_HI,
+};
+
+static void ls2k2000_mmc_reorder_cmd_data(struct loongson2_mmc_host *host,
+					  struct mmc_command *cmd)
+{
+	struct scatterlist *sg;
+	u32 *data;
+	int i, j;
+
+	if (cmd->opcode != SD_SWITCH || mmc_cmd_type(cmd) != MMC_CMD_ADTC)
+		return;
+
+	for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) {
+		data = sg_virt(&sg[i]);
+		for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++)
+			data[j] = bitrev8x4(data[j]);
+	}
+}
+
+/*
+ * This is a controller hardware defect. Single/multiple block write commands
+ * must be sent after the TX FULL flag is set, otherwise a data timeout interrupt
+ * will occur.
+ */
+static void ls2k2000_mmc_fix_data_timeout(struct loongson2_mmc_host *host,
+					  struct mmc_command *cmd)
+{
+	int val;
+
+	if (cmd->opcode != MMC_WRITE_BLOCK && cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK)
+		return;
+
+	regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val,
+				 (val & LOONGSON2_MMC_FSTS_TXFULL), 0, 500);
+}
+
+static int loongson2_mmc_prepare_internal_dma(struct loongson2_mmc_host *host,
+					      struct mmc_data *data)
+{
+	struct loongson2_dma_desc *pdes = (struct loongson2_dma_desc *)host->sg_cpu;
+	struct mmc_host *mmc = mmc_from_priv(host);
+	dma_addr_t next_desc = host->sg_dma;
+	struct scatterlist *sg;
+	int reg_lo, reg_hi;
+	u64 dma_order;
+	int i, ret;
+
+	ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len,
+			 mmc_get_dma_dir(data));
+	if (!ret)
+		return -ENOMEM;
+
+	for_each_sg(data->sg, sg, data->sg_len, i) {
+		pdes[i].len = sg_dma_len(&sg[i]) / 4;
+		pdes[i].step_len = 0;
+		pdes[i].step_times = 1;
+		pdes[i].mem_addr = lower_32_bits(sg_dma_address(&sg[i]));
+		pdes[i].high_mem_addr = upper_32_bits(sg_dma_address(&sg[i]));
+		pdes[i].apb_addr = host->res->start + LOONGSON2_MMC_REG_DATA;
+		pdes[i].cmd = LOONGSON2_MMC_DMA_INT;
+
+		if (data->flags & MMC_DATA_READ) {
+			reg_lo = LOONGSON2_MMC_REG_RDMA_LO;
+			reg_hi = LOONGSON2_MMC_REG_RDMA_HI;
+		} else {
+			pdes[i].cmd |= LOONGSON2_MMC_DMA_DATA_DIR;
+			reg_lo = LOONGSON2_MMC_REG_WDMA_LO;
+			reg_hi = LOONGSON2_MMC_REG_WDMA_HI;
+		}
+
+		next_desc += sizeof(struct loongson2_dma_desc);
+		pdes[i].ndesc_addr = lower_32_bits(next_desc) |
+				     LOONGSON2_MMC_DMA_DESC_EN;
+		pdes[i].high_ndesc_addr = upper_32_bits(next_desc);
+	}
+
+	/* Setting the last descriptor enable bit */
+	pdes[i - 1].ndesc_addr &= ~LOONGSON2_MMC_DMA_DESC_EN;
+
+	dma_order = (host->sg_dma & ~LOONGSON2_MMC_DMA_CONFIG_MASK) |
+		    LOONGSON2_MMC_DMA_64BIT_EN |
+		    LOONGSON2_MMC_DMA_START;
+
+	regmap_write(host->regmap, reg_hi, upper_32_bits(dma_order));
+	regmap_write(host->regmap, reg_lo, lower_32_bits(dma_order));
+
+	return 0;
+}
+
+static int loongson2_mmc_set_internal_dma(struct loongson2_mmc_host *host,
+					  struct platform_device *pdev)
+{
+	host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE,
+					  &host->sg_dma, GFP_KERNEL);
+	if (!host->sg_cpu)
+		return -ENOMEM;
+
+	memset(host->sg_cpu, 0, PAGE_SIZE);
+	return 0;
+}
+
+static void loongson2_mmc_release_internal_dma(struct loongson2_mmc_host *host,
+					       struct device *dev)
+{
+	dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
+}
+
+static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = {
+	.regmap_config		= &ls2k2000_mmc_regmap_config,
+	.reorder_cmd_data	= ls2k2000_mmc_reorder_cmd_data,
+	.fix_data_timeout	= ls2k2000_mmc_fix_data_timeout,
+	.setting_dma		= loongson2_mmc_set_internal_dma,
+	.prepare_dma		= loongson2_mmc_prepare_internal_dma,
+	.release_dma		= loongson2_mmc_release_internal_dma,
+};
+
 static int loongson2_mmc_resource_request(struct platform_device *pdev,
 					  struct loongson2_mmc_host *host)
 {
@@ -777,6 +988,7 @@ static void loongson2_mmc_remove(struct platform_device *pdev)
 static const struct of_device_id loongson2_mmc_of_ids[] = {
 	{ .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata },
 	{ .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata },
+	{ .compatible = "loongson,ls2k2000-mmc", .data = &ls2k2000_mmc_pdata },
 	{ },
 };
 MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids);
-- 
2.47.1


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

* Re: [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding
  2025-06-18  8:07 ` [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
@ 2025-06-18 13:44   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 11+ messages in thread
From: Krzysztof Kozlowski @ 2025-06-18 13:44 UTC (permalink / raw)
  To: Binbin Zhou, Binbin Zhou, Huacai Chen, Ulf Hansson
  Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang

On 18/06/2025 10:07, Binbin Zhou wrote:
> Add the Loongson-2K SoC's SD/SDIO/eMMC controller binding with DT schema
> format using json-schema.
> 
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
>  .../bindings/mmc/loongson,ls2k0500-mmc.yaml   | 69 +++++++++++++++++++
>  MAINTAINERS                                   |  6 ++
<form letter>
Please use scripts/get_maintainers.pl to get a list of necessary people
and lists to CC. It might happen, that command when run on an older
kernel, gives you outdated entries. Therefore please be sure you base
your patches on recent Linux kernel.

Tools like b4 or scripts/get_maintainer.pl provide you proper list of
people, so fix your workflow. Tools might also fail if you work on some
ancient tree (don't, instead use mainline) or work on fork of kernel
(don't, instead use mainline). Just use b4 and everything should be
fine, although remember about `b4 prep --auto-to-cc` if you added new
patches to the patchset.

You missed at least devicetree list (maybe more), so this won't be
tested by automated tooling. Performing review on untested code might be
a waste of time.

Please kindly resend and include all necessary To/Cc entries.
</form letter>


Best regards,
Krzysztof

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

* Re: [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver
  2025-06-18  8:07 ` [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou
@ 2025-06-19  4:02   ` Huacai Chen
  2025-06-20 13:20   ` kernel test robot
  1 sibling, 0 replies; 11+ messages in thread
From: Huacai Chen @ 2025-06-19  4:02 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Ulf Hansson, Xuerui Wang, loongarch,
	linux-mmc, wanghongliang

Hi, Binbin,

On Wed, Jun 18, 2025 at 4:08 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
>
> The MMC controllers on the Loongson-2K series CPUs are similar,
> except for the interface characteristics and the use of DMA controllers.
>
> This patch describes the MMC controllers on the Loongson-2K0500/2K1000,
> with the distinguishing feature being the use of an externally shared
> APBDMA engine.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
>  MAINTAINERS                      |   1 +
>  drivers/mmc/host/Kconfig         |  13 +
>  drivers/mmc/host/Makefile        |   1 +
>  drivers/mmc/host/loongson2-mmc.c | 820 +++++++++++++++++++++++++++++++
>  4 files changed, 835 insertions(+)
>  create mode 100644 drivers/mmc/host/loongson2-mmc.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2d1cdd2cfc2b..4a4892613c66 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14180,6 +14180,7 @@ M:      Binbin Zhou <zhoubinbin@loongson.cn>
>  L:     linux-mmc@vger.kernel.org
>  S:     Maintained
>  F:     Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml
> +F:     drivers/mmc/host/loongson2-mmc.c
>
>  LOONGSON-2 SOC SERIES PM DRIVER
>  M:     Yinbo Zhu <zhuyinbo@loongson.cn>
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index c3f0f41a426d..7232de1c0688 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -1111,6 +1111,19 @@ config MMC_OWL
>           This selects support for the SD/MMC Host Controller on
>           Actions Semi Owl SoCs.
>
> +config MMC_LOONGSON2
> +       tristate "Loongson-2K SD/SDIO/eMMC Host Interface support"
> +       depends on LOONGARCH || COMPILE_TEST
> +       depends on HAS_DMA
> +       help
> +         This selects support for the SD/SDIO/eMMC Host Controller on
> +         Loongson-2K series CPUs.
> +
> +         To compile this driver as a module, choose M here: the
> +         module will be called mmc_loongson2.
> +
> +         If unsure, say N.
> +
>  config MMC_SDHCI_EXTERNAL_DMA
>         bool
>
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index 75bafc7b162b..5057fea8afb6 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -72,6 +72,7 @@ obj-$(CONFIG_MMC_USDHI6ROL0)  += usdhi6rol0.o
>  obj-$(CONFIG_MMC_TOSHIBA_PCI)  += toshsd.o
>  obj-$(CONFIG_MMC_BCM2835)      += bcm2835.o
>  obj-$(CONFIG_MMC_OWL)          += owl-mmc.o
> +obj-$(CONFIG_MMC_LOONGSON2)    += loongson2-mmc.o
>
>  obj-$(CONFIG_MMC_REALTEK_PCI)  += rtsx_pci_sdmmc.o
>  obj-$(CONFIG_MMC_REALTEK_USB)  += rtsx_usb_sdmmc.o
> diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c
> new file mode 100644
> index 000000000000..872f5dc21b21
> --- /dev/null
> +++ b/drivers/mmc/host/loongson2-mmc.c
> @@ -0,0 +1,820 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Loongson-2K MMC/SDIO controller driver
> + *
> + * Copyright (C) 2018-2025 Loongson Technology Corporation Limited.
> + *
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitrev.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/dmaengine.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/mmc/core.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/sd.h>
> +#include <linux/mmc/sdio.h>
> +#include <linux/mmc/slot-gpio.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +#define LOONGSON2_MMC_REG_CTL          0x00 /* Control Register */
> +#define LOONGSON2_MMC_REG_PRE          0x04 /* Prescaler Register */
> +#define LOONGSON2_MMC_REG_CARG         0x08 /* Command Register */
> +#define LOONGSON2_MMC_REG_CCTL         0x0c /* Command Control Register */
> +#define LOONGSON2_MMC_REG_CSTS         0x10 /* Command Status Register */
> +#define LOONGSON2_MMC_REG_RSP0         0x14 /* Command Response Register 0 */
> +#define LOONGSON2_MMC_REG_RSP1         0x18 /* Command Response Register 1 */
> +#define LOONGSON2_MMC_REG_RSP2         0x1c /* Command Response Register 2 */
> +#define LOONGSON2_MMC_REG_RSP3         0x20 /* Command Response Register 3 */
> +#define LOONGSON2_MMC_REG_TIMER                0x24 /* Data Timeout Register */
> +#define LOONGSON2_MMC_REG_BSIZE                0x28 /* Block Size Register */
> +#define LOONGSON2_MMC_REG_DCTL         0x2c /* Data Control Register */
> +#define LOONGSON2_MMC_REG_DCNT         0x30 /* Data Counter Register */
> +#define LOONGSON2_MMC_REG_DSTS         0x34 /* Data Status Register */
> +#define LOONGSON2_MMC_REG_FSTS         0x38 /* FIFO Status Register */
> +#define LOONGSON2_MMC_REG_INT          0x3c /* Interrupt Register */
> +#define LOONGSON2_MMC_REG_DATA         0x40 /* Data Register */
> +#define LOONGSON2_MMC_REG_IEN          0x64 /* Interrupt Enable Register */
> +
> +/* Bitfields of control register */
> +#define LOONGSON2_MMC_CTL_ENCLK                BIT(0)
> +#define LOONGSON2_MMC_CTL_EXTCLK       BIT(1)
> +#define LOONGSON2_MMC_CTL_RESET                BIT(8)
> +
> +/* Bitfields of prescaler register */
> +#define LOONGSON2_MMC_PRE              GENMASK(9, 0)
> +#define LOONGSON2_MMC_PRE_EN           BIT(31)
> +
> +/* Bitfields of command control register */
> +#define LOONGSON2_MMC_CCTL_INDEX       GENMASK(5, 0)
> +#define LOONGSON2_MMC_CCTL_HOST                BIT(6)
> +#define LOONGSON2_MMC_CCTL_START       BIT(8)
> +#define LOONGSON2_MMC_CCTL_WAIT_RSP    BIT(9)
> +#define LOONGSON2_MMC_CCTL_LONG_RSP    BIT(10)
> +#define LOONGSON2_MMC_CCTL_ABORT       BIT(12)
> +#define LOONGSON2_MMC_CCTL_CHECK       BIT(13)
> +#define LOONGSON2_MMC_CCTL_SDIO                BIT(14)
> +#define LOONGSON2_MMC_CCTL_CMD6                BIT(18)
> +
> +/* Bitfields of command status register */
> +#define LOONGSON2_MMC_CSTS_INDEX       GENMASK(7, 0)
> +#define LOONGSON2_MMC_CSTS_ON          BIT(8)
> +#define LOONGSON2_MMC_CSTS_RSP         BIT(9)
> +#define LOONGSON2_MMC_CSTS_TIMEOUT     BIT(10)
> +#define LOONGSON2_MMC_CSTS_END         BIT(11)
> +#define LOONGSON2_MMC_CSTS_CRC_ERR     BIT(12)
> +#define LOONGSON2_MMC_CSTS_AUTO_STOP   BIT(13)
> +#define LOONGSON2_MMC_CSTS_FIN         BIT(14)
> +
> +/* Bitfields of data timeout register */
> +#define LOONGSON2_MMC_DTIMR            GENMASK(23, 0)
> +
> +/* Bitfields of block size register */
> +#define LOONGSON2_MMC_BSIZE            GENMASK(11, 0)
> +
> +/* Bitfields of data control register */
> +#define LOONGSON2_MMC_DCTL_BNUM                GENMASK(11, 0)
> +#define LOONGSON2_MMC_DCTL_START       BIT(14)
> +#define LOONGSON2_MMC_DCTL_ENDMA       BIT(15)
> +#define LOONGSON2_MMC_DCTL_WIDE                BIT(16)
> +#define LOONGSON2_MMC_DCTL_RWAIT       BIT(17)
> +#define LOONGSON2_MMC_DCTL_IO_SUSPEND  BIT(18)
> +#define LOONGSON2_MMC_DCTL_IO_RESUME   BIT(19)
> +#define LOONGSON2_MMC_DCTL_RW_RESUME   BIT(20)
> +#define LOONGSON2_MMC_DCTL_8BIT_BUS    BIT(26)
> +
> +/* Bitfields of sata counter register */
> +#define LOONGSON2_MMC_DCNT_BNUM                GENMASK(11, 0)
> +#define LOONGSON2_MMC_DCNT_BYTE                GENMASK(23, 12)
> +
> +/* Bitfields of command status register */
> +#define LOONGSON2_MMC_DSTS_RXON                BIT(0)
> +#define LOONGSON2_MMC_DSTS_TXON                BIT(1)
> +#define LOONGSON2_MMC_DSTS_SBITERR     BIT(2)
> +#define LOONGSON2_MMC_DSTS_BUSYFIN     BIT(3)
> +#define LOONGSON2_MMC_DSTS_XFERFIN     BIT(4)
> +#define LOONGSON2_MMC_DSTS_DTIMEOUT    BIT(5)
> +#define LOONGSON2_MMC_DSTS_RXCRC       BIT(6)
> +#define LOONGSON2_MMC_DSTS_TXCRC       BIT(7)
> +#define LOONGSON2_MMC_DSTS_IRQ         BIT(8)
> +#define LOONGSON2_MMC_DSTS_START       BIT(13)
> +#define LOONGSON2_MMC_DSTS_RESUME      BIT(15)
> +#define LOONGSON2_MMC_DSTS_SUSPEND     BIT(16)
> +
> +/* Bitfields of interrupt register */
> +#define LOONGSON2_MMC_INT_DFIN         BIT(0)
> +#define LOONGSON2_MMC_INT_DTIMEOUT     BIT(1)
> +#define LOONGSON2_MMC_INT_RXCRC                BIT(2)
> +#define LOONGSON2_MMC_INT_TXCRC                BIT(3)
> +#define LOONGSON2_MMC_INT_PROGERR      BIT(4)
> +#define LOONGSON2_MMC_INT_SDIOIRQ      BIT(5)
> +#define LOONGSON2_MMC_INT_CSENT                BIT(6)
> +#define LOONGSON2_MMC_INT_CTIMEOUT     BIT(7)
> +#define LOONGSON2_MMC_INT_RESPCRC      BIT(8)
> +#define LOONGSON2_MMC_INT_BUSYEND      BIT(9)
> +
> +/* Bitfields of interrupt enable register */
> +#define LOONGSON2_MMC_IEN_DFIN         BIT(0)
> +#define LOONGSON2_MMC_IEN_DTIMEOUT     BIT(1)
> +#define LOONGSON2_MMC_IEN_RXCRC                BIT(2)
> +#define LOONGSON2_MMC_IEN_TXCRC                BIT(3)
> +#define LOONGSON2_MMC_IEN_PROGERR      BIT(4)
> +#define LOONGSON2_MMC_IEN_SDIOIRQ      BIT(5)
> +#define LOONGSON2_MMC_IEN_CSENT                BIT(6)
> +#define LOONGSON2_MMC_IEN_CTIMEOUT     BIT(7)
> +#define LOONGSON2_MMC_IEN_RESPCRC      BIT(8)
> +#define LOONGSON2_MMC_IEN_BUSYEND      BIT(9)
> +
> +#define LOONGSON2_MMC_IEN_ALL          GENMASK(9, 0)
> +#define LOONGSON2_MMC_INT_CLEAR                GENMASK(9, 0)
> +
> +/* Loongson-2K1000 SDIO2 DMA routing register */
> +#define LS2K1000_SDIO_DMA_MASK         GENMASK(17, 15)
> +#define LS2K1000_DMA0_CONF             0x0
> +#define LS2K1000_DMA1_CONF             0x1
> +#define LS2K1000_DMA2_CONF             0x2
> +#define LS2K1000_DMA3_CONF             0x3
> +#define LS2K1000_DMA4_CONF             0x4
> +
> +/* Loongson-2K0500 SDIO2 DMA routing register */
> +#define LS2K0500_SDIO_DMA_MASK         GENMASK(15, 14)
> +#define LS2K0500_DMA0_CONF             0x1
> +#define LS2K0500_DMA1_CONF             0x2
> +#define LS2K0500_DMA2_CONF             0x3
> +
> +enum loongson2_mmc_state {
> +       STATE_NONE,
> +       STATE_FINALIZE,
> +       STATE_CMDSENT,
> +       STATE_RSPFIN,
> +       STATE_XFERFINISH,
> +       STATE_XFERFINISH_RSPFIN,
> +};
> +
> +struct loongson2_dma_desc {
> +       u32 ndesc_addr;
> +       u32 mem_addr;
> +       u32 apb_addr;
> +       u32 len;
> +       u32 step_len;
> +       u32 step_times;
> +       u32 cmd;
> +       u32 stats;
> +       u32 high_ndesc_addr;
> +       u32 high_mem_addr;
> +       u32 reserved[2];
> +} __packed;
> +
> +struct loongson2_mmc_host {
> +       struct device *dev;
> +       struct mmc_request *mrq;
> +       struct regmap *regmap;
> +       struct resource *res;
> +       struct clk *clk;
> +       u64 rate;
> +       int dma_complete;
> +       struct dma_chan *chan;
> +       int cmd_is_stop;
> +       int bus_width;
> +       spinlock_t lock; /* Prevent races with irq handler */
> +       enum loongson2_mmc_state state;
> +       const struct loongson2_mmc_pdata *pdata;
> +};
> +
> +struct loongson2_mmc_pdata {
> +       const struct regmap_config *regmap_config;
> +       void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
> +       int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev);
> +       int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data);
> +       void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev);
> +};
> +
> +static void loongson2_mmc_send_command(struct loongson2_mmc_host *host,
> +                                      struct mmc_command *cmd)
> +{
> +       u32 cctrl;
> +
> +       if (cmd->data)
> +               host->state = STATE_XFERFINISH_RSPFIN;
> +       else if (cmd->flags & MMC_RSP_PRESENT)
> +               host->state = STATE_RSPFIN;
> +       else
> +               host->state = STATE_CMDSENT;
> +
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_CARG, cmd->arg);
> +
> +       cctrl = FIELD_PREP(LOONGSON2_MMC_CCTL_INDEX, cmd->opcode);
> +       cctrl |= LOONGSON2_MMC_CCTL_HOST | LOONGSON2_MMC_CCTL_START;
> +
> +       if (cmd->opcode == SD_SWITCH && cmd->data)
> +               cctrl |= LOONGSON2_MMC_CCTL_CMD6;
> +
> +       if (cmd->flags & MMC_RSP_PRESENT)
> +               cctrl |= LOONGSON2_MMC_CCTL_WAIT_RSP;
> +
> +       if (cmd->flags & MMC_RSP_136)
> +               cctrl |= LOONGSON2_MMC_CCTL_LONG_RSP;
> +
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_CCTL, cctrl);
> +}
> +
> +static int loongson2_mmc_setup_data(struct loongson2_mmc_host *host,
> +                                   struct mmc_data *data)
> +{
> +       u32 dctrl;
> +
> +       if ((data->blksz & 3) != 0)
> +               return -EINVAL;
> +
> +       dctrl = FIELD_PREP(LOONGSON2_MMC_DCTL_BNUM, data->blocks);
> +       dctrl |= LOONGSON2_MMC_DCTL_START | LOONGSON2_MMC_DCTL_ENDMA;
> +
> +       if (host->bus_width == MMC_BUS_WIDTH_4)
> +               dctrl |= LOONGSON2_MMC_DCTL_WIDE;
> +       else if (host->bus_width == MMC_BUS_WIDTH_8)
> +               dctrl |= LOONGSON2_MMC_DCTL_8BIT_BUS;
> +
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_DCTL, dctrl);
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_BSIZE, data->blksz);
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_TIMER, U32_MAX);
> +
> +       return 0;
> +}
> +
> +static int loongson2_mmc_prepare_dma(struct loongson2_mmc_host *host,
> +                                    struct mmc_data *data)
> +{
> +       int ret;
> +
> +       if (!data)
> +               return 0;
> +
> +       ret = loongson2_mmc_setup_data(host, data);
> +       if (ret)
> +               return ret;
> +
> +       host->dma_complete = 0;
> +
> +       return host->pdata->prepare_dma(host, data);
> +}
> +
> +static void loongson2_mmc_send_request(struct mmc_host *mmc)
> +{
> +       int ret;
> +       struct loongson2_mmc_host *host = mmc_priv(mmc);
> +       struct mmc_request *mrq = host->mrq;
> +       struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
> +
> +       ret = loongson2_mmc_prepare_dma(host, cmd->data);
> +       if (ret) {
> +               dev_err(host->dev, "DMA data prepared failed with %d\n", ret);
> +               cmd->error = ret;
> +               cmd->data->error = ret;
> +               mmc_request_done(mmc, mrq);
> +               return;
> +       }
> +
> +       loongson2_mmc_send_command(host, cmd);
> +
> +       /* Fix deselect card */
> +       if (cmd->opcode == MMC_SELECT_CARD && cmd->arg == 0) {
> +               cmd->error = 0;
> +               mmc_request_done(mmc, mrq);
> +       }
> +}
> +
> +static irqreturn_t loongson2_mmc_irq_worker(int irq, void *devid)
> +{
> +       struct loongson2_mmc_host *host = (struct loongson2_mmc_host *)devid;
> +       struct mmc_host *mmc = mmc_from_priv(host);
> +       struct mmc_request *mrq = host->mrq;
> +       struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
> +
> +       if (cmd->data)
> +               dma_unmap_sg(mmc_dev(mmc), cmd->data->sg, cmd->data->sg_len,
> +                            mmc_get_dma_dir(cmd->data));
> +
> +       if (cmd->data && !cmd->error &&
> +           !cmd->data->error && !host->dma_complete)
> +               return IRQ_HANDLED;
> +
> +       /* Read response from controller. */
> +       regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP0, &cmd->resp[0]);
> +       regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP1, &cmd->resp[1]);
> +       regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP2, &cmd->resp[2]);
> +       regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP3, &cmd->resp[3]);
> +
> +       /* Cleanup controller */
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_CARG, 0);
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_CCTL, 0);
> +
> +       if (cmd->data && cmd->error)
> +               cmd->data->error = cmd->error;
> +
> +       if (cmd->data && cmd->data->stop && !host->cmd_is_stop) {
> +               host->cmd_is_stop = 1;
> +               loongson2_mmc_send_request(mmc);
> +               return IRQ_HANDLED;
> +       }
> +
> +       /* If we have no data transfer we are finished here */
> +       if (!mrq->data)
> +               goto request_done;
> +
> +       /* Calculate the amount of bytes transfer if there was no error */
> +       if (mrq->data->error == 0) {
> +               mrq->data->bytes_xfered =
> +                       (mrq->data->blocks * mrq->data->blksz);
> +       } else {
> +               mrq->data->bytes_xfered = 0;
> +       }
> +
> +request_done:
> +       host->state = STATE_NONE;
> +       host->mrq = NULL;
> +       mmc_request_done(mmc, mrq);
> +       return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t loongson2_mmc_irq(int irq, void *devid)
> +{
> +       struct loongson2_mmc_host *host = (struct loongson2_mmc_host *)devid;
> +       struct mmc_host *mmc = mmc_from_priv(host);
> +       struct mmc_command *cmd;
> +       unsigned long iflags;
> +       u32 dsts, imsk;
> +
> +       regmap_read(host->regmap, LOONGSON2_MMC_REG_INT, &imsk);
> +       regmap_read(host->regmap, LOONGSON2_MMC_REG_DSTS, &dsts);
> +
> +       if ((dsts & LOONGSON2_MMC_DSTS_IRQ) &&
> +           (imsk & LOONGSON2_MMC_INT_SDIOIRQ)) {
> +               regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_INT,
> +                                  LOONGSON2_MMC_INT_SDIOIRQ,
> +                                  LOONGSON2_MMC_INT_SDIOIRQ);
The last two lines can be combined.

> +
> +               sdio_signal_irq(mmc);
> +               return IRQ_HANDLED;
> +       }
> +
> +       spin_lock_irqsave(&host->lock, iflags);
> +
> +       if (host->state == STATE_NONE || host->state == STATE_FINALIZE ||
> +           !host->mrq)
The if condition can be put  in one line here.

> +               goto irq_out;
> +
> +       cmd = host->cmd_is_stop ? host->mrq->stop : host->mrq->cmd;
> +       if (!cmd)
> +               goto irq_out;
> +
> +       cmd->error = 0;
> +
> +       if (imsk & LOONGSON2_MMC_INT_CTIMEOUT) {
> +               cmd->error = -ETIMEDOUT;
> +               goto close_transfer;
> +       }
> +
> +       if (imsk & LOONGSON2_MMC_INT_CSENT) {
> +               if (host->state == STATE_RSPFIN || host->state == STATE_CMDSENT)
> +                       goto close_transfer;
> +
> +               if (host->state == STATE_XFERFINISH_RSPFIN)
> +                       host->state = STATE_XFERFINISH;
> +       }
> +
> +       if (!cmd->data)
> +               goto irq_out;
> +
> +       if (imsk & (LOONGSON2_MMC_INT_RXCRC | LOONGSON2_MMC_INT_TXCRC)) {
> +               cmd->data->error = -EILSEQ;
> +               goto close_transfer;
> +       }
> +
> +       if (imsk & LOONGSON2_MMC_INT_DTIMEOUT) {
> +               cmd->data->error = -ETIMEDOUT;
> +               goto close_transfer;
> +       }
> +
> +       if (imsk & LOONGSON2_MMC_INT_DFIN) {
> +               if (host->state == STATE_XFERFINISH) {
> +                       host->dma_complete = 1;
> +                       goto close_transfer;
> +               }
> +
> +               if (host->state == STATE_XFERFINISH_RSPFIN)
> +                       host->state = STATE_RSPFIN;
> +       }
> +
> +irq_out:
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, imsk);
> +       spin_unlock_irqrestore(&host->lock, iflags);
> +       return IRQ_HANDLED;
> +
> +close_transfer:
> +       host->state = STATE_FINALIZE;
> +       host->pdata->reorder_cmd_data(host, cmd);
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, imsk);
> +       spin_unlock_irqrestore(&host->lock, iflags);
> +       return IRQ_WAKE_THREAD;
> +}
> +
> +static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_ios *ios)
> +{
> +       u32 pre;
> +
> +       pre = DIV_ROUND_UP(host->rate, ios->clock);
> +       if (pre > 255)
> +               pre = 255;
> +
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_PRE, pre | LOONGSON2_MMC_PRE_EN);
> +
> +       regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL,
> +                          LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK);
> +}
> +
> +static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> +       struct loongson2_mmc_host *host = mmc_priv(mmc);
> +       int ret;
> +
> +       if (ios->power_mode == MMC_POWER_UP) {
> +               if (!IS_ERR(mmc->supply.vmmc)) {
> +                       ret = mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd);
> +                       if (ret) {
> +                               dev_err(host->dev, "failed to enable vmmc regulator\n");
> +                               /*return, if failed turn on vmmc*/
There should be spaces between /**/ and the comments, and the whole
comments can be put after the "return;" statement.

> +                               return;
> +                       }
> +               }
> +               regmap_write(host->regmap, LOONGSON2_MMC_REG_CTL, LOONGSON2_MMC_CTL_RESET);
> +               mdelay(10);
> +               regmap_write(host->regmap, LOONGSON2_MMC_REG_CTL, LOONGSON2_MMC_CTL_EXTCLK);
> +               regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, LOONGSON2_MMC_IEN_ALL);
> +               regmap_write(host->regmap, LOONGSON2_MMC_REG_IEN, LOONGSON2_MMC_INT_CLEAR);
> +       } else if (ios->power_mode == MMC_POWER_OFF) {
> +               regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL,
> +                                  LOONGSON2_MMC_CTL_RESET, LOONGSON2_MMC_CTL_RESET);
> +               if (!IS_ERR(mmc->supply.vmmc))
> +                       mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0);
> +               return;
> +       }
> +
> +       loongson2_mmc_set_clk(host, ios);
> +
> +       host->bus_width = ios->bus_width;
> +}
> +
> +static void loongson2_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
> +{
> +       struct loongson2_mmc_host *host = mmc_priv(mmc);
> +
> +       host->cmd_is_stop = 0;
> +       host->mrq = mrq;
> +       loongson2_mmc_send_request(mmc);
> +}
> +
> +static void loongson2_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
> +{
> +       struct loongson2_mmc_host *host = mmc_priv(mmc);
> +
> +       regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_IEN,
> +                          LOONGSON2_MMC_INT_SDIOIRQ, enable);
The statement can be put  in one line here, too.

> +}
> +
> +static void loongson2_mmc_ack_sdio_irq(struct mmc_host *mmc)
> +{
> +       loongson2_mmc_enable_sdio_irq(mmc, 1);
> +}
> +
> +static struct mmc_host_ops loongson2_mmc_ops = {
> +       .request        = loongson2_mmc_request,
> +       .set_ios        = loongson2_mmc_set_ios,
> +       .get_ro         = mmc_gpio_get_ro,
> +       .get_cd         = mmc_gpio_get_cd,
> +       .enable_sdio_irq = loongson2_mmc_enable_sdio_irq,
> +       .ack_sdio_irq   = loongson2_mmc_ack_sdio_irq,
> +};
> +
> +static const struct regmap_config ls2k1000_mmc_regmap_config = {
> +       .reg_bits = 32,
> +       .val_bits = 32,
> +       .reg_stride = 4,
> +       .max_register = LOONGSON2_MMC_REG_IEN,
> +};
> +
> +static int loongson2_reorder_cmd_list[] = { SD_APP_SEND_SCR, SD_APP_SEND_NUM_WR_BLKS,
> +                                           SD_APP_SD_STATUS, MMC_SEND_WRITE_PROT,
> +                                           SD_SWITCH };
The last two lines can be combined.

> +
> +/*
> + * According to SD spec, ACMD13, ACMD22, ACMD51 and CMD30
> + * response datas has different byte order with usual data packets.
> + * However sdio controller will send these datas in usual data format,
> + * so we need to adjust these datas to a protocol consistent byte order.
> + */
> +static void loongson2_mmc_reorder_cmd_data(struct loongson2_mmc_host *host,
> +                                          struct mmc_command *cmd)
> +{
> +       struct scatterlist *sg;
> +       u32 *data;
> +       int i, j;
> +
> +       if (mmc_cmd_type(cmd) != MMC_CMD_ADTC)
> +               return;
> +
> +       for (i = 0; i < ARRAY_SIZE(loongson2_reorder_cmd_list); i++)
> +               if (cmd->opcode == loongson2_reorder_cmd_list[i])
> +                       break;
> +
> +       if (i == ARRAY_SIZE(loongson2_reorder_cmd_list))
> +               return;
> +
> +       for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) {
> +               data = sg_virt(&sg[i]);
> +               for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++)
> +                       if (cmd->opcode == SD_SWITCH)
> +                               data[j] = bitrev8x4(data[j]);
> +                       else
> +                               data[j] = (__force u32)cpu_to_be32(data[j]);
> +       }
> +}
> +
> +static int loongson2_mmc_prepare_external_dma(struct loongson2_mmc_host *host,
> +                                             struct mmc_data *data)
> +{
> +       struct mmc_host *mmc = mmc_from_priv(host);
> +       struct dma_slave_config dma_conf = { };
> +       struct dma_async_tx_descriptor *desc;
> +       int ret;
> +
> +       ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len,
> +                        mmc_get_dma_dir(data));
The statement can be put  in one line here, too.

> +       if (!ret)
> +               return -ENOMEM;
> +
> +       dma_conf.src_addr = host->res->start + LOONGSON2_MMC_REG_DATA,
> +       dma_conf.dst_addr = host->res->start + LOONGSON2_MMC_REG_DATA,
> +       dma_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
> +       dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
> +       dma_conf.direction = !(data->flags & MMC_DATA_WRITE) ?
> +                            DMA_DEV_TO_MEM : DMA_MEM_TO_DEV;
The statement can be put  in one line here, too.

> +
> +       dmaengine_slave_config(host->chan, &dma_conf);
> +       desc = dmaengine_prep_slave_sg(host->chan, data->sg, data->sg_len,
> +                                      dma_conf.direction,
> +                                      DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
> +       if (!desc)
> +               goto unmap_exit;
> +
> +       dmaengine_submit(desc);
> +       dma_async_issue_pending(host->chan);
> +
> +       return 0;
> +
> +unmap_exit:
> +       dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len,
> +                    mmc_get_dma_dir(data));
The statement can be put  in one line here, too.

> +       return -ENOMEM;
> +}
> +
> +static void loongson2_mmc_release_external_dma(struct loongson2_mmc_host *host,
> +                                              struct device *dev)
> +{
> +       dma_release_channel(host->chan);
> +}
> +
> +static int ls2k0500_mmc_set_external_dma(struct loongson2_mmc_host *host,
> +                                        struct platform_device *pdev)
> +{
> +       int ret, val;
> +       void __iomem *regs;
> +
> +       regs = devm_platform_ioremap_resource(pdev, 1);
> +       if (IS_ERR(regs))
> +               return PTR_ERR(regs);
> +
> +       val = readl(regs);
> +       val |= FIELD_PREP(LS2K0500_SDIO_DMA_MASK, LS2K0500_DMA2_CONF);
> +       writel(val, regs);
> +
> +       host->chan = dma_request_chan(&pdev->dev, "rx-tx");
> +       ret = PTR_ERR_OR_ZERO(host->chan);
> +       if (ret) {
> +               dev_err(&pdev->dev, "cannot get DMA channel.\n");
> +               return  ret;
Too many spaces here.

> +       }
> +
> +       return 0;
> +}
> +
> +static struct loongson2_mmc_pdata ls2k0500_mmc_pdata = {
> +       .regmap_config          = &ls2k1000_mmc_regmap_config,
> +       .reorder_cmd_data       = loongson2_mmc_reorder_cmd_data,
> +       .setting_dma            = ls2k0500_mmc_set_external_dma,
> +       .prepare_dma            = loongson2_mmc_prepare_external_dma,
> +       .release_dma            = loongson2_mmc_release_external_dma,
> +};
> +
> +static int ls2k1000_mmc_set_external_dma(struct loongson2_mmc_host *host,
> +                                        struct platform_device *pdev)
> +{
> +       int ret, val;
> +       void __iomem *regs;
> +
> +       regs = devm_platform_ioremap_resource(pdev, 1);
> +       if (IS_ERR(regs))
> +               return PTR_ERR(regs);
> +
> +       val = readl(regs);
> +       val |= FIELD_PREP(LS2K1000_SDIO_DMA_MASK, LS2K1000_DMA1_CONF);
> +       writel(val, regs);
> +
> +       host->chan = dma_request_chan(&pdev->dev, "rx-tx");
> +       ret = PTR_ERR_OR_ZERO(host->chan);
> +       if (ret) {
> +               dev_err(&pdev->dev, "cannot get DMA channel.\n");
> +               return  ret;
> +       }
> +
> +       return 0;
> +}
> +
> +static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = {
> +       .regmap_config          = &ls2k1000_mmc_regmap_config,
> +       .reorder_cmd_data       = loongson2_mmc_reorder_cmd_data,
> +       .setting_dma            = ls2k1000_mmc_set_external_dma,
> +       .prepare_dma            = loongson2_mmc_prepare_external_dma,
> +       .release_dma            = loongson2_mmc_release_external_dma,
> +};
> +
> +static int loongson2_mmc_resource_request(struct platform_device *pdev,
> +                                         struct loongson2_mmc_host *host)
> +{
> +       struct device *dev = &pdev->dev;
> +       void __iomem *base;
> +       int ret, irq;
> +
> +       base = devm_platform_get_and_ioremap_resource(pdev, 0, &host->res);
> +       if (IS_ERR(base))
> +               return PTR_ERR(base);
> +
> +       host->regmap = devm_regmap_init_mmio(dev, base, host->pdata->regmap_config);
> +       if (IS_ERR(host->regmap))
> +               return PTR_ERR(host->regmap);
> +
> +       host->clk = devm_clk_get_optional_enabled(dev, NULL);
> +       if (IS_ERR(host->clk))
> +               return PTR_ERR(host->clk);
> +
> +       if (host->clk) {
> +               ret = devm_clk_rate_exclusive_get(dev, host->clk);
> +               if (ret)
> +                       return PTR_ERR(host->clk);
> +
> +               host->rate = clk_get_rate(host->clk);
> +       } else {
> +               /* For ACPI, we get rate through clock-frequency attribute */
> +               device_property_read_u64(dev, "clock-frequency", &host->rate);
> +       }
> +
> +       irq = platform_get_irq(pdev, 0);
> +       if (irq < 0)
> +               return irq;
> +
> +       ret = devm_request_threaded_irq(dev, irq, loongson2_mmc_irq,
> +                                       loongson2_mmc_irq_worker,
> +                                       IRQF_ONESHOT, "loongson2-mmc", host);
> +       if (ret)
> +               return ret;
> +
> +       ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
> +       if (ret)
> +               return ret;
> +
> +       return host->pdata->setting_dma(host, pdev);
> +}
> +
> +static int loongson2_mmc_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct loongson2_mmc_host *host;
> +       struct mmc_host *mmc;
> +       int ret;
> +
> +       mmc = devm_mmc_alloc_host(dev, sizeof(*host));
> +       if (!mmc)
> +               return -ENOMEM;
> +
> +       platform_set_drvdata(pdev, mmc);
> +
> +       host = mmc_priv(mmc);
> +       host->state = STATE_NONE;
> +       spin_lock_init(&host->lock);
> +
> +       host->pdata = device_get_match_data(dev);
> +       if (!host->pdata)
> +               return dev_err_probe(dev, -EINVAL, "Failed to get match data\n");
> +
> +       ret = loongson2_mmc_resource_request(pdev, host);
> +       if (ret)
> +               return dev_err_probe(dev, ret, "Failed to request resource\n");
> +
> +       mmc->ops = &loongson2_mmc_ops;
> +       mmc->f_min = DIV_ROUND_UP(host->rate, 256);
> +       mmc->f_max = host->rate;
> +       mmc->max_blk_count = 4095;
> +       mmc->max_blk_size = 4095;
> +       mmc->max_req_size = mmc->max_blk_count * mmc->max_blk_size;
> +       mmc->max_segs = 1;
> +       mmc->max_seg_size = mmc->max_req_size;
> +
> +       /* Process SDIO IRQs through the sdio_irq_work. */
> +       if (mmc->caps & MMC_CAP_SDIO_IRQ)
> +               mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD;
> +
> +       ret = mmc_regulator_get_supply(mmc);
> +       if (ret || mmc->ocr_avail == 0) {
> +               dev_warn(dev, "Can't get voltage, defaulting to 3.3V\n");
> +               mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
> +       }
> +
> +       ret = mmc_of_parse(mmc);
> +       if (ret) {
> +               dev_err(dev, "Failed to parse device node\n");
> +               goto free_dma;
> +       }
> +
> +       ret = mmc_add_host(mmc);
> +       if (ret) {
> +               dev_err(dev, "Failed to add mmc host\n");
> +               goto free_dma;
> +       }
> +
> +       return 0;
> +
> +free_dma:
> +       host->pdata->release_dma(host, dev);
> +       return ret;
> +}
> +
> +static void loongson2_mmc_remove(struct platform_device *pdev)
> +{
> +       struct mmc_host *mmc  = platform_get_drvdata(pdev);
> +       struct loongson2_mmc_host *host = mmc_priv(mmc);
> +
> +       mmc_remove_host(mmc);
> +       host->pdata->release_dma(host, &pdev->dev);
> +}
> +
> +static const struct of_device_id loongson2_mmc_of_ids[] = {
> +       { .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata },
> +       { .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata },
> +       { },
> +};
> +MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids);
> +
> +static int loongson2_mmc_suspend(struct device *dev)
> +{
> +       struct mmc_host *mmc  = dev_get_drvdata(dev);
Too many spaces here.

> +       struct loongson2_mmc_host *host = mmc_priv(mmc);
> +
> +       clk_disable_unprepare(host->clk);
> +
> +       return 0;
> +}
> +
> +static int loongson2_mmc_resume(struct device *dev)
> +{
> +       struct mmc_host *mmc  = dev_get_drvdata(dev);
Too many spaces here.

> +       struct loongson2_mmc_host *host = mmc_priv(mmc);
> +
> +       return clk_prepare_enable(host->clk);
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(loongson2_mmc_pm_ops, loongson2_mmc_suspend,
> +                               loongson2_mmc_resume);
The statement can be put  in one line here, too.


Huacai

> +
> +static struct platform_driver loongson2_mmc_driver = {
> +       .driver = {
> +               .name = "loongson2-mmc",
> +               .of_match_table = loongson2_mmc_of_ids,
> +               .pm = pm_ptr(&loongson2_mmc_pm_ops),
> +               .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> +       },
> +       .probe = loongson2_mmc_probe,
> +       .remove = loongson2_mmc_remove,
> +};
> +
> +module_platform_driver(loongson2_mmc_driver);
> +
> +MODULE_DESCRIPTION("Loongson-2K SD/SDIO/eMMC Interface driver");
> +MODULE_AUTHOR("Loongson Technology Corporation Limited");
> +MODULE_LICENSE("GPL");
> --
> 2.47.1
>

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

* Re: [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver
  2025-06-18  8:08 ` [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver Binbin Zhou
@ 2025-06-19  4:02   ` Huacai Chen
  2025-06-20  3:06     ` Binbin Zhou
  2025-06-23 11:24   ` Ulf Hansson
  1 sibling, 1 reply; 11+ messages in thread
From: Huacai Chen @ 2025-06-19  4:02 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Ulf Hansson, Xuerui Wang, loongarch,
	linux-mmc, wanghongliang

Hi, Binbin,

On Wed, Jun 18, 2025 at 4:08 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
>
> This patch describes the two MMC controllers of the Loongson-2K2000 SoC,
> one providing an eMMC interface and the other exporting an SD/SDIO
> interface.
>
> Compared to the Loongson-2K1000's MMC controllers, their internals are
> similar, except that we use an internally exclusive DMA engine instead of
> an externally shared APBDMA engine.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
>  drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++
>  1 file changed, 212 insertions(+)
>
> diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c
> index 872f5dc21b21..75144221a821 100644
> --- a/drivers/mmc/host/loongson2-mmc.c
> +++ b/drivers/mmc/host/loongson2-mmc.c
> @@ -44,6 +44,18 @@
>  #define LOONGSON2_MMC_REG_DATA         0x40 /* Data Register */
>  #define LOONGSON2_MMC_REG_IEN          0x64 /* Interrupt Enable Register */
>
> +/* EMMC DLL Mode Registers */
> +#define LOONGSON2_MMC_REG_DLLVAL       0xf0 /* DLL Master Lock-value Register */
> +#define LOONGSON2_MMC_REG_DLLCTL       0xf4 /* DLL Control Register */
> +#define LOONGSON2_MMC_REG_DELAY                0xf8 /* DLL Delayed Parameter Register */
> +#define LOONGSON2_MMC_REG_SEL          0xfc /* Bus Mode Selection Register */
> +
> +/* Exclusive DMA R/W Registers */
> +#define LOONGSON2_MMC_REG_WDMA_LO      0x400
> +#define LOONGSON2_MMC_REG_WDMA_HI      0x404
> +#define LOONGSON2_MMC_REG_RDMA_LO      0x800
> +#define LOONGSON2_MMC_REG_RDMA_HI      0x804
> +
>  /* Bitfields of control register */
>  #define LOONGSON2_MMC_CTL_ENCLK                BIT(0)
>  #define LOONGSON2_MMC_CTL_EXTCLK       BIT(1)
> @@ -109,6 +121,9 @@
>  #define LOONGSON2_MMC_DSTS_RESUME      BIT(15)
>  #define LOONGSON2_MMC_DSTS_SUSPEND     BIT(16)
>
> +/* Bitfields of FIFO Status Register */
> +#define LOONGSON2_MMC_FSTS_TXFULL      BIT(11)
> +
>  /* Bitfields of interrupt register */
>  #define LOONGSON2_MMC_INT_DFIN         BIT(0)
>  #define LOONGSON2_MMC_INT_DTIMEOUT     BIT(1)
> @@ -136,6 +151,41 @@
>  #define LOONGSON2_MMC_IEN_ALL          GENMASK(9, 0)
>  #define LOONGSON2_MMC_INT_CLEAR                GENMASK(9, 0)
>
> +/* Bitfields of DLL master lock-value register */
> +#define LOONGSON2_MMC_DLLVAL_DONE      BIT(8)
> +
> +/* Bitfields of DLL control register */
> +#define LOONGSON2_MMC_DLLCTL_TIME      GENMASK(7, 0)
> +#define LOONGSON2_MMC_DLLCTL_INCRE     GENMASK(15, 8)
> +#define LOONGSON2_MMC_DLLCTL_START     GENMASK(23, 16)
> +#define LOONGSON2_MMC_DLLCTL_CLK_MODE  BIT(24)
> +#define LOONGSON2_MMC_DLLCTL_START_BIT BIT(25)
> +#define LOONGSON2_MMC_DLLCTL_TIME_BPASS        GENMASK(29, 26)
> +
> +#define LOONGSON2_MMC_DELAY_PAD                GENMASK(7, 0)
> +#define LOONGSON2_MMC_DELAY_RD         GENMASK(15, 8)
> +
> +#define LOONGSON2_MMC_SEL_DATA         BIT(0)  /* 0: SDR, 1: DDR */
> +#define LOONGSON2_MMC_SEL_BUS          BIT(0)  /* 0: EMMC, 1: SDIO */
> +
> +/* Internal dma controller registers */
> +
> +/* Bitfields of Global Configuration Register */
> +#define LOONGSON2_MMC_DMA_64BIT_EN     BIT(0) /* 1: 64 bit support */
> +#define LOONGSON2_MMC_DMA_UNCOHERENT_EN        BIT(1) /* 0: cache, 1: uncache */
> +#define LOONGSON2_MMC_DMA_ASK_VALID    BIT(2)
> +#define LOONGSON2_MMC_DMA_START                BIT(3) /* DMA start operation */
> +#define LOONGSON2_MMC_DMA_STOP         BIT(4) /* DMA stop operation */
> +#define LOONGSON2_MMC_DMA_CONFIG_MASK  GENMASK_ULL(4, 0) /* DMA controller config bits mask */
> +
> +/* Bitfields of ndesc_addr field of HW descriptor */
> +#define LOONGSON2_MMC_DMA_DESC_EN      BIT(0) /*1: The next descriptor is valid */
> +#define LOONGSON2_MMC_DMA_DESC_ADDR_LOW        GENMASK(31, 1)
> +
> +/* Bitfields of cmd field of HW descriptor */
> +#define LOONGSON2_MMC_DMA_INT          BIT(1)  /* Enable DMA interrupts */
> +#define LOONGSON2_MMC_DMA_DATA_DIR     BIT(12) /* 1: write to device, 0: read from device */
> +
>  /* Loongson-2K1000 SDIO2 DMA routing register */
>  #define LS2K1000_SDIO_DMA_MASK         GENMASK(17, 15)
>  #define LS2K1000_DMA0_CONF             0x0
> @@ -180,6 +230,8 @@ struct loongson2_mmc_host {
>         struct resource *res;
>         struct clk *clk;
>         u64 rate;
> +       void *sg_cpu;
> +       dma_addr_t sg_dma;
>         int dma_complete;
>         struct dma_chan *chan;
>         int cmd_is_stop;
> @@ -192,6 +244,7 @@ struct loongson2_mmc_host {
>  struct loongson2_mmc_pdata {
>         const struct regmap_config *regmap_config;
>         void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
> +       void (*fix_data_timeout)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
>         int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev);
>         int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data);
>         void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev);
> @@ -282,6 +335,9 @@ static void loongson2_mmc_send_request(struct mmc_host *mmc)
>                 return;
>         }
>
> +       if (host->pdata->fix_data_timeout)
> +               host->pdata->fix_data_timeout(host, cmd);
> +
>         loongson2_mmc_send_command(host, cmd);
>
>         /* Fix deselect card */
> @@ -426,6 +482,36 @@ static irqreturn_t loongson2_mmc_irq(int irq, void *devid)
>         return IRQ_WAKE_THREAD;
>  }
>
> +static void loongson2_mmc_dll_mode_init(struct loongson2_mmc_host *host)
> +{
> +       u32 val, pad_delay, delay, ret;
> +
> +       regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_SEL,
> +                          LOONGSON2_MMC_SEL_DATA, LOONGSON2_MMC_SEL_DATA);
> +
> +       val = FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME, 0xc8)
> +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_INCRE, 0x1)
> +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START, 0x1)
> +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_CLK_MODE, 0x1)
> +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START_BIT, 0x1)
> +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME_BPASS, 0xf);
> +
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_DLLCTL, val);
> +
> +       ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_DLLVAL, val,
> +                                      (val & LOONGSON2_MMC_DLLVAL_DONE), 0, 4000);
> +       if (ret < 0)
> +               return;
> +
> +       regmap_read(host->regmap, LOONGSON2_MMC_REG_DLLVAL, &val);
> +       pad_delay = FIELD_GET(GENMASK(7, 1), val);
> +
> +       delay = FIELD_PREP(LOONGSON2_MMC_DELAY_PAD, pad_delay)
> +             | FIELD_PREP(LOONGSON2_MMC_DELAY_RD, pad_delay + 1);
> +
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_DELAY, delay);
> +}
> +
>  static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_ios *ios)
>  {
>         u32 pre;
> @@ -438,6 +524,10 @@ static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_io
>
>         regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL,
>                            LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK);
> +
> +       /* EMMC DLL mode setting */
> +       if (ios->timing == MMC_TIMING_UHS_DDR50 || ios->timing == MMC_TIMING_MMC_DDR52)
> +               loongson2_mmc_dll_mode_init(host);
>  }
>
>  static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> @@ -655,6 +745,127 @@ static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = {
>         .release_dma            = loongson2_mmc_release_external_dma,
>  };
>
> +static const struct regmap_config ls2k2000_mmc_regmap_config = {
> +       .reg_bits = 32,
> +       .val_bits = 32,
> +       .reg_stride = 4,
> +       .max_register = LOONGSON2_MMC_REG_RDMA_HI,
> +};
> +
> +static void ls2k2000_mmc_reorder_cmd_data(struct loongson2_mmc_host *host,
> +                                         struct mmc_command *cmd)
> +{
> +       struct scatterlist *sg;
> +       u32 *data;
> +       int i, j;
> +
> +       if (cmd->opcode != SD_SWITCH || mmc_cmd_type(cmd) != MMC_CMD_ADTC)
> +               return;
> +
> +       for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) {
> +               data = sg_virt(&sg[i]);
> +               for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++)
> +                       data[j] = bitrev8x4(data[j]);
> +       }
> +}
> +
> +/*
> + * This is a controller hardware defect. Single/multiple block write commands
> + * must be sent after the TX FULL flag is set, otherwise a data timeout interrupt
> + * will occur.
> + */
> +static void ls2k2000_mmc_fix_data_timeout(struct loongson2_mmc_host *host,
> +                                         struct mmc_command *cmd)
> +{
> +       int val;
> +
> +       if (cmd->opcode != MMC_WRITE_BLOCK && cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK)
> +               return;
> +
> +       regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val,
> +                                (val & LOONGSON2_MMC_FSTS_TXFULL), 0, 500);
> +}
> +
> +static int loongson2_mmc_prepare_internal_dma(struct loongson2_mmc_host *host,
> +                                             struct mmc_data *data)
> +{
> +       struct loongson2_dma_desc *pdes = (struct loongson2_dma_desc *)host->sg_cpu;
> +       struct mmc_host *mmc = mmc_from_priv(host);
> +       dma_addr_t next_desc = host->sg_dma;
> +       struct scatterlist *sg;
> +       int reg_lo, reg_hi;
> +       u64 dma_order;
> +       int i, ret;
> +
> +       ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len,
> +                        mmc_get_dma_dir(data));
> +       if (!ret)
> +               return -ENOMEM;
> +
> +       for_each_sg(data->sg, sg, data->sg_len, i) {
> +               pdes[i].len = sg_dma_len(&sg[i]) / 4;
> +               pdes[i].step_len = 0;
> +               pdes[i].step_times = 1;
> +               pdes[i].mem_addr = lower_32_bits(sg_dma_address(&sg[i]));
> +               pdes[i].high_mem_addr = upper_32_bits(sg_dma_address(&sg[i]));
> +               pdes[i].apb_addr = host->res->start + LOONGSON2_MMC_REG_DATA;
> +               pdes[i].cmd = LOONGSON2_MMC_DMA_INT;
> +
> +               if (data->flags & MMC_DATA_READ) {
> +                       reg_lo = LOONGSON2_MMC_REG_RDMA_LO;
> +                       reg_hi = LOONGSON2_MMC_REG_RDMA_HI;
> +               } else {
> +                       pdes[i].cmd |= LOONGSON2_MMC_DMA_DATA_DIR;
> +                       reg_lo = LOONGSON2_MMC_REG_WDMA_LO;
> +                       reg_hi = LOONGSON2_MMC_REG_WDMA_HI;
> +               }
> +
> +               next_desc += sizeof(struct loongson2_dma_desc);
> +               pdes[i].ndesc_addr = lower_32_bits(next_desc) |
> +                                    LOONGSON2_MMC_DMA_DESC_EN;
> +               pdes[i].high_ndesc_addr = upper_32_bits(next_desc);
> +       }
> +
> +       /* Setting the last descriptor enable bit */
> +       pdes[i - 1].ndesc_addr &= ~LOONGSON2_MMC_DMA_DESC_EN;
> +
> +       dma_order = (host->sg_dma & ~LOONGSON2_MMC_DMA_CONFIG_MASK) |
> +                   LOONGSON2_MMC_DMA_64BIT_EN |
> +                   LOONGSON2_MMC_DMA_START;
> +
> +       regmap_write(host->regmap, reg_hi, upper_32_bits(dma_order));
> +       regmap_write(host->regmap, reg_lo, lower_32_bits(dma_order));
> +
> +       return 0;
> +}
> +
> +static int loongson2_mmc_set_internal_dma(struct loongson2_mmc_host *host,
> +                                         struct platform_device *pdev)
> +{
> +       host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE,
> +                                         &host->sg_dma, GFP_KERNEL);
> +       if (!host->sg_cpu)
> +               return -ENOMEM;
> +
> +       memset(host->sg_cpu, 0, PAGE_SIZE);
> +       return 0;
> +}
> +
> +static void loongson2_mmc_release_internal_dma(struct loongson2_mmc_host *host,
> +                                              struct device *dev)
> +{
> +       dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
> +}
> +
> +static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = {
> +       .regmap_config          = &ls2k2000_mmc_regmap_config,
> +       .reorder_cmd_data       = ls2k2000_mmc_reorder_cmd_data,
> +       .fix_data_timeout       = ls2k2000_mmc_fix_data_timeout,
> +       .setting_dma            = loongson2_mmc_set_internal_dma,
> +       .prepare_dma            = loongson2_mmc_prepare_internal_dma,
> +       .release_dma            = loongson2_mmc_release_internal_dma,
> +};
From the whole series we get:

+static struct loongson2_mmc_pdata ls2k0500_mmc_pdata = {
+ .regmap_config = &ls2k1000_mmc_regmap_config,
+ .reorder_cmd_data = loongson2_mmc_reorder_cmd_data,
+ .setting_dma = ls2k0500_mmc_set_external_dma,
+ .prepare_dma = loongson2_mmc_prepare_external_dma,
+ .release_dma = loongson2_mmc_release_external_dma,
+};

+static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = {
+ .regmap_config = &ls2k1000_mmc_regmap_config,
+ .reorder_cmd_data = loongson2_mmc_reorder_cmd_data,
+ .setting_dma = ls2k1000_mmc_set_external_dma,
+ .prepare_dma = loongson2_mmc_prepare_external_dma,
+ .release_dma = loongson2_mmc_release_external_dma,
+};

+static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = {
+ .regmap_config = &ls2k2000_mmc_regmap_config,
+ .reorder_cmd_data = ls2k2000_mmc_reorder_cmd_data,
+ .fix_data_timeout = ls2k2000_mmc_fix_data_timeout,
+ .setting_dma = loongson2_mmc_set_internal_dma,
+ .prepare_dma = loongson2_mmc_prepare_internal_dma,
+ .release_dma = loongson2_mmc_release_internal_dma,
+};

The prefix of function names are confusing, can we rename them better?


Huacai




> +
>  static int loongson2_mmc_resource_request(struct platform_device *pdev,
>                                           struct loongson2_mmc_host *host)
>  {
> @@ -777,6 +988,7 @@ static void loongson2_mmc_remove(struct platform_device *pdev)
>  static const struct of_device_id loongson2_mmc_of_ids[] = {
>         { .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata },
>         { .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata },
> +       { .compatible = "loongson,ls2k2000-mmc", .data = &ls2k2000_mmc_pdata },
>         { },
>  };
>  MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids);
> --
> 2.47.1
>
>

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

* Re: [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver
  2025-06-19  4:02   ` Huacai Chen
@ 2025-06-20  3:06     ` Binbin Zhou
  0 siblings, 0 replies; 11+ messages in thread
From: Binbin Zhou @ 2025-06-20  3:06 UTC (permalink / raw)
  To: Huacai Chen
  Cc: Binbin Zhou, Huacai Chen, Ulf Hansson, Xuerui Wang, loongarch,
	linux-mmc, wanghongliang

On Thu, Jun 19, 2025 at 12:03 PM Huacai Chen <chenhuacai@kernel.org> wrote:
>
> Hi, Binbin,
>
> On Wed, Jun 18, 2025 at 4:08 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote:
> >
> > This patch describes the two MMC controllers of the Loongson-2K2000 SoC,
> > one providing an eMMC interface and the other exporting an SD/SDIO
> > interface.
> >
> > Compared to the Loongson-2K1000's MMC controllers, their internals are
> > similar, except that we use an internally exclusive DMA engine instead of
> > an externally shared APBDMA engine.
> >
> > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > ---
> >  drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++
> >  1 file changed, 212 insertions(+)
> >
> > diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c
> > index 872f5dc21b21..75144221a821 100644
> > --- a/drivers/mmc/host/loongson2-mmc.c
> > +++ b/drivers/mmc/host/loongson2-mmc.c
> > @@ -44,6 +44,18 @@
> >  #define LOONGSON2_MMC_REG_DATA         0x40 /* Data Register */
> >  #define LOONGSON2_MMC_REG_IEN          0x64 /* Interrupt Enable Register */
> >
> > +/* EMMC DLL Mode Registers */
> > +#define LOONGSON2_MMC_REG_DLLVAL       0xf0 /* DLL Master Lock-value Register */
> > +#define LOONGSON2_MMC_REG_DLLCTL       0xf4 /* DLL Control Register */
> > +#define LOONGSON2_MMC_REG_DELAY                0xf8 /* DLL Delayed Parameter Register */
> > +#define LOONGSON2_MMC_REG_SEL          0xfc /* Bus Mode Selection Register */
> > +
> > +/* Exclusive DMA R/W Registers */
> > +#define LOONGSON2_MMC_REG_WDMA_LO      0x400
> > +#define LOONGSON2_MMC_REG_WDMA_HI      0x404
> > +#define LOONGSON2_MMC_REG_RDMA_LO      0x800
> > +#define LOONGSON2_MMC_REG_RDMA_HI      0x804
> > +
> >  /* Bitfields of control register */
> >  #define LOONGSON2_MMC_CTL_ENCLK                BIT(0)
> >  #define LOONGSON2_MMC_CTL_EXTCLK       BIT(1)
> > @@ -109,6 +121,9 @@
> >  #define LOONGSON2_MMC_DSTS_RESUME      BIT(15)
> >  #define LOONGSON2_MMC_DSTS_SUSPEND     BIT(16)
> >
> > +/* Bitfields of FIFO Status Register */
> > +#define LOONGSON2_MMC_FSTS_TXFULL      BIT(11)
> > +
> >  /* Bitfields of interrupt register */
> >  #define LOONGSON2_MMC_INT_DFIN         BIT(0)
> >  #define LOONGSON2_MMC_INT_DTIMEOUT     BIT(1)
> > @@ -136,6 +151,41 @@
> >  #define LOONGSON2_MMC_IEN_ALL          GENMASK(9, 0)
> >  #define LOONGSON2_MMC_INT_CLEAR                GENMASK(9, 0)
> >
> > +/* Bitfields of DLL master lock-value register */
> > +#define LOONGSON2_MMC_DLLVAL_DONE      BIT(8)
> > +
> > +/* Bitfields of DLL control register */
> > +#define LOONGSON2_MMC_DLLCTL_TIME      GENMASK(7, 0)
> > +#define LOONGSON2_MMC_DLLCTL_INCRE     GENMASK(15, 8)
> > +#define LOONGSON2_MMC_DLLCTL_START     GENMASK(23, 16)
> > +#define LOONGSON2_MMC_DLLCTL_CLK_MODE  BIT(24)
> > +#define LOONGSON2_MMC_DLLCTL_START_BIT BIT(25)
> > +#define LOONGSON2_MMC_DLLCTL_TIME_BPASS        GENMASK(29, 26)
> > +
> > +#define LOONGSON2_MMC_DELAY_PAD                GENMASK(7, 0)
> > +#define LOONGSON2_MMC_DELAY_RD         GENMASK(15, 8)
> > +
> > +#define LOONGSON2_MMC_SEL_DATA         BIT(0)  /* 0: SDR, 1: DDR */
> > +#define LOONGSON2_MMC_SEL_BUS          BIT(0)  /* 0: EMMC, 1: SDIO */
> > +
> > +/* Internal dma controller registers */
> > +
> > +/* Bitfields of Global Configuration Register */
> > +#define LOONGSON2_MMC_DMA_64BIT_EN     BIT(0) /* 1: 64 bit support */
> > +#define LOONGSON2_MMC_DMA_UNCOHERENT_EN        BIT(1) /* 0: cache, 1: uncache */
> > +#define LOONGSON2_MMC_DMA_ASK_VALID    BIT(2)
> > +#define LOONGSON2_MMC_DMA_START                BIT(3) /* DMA start operation */
> > +#define LOONGSON2_MMC_DMA_STOP         BIT(4) /* DMA stop operation */
> > +#define LOONGSON2_MMC_DMA_CONFIG_MASK  GENMASK_ULL(4, 0) /* DMA controller config bits mask */
> > +
> > +/* Bitfields of ndesc_addr field of HW descriptor */
> > +#define LOONGSON2_MMC_DMA_DESC_EN      BIT(0) /*1: The next descriptor is valid */
> > +#define LOONGSON2_MMC_DMA_DESC_ADDR_LOW        GENMASK(31, 1)
> > +
> > +/* Bitfields of cmd field of HW descriptor */
> > +#define LOONGSON2_MMC_DMA_INT          BIT(1)  /* Enable DMA interrupts */
> > +#define LOONGSON2_MMC_DMA_DATA_DIR     BIT(12) /* 1: write to device, 0: read from device */
> > +
> >  /* Loongson-2K1000 SDIO2 DMA routing register */
> >  #define LS2K1000_SDIO_DMA_MASK         GENMASK(17, 15)
> >  #define LS2K1000_DMA0_CONF             0x0
> > @@ -180,6 +230,8 @@ struct loongson2_mmc_host {
> >         struct resource *res;
> >         struct clk *clk;
> >         u64 rate;
> > +       void *sg_cpu;
> > +       dma_addr_t sg_dma;
> >         int dma_complete;
> >         struct dma_chan *chan;
> >         int cmd_is_stop;
> > @@ -192,6 +244,7 @@ struct loongson2_mmc_host {
> >  struct loongson2_mmc_pdata {
> >         const struct regmap_config *regmap_config;
> >         void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
> > +       void (*fix_data_timeout)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
> >         int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev);
> >         int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data);
> >         void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev);
> > @@ -282,6 +335,9 @@ static void loongson2_mmc_send_request(struct mmc_host *mmc)
> >                 return;
> >         }
> >
> > +       if (host->pdata->fix_data_timeout)
> > +               host->pdata->fix_data_timeout(host, cmd);
> > +
> >         loongson2_mmc_send_command(host, cmd);
> >
> >         /* Fix deselect card */
> > @@ -426,6 +482,36 @@ static irqreturn_t loongson2_mmc_irq(int irq, void *devid)
> >         return IRQ_WAKE_THREAD;
> >  }
> >
> > +static void loongson2_mmc_dll_mode_init(struct loongson2_mmc_host *host)
> > +{
> > +       u32 val, pad_delay, delay, ret;
> > +
> > +       regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_SEL,
> > +                          LOONGSON2_MMC_SEL_DATA, LOONGSON2_MMC_SEL_DATA);
> > +
> > +       val = FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME, 0xc8)
> > +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_INCRE, 0x1)
> > +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START, 0x1)
> > +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_CLK_MODE, 0x1)
> > +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START_BIT, 0x1)
> > +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME_BPASS, 0xf);
> > +
> > +       regmap_write(host->regmap, LOONGSON2_MMC_REG_DLLCTL, val);
> > +
> > +       ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_DLLVAL, val,
> > +                                      (val & LOONGSON2_MMC_DLLVAL_DONE), 0, 4000);
> > +       if (ret < 0)
> > +               return;
> > +
> > +       regmap_read(host->regmap, LOONGSON2_MMC_REG_DLLVAL, &val);
> > +       pad_delay = FIELD_GET(GENMASK(7, 1), val);
> > +
> > +       delay = FIELD_PREP(LOONGSON2_MMC_DELAY_PAD, pad_delay)
> > +             | FIELD_PREP(LOONGSON2_MMC_DELAY_RD, pad_delay + 1);
> > +
> > +       regmap_write(host->regmap, LOONGSON2_MMC_REG_DELAY, delay);
> > +}
> > +
> >  static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_ios *ios)
> >  {
> >         u32 pre;
> > @@ -438,6 +524,10 @@ static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_io
> >
> >         regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL,
> >                            LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK);
> > +
> > +       /* EMMC DLL mode setting */
> > +       if (ios->timing == MMC_TIMING_UHS_DDR50 || ios->timing == MMC_TIMING_MMC_DDR52)
> > +               loongson2_mmc_dll_mode_init(host);
> >  }
> >
> >  static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> > @@ -655,6 +745,127 @@ static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = {
> >         .release_dma            = loongson2_mmc_release_external_dma,
> >  };
> >
> > +static const struct regmap_config ls2k2000_mmc_regmap_config = {
> > +       .reg_bits = 32,
> > +       .val_bits = 32,
> > +       .reg_stride = 4,
> > +       .max_register = LOONGSON2_MMC_REG_RDMA_HI,
> > +};
> > +
> > +static void ls2k2000_mmc_reorder_cmd_data(struct loongson2_mmc_host *host,
> > +                                         struct mmc_command *cmd)
> > +{
> > +       struct scatterlist *sg;
> > +       u32 *data;
> > +       int i, j;
> > +
> > +       if (cmd->opcode != SD_SWITCH || mmc_cmd_type(cmd) != MMC_CMD_ADTC)
> > +               return;
> > +
> > +       for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) {
> > +               data = sg_virt(&sg[i]);
> > +               for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++)
> > +                       data[j] = bitrev8x4(data[j]);
> > +       }
> > +}
> > +
> > +/*
> > + * This is a controller hardware defect. Single/multiple block write commands
> > + * must be sent after the TX FULL flag is set, otherwise a data timeout interrupt
> > + * will occur.
> > + */
> > +static void ls2k2000_mmc_fix_data_timeout(struct loongson2_mmc_host *host,
> > +                                         struct mmc_command *cmd)
> > +{
> > +       int val;
> > +
> > +       if (cmd->opcode != MMC_WRITE_BLOCK && cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK)
> > +               return;
> > +
> > +       regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val,
> > +                                (val & LOONGSON2_MMC_FSTS_TXFULL), 0, 500);
> > +}
> > +
> > +static int loongson2_mmc_prepare_internal_dma(struct loongson2_mmc_host *host,
> > +                                             struct mmc_data *data)
> > +{
> > +       struct loongson2_dma_desc *pdes = (struct loongson2_dma_desc *)host->sg_cpu;
> > +       struct mmc_host *mmc = mmc_from_priv(host);
> > +       dma_addr_t next_desc = host->sg_dma;
> > +       struct scatterlist *sg;
> > +       int reg_lo, reg_hi;
> > +       u64 dma_order;
> > +       int i, ret;
> > +
> > +       ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len,
> > +                        mmc_get_dma_dir(data));
> > +       if (!ret)
> > +               return -ENOMEM;
> > +
> > +       for_each_sg(data->sg, sg, data->sg_len, i) {
> > +               pdes[i].len = sg_dma_len(&sg[i]) / 4;
> > +               pdes[i].step_len = 0;
> > +               pdes[i].step_times = 1;
> > +               pdes[i].mem_addr = lower_32_bits(sg_dma_address(&sg[i]));
> > +               pdes[i].high_mem_addr = upper_32_bits(sg_dma_address(&sg[i]));
> > +               pdes[i].apb_addr = host->res->start + LOONGSON2_MMC_REG_DATA;
> > +               pdes[i].cmd = LOONGSON2_MMC_DMA_INT;
> > +
> > +               if (data->flags & MMC_DATA_READ) {
> > +                       reg_lo = LOONGSON2_MMC_REG_RDMA_LO;
> > +                       reg_hi = LOONGSON2_MMC_REG_RDMA_HI;
> > +               } else {
> > +                       pdes[i].cmd |= LOONGSON2_MMC_DMA_DATA_DIR;
> > +                       reg_lo = LOONGSON2_MMC_REG_WDMA_LO;
> > +                       reg_hi = LOONGSON2_MMC_REG_WDMA_HI;
> > +               }
> > +
> > +               next_desc += sizeof(struct loongson2_dma_desc);
> > +               pdes[i].ndesc_addr = lower_32_bits(next_desc) |
> > +                                    LOONGSON2_MMC_DMA_DESC_EN;
> > +               pdes[i].high_ndesc_addr = upper_32_bits(next_desc);
> > +       }
> > +
> > +       /* Setting the last descriptor enable bit */
> > +       pdes[i - 1].ndesc_addr &= ~LOONGSON2_MMC_DMA_DESC_EN;
> > +
> > +       dma_order = (host->sg_dma & ~LOONGSON2_MMC_DMA_CONFIG_MASK) |
> > +                   LOONGSON2_MMC_DMA_64BIT_EN |
> > +                   LOONGSON2_MMC_DMA_START;
> > +
> > +       regmap_write(host->regmap, reg_hi, upper_32_bits(dma_order));
> > +       regmap_write(host->regmap, reg_lo, lower_32_bits(dma_order));
> > +
> > +       return 0;
> > +}
> > +
> > +static int loongson2_mmc_set_internal_dma(struct loongson2_mmc_host *host,
> > +                                         struct platform_device *pdev)
> > +{
> > +       host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE,
> > +                                         &host->sg_dma, GFP_KERNEL);
> > +       if (!host->sg_cpu)
> > +               return -ENOMEM;
> > +
> > +       memset(host->sg_cpu, 0, PAGE_SIZE);
> > +       return 0;
> > +}
> > +
> > +static void loongson2_mmc_release_internal_dma(struct loongson2_mmc_host *host,
> > +                                              struct device *dev)
> > +{
> > +       dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
> > +}
> > +
> > +static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = {
> > +       .regmap_config          = &ls2k2000_mmc_regmap_config,
> > +       .reorder_cmd_data       = ls2k2000_mmc_reorder_cmd_data,
> > +       .fix_data_timeout       = ls2k2000_mmc_fix_data_timeout,
> > +       .setting_dma            = loongson2_mmc_set_internal_dma,
> > +       .prepare_dma            = loongson2_mmc_prepare_internal_dma,
> > +       .release_dma            = loongson2_mmc_release_internal_dma,
> > +};
> From the whole series we get:
>
> +static struct loongson2_mmc_pdata ls2k0500_mmc_pdata = {
> + .regmap_config = &ls2k1000_mmc_regmap_config,
> + .reorder_cmd_data = loongson2_mmc_reorder_cmd_data,
> + .setting_dma = ls2k0500_mmc_set_external_dma,
> + .prepare_dma = loongson2_mmc_prepare_external_dma,
> + .release_dma = loongson2_mmc_release_external_dma,
> +};
>
> +static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = {
> + .regmap_config = &ls2k1000_mmc_regmap_config,
> + .reorder_cmd_data = loongson2_mmc_reorder_cmd_data,
> + .setting_dma = ls2k1000_mmc_set_external_dma,
> + .prepare_dma = loongson2_mmc_prepare_external_dma,
> + .release_dma = loongson2_mmc_release_external_dma,
> +};
>
> +static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = {
> + .regmap_config = &ls2k2000_mmc_regmap_config,
> + .reorder_cmd_data = ls2k2000_mmc_reorder_cmd_data,
> + .fix_data_timeout = ls2k2000_mmc_fix_data_timeout,
> + .setting_dma = loongson2_mmc_set_internal_dma,
> + .prepare_dma = loongson2_mmc_prepare_internal_dma,
> + .release_dma = loongson2_mmc_release_internal_dma,
> +};
>
> The prefix of function names are confusing, can we rename them better?

OK, for regmap_config, reorder_cmd_data and reorder_cmd_data,  I will
use ls2k as a prefix.

ls2k1000_mmc_regmap_config -> ls2k0500_mmc_regmap_config
loongson2_mmc_reorder_cmd_data -> ls2k0500_mmc_reorder_cmd_data.
loongson2_mmc_set_internal_dma -> ls2k2000_mmc_set_internal_dma
>
>
> Huacai
>
>
>
>
> > +
> >  static int loongson2_mmc_resource_request(struct platform_device *pdev,
> >                                           struct loongson2_mmc_host *host)
> >  {
> > @@ -777,6 +988,7 @@ static void loongson2_mmc_remove(struct platform_device *pdev)
> >  static const struct of_device_id loongson2_mmc_of_ids[] = {
> >         { .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata },
> >         { .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata },
> > +       { .compatible = "loongson,ls2k2000-mmc", .data = &ls2k2000_mmc_pdata },
> >         { },
> >  };
> >  MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids);
> > --
> > 2.47.1
> >
> >

-- 
Thanks.
Binbin

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

* Re: [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver
  2025-06-18  8:07 ` [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou
  2025-06-19  4:02   ` Huacai Chen
@ 2025-06-20 13:20   ` kernel test robot
  1 sibling, 0 replies; 11+ messages in thread
From: kernel test robot @ 2025-06-20 13:20 UTC (permalink / raw)
  To: Binbin Zhou, Binbin Zhou, Huacai Chen, Ulf Hansson
  Cc: oe-kbuild-all, Xuerui Wang, loongarch, linux-mmc, wanghongliang

Hi Binbin,

kernel test robot noticed the following build errors:

[auto build test ERROR on 187715cfd12932a528ff3a3952648e2b55381d4c]

url:    https://github.com/intel-lab-lkp/linux/commits/Binbin-Zhou/dt-bindings-mmc-Add-Loongson-2K-SD-SDIO-eMMC-controller-binding/20250618-160908
base:   187715cfd12932a528ff3a3952648e2b55381d4c
patch link:    https://lore.kernel.org/r/abfe54473135df478db14b5ef0e1773326455f21.1750216134.git.zhoubinbin%40loongson.cn
patch subject: [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver
config: microblaze-randconfig-r133-20250620 (https://download.01.org/0day-ci/archive/20250620/202506202031.TNchn822-lkp@intel.com/config)
compiler: microblaze-linux-gcc (GCC) 8.5.0
reproduce: (https://download.01.org/0day-ci/archive/20250620/202506202031.TNchn822-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202506202031.TNchn822-lkp@intel.com/

All errors (new ones prefixed by >>):

   microblaze-linux-ld: drivers/mmc/host/loongson2-mmc.o: in function `loongson2_mmc_set_ios':
>> .tmp_gl_loongson2-mmc.o:(.text+0xec8): undefined reference to `__udivdi3'

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver
  2025-06-18  8:08 ` [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver Binbin Zhou
  2025-06-19  4:02   ` Huacai Chen
@ 2025-06-23 11:24   ` Ulf Hansson
  1 sibling, 0 replies; 11+ messages in thread
From: Ulf Hansson @ 2025-06-23 11:24 UTC (permalink / raw)
  To: Binbin Zhou
  Cc: Binbin Zhou, Huacai Chen, Huacai Chen, Xuerui Wang, loongarch,
	linux-mmc, wanghongliang

On Wed, 18 Jun 2025 at 10:08, Binbin Zhou <zhoubinbin@loongson.cn> wrote:
>
> This patch describes the two MMC controllers of the Loongson-2K2000 SoC,
> one providing an eMMC interface and the other exporting an SD/SDIO
> interface.
>
> Compared to the Loongson-2K1000's MMC controllers, their internals are
> similar, except that we use an internally exclusive DMA engine instead of
> an externally shared APBDMA engine.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
>  drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++
>  1 file changed, 212 insertions(+)
>
> diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c
> index 872f5dc21b21..75144221a821 100644
> --- a/drivers/mmc/host/loongson2-mmc.c
> +++ b/drivers/mmc/host/loongson2-mmc.c

[...]

> +static void loongson2_mmc_dll_mode_init(struct loongson2_mmc_host *host)
> +{
> +       u32 val, pad_delay, delay, ret;
> +
> +       regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_SEL,
> +                          LOONGSON2_MMC_SEL_DATA, LOONGSON2_MMC_SEL_DATA);
> +
> +       val = FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME, 0xc8)
> +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_INCRE, 0x1)
> +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START, 0x1)
> +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_CLK_MODE, 0x1)
> +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START_BIT, 0x1)
> +           | FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME_BPASS, 0xf);
> +
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_DLLCTL, val);
> +
> +       ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_DLLVAL, val,
> +                                      (val & LOONGSON2_MMC_DLLVAL_DONE), 0, 4000);

Please use a define to specify the 4000us timeout.

> +       if (ret < 0)
> +               return;
> +
> +       regmap_read(host->regmap, LOONGSON2_MMC_REG_DLLVAL, &val);
> +       pad_delay = FIELD_GET(GENMASK(7, 1), val);
> +
> +       delay = FIELD_PREP(LOONGSON2_MMC_DELAY_PAD, pad_delay)
> +             | FIELD_PREP(LOONGSON2_MMC_DELAY_RD, pad_delay + 1);
> +
> +       regmap_write(host->regmap, LOONGSON2_MMC_REG_DELAY, delay);
> +}

[...]

> +/*
> + * This is a controller hardware defect. Single/multiple block write commands
> + * must be sent after the TX FULL flag is set, otherwise a data timeout interrupt
> + * will occur.
> + */
> +static void ls2k2000_mmc_fix_data_timeout(struct loongson2_mmc_host *host,
> +                                         struct mmc_command *cmd)
> +{
> +       int val;
> +
> +       if (cmd->opcode != MMC_WRITE_BLOCK && cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK)
> +               return;
> +
> +       regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val,
> +                                (val & LOONGSON2_MMC_FSTS_TXFULL), 0, 500);

Ditto.

[...]

Kind regards
Uffe

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

end of thread, other threads:[~2025-06-23 11:24 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-18  8:07 [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou
2025-06-18  8:07 ` [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
2025-06-18 13:44   ` Krzysztof Kozlowski
2025-06-18  8:07 ` [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou
2025-06-19  4:02   ` Huacai Chen
2025-06-20 13:20   ` kernel test robot
2025-06-18  8:07 ` [PATCH v3 3/4] dt-bindings: mmc: loongson,ls2k0500-mmc: Add compatible for Loongson-2K2000 Binbin Zhou
2025-06-18  8:08 ` [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver Binbin Zhou
2025-06-19  4:02   ` Huacai Chen
2025-06-20  3:06     ` Binbin Zhou
2025-06-23 11:24   ` Ulf Hansson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox