]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver
authorBinbin Zhou <zhoubinbin@loongson.cn>
Tue, 24 Jun 2025 11:58:40 +0000 (19:58 +0800)
committerUlf Hansson <ulf.hansson@linaro.org>
Thu, 3 Jul 2025 10:56:41 +0000 (12:56 +0200)
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>
drivers/mmc/host/loongson2-mmc.c

index 567a1b8c57c4924f48ed71ada71621174bdc469b..515ccf834f0ab0be75cb24ef81583f35af18d91f 100644 (file)
 #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_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_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);