Files
openloongarch-uboot-2022.04/drivers/mmc/ls_mmc.c
T
maoxiaochuan a8e33e1ce5 Update to 2c60245b2bb
1.drivers/mmc:ls_mmc:Enhance compatibiltiy
2.mmc:support HS200
3.support boot abi2.0 kernel(linux 6.9 have be tested)
4.update set_env.sh
5.some bugs fix
2024-09-30 16:52:12 +08:00

475 lines
12 KiB
C

#include <common.h>
#include <dm.h>
#include <mmc.h>
#include <clk.h>
#include <clock_legacy.h>
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/delay.h>
#include <asm/addrspace.h>
#include <asm/io.h>
#include <linux/io.h>
#define SDIO_DMA_DESCADDR PHYS_TO_UNCACHED(0x80000)
#define SDICON 0x00
#define SDIPRE 0x04
#define SDICMDARG 0x08
#define SDICMDCON 0x0c
#define SDICMDSTA 0x10
#define SDIRSP0 0x14
#define SDIRSP1 0x18
#define SDIRSP2 0x1C
#define SDIRSP3 0x20
#define SDIDTIMER 0x24
#define SDIBSIZE 0x28
#define SDIDATCON 0x2C
#define SDIDATCNT 0x30
#define SDIDATSTA 0x34
#define SDIFSTA 0x38
#define SDIINTMSK 0x3C
#define SDIWRDAT 0x40
#define SDISTAADD0 0x44
#define SDISTAADD1 0x48
#define SDISTAADD2 0x4c
#define SDISTAADD3 0x50
#define SDISTAADD4 0x54
#define SDISTAADD5 0x58
#define SDISTAADD6 0x5c
#define SDISTAADD7 0x60
#define SDIINTEN 0x64
#define SDIO_DES_ADDR (SDIO_BASE + SDIWRDAT)
#define SDIO_CON_ENCLK BIT(0)
#define SDIO_CON_DIACLK 0
#define SDIO_CON_SOFTRST BIT(8)
#define SDIO_PRE_REVCLOCK BIT(31)
#define SDIO_CMDCON_CMST BIT(8)
#define SDIO_CMDCON_WAITRSP BIT(9)
#define SDIO_CMDCON_LONGRSP BIT(10)
#define SDIO_CMDCON_AUTOSTOP BIT(12)
#define SDIO_CMDCON_CHECKCRC BIT(13)
#define SDIO_CMDCON_SDIOEN BIT(14)
#define SDIO_DATCON_DTST BIT(14)
#define SDIO_DATCON_DMAEN BIT(15)
#define SDIO_DATCON_WIDEMODE BIT(16)
#define SDIO_DATCON_WIDEMODE8B BIT(26)
#define SDIO_INTMSK_DATFIN BIT(0)
#define SDIO_INTMSK_DATTOUT BIT(1)
#define SDIO_INTMSK_DATCRC BIT(2)
#define SDIO_INTMSK_CRCSTA BIT(3)
#define SDIO_INTMSK_PROGERR BIT(4)
#define SDIO_INTMSK_SDIO BIT(5)
#define SDIO_INTMSK_CMDFIN BIT(6)
#define SDIO_INTMSK_CMDTOUT BIT(7)
#define SDIO_INTMSK_RSPCRC BIT(8)
#define SDIO_INTMSK_R1BFIN BIT(9)
#define SDIO_MAKE_CMD(c,f) ((c & 0x3f) | 0x140 | (f))
#define SDIO_GET_CMDINT(c) (c & 0x3c0)
#define SDIO_GET_DATAINT(c) (c & 0x1f)
#define SDIO_GET_CMDIDX(c) (c & 0x3f)
#define SDIO_GET_CMD(c) ((c>>8) & 0x3f)
#define DMA_CMD_INTMSK BIT(0)
#define DMA_CMD_WRITE BIT(12)
#define DMA_OREDER_ASK BIT(2)
#define DMA_OREDER_START BIT(3)
#define DMA_OREDER_STOP BIT(4)
#define DMA_ALIGNED 32
#define DMA_ALIGNED_MSK (~(DMA_ALIGNED - 1))
struct sdio_mmc_plat {
struct mmc_config cfg;
struct mmc mmc;
};
struct sdio_host {
const char *name;
phys_addr_t ioaddr;
struct mmc *mmc;
struct mmc_config *cfg;
void *dma_desc_addr;
void *wdma_order_addr;
void *rdma_order_addr;
unsigned int clock;
bool use_sdcard;
};
struct dma_desc {
unsigned int order_addr_low;
unsigned int saddr_low;
unsigned int daddr;
unsigned int length;
unsigned int step_length;
unsigned int step_times;
unsigned int cmd;
unsigned int order_addr_high;
unsigned int saddr_high;
};
static inline void sdio_writel(struct sdio_host *host, int reg, u32 val)
{
writel(val, host->ioaddr + reg);
}
static inline u32 sdio_readl(struct sdio_host *host, int reg)
{
return readl(host->ioaddr + reg);
}
static int sdio_prepare_data(struct sdio_host* host, struct mmc_data *data)
{
struct mmc *mmc = host->mmc;
unsigned int sdidatcon = SDIO_DATCON_DTST | SDIO_DATCON_DMAEN | data->blocks;
if (data->blocks > 4096 || data->blocks == 0) {
pr_debug("DATA block argument is invalid\n");
return -EINVAL;
}
if (mmc->bus_width == 4 || mmc->selected_mode & MMC_MODE_4BIT) {
sdidatcon |= SDIO_DATCON_WIDEMODE;
} else if (mmc->bus_width == 8 || mmc->selected_mode & MMC_MODE_8BIT)
sdidatcon |= SDIO_DATCON_WIDEMODE8B;
sdio_writel(host, SDIBSIZE, data->blocksize);
sdio_writel(host, SDIDTIMER, 0x7fffff);
sdio_writel(host, SDIDATCON, sdidatcon);
return 0;
}
static int sdio_prepare_dma(struct sdio_host *host, struct mmc_data *data)
{
struct dma_desc* desc = (struct dma_desc *)host->dma_desc_addr;
int data_size = data->blocksize * data->blocks;
unsigned long data_phy_addr;
void *dma_order_addr;
if (data->flags == MMC_DATA_READ) {
data_phy_addr = (unsigned long)data->dest;
} else {
data_phy_addr = (unsigned long)data->src;
}
if (desc == NULL || !((unsigned long long)desc & UNCACHED_MEMORY_ADDR) || !host->wdma_order_addr || !host->rdma_order_addr) {
pr_debug("Invalid DMA address.\n");
return -EINVAL;
}
desc->order_addr_low = 0x0;
desc->saddr_low = data_phy_addr;
desc->daddr = host->ioaddr + SDIWRDAT;
desc->length = data_size / 4;
desc->step_length = 0x1;
desc->step_times = 0x1;
pr_debug("data_phy_addr = 0x%lx,data->flags=%d\n",data_phy_addr,data->flags);
pr_debug("desc->order_addr_low = 0x%x\n",desc->order_addr_low);
pr_debug("desc->saddr_low = 0x%x\n",desc->saddr_low);
pr_debug("desc->daddr = 0x%x\n",desc->daddr);
pr_debug("desc->length = 0x%x\n",desc->length);
if (data->flags == MMC_DATA_READ) {
desc->cmd = DMA_CMD_INTMSK;
dma_order_addr = host->rdma_order_addr;
} else {
desc->cmd = DMA_CMD_INTMSK | DMA_CMD_WRITE;
dma_order_addr = host->wdma_order_addr;
}
iowrite64((unsigned long)host->dma_desc_addr | DMA_OREDER_START, dma_order_addr);
return 0;
}
static int sdio_transfer_data(struct sdio_host *host, struct mmc_data *data)
{
int data_fin = 0;
int sdiintmsk = 0;
unsigned int err = 0;
int timeout = 20000000;
while (data_fin == 0 && timeout--) {
sdiintmsk = sdio_readl(host, SDIINTMSK);
data_fin = SDIO_GET_DATAINT(sdiintmsk);
}
if (timeout < 0) {
pr_debug("wait sdio data irq timeout.\n");
return -ETIMEDOUT;
}
if (sdiintmsk & SDIO_INTMSK_CRCSTA) {
pr_debug("DATA crc state error.\n");
err = -ETIMEDOUT;
} else if (sdiintmsk & SDIO_INTMSK_DATCRC) {
pr_debug("DATA CRC error.\n");
err = -1;
} else if (sdiintmsk & SDIO_INTMSK_PROGERR) {
pr_debug("DATA program error.\n");
err = -1;
} else if (sdiintmsk & SDIO_INTMSK_DATTOUT) {
pr_debug("DATA timeout.\n");
err = -ETIMEDOUT;
}
sdio_writel(host, SDIINTMSK, sdiintmsk & 0x1e);
return err;
}
static unsigned int sdio_cmd_prepare_flag (struct mmc_cmd* cmd)
{
unsigned int flag = 0;
unsigned short cmd_index;
unsigned int rsp;
cmd_index = cmd->cmdidx;
rsp = cmd->resp_type;
if (rsp == MMC_RSP_NONE) {
return flag;
}
if (rsp & MMC_RSP_PRESENT) {
flag |= SDIO_CMDCON_WAITRSP;
}
// if response type is R2 and the crc bit set on, some unknow error will happen
if (rsp & MMC_RSP_CRC) {
flag |= SDIO_CMDCON_CHECKCRC;
}
if (rsp & MMC_RSP_136) {
flag |= SDIO_CMDCON_LONGRSP;
}
if (cmd_index == MMC_CMD_READ_MULTIPLE_BLOCK || cmd_index == MMC_CMD_WRITE_MULTIPLE_BLOCK) {
flag |= SDIO_CMDCON_AUTOSTOP;
}
return flag;
}
static int sdio_send_cmd(struct udevice *dev, struct mmc_cmd *cmd, struct mmc_data *data)
{
struct sdio_host *host = dev_get_priv(dev);
unsigned int sdiintmsk;
unsigned int flag = 0;
unsigned short cmd_index;
unsigned int cmd_fin = 0;
int timeout = 20000;
int err = 0;
sdio_writel(host, SDIINTEN, 0x3ff);
cmd_index = cmd->cmdidx;
flag = sdio_cmd_prepare_flag(cmd);
pr_debug("cmd_index = %d,cmd_arg = 0x%x\n",cmd->cmdidx,cmd->cmdarg);
if(cmd_index == MMC_CMD_STOP_TRANSMISSION)
return 0;
// EMMC 先准备数据,再发指令,且不用延迟
if (!host->use_sdcard && data) {
err = sdio_prepare_data(host, data);
if (err) {
return err;
}
err = sdio_prepare_dma(host, data);
if (err) {
return err;
}
}
sdio_writel(host, SDICMDARG, cmd->cmdarg);
sdio_writel(host, SDICMDCON, SDIO_MAKE_CMD(cmd_index, flag));
// 部分EMMC芯片需要延迟
// 否则指令发送频率一高会有数据传输问题
udelay(1);
while ((cmd_fin == 0) && timeout--) {
sdiintmsk = sdio_readl(host, SDIINTMSK);
cmd_fin = SDIO_GET_CMDINT(sdiintmsk);
if (timeout == 0) {
pr_debug("CMD check timeout!\n");
err = -ETIMEDOUT;
goto out;
}
}
if (sdiintmsk & SDIO_INTMSK_RSPCRC) {
pr_debug("CMD %d CRC error.\n", cmd_index);
err = -1;
goto out;
} else if (sdiintmsk & SDIO_INTMSK_CMDTOUT) {
pr_debug("CMD %d TIMEOUT error.\n", cmd_index);
err = -ETIMEDOUT;
goto out;
} else if (sdiintmsk & SDIO_INTMSK_CMDFIN) {
int i;
if (flag & SDIO_CMDCON_LONGRSP) {
for (i = 0; i < 4; i++) {
cmd->response[i] = sdio_readl(host, SDIRSP0 + i*4);
}
} else {
cmd->response[0] = sdio_readl(host, SDIRSP0);
}
} else {
pr_debug("CMD %d NOT finished, INT_MSK reg is 0x%x\n", cmd_index, sdiintmsk);
err = -1;
goto out;
}
// CMD 响应成功后再准备传输数据,否则SD卡会出奇怪的问题
if (data) {
if (host->use_sdcard)
{
err = sdio_prepare_data(host, data);
if (err) {
return err;
}
// SD卡必须延时,否则丢读据
mdelay(1);
err = sdio_prepare_dma(host, data);
if (err) {
return err;
}
}
err = sdio_transfer_data(host, data);
}
out:
sdio_writel(host, SDIINTMSK, (sdiintmsk & 0x1fe));
sdio_writel(host, SDIINTEN, 0x0);
return err;
}
static void sdio_set_clk(struct sdio_host *host, unsigned int clk)
{
unsigned int clk_pre;
if (clk == 0) {
sdio_writel(host, SDICON, SDIO_CON_DIACLK);
return;
}
clk_pre = DIV_ROUND_UP(host->clock,clk);
if(clk_pre > 0xff || clk_pre == 0x0)
clk_pre = 0xff;
sdio_writel(host, SDICON, SDIO_CON_ENCLK);
sdio_writel(host, SDIPRE, clk_pre | SDIO_PRE_REVCLOCK);
}
static int sdio_set_ios(struct udevice *dev)
{
struct sdio_host *host = dev_get_priv(dev);
struct mmc *mmc = mmc_get_mmc_dev(dev);
if (mmc->clock != host->clock) {
sdio_set_clk(host, mmc->clock);
}
if (mmc->clk_disable) {
sdio_set_clk(host, 0);
}
return 0;
}
static int sdio_mmc_get_cd(struct udevice *dev)
{
return 1;
}
static const struct dm_mmc_ops sdio_mmc_ops = {
.send_cmd = sdio_send_cmd,
.set_ios = sdio_set_ios,
.get_cd = sdio_mmc_get_cd,
};
static int sdio_mmc_probe(struct udevice *dev)
{
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
struct sdio_mmc_plat *plat = dev_get_plat(dev);
struct sdio_host *host = dev_get_priv(dev);
plat->mmc.priv = host;
upriv->mmc = &plat->mmc;
host->mmc = upriv->mmc;
return mmc_init(&plat->mmc);
}
static int sdio_mmc_bind(struct udevice *dev)
{
struct sdio_mmc_plat *plat = dev_get_plat(dev);
return mmc_bind(dev, &plat->mmc, &plat->cfg);
}
static int sdio_mmc_of_to_plat(struct udevice *dev)
{
struct sdio_mmc_plat *plat = dev_get_plat(dev);
struct sdio_host *host = dev_get_priv(dev);
struct mmc_config *cfg;
struct clk clk;
int err;
err = clk_get_by_index(dev, 0, &clk);
if (!err) {
err = clk_get_rate(&clk);
if (!IS_ERR_VALUE(err))
host->clock = err;
} else if (err != -ENOENT && err != -ENODEV && err != -ENOSYS) {
pr_debug("mmc failed to get clock\n");
return err;
}
if (!host->clock)
host->clock = dev_read_u32_default(dev, "clock-frequency",
200000000);
if (!host->clock)
host->clock = 200000000;
if (!host->clock) {
pr_debug("mmc clock not defined\n");
return -EINVAL;
}
host->ioaddr = PHYS_TO_UNCACHED((phys_addr_t)dev_read_addr(dev));
host->dma_desc_addr = (unsigned int *)SDIO_DMA_DESCADDR;
host->wdma_order_addr = (unsigned int *)(host->ioaddr + 0x400);
host->rdma_order_addr = (unsigned int *)(host->ioaddr + 0x800);
host->use_sdcard = dev_read_bool(dev, "use-sdcard");
cfg = &plat->cfg;
cfg->name = dev->name;
cfg->host_caps = MMC_MODE_HS_52MHz | MMC_MODE_HS | MMC_MODE_4BIT | MMC_MODE_8BIT;
cfg->voltages = MMC_VDD_32_33|MMC_VDD_33_34;
cfg->f_min = host->clock / 256;
cfg->f_max = host->clock;
cfg->b_max = 1024;
return 0;
}
static const struct udevice_id sdio_mmc_match[] = {
{ .compatible = "loongson,ls-mmc" },
{ }
};
U_BOOT_DRIVER(ls_mmc) = {
.name = "ls_mmc",
.id = UCLASS_MMC,
.of_match = sdio_mmc_match,
.bind = sdio_mmc_bind,
.of_to_plat = sdio_mmc_of_to_plat,
.probe = sdio_mmc_probe,
.ops = &sdio_mmc_ops,
.priv_auto = sizeof(struct sdio_host),
.plat_auto = sizeof(struct sdio_mmc_plat),
};