mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver

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

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

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
Reviewed-by: Huacai Chen <chenhuacai@loongson.cn>
Link: https://lore.kernel.org/r/1df46b976abd36003bd553ad8a039e5c97369df0.1750765495.git.zhoubinbin@loongson.cn
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
Binbin Zhou 2025-06-24 19:58:40 +08:00 committed by Ulf Hansson
parent 96e72886a4
commit d0f8e961de

View File

@ -44,6 +44,18 @@
#define LOONGSON2_MMC_REG_DATA 0x40 /* Data Register */
#define LOONGSON2_MMC_REG_IEN 0x64 /* Interrupt Enable Register */
/* EMMC DLL Mode Registers */
#define LOONGSON2_MMC_REG_DLLVAL 0xf0 /* DLL Master Lock-value Register */
#define LOONGSON2_MMC_REG_DLLCTL 0xf4 /* DLL Control Register */
#define LOONGSON2_MMC_REG_DELAY 0xf8 /* DLL Delayed Parameter Register */
#define LOONGSON2_MMC_REG_SEL 0xfc /* Bus Mode Selection Register */
/* Exclusive DMA R/W Registers */
#define LOONGSON2_MMC_REG_WDMA_LO 0x400
#define LOONGSON2_MMC_REG_WDMA_HI 0x404
#define LOONGSON2_MMC_REG_RDMA_LO 0x800
#define LOONGSON2_MMC_REG_RDMA_HI 0x804
/* Bitfields of control register */
#define LOONGSON2_MMC_CTL_ENCLK BIT(0)
#define LOONGSON2_MMC_CTL_EXTCLK BIT(1)
@ -109,6 +121,9 @@
#define LOONGSON2_MMC_DSTS_RESUME BIT(15)
#define LOONGSON2_MMC_DSTS_SUSPEND BIT(16)
/* Bitfields of FIFO Status Register */
#define LOONGSON2_MMC_FSTS_TXFULL BIT(11)
/* Bitfields of interrupt register */
#define LOONGSON2_MMC_INT_DFIN BIT(0)
#define LOONGSON2_MMC_INT_DTIMEOUT BIT(1)
@ -136,6 +151,44 @@
#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
@ -159,6 +212,20 @@ enum loongson2_mmc_state {
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;
@ -166,6 +233,8 @@ struct loongson2_mmc_host {
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;
@ -178,6 +247,7 @@ struct loongson2_mmc_host {
struct loongson2_mmc_pdata {
const struct regmap_config *regmap_config;
void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
void (*fix_data_timeout)(struct loongson2_mmc_host *host, struct mmc_command *cmd);
int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev);
int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data);
void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev);
@ -268,6 +338,9 @@ static void loongson2_mmc_send_request(struct mmc_host *mmc)
return;
}
if (host->pdata->fix_data_timeout)
host->pdata->fix_data_timeout(host, cmd);
loongson2_mmc_send_command(host, cmd);
/* Fix deselect card */
@ -410,6 +483,37 @@ static irqreturn_t loongson2_mmc_irq(int irq, void *devid)
return IRQ_WAKE_THREAD;
}
static void loongson2_mmc_dll_mode_init(struct loongson2_mmc_host *host)
{
u32 val, pad_delay, delay, ret;
regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_SEL,
LOONGSON2_MMC_SEL_DATA, LOONGSON2_MMC_SEL_DATA);
val = FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME, 0xc8)
| FIELD_PREP(LOONGSON2_MMC_DLLCTL_INCRE, 0x1)
| FIELD_PREP(LOONGSON2_MMC_DLLCTL_START, 0x1)
| FIELD_PREP(LOONGSON2_MMC_DLLCTL_CLK_MODE, 0x1)
| FIELD_PREP(LOONGSON2_MMC_DLLCTL_START_BIT, 0x1)
| FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME_BPASS, 0xf);
regmap_write(host->regmap, LOONGSON2_MMC_REG_DLLCTL, val);
ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_DLLVAL, val,
(val & LOONGSON2_MMC_DLLVAL_DONE), 0,
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;
@ -422,6 +526,10 @@ static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_io
regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL,
LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK);
/* EMMC DLL mode setting */
if (ios->timing == MMC_TIMING_UHS_DDR50 || ios->timing == MMC_TIMING_MMC_DDR52)
loongson2_mmc_dll_mode_init(host);
}
static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
@ -634,6 +742,128 @@ static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = {
.release_dma = loongson2_mmc_release_external_dma,
};
static const struct regmap_config ls2k2000_mmc_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.max_register = LOONGSON2_MMC_REG_RDMA_HI,
};
static void ls2k2000_mmc_reorder_cmd_data(struct loongson2_mmc_host *host,
struct mmc_command *cmd)
{
struct scatterlist *sg;
u32 *data;
int i, j;
if (cmd->opcode != SD_SWITCH || mmc_cmd_type(cmd) != MMC_CMD_ADTC)
return;
for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) {
data = sg_virt(&sg[i]);
for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++)
data[j] = bitrev8x4(data[j]);
}
}
/*
* This is a controller hardware defect. Single/multiple block write commands
* must be sent after the TX FULL flag is set, otherwise a data timeout interrupt
* will occur.
*/
static void ls2k2000_mmc_fix_data_timeout(struct loongson2_mmc_host *host,
struct mmc_command *cmd)
{
int val;
if (cmd->opcode != MMC_WRITE_BLOCK && cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK)
return;
regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val,
(val & LOONGSON2_MMC_FSTS_TXFULL), 0,
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 loongson2_mmc_set_internal_dma(struct loongson2_mmc_host *host,
struct platform_device *pdev)
{
host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE,
&host->sg_dma, GFP_KERNEL);
if (!host->sg_cpu)
return -ENOMEM;
memset(host->sg_cpu, 0, PAGE_SIZE);
return 0;
}
static void loongson2_mmc_release_internal_dma(struct loongson2_mmc_host *host,
struct device *dev)
{
dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
}
static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = {
.regmap_config = &ls2k2000_mmc_regmap_config,
.reorder_cmd_data = ls2k2000_mmc_reorder_cmd_data,
.fix_data_timeout = ls2k2000_mmc_fix_data_timeout,
.setting_dma = loongson2_mmc_set_internal_dma,
.prepare_dma = loongson2_mmc_prepare_internal_dma,
.release_dma = loongson2_mmc_release_internal_dma,
};
static int loongson2_mmc_resource_request(struct platform_device *pdev,
struct loongson2_mmc_host *host)
{
@ -756,6 +986,7 @@ static void loongson2_mmc_remove(struct platform_device *pdev)
static const struct of_device_id loongson2_mmc_of_ids[] = {
{ .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata },
{ .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata },
{ .compatible = "loongson,ls2k2000-mmc", .data = &ls2k2000_mmc_pdata },
{ },
};
MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids);