Contributors: 3
Author Tokens Token Proportion Commits Commit Proportion
Binbin Zhou 4875 99.94% 3 60.00%
Sergio Perez Gonzalez 2 0.04% 1 20.00%
Dan Carpenter 1 0.02% 1 20.00%
Total 4878 5


// 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 */

/* EMMC DLL Mode Registers */
#define LOONGSON2_MMC_REG_DLLVAL	0xf0 /* DLL Master Lock-value Register */
#define LOONGSON2_MMC_REG_DLLCTL	0xf4 /* DLL Control Register */
#define LOONGSON2_MMC_REG_DELAY		0xf8 /* DLL Delayed Parameter Register */
#define LOONGSON2_MMC_REG_SEL		0xfc /* Bus Mode Selection Register */

/* Exclusive DMA R/W Registers */
#define LOONGSON2_MMC_REG_WDMA_LO	0x400
#define LOONGSON2_MMC_REG_WDMA_HI	0x404
#define LOONGSON2_MMC_REG_RDMA_LO	0x800
#define LOONGSON2_MMC_REG_RDMA_HI	0x804

/* Bitfields of control register */
#define LOONGSON2_MMC_CTL_ENCLK		BIT(0)
#define LOONGSON2_MMC_CTL_EXTCLK	BIT(1)
#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 FIFO Status Register */
#define LOONGSON2_MMC_FSTS_TXFULL	BIT(11)

/* Bitfields of interrupt register */
#define LOONGSON2_MMC_INT_DFIN		BIT(0)
#define LOONGSON2_MMC_INT_DTIMEOUT	BIT(1)
#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)

/* Bitfields of DLL master lock-value register */
#define LOONGSON2_MMC_DLLVAL_DONE	BIT(8)

/* Bitfields of DLL control register */
#define LOONGSON2_MMC_DLLCTL_TIME	GENMASK(7, 0)
#define LOONGSON2_MMC_DLLCTL_INCRE	GENMASK(15, 8)
#define LOONGSON2_MMC_DLLCTL_START	GENMASK(23, 16)
#define LOONGSON2_MMC_DLLCTL_CLK_MODE	BIT(24)
#define LOONGSON2_MMC_DLLCTL_START_BIT	BIT(25)
#define LOONGSON2_MMC_DLLCTL_TIME_BPASS	GENMASK(29, 26)

#define LOONGSON2_MMC_DELAY_PAD		GENMASK(7, 0)
#define LOONGSON2_MMC_DELAY_RD		GENMASK(15, 8)

#define LOONGSON2_MMC_SEL_DATA		BIT(0)	/* 0: SDR, 1: DDR */
#define LOONGSON2_MMC_SEL_BUS		BIT(0)	/* 0: EMMC, 1: SDIO */

/* Internal dma controller registers */

/* Bitfields of Global Configuration Register */
#define LOONGSON2_MMC_DMA_64BIT_EN	BIT(0) /* 1: 64 bit support */
#define LOONGSON2_MMC_DMA_UNCOHERENT_EN	BIT(1) /* 0: cache, 1: uncache */
#define LOONGSON2_MMC_DMA_ASK_VALID	BIT(2)
#define LOONGSON2_MMC_DMA_START		BIT(3) /* DMA start operation */
#define LOONGSON2_MMC_DMA_STOP		BIT(4) /* DMA stop operation */
#define LOONGSON2_MMC_DMA_CONFIG_MASK	GENMASK_ULL(4, 0) /* DMA controller config bits mask */

/* Bitfields of ndesc_addr field of HW descriptor */
#define LOONGSON2_MMC_DMA_DESC_EN	BIT(0) /*1: The next descriptor is valid */
#define LOONGSON2_MMC_DMA_DESC_ADDR_LOW	GENMASK(31, 1)

/* Bitfields of cmd field of HW descriptor */
#define LOONGSON2_MMC_DMA_INT		BIT(1)	/* Enable DMA interrupts */
#define LOONGSON2_MMC_DMA_DATA_DIR	BIT(12) /* 1: write to device, 0: read from device */

#define LOONGSON2_MMC_DLLVAL_TIMEOUT_US		4000
#define LOONGSON2_MMC_TXFULL_TIMEOUT_US		500

/* 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;
	u32 current_clk;
	void *sg_cpu;
	dma_addr_t sg_dma;
	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);
	void (*fix_data_timeout)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
	int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev);
	int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data);
	void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev);
};

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;
	}

	if (host->pdata->fix_data_timeout)
		host->pdata->fix_data_timeout(host, cmd);

	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_dll_mode_init(struct loongson2_mmc_host *host)
{
	u32 val, pad_delay, delay;
	int ret;

	regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_SEL,
			   LOONGSON2_MMC_SEL_DATA, LOONGSON2_MMC_SEL_DATA);

	val = FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME, 0xc8)
	    | FIELD_PREP(LOONGSON2_MMC_DLLCTL_INCRE, 0x1)
	    | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START, 0x1)
	    | FIELD_PREP(LOONGSON2_MMC_DLLCTL_CLK_MODE, 0x1)
	    | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START_BIT, 0x1)
	    | FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME_BPASS, 0xf);

	regmap_write(host->regmap, LOONGSON2_MMC_REG_DLLCTL, val);

	ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_DLLVAL, val,
				       (val & LOONGSON2_MMC_DLLVAL_DONE), 0,
				       LOONGSON2_MMC_DLLVAL_TIMEOUT_US);
	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;

	pre = DIV_ROUND_UP(host->current_clk, 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);

	/* EMMC DLL mode setting */
	if (ios->timing == MMC_TIMING_UHS_DDR50 || ios->timing == MMC_TIMING_MMC_DDR52)
		loongson2_mmc_dll_mode_init(host);
}

static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
	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; /* return, if failed turn on vmmc */
			}
		}
		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 ls2k0500_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 ls2k0500_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		= &ls2k0500_mmc_regmap_config,
	.reorder_cmd_data	= ls2k0500_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		= &ls2k0500_mmc_regmap_config,
	.reorder_cmd_data	= ls2k0500_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 const struct regmap_config ls2k2000_mmc_regmap_config = {
	.reg_bits = 32,
	.val_bits = 32,
	.reg_stride = 4,
	.max_register = LOONGSON2_MMC_REG_RDMA_HI,
};

static void ls2k2000_mmc_reorder_cmd_data(struct loongson2_mmc_host *host,
					  struct mmc_command *cmd)
{
	struct scatterlist *sg;
	u32 *data;
	int i, j;

	if (cmd->opcode != SD_SWITCH || mmc_cmd_type(cmd) != MMC_CMD_ADTC)
		return;

	for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) {
		data = sg_virt(&sg[i]);
		for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++)
			data[j] = bitrev8x4(data[j]);
	}
}

/*
 * This is a controller hardware defect. Single/multiple block write commands
 * must be sent after the TX FULL flag is set, otherwise a data timeout interrupt
 * will occur.
 */
static void ls2k2000_mmc_fix_data_timeout(struct loongson2_mmc_host *host,
					  struct mmc_command *cmd)
{
	int val;

	if (cmd->opcode != MMC_WRITE_BLOCK && cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK)
		return;

	regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val,
				 (val & LOONGSON2_MMC_FSTS_TXFULL), 0,
				 LOONGSON2_MMC_TXFULL_TIMEOUT_US);
}

static int loongson2_mmc_prepare_internal_dma(struct loongson2_mmc_host *host,
					      struct mmc_data *data)
{
	struct loongson2_dma_desc *pdes = (struct loongson2_dma_desc *)host->sg_cpu;
	struct mmc_host *mmc = mmc_from_priv(host);
	dma_addr_t next_desc = host->sg_dma;
	struct scatterlist *sg;
	int reg_lo, reg_hi;
	u64 dma_order;
	int i, ret;

	ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len,
			 mmc_get_dma_dir(data));
	if (!ret)
		return -ENOMEM;

	for_each_sg(data->sg, sg, data->sg_len, i) {
		pdes[i].len = sg_dma_len(&sg[i]) / 4;
		pdes[i].step_len = 0;
		pdes[i].step_times = 1;
		pdes[i].mem_addr = lower_32_bits(sg_dma_address(&sg[i]));
		pdes[i].high_mem_addr = upper_32_bits(sg_dma_address(&sg[i]));
		pdes[i].apb_addr = host->res->start + LOONGSON2_MMC_REG_DATA;
		pdes[i].cmd = LOONGSON2_MMC_DMA_INT;

		if (data->flags & MMC_DATA_READ) {
			reg_lo = LOONGSON2_MMC_REG_RDMA_LO;
			reg_hi = LOONGSON2_MMC_REG_RDMA_HI;
		} else {
			pdes[i].cmd |= LOONGSON2_MMC_DMA_DATA_DIR;
			reg_lo = LOONGSON2_MMC_REG_WDMA_LO;
			reg_hi = LOONGSON2_MMC_REG_WDMA_HI;
		}

		next_desc += sizeof(struct loongson2_dma_desc);
		pdes[i].ndesc_addr = lower_32_bits(next_desc) |
				     LOONGSON2_MMC_DMA_DESC_EN;
		pdes[i].high_ndesc_addr = upper_32_bits(next_desc);
	}

	/* Setting the last descriptor enable bit */
	pdes[i - 1].ndesc_addr &= ~LOONGSON2_MMC_DMA_DESC_EN;

	dma_order = (host->sg_dma & ~LOONGSON2_MMC_DMA_CONFIG_MASK) |
		    LOONGSON2_MMC_DMA_64BIT_EN |
		    LOONGSON2_MMC_DMA_START;

	regmap_write(host->regmap, reg_hi, upper_32_bits(dma_order));
	regmap_write(host->regmap, reg_lo, lower_32_bits(dma_order));

	return 0;
}

static int ls2k2000_mmc_set_internal_dma(struct loongson2_mmc_host *host,
					 struct platform_device *pdev)
{
	host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE,
					  &host->sg_dma, GFP_KERNEL);
	if (!host->sg_cpu)
		return -ENOMEM;

	memset(host->sg_cpu, 0, PAGE_SIZE);
	return 0;
}

static void loongson2_mmc_release_internal_dma(struct loongson2_mmc_host *host,
					       struct device *dev)
{
	dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
}

static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = {
	.regmap_config		= &ls2k2000_mmc_regmap_config,
	.reorder_cmd_data	= ls2k2000_mmc_reorder_cmd_data,
	.fix_data_timeout	= ls2k2000_mmc_fix_data_timeout,
	.setting_dma		= ls2k2000_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)
{
	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 ret;

		host->current_clk = clk_get_rate(host->clk);
	} else {
		/* For ACPI, the clock is accessed via the clock-frequency attribute. */
		device_property_read_u32(dev, "clock-frequency", &host->current_clk);
	}

	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->current_clk, 256);
	mmc->f_max = host->current_clk;
	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 },
	{ .compatible = "loongson,ls2k2000-mmc", .data = &ls2k2000_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");