// SPDX-License-Identifier: GPL-2.0-only /* * Loongson-2K MMC/SDIO controller driver * * Copyright (C) 2018-2025 Loongson Technology Corporation Limited. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");