From: Binbin Zhou Date: Tue, 24 Jun 2025 11:58:40 +0000 (+0800) Subject: mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d0f8e961deae82e077d7f2ced1a20106605cf4e9;p=thirdparty%2Fkernel%2Fstable.git 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 Reviewed-by: Huacai Chen Link: https://lore.kernel.org/r/1df46b976abd36003bd553ad8a039e5c97369df0.1750765495.git.zhoubinbin@loongson.cn Signed-off-by: Ulf Hansson --- diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c index 567a1b8c57c4..515ccf834f0a 100644 --- a/drivers/mmc/host/loongson2-mmc.c +++ b/drivers/mmc/host/loongson2-mmc.c @@ -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 @@ close_transfer: 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);