* [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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).