diff mbox series

[v3,2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver

Message ID abfe54473135df478db14b5ef0e1773326455f21.1750216134.git.zhoubinbin@loongson.cn
State New
Headers show
Series LoongArch: Introduce the Loongson-2K MMC host controller driver | expand

Commit Message

Binbin Zhou June 18, 2025, 8:07 a.m. UTC
The MMC controllers on the Loongson-2K series CPUs are similar,
except for the interface characteristics and the use of DMA controllers.

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

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

Comments

Huacai Chen June 19, 2025, 4:02 a.m. UTC | #1
Hi, Binbin,

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

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

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

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

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

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

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

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

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

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

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

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


Huacai

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

Patch

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