Message ID | aa9cfb9707334cd2e56692397dd4f54c418262ce.1750216134.git.zhoubinbin@loongson.cn |
---|---|
State | New |
Headers | show |
Series | LoongArch: Introduce the Loongson-2K MMC host controller driver | expand |
Hi, Binbin, On Wed, Jun 18, 2025 at 4:08 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote: > > 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> > --- > drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++ > 1 file changed, 212 insertions(+) > > diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c > index 872f5dc21b21..75144221a821 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,41 @@ > #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 */ > + > /* Loongson-2K1000 SDIO2 DMA routing register */ > #define LS2K1000_SDIO_DMA_MASK GENMASK(17, 15) > #define LS2K1000_DMA0_CONF 0x0 > @@ -180,6 +230,8 @@ struct loongson2_mmc_host { > struct resource *res; > struct clk *clk; > u64 rate; > + void *sg_cpu; > + dma_addr_t sg_dma; > int dma_complete; > struct dma_chan *chan; > int cmd_is_stop; > @@ -192,6 +244,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); > @@ -282,6 +335,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 */ > @@ -426,6 +482,36 @@ 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, 4000); > + 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; > @@ -438,6 +524,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) > @@ -655,6 +745,127 @@ 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, 500); > +} > + > +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, > +};
On Thu, Jun 19, 2025 at 12:03 PM Huacai Chen <chenhuacai@kernel.org> wrote: > > Hi, Binbin, > > On Wed, Jun 18, 2025 at 4:08 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote: > > > > 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> > > --- > > drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++ > > 1 file changed, 212 insertions(+) > > > > diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c > > index 872f5dc21b21..75144221a821 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,41 @@ > > #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 */ > > + > > /* Loongson-2K1000 SDIO2 DMA routing register */ > > #define LS2K1000_SDIO_DMA_MASK GENMASK(17, 15) > > #define LS2K1000_DMA0_CONF 0x0 > > @@ -180,6 +230,8 @@ struct loongson2_mmc_host { > > struct resource *res; > > struct clk *clk; > > u64 rate; > > + void *sg_cpu; > > + dma_addr_t sg_dma; > > int dma_complete; > > struct dma_chan *chan; > > int cmd_is_stop; > > @@ -192,6 +244,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); > > @@ -282,6 +335,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 */ > > @@ -426,6 +482,36 @@ 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, 4000); > > + 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; > > @@ -438,6 +524,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) > > @@ -655,6 +745,127 @@ 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, 500); > > +} > > + > > +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, > > +}; > From the whole series we get: > > +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 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 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, > +}; > > The prefix of function names are confusing, can we rename them better? OK, for regmap_config, reorder_cmd_data and reorder_cmd_data, I will use ls2k as a prefix. ls2k1000_mmc_regmap_config -> ls2k0500_mmc_regmap_config loongson2_mmc_reorder_cmd_data -> ls2k0500_mmc_reorder_cmd_data. loongson2_mmc_set_internal_dma -> ls2k2000_mmc_set_internal_dma > > > Huacai > > > > > > + > > static int loongson2_mmc_resource_request(struct platform_device *pdev, > > struct loongson2_mmc_host *host) > > { > > @@ -777,6 +988,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); > > -- > > 2.47.1 > > > >
diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c index 872f5dc21b21..75144221a821 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,41 @@ #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 */ + /* Loongson-2K1000 SDIO2 DMA routing register */ #define LS2K1000_SDIO_DMA_MASK GENMASK(17, 15) #define LS2K1000_DMA0_CONF 0x0 @@ -180,6 +230,8 @@ struct loongson2_mmc_host { struct resource *res; struct clk *clk; u64 rate; + void *sg_cpu; + dma_addr_t sg_dma; int dma_complete; struct dma_chan *chan; int cmd_is_stop; @@ -192,6 +244,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); @@ -282,6 +335,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 */ @@ -426,6 +482,36 @@ 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, 4000); + 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; @@ -438,6 +524,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) @@ -655,6 +745,127 @@ 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, 500); +} + +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) { @@ -777,6 +988,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);
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> --- drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+)