* [PATCH v1 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver
@ 2025-04-10 8:40 Binbin Zhou
2025-04-10 8:40 ` [PATCH v1 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
` (3 more replies)
0 siblings, 4 replies; 10+ messages in thread
From: Binbin Zhou @ 2025-04-10 8:40 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ulf Hansson
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-mmc,
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.
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,ls2k-mmc: Add compatible for
Loongson-2K2000
mmc: loongson2: Add Loongson-2K2000 SD/SDIO controller driver
.../bindings/mmc/loongson,ls2k-mmc.yaml | 110 +++
MAINTAINERS | 8 +
drivers/mmc/host/Kconfig | 13 +
drivers/mmc/host/Makefile | 1 +
drivers/mmc/host/loongson2-mmc.c | 781 ++++++++++++++++++
drivers/mmc/host/loongson2-mmc.h | 224 +++++
6 files changed, 1137 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
create mode 100644 drivers/mmc/host/loongson2-mmc.c
create mode 100644 drivers/mmc/host/loongson2-mmc.h
base-commit: 6b8dba9a7fdba6d669e4119e390a071e44383934
--
2.47.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v1 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding
2025-04-10 8:40 [PATCH v1 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou
@ 2025-04-10 8:40 ` Binbin Zhou
2025-04-11 17:03 ` Rob Herring (Arm)
2025-04-10 8:40 ` [PATCH v1 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou
` (2 subsequent siblings)
3 siblings, 1 reply; 10+ messages in thread
From: Binbin Zhou @ 2025-04-10 8:40 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ulf Hansson
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-mmc,
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,ls2k-mmc.yaml | 67 +++++++++++++++++++
MAINTAINERS | 6 ++
2 files changed, 73 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
diff --git a/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml b/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
new file mode 100644
index 000000000000..f3e94f5f3a35
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mmc/loongson,ls2k-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:
+ maxItems: 2
+
+ 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 96b827049501..32c3733a764a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13935,6 +13935,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: Supported
+F: Documentation/devicetree/bindings/mmc/loongson,ls2k-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] 10+ messages in thread
* [PATCH v1 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver
2025-04-10 8:40 [PATCH v1 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou
2025-04-10 8:40 ` [PATCH v1 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
@ 2025-04-10 8:40 ` Binbin Zhou
2025-04-28 15:09 ` Ulf Hansson
2025-04-10 8:40 ` [PATCH v1 3/4] dt-bindings: mmc: loongson,ls2k-mmc: Add compatible for Loongson-2K2000 Binbin Zhou
2025-04-10 8:41 ` [PATCH v1 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO controller driver Binbin Zhou
3 siblings, 1 reply; 10+ messages in thread
From: Binbin Zhou @ 2025-04-10 8:40 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ulf Hansson
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-mmc,
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 | 2 +
drivers/mmc/host/Kconfig | 13 +
drivers/mmc/host/Makefile | 1 +
drivers/mmc/host/loongson2-mmc.c | 636 +++++++++++++++++++++++++++++++
drivers/mmc/host/loongson2-mmc.h | 177 +++++++++
5 files changed, 829 insertions(+)
create mode 100644 drivers/mmc/host/loongson2-mmc.c
create mode 100644 drivers/mmc/host/loongson2-mmc.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 32c3733a764a..e218a3e204ef 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13940,6 +13940,8 @@ M: Binbin Zhou <zhoubinbin@loongson.cn>
L: linux-mmc@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
+F: drivers/mmc/host/loongson2-mmc.c
+F: drivers/mmc/host/loongson2-mmc.h
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 264e11fa58ea..c8f46901c0e8 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -1097,6 +1097,19 @@ config MMC_OWL
This selects support for the SD/MMC Host Controller on
Actions Semi Owl SoCs.
+config MMC_LOONGSON2
+ tristate "Loongso-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 5147467ec825..ad09453d65da 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..6348728694e7
--- /dev/null
+++ b/drivers/mmc/host/loongson2-mmc.c
@@ -0,0 +1,636 @@
+// 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>
+
+#include "loongson2-mmc.h"
+
+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 no irq */
+ 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_request *mrq = host->mrq;
+ struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd;
+
+ if (cmd->data)
+ dma_unmap_sg(mmc_dev(host->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(host->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(host->mmc, mrq);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t loongson2_mmc_irq(int irq, void *dev_id)
+{
+ struct loongson2_mmc_host *host = dev_id;
+ 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);
+
+ mmc_signal_sdio_irq(host->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);
+}
+
+static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+ struct loongson2_mmc_host *host = mmc_priv(mmc);
+
+ switch (ios->power_mode) {
+ case MMC_POWER_ON:
+ case MMC_POWER_UP:
+ regmap_write(host->regmap, LOONGSON2_MMC_REG_CTL, LOONGSON2_MMC_CTL_RESET);
+ mdelay(10);
+ regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, LOONGSON2_MMC_IEN_ALL);
+ regmap_write(host->regmap, LOONGSON2_MMC_REG_IEN, LOONGSON2_MMC_INT_CLEAR);
+ break;
+ case MMC_POWER_OFF:
+ regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL,
+ LOONGSON2_MMC_CTL_RESET, LOONGSON2_MMC_CTL_RESET);
+ return;
+ default:
+ return;
+ }
+
+ loongson2_mmc_set_clk(host, ios);
+
+ /* Set CLOCK_ENABLE */
+ if (ios->clock)
+ regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL,
+ LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK);
+
+ 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 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,
+};
+
+static const struct regmap_config ls2k1000_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] = cpu_to_be32(data[j]);
+ }
+}
+
+static int loongson2_mmc_prepare_external_dma(struct loongson2_mmc_host *host,
+ struct mmc_data *data)
+{
+ struct dma_async_tx_descriptor *desc;
+ struct dma_slave_config conf = {
+ .src_addr = host->res->start + LOONGSON2_MMC_REG_DATA,
+ .dst_addr = host->res->start + LOONGSON2_MMC_REG_DATA,
+ .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+ .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
+ };
+
+ conf.direction = !(data->flags & MMC_DATA_WRITE) ?
+ DMA_DEV_TO_MEM : DMA_MEM_TO_DEV;
+
+ dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
+ mmc_get_dma_dir(data));
+
+ dmaengine_slave_config(host->chan, &conf);
+ desc = dmaengine_prep_slave_sg(host->chan, data->sg, data->sg_len,
+ 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(host->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_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_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 = host->pdata->setting_dma(host, pdev);
+ if (ret)
+ return ret;
+
+ return dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+}
+
+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 = mmc_alloc_host(sizeof(*host), dev);
+ if (!mmc) {
+ dev_err(dev, "Failed to alloc mmc host\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, mmc);
+
+ host = mmc_priv(mmc);
+ host->mmc = mmc;
+ host->state = STATE_NONE;
+ spin_lock_init(&host->lock);
+
+ host->pdata = device_get_match_data(dev);
+ if (!host->pdata) {
+ dev_err(dev, "Failed to get match data\n");
+ ret = -EINVAL;
+ goto free_host;
+ }
+
+ ret = loongson2_mmc_resource_request(pdev, host);
+ if (ret) {
+ dev_err(dev, "Failed to request resource\n");
+ goto free_host;
+ }
+
+ mmc->ops = &loongson2_mmc_ops;
+ mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+ 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;
+
+ 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);
+free_host:
+ mmc_free_host(mmc);
+ 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);
+ mmc_free_host(mmc);
+}
+
+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");
diff --git a/drivers/mmc/host/loongson2-mmc.h b/drivers/mmc/host/loongson2-mmc.h
new file mode 100644
index 000000000000..4d8ada650350
--- /dev/null
+++ b/drivers/mmc/host/loongson2-mmc.h
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef LOONGSON2_MMC_H
+#define LOONGSON2_MMC_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_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_host *mmc;
+ 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;
+ 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);
+};
+#endif
--
2.47.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v1 3/4] dt-bindings: mmc: loongson,ls2k-mmc: Add compatible for Loongson-2K2000
2025-04-10 8:40 [PATCH v1 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou
2025-04-10 8:40 ` [PATCH v1 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
2025-04-10 8:40 ` [PATCH v1 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou
@ 2025-04-10 8:40 ` Binbin Zhou
2025-04-11 17:05 ` Rob Herring
2025-04-10 8:41 ` [PATCH v1 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO controller driver Binbin Zhou
3 siblings, 1 reply; 10+ messages in thread
From: Binbin Zhou @ 2025-04-10 8:40 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ulf Hansson
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-mmc,
Binbin Zhou
Add the devicetree compatible for Loongson-2K2000 EMMC/SD/SDIO controller.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
.../bindings/mmc/loongson,ls2k-mmc.yaml | 47 ++++++++++++++++++-
1 file changed, 45 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml b/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
index f3e94f5f3a35..24d217a9bbe6 100644
--- a/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
+++ b/Documentation/devicetree/bindings/mmc/loongson,ls2k-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
maxItems: 2
interrupts:
@@ -42,11 +47,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>
@@ -65,3 +90,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] 10+ messages in thread
* [PATCH v1 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO controller driver
2025-04-10 8:40 [PATCH v1 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou
` (2 preceding siblings ...)
2025-04-10 8:40 ` [PATCH v1 3/4] dt-bindings: mmc: loongson,ls2k-mmc: Add compatible for Loongson-2K2000 Binbin Zhou
@ 2025-04-10 8:41 ` Binbin Zhou
3 siblings, 0 replies; 10+ messages in thread
From: Binbin Zhou @ 2025-04-10 8:41 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ulf Hansson
Cc: Huacai Chen, Xuerui Wang, loongarch, devicetree, linux-mmc,
Binbin Zhou
This patch introduces the eMMC controller and SDIO controller built into
the Loongson-2K2000 SoC. They have similar register structures and both
support only exclusive DMA communication.
Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
---
drivers/mmc/host/loongson2-mmc.c | 149 ++++++++++++++++++++++++++++++-
drivers/mmc/host/loongson2-mmc.h | 47 ++++++++++
2 files changed, 194 insertions(+), 2 deletions(-)
diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c
index 6348728694e7..7e1cca47b496 100644
--- a/drivers/mmc/host/loongson2-mmc.c
+++ b/drivers/mmc/host/loongson2-mmc.c
@@ -98,7 +98,7 @@ static int loongson2_mmc_prepare_dma(struct loongson2_mmc_host *host,
static void loongson2_mmc_send_request(struct mmc_host *mmc)
{
- int ret;
+ int ret, val;
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;
@@ -112,6 +112,14 @@ static void loongson2_mmc_send_request(struct mmc_host *mmc)
return;
}
+ if (cmd->opcode == MMC_WRITE_BLOCK || cmd->opcode == MMC_WRITE_MULTIPLE_BLOCK) {
+ ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val,
+ (val & LOONGSON2_MMC_FSTS_TXFULL), 0, 500);
+ if (ret < 0)
+ return;
+ }
+
+ /* Send command */
loongson2_mmc_send_command(host, cmd);
/* Fix deselect card no irq */
@@ -254,6 +262,36 @@ static irqreturn_t loongson2_mmc_irq(int irq, void *dev_id)
return IRQ_WAKE_THREAD;
}
+static void loongson2_mmc_ddr_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_BYPASS, 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;
@@ -263,6 +301,10 @@ static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_io
pre = 255;
regmap_write(host->regmap, LOONGSON2_MMC_REG_PRE, pre | LOONGSON2_MMC_PRE_EN);
+
+ /* EMMC DDR mode setting */
+ if (ios->timing == MMC_TIMING_UHS_DDR50 || ios->timing == MMC_TIMING_MMC_DDR52)
+ loongson2_mmc_ddr_mode_init(host);
}
static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
@@ -469,6 +511,108 @@ 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 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 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]);
+ }
+}
+
+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;
+ 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(host->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 struct loongson2_mmc_pdata ls2k2000_mmc_pdata = {
+ .regmap_config = ls2k2000_mmc_regmap_config,
+ .reorder_cmd_data = ls2k2000_mmc_reorder_cmd_data,
+ .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)
{
@@ -593,7 +737,8 @@ 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);
diff --git a/drivers/mmc/host/loongson2-mmc.h b/drivers/mmc/host/loongson2-mmc.h
index 4d8ada650350..dfd523fea081 100644
--- a/drivers/mmc/host/loongson2-mmc.h
+++ b/drivers/mmc/host/loongson2-mmc.h
@@ -22,6 +22,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
+#define LOONGSON2_MMC_REG_DLLCTL 0xf4
+#define LOONGSON2_MMC_REG_DELAY 0xf8
+#define LOONGSON2_MMC_REG_SEL 0xfc
+
+/* 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_RESET BIT(8)
@@ -86,6 +98,8 @@
#define LOONGSON2_MMC_DSTS_RESUME BIT(15)
#define LOONGSON2_MMC_DSTS_SUSPEND BIT(16)
+#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)
@@ -113,6 +127,37 @@
#define LOONGSON2_MMC_IEN_ALL GENMASK(9, 0)
#define LOONGSON2_MMC_INT_CLEAR GENMASK(9, 0)
+#define LOONGSON2_MMC_DLLVAL_DONE BIT(8)
+
+#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_BYPASS 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 */
+
+/* Bitfields in 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 in 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 in 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
@@ -158,6 +203,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;
--
2.47.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v1 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding
2025-04-10 8:40 ` [PATCH v1 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
@ 2025-04-11 17:03 ` Rob Herring (Arm)
0 siblings, 0 replies; 10+ messages in thread
From: Rob Herring (Arm) @ 2025-04-11 17:03 UTC (permalink / raw)
To: Binbin Zhou
Cc: Rob Herring, linux-mmc, Krzysztof Kozlowski, loongarch,
Xuerui Wang, Huacai Chen, Conor Dooley, Huacai Chen, Binbin Zhou,
Ulf Hansson, devicetree
On Thu, 10 Apr 2025 16:40:35 +0800, 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,ls2k-mmc.yaml | 67 +++++++++++++++++++
> MAINTAINERS | 6 ++
> 2 files changed, 73 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v1 3/4] dt-bindings: mmc: loongson,ls2k-mmc: Add compatible for Loongson-2K2000
2025-04-10 8:40 ` [PATCH v1 3/4] dt-bindings: mmc: loongson,ls2k-mmc: Add compatible for Loongson-2K2000 Binbin Zhou
@ 2025-04-11 17:05 ` Rob Herring
2025-04-14 1:31 ` Binbin Zhou
0 siblings, 1 reply; 10+ messages in thread
From: Rob Herring @ 2025-04-11 17:05 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Krzysztof Kozlowski, Conor Dooley,
Ulf Hansson, Huacai Chen, Xuerui Wang, loongarch, devicetree,
linux-mmc
On Thu, Apr 10, 2025 at 04:40:37PM +0800, Binbin Zhou wrote:
> Add the devicetree compatible for Loongson-2K2000 EMMC/SD/SDIO controller.
>
> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> ---
> .../bindings/mmc/loongson,ls2k-mmc.yaml | 47 ++++++++++++++++++-
> 1 file changed, 45 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml b/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
> index f3e94f5f3a35..24d217a9bbe6 100644
> --- a/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
> +++ b/Documentation/devicetree/bindings/mmc/loongson,ls2k-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
> maxItems: 2
Missed this in the first patch, but you need to define what each entry
is.
>
> interrupts:
> @@ -42,11 +47,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>
> @@ -65,3 +90,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 [flat|nested] 10+ messages in thread
* Re: [PATCH v1 3/4] dt-bindings: mmc: loongson,ls2k-mmc: Add compatible for Loongson-2K2000
2025-04-11 17:05 ` Rob Herring
@ 2025-04-14 1:31 ` Binbin Zhou
0 siblings, 0 replies; 10+ messages in thread
From: Binbin Zhou @ 2025-04-14 1:31 UTC (permalink / raw)
To: Rob Herring
Cc: Binbin Zhou, Huacai Chen, Krzysztof Kozlowski, Conor Dooley,
Ulf Hansson, Huacai Chen, Xuerui Wang, loongarch, devicetree,
linux-mmc
Hi Rob:
Thanks for your reply.
On Sat, Apr 12, 2025 at 1:05 AM Rob Herring <robh@kernel.org> wrote:
>
> On Thu, Apr 10, 2025 at 04:40:37PM +0800, Binbin Zhou wrote:
> > Add the devicetree compatible for Loongson-2K2000 EMMC/SD/SDIO controller.
> >
> > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
> > ---
> > .../bindings/mmc/loongson,ls2k-mmc.yaml | 47 ++++++++++++++++++-
> > 1 file changed, 45 insertions(+), 2 deletions(-)
> >
> > diff --git a/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml b/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
> > index f3e94f5f3a35..24d217a9bbe6 100644
> > --- a/Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
> > +++ b/Documentation/devicetree/bindings/mmc/loongson,ls2k-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
> > maxItems: 2
>
> Missed this in the first patch, but you need to define what each entry
> is.
Are you thinking that “minItems” should be added in patch-1?
I'm not sure if I'm right, but for patch-1 (Loongson-2K1000), his reg
count must be 2, so it doesn't need the “ minItems: 1” limitation,
whereas Loongson-2K2000 needs this limitation, and so it was put in
this patch.
Of course, for the reg description, it is true that I missed it, and I
will add it in patch-1.
>
> >
> > interrupts:
> > @@ -42,11 +47,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>
> > @@ -65,3 +90,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
> >
--
Thanks.
Binbin
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v1 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver
2025-04-10 8:40 ` [PATCH v1 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou
@ 2025-04-28 15:09 ` Ulf Hansson
2025-05-06 2:50 ` Binbin Zhou
0 siblings, 1 reply; 10+ messages in thread
From: Ulf Hansson @ 2025-04-28 15:09 UTC (permalink / raw)
To: Binbin Zhou
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Huacai Chen, Xuerui Wang, loongarch, devicetree,
linux-mmc
On Thu, 10 Apr 2025 at 10:41, 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>
Overall this looks good to me. A few things though, please have a look below.
> ---
> MAINTAINERS | 2 +
> drivers/mmc/host/Kconfig | 13 +
> drivers/mmc/host/Makefile | 1 +
> drivers/mmc/host/loongson2-mmc.c | 636 +++++++++++++++++++++++++++++++
> drivers/mmc/host/loongson2-mmc.h | 177 +++++++++
> 5 files changed, 829 insertions(+)
> create mode 100644 drivers/mmc/host/loongson2-mmc.c
> create mode 100644 drivers/mmc/host/loongson2-mmc.h
I think we can put all code in the c-file, no need for a separate header file.
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 32c3733a764a..e218a3e204ef 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -13940,6 +13940,8 @@ M: Binbin Zhou <zhoubinbin@loongson.cn>
> L: linux-mmc@vger.kernel.org
> S: Supported
> F: Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
> +F: drivers/mmc/host/loongson2-mmc.c
> +F: drivers/mmc/host/loongson2-mmc.h
>
[...]
> +static irqreturn_t loongson2_mmc_irq(int irq, void *dev_id)
> +{
> + struct loongson2_mmc_host *host = dev_id;
> + 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);
> +
> + mmc_signal_sdio_irq(host->mmc);
mmc_signal_sdio_irq() is the legacy interface for managing SDIO irqs.
Please convert to use sdio_signal_irq() instead, along with the
->ack_sdio_irq() callback.
> + 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 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);
Is ACPI supported too?
> + }
> +
> + 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 = host->pdata->setting_dma(host, pdev);
> + if (ret)
> + return ret;
> +
> + return dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
> +}
> +
> +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 = mmc_alloc_host(sizeof(*host), dev);
We have devm_mmc_alloc_host() too, perhaps worth to use it to simplify
the code a bit.
> + if (!mmc) {
> + dev_err(dev, "Failed to alloc mmc host\n");
> + return -ENOMEM;
> + }
> +
> + platform_set_drvdata(pdev, mmc);
> +
> + host = mmc_priv(mmc);
> + host->mmc = mmc;
> + host->state = STATE_NONE;
> + spin_lock_init(&host->lock);
> +
> + host->pdata = device_get_match_data(dev);
> + if (!host->pdata) {
> + dev_err(dev, "Failed to get match data\n");
> + ret = -EINVAL;
> + goto free_host;
> + }
> +
> + ret = loongson2_mmc_resource_request(pdev, host);
> + if (ret) {
> + dev_err(dev, "Failed to request resource\n");
> + goto free_host;
> + }
> +
> + mmc->ops = &loongson2_mmc_ops;
> + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
How is power to the card controlled for these platforms?
The preferred way is to use the regulator API to manage this - and the
mmc core also provides a couple of helper functions for this, like
mmc_regulator_get_supply(), for example.
> + 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;
> +
> + 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);
> +free_host:
> + mmc_free_host(mmc);
> + return ret;
> +}
> +
[...]
Kind regards
Uffe
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v1 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver
2025-04-28 15:09 ` Ulf Hansson
@ 2025-05-06 2:50 ` Binbin Zhou
0 siblings, 0 replies; 10+ messages in thread
From: Binbin Zhou @ 2025-05-06 2:50 UTC (permalink / raw)
To: Ulf Hansson
Cc: Binbin Zhou, Huacai Chen, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Huacai Chen, Xuerui Wang, loongarch, devicetree,
linux-mmc
Hi Ulf:
Thanks for your review and sorry for the late reply.
On Mon, Apr 28, 2025 at 11:10 PM Ulf Hansson <ulf.hansson@linaro.org> wrote:
>
> On Thu, 10 Apr 2025 at 10:41, 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>
>
> Overall this looks good to me. A few things though, please have a look below.
>
> > ---
> > MAINTAINERS | 2 +
> > drivers/mmc/host/Kconfig | 13 +
> > drivers/mmc/host/Makefile | 1 +
> > drivers/mmc/host/loongson2-mmc.c | 636 +++++++++++++++++++++++++++++++
> > drivers/mmc/host/loongson2-mmc.h | 177 +++++++++
> > 5 files changed, 829 insertions(+)
> > create mode 100644 drivers/mmc/host/loongson2-mmc.c
> > create mode 100644 drivers/mmc/host/loongson2-mmc.h
>
> I think we can put all code in the c-file, no need for a separate header file.
OK, I will do it.
>
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 32c3733a764a..e218a3e204ef 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -13940,6 +13940,8 @@ M: Binbin Zhou <zhoubinbin@loongson.cn>
> > L: linux-mmc@vger.kernel.org
> > S: Supported
> > F: Documentation/devicetree/bindings/mmc/loongson,ls2k-mmc.yaml
> > +F: drivers/mmc/host/loongson2-mmc.c
> > +F: drivers/mmc/host/loongson2-mmc.h
> >
>
> [...]
>
> > +static irqreturn_t loongson2_mmc_irq(int irq, void *dev_id)
> > +{
> > + struct loongson2_mmc_host *host = dev_id;
> > + 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);
> > +
> > + mmc_signal_sdio_irq(host->mmc);
>
> mmc_signal_sdio_irq() is the legacy interface for managing SDIO irqs.
>
> Please convert to use sdio_signal_irq() instead, along with the
> ->ack_sdio_irq() callback.
OK..
>
> > + 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 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);
>
> Is ACPI supported too?
Yes, like on the Loongson-2K2000 platform, we support ACPI and FDT.
For ACPI, we also match by compatible strings, so the interface is the
same as for FDT, except that the in-node attribute descriptions are
not exactly the same, e.g. clock-frequency.
>
> > + }
> > +
> > + 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 = host->pdata->setting_dma(host, pdev);
> > + if (ret)
> > + return ret;
> > +
> > + return dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
> > +}
> > +
> > +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 = mmc_alloc_host(sizeof(*host), dev);
>
> We have devm_mmc_alloc_host() too, perhaps worth to use it to simplify
> the code a bit.
This is good news and I will do it.
>
> > + if (!mmc) {
> > + dev_err(dev, "Failed to alloc mmc host\n");
> > + return -ENOMEM;
> > + }
> > +
> > + platform_set_drvdata(pdev, mmc);
> > +
> > + host = mmc_priv(mmc);
> > + host->mmc = mmc;
> > + host->state = STATE_NONE;
> > + spin_lock_init(&host->lock);
> > +
> > + host->pdata = device_get_match_data(dev);
> > + if (!host->pdata) {
> > + dev_err(dev, "Failed to get match data\n");
> > + ret = -EINVAL;
> > + goto free_host;
> > + }
> > +
> > + ret = loongson2_mmc_resource_request(pdev, host);
> > + if (ret) {
> > + dev_err(dev, "Failed to request resource\n");
> > + goto free_host;
> > + }
> > +
> > + mmc->ops = &loongson2_mmc_ops;
> > + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
>
> How is power to the card controlled for these platforms?
>
> The preferred way is to use the regulator API to manage this - and the
> mmc core also provides a couple of helper functions for this, like
> mmc_regulator_get_supply(), for example.
Emm, I will try to use this API.
>
> > + 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;
> > +
> > + 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);
> > +free_host:
> > + mmc_free_host(mmc);
> > + return ret;
> > +}
> > +
>
> [...]
>
> Kind regards
> Uffe
--
Thanks.
Binbin
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-05-06 2:50 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-10 8:40 [PATCH v1 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou
2025-04-10 8:40 ` [PATCH v1 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
2025-04-11 17:03 ` Rob Herring (Arm)
2025-04-10 8:40 ` [PATCH v1 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou
2025-04-28 15:09 ` Ulf Hansson
2025-05-06 2:50 ` Binbin Zhou
2025-04-10 8:40 ` [PATCH v1 3/4] dt-bindings: mmc: loongson,ls2k-mmc: Add compatible for Loongson-2K2000 Binbin Zhou
2025-04-11 17:05 ` Rob Herring
2025-04-14 1:31 ` Binbin Zhou
2025-04-10 8:41 ` [PATCH v1 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO controller driver Binbin Zhou
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.