mirror of
https://github.com/brain-hackers/u-boot-brain
synced 2024-08-19 00:18:51 +09:00
![Tom Rini](/assets/img/avatar_default.png)
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
481 lines
11 KiB
C
481 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Faraday MMC/SD Host Controller
|
|
*
|
|
* (C) Copyright 2010 Faraday Technology
|
|
* Dante Su <dantesu@faraday-tech.com>
|
|
*
|
|
* Copyright 2018 Andes Technology, Inc.
|
|
* Author: Rick Chen (rick@andestech.com)
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <clk.h>
|
|
#include <malloc.h>
|
|
#include <part.h>
|
|
#include <mmc.h>
|
|
#include <linux/io.h>
|
|
#include <linux/errno.h>
|
|
#include <asm/byteorder.h>
|
|
#include <faraday/ftsdc010.h>
|
|
#include "ftsdc010_mci.h"
|
|
#include <dm.h>
|
|
#include <dt-structs.h>
|
|
#include <errno.h>
|
|
#include <mapmem.h>
|
|
#include <pwrseq.h>
|
|
#include <syscon.h>
|
|
#include <linux/err.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
#define CFG_CMD_TIMEOUT (CONFIG_SYS_HZ >> 4) /* 250 ms */
|
|
#define CFG_RST_TIMEOUT CONFIG_SYS_HZ /* 1 sec reset timeout */
|
|
|
|
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
|
struct ftsdc010 {
|
|
fdt32_t bus_width;
|
|
bool cap_mmc_highspeed;
|
|
bool cap_sd_highspeed;
|
|
fdt32_t clock_freq_min_max[2];
|
|
struct phandle_2_cell clocks[4];
|
|
fdt32_t fifo_depth;
|
|
fdt32_t reg[2];
|
|
};
|
|
#endif
|
|
|
|
struct ftsdc010_plat {
|
|
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
|
struct ftsdc010 dtplat;
|
|
#endif
|
|
struct mmc_config cfg;
|
|
struct mmc mmc;
|
|
};
|
|
|
|
struct ftsdc_priv {
|
|
struct clk clk;
|
|
struct ftsdc010_chip chip;
|
|
int fifo_depth;
|
|
bool fifo_mode;
|
|
u32 minmax[2];
|
|
};
|
|
|
|
static inline int ftsdc010_send_cmd(struct mmc *mmc, struct mmc_cmd *mmc_cmd)
|
|
{
|
|
struct ftsdc010_chip *chip = mmc->priv;
|
|
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
|
int ret = -ETIMEDOUT;
|
|
uint32_t ts, st;
|
|
uint32_t cmd = FTSDC010_CMD_IDX(mmc_cmd->cmdidx);
|
|
uint32_t arg = mmc_cmd->cmdarg;
|
|
uint32_t flags = mmc_cmd->resp_type;
|
|
|
|
cmd |= FTSDC010_CMD_CMD_EN;
|
|
|
|
if (chip->acmd) {
|
|
cmd |= FTSDC010_CMD_APP_CMD;
|
|
chip->acmd = 0;
|
|
}
|
|
|
|
if (flags & MMC_RSP_PRESENT)
|
|
cmd |= FTSDC010_CMD_NEED_RSP;
|
|
|
|
if (flags & MMC_RSP_136)
|
|
cmd |= FTSDC010_CMD_LONG_RSP;
|
|
|
|
writel(FTSDC010_STATUS_RSP_MASK | FTSDC010_STATUS_CMD_SEND,
|
|
®s->clr);
|
|
writel(arg, ®s->argu);
|
|
writel(cmd, ®s->cmd);
|
|
|
|
if (!(flags & (MMC_RSP_PRESENT | MMC_RSP_136))) {
|
|
for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) {
|
|
if (readl(®s->status) & FTSDC010_STATUS_CMD_SEND) {
|
|
writel(FTSDC010_STATUS_CMD_SEND, ®s->clr);
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
st = 0;
|
|
for (ts = get_timer(0); get_timer(ts) < CFG_CMD_TIMEOUT; ) {
|
|
st = readl(®s->status);
|
|
writel(st & FTSDC010_STATUS_RSP_MASK, ®s->clr);
|
|
if (st & FTSDC010_STATUS_RSP_MASK)
|
|
break;
|
|
}
|
|
if (st & FTSDC010_STATUS_RSP_CRC_OK) {
|
|
if (flags & MMC_RSP_136) {
|
|
mmc_cmd->response[0] = readl(®s->rsp3);
|
|
mmc_cmd->response[1] = readl(®s->rsp2);
|
|
mmc_cmd->response[2] = readl(®s->rsp1);
|
|
mmc_cmd->response[3] = readl(®s->rsp0);
|
|
} else {
|
|
mmc_cmd->response[0] = readl(®s->rsp0);
|
|
}
|
|
ret = 0;
|
|
} else {
|
|
debug("ftsdc010: rsp err (cmd=%d, st=0x%x)\n",
|
|
mmc_cmd->cmdidx, st);
|
|
}
|
|
}
|
|
|
|
if (ret) {
|
|
debug("ftsdc010: cmd timeout (op code=%d)\n",
|
|
mmc_cmd->cmdidx);
|
|
} else if (mmc_cmd->cmdidx == MMC_CMD_APP_CMD) {
|
|
chip->acmd = 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ftsdc010_clkset(struct mmc *mmc, uint32_t rate)
|
|
{
|
|
struct ftsdc010_chip *chip = mmc->priv;
|
|
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
|
uint32_t div;
|
|
|
|
for (div = 0; div < 0x7f; ++div) {
|
|
if (rate >= chip->sclk / (2 * (div + 1)))
|
|
break;
|
|
}
|
|
chip->rate = chip->sclk / (2 * (div + 1));
|
|
|
|
writel(FTSDC010_CCR_CLK_DIV(div), ®s->ccr);
|
|
|
|
if (IS_SD(mmc)) {
|
|
setbits_le32(®s->ccr, FTSDC010_CCR_CLK_SD);
|
|
|
|
if (chip->rate > 25000000)
|
|
setbits_le32(®s->ccr, FTSDC010_CCR_CLK_HISPD);
|
|
else
|
|
clrbits_le32(®s->ccr, FTSDC010_CCR_CLK_HISPD);
|
|
}
|
|
}
|
|
|
|
static int ftsdc010_wait(struct ftsdc010_mmc __iomem *regs, uint32_t mask)
|
|
{
|
|
int ret = -ETIMEDOUT;
|
|
uint32_t st, timeout = 10000000;
|
|
while (timeout--) {
|
|
st = readl(®s->status);
|
|
if (!(st & mask))
|
|
continue;
|
|
writel(st & mask, ®s->clr);
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
if (ret){
|
|
debug("ftsdc010: wait st(0x%x) timeout\n", mask);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* u-boot mmc api
|
|
*/
|
|
static int ftsdc010_request(struct udevice *dev, struct mmc_cmd *cmd,
|
|
struct mmc_data *data)
|
|
{
|
|
struct mmc *mmc = mmc_get_mmc_dev(dev);
|
|
int ret = -EOPNOTSUPP;
|
|
uint32_t len = 0;
|
|
struct ftsdc010_chip *chip = mmc->priv;
|
|
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
|
|
|
if (data && (data->flags & MMC_DATA_WRITE) && chip->wprot) {
|
|
printf("ftsdc010: the card is write protected!\n");
|
|
return ret;
|
|
}
|
|
|
|
if (data) {
|
|
uint32_t dcr;
|
|
|
|
len = data->blocksize * data->blocks;
|
|
|
|
/* 1. data disable + fifo reset */
|
|
dcr = 0;
|
|
#ifdef CONFIG_FTSDC010_SDIO
|
|
dcr |= FTSDC010_DCR_FIFO_RST;
|
|
#endif
|
|
writel(dcr, ®s->dcr);
|
|
|
|
/* 2. clear status register */
|
|
writel(FTSDC010_STATUS_DATA_MASK | FTSDC010_STATUS_FIFO_URUN
|
|
| FTSDC010_STATUS_FIFO_ORUN, ®s->clr);
|
|
|
|
/* 3. data timeout (1 sec) */
|
|
writel(chip->rate, ®s->dtr);
|
|
|
|
/* 4. data length (bytes) */
|
|
writel(len, ®s->dlr);
|
|
|
|
/* 5. data enable */
|
|
dcr = (ffs(data->blocksize) - 1) | FTSDC010_DCR_DATA_EN;
|
|
if (data->flags & MMC_DATA_WRITE)
|
|
dcr |= FTSDC010_DCR_DATA_WRITE;
|
|
writel(dcr, ®s->dcr);
|
|
}
|
|
|
|
ret = ftsdc010_send_cmd(mmc, cmd);
|
|
if (ret) {
|
|
printf("ftsdc010: CMD%d failed\n", cmd->cmdidx);
|
|
return ret;
|
|
}
|
|
|
|
if (!data)
|
|
return ret;
|
|
|
|
if (data->flags & MMC_DATA_WRITE) {
|
|
const uint8_t *buf = (const uint8_t *)data->src;
|
|
|
|
while (len > 0) {
|
|
int wlen;
|
|
|
|
/* wait for tx ready */
|
|
ret = ftsdc010_wait(regs, FTSDC010_STATUS_FIFO_URUN);
|
|
if (ret)
|
|
break;
|
|
|
|
/* write bytes to ftsdc010 */
|
|
for (wlen = 0; wlen < len && wlen < chip->fifo; ) {
|
|
writel(*(uint32_t *)buf, ®s->dwr);
|
|
buf += 4;
|
|
wlen += 4;
|
|
}
|
|
|
|
len -= wlen;
|
|
}
|
|
|
|
} else {
|
|
uint8_t *buf = (uint8_t *)data->dest;
|
|
|
|
while (len > 0) {
|
|
int rlen;
|
|
|
|
/* wait for rx ready */
|
|
ret = ftsdc010_wait(regs, FTSDC010_STATUS_FIFO_ORUN);
|
|
if (ret)
|
|
break;
|
|
|
|
/* fetch bytes from ftsdc010 */
|
|
for (rlen = 0; rlen < len && rlen < chip->fifo; ) {
|
|
*(uint32_t *)buf = readl(®s->dwr);
|
|
buf += 4;
|
|
rlen += 4;
|
|
}
|
|
|
|
len -= rlen;
|
|
}
|
|
|
|
}
|
|
|
|
if (!ret) {
|
|
ret = ftsdc010_wait(regs,
|
|
FTSDC010_STATUS_DATA_END | FTSDC010_STATUS_DATA_CRC_OK);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ftsdc010_set_ios(struct udevice *dev)
|
|
{
|
|
struct mmc *mmc = mmc_get_mmc_dev(dev);
|
|
struct ftsdc010_chip *chip = mmc->priv;
|
|
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
|
|
|
ftsdc010_clkset(mmc, mmc->clock);
|
|
|
|
clrbits_le32(®s->bwr, FTSDC010_BWR_MODE_MASK);
|
|
switch (mmc->bus_width) {
|
|
case 4:
|
|
setbits_le32(®s->bwr, FTSDC010_BWR_MODE_4BIT);
|
|
break;
|
|
case 8:
|
|
setbits_le32(®s->bwr, FTSDC010_BWR_MODE_8BIT);
|
|
break;
|
|
default:
|
|
setbits_le32(®s->bwr, FTSDC010_BWR_MODE_1BIT);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ftsdc010_get_cd(struct udevice *dev)
|
|
{
|
|
struct mmc *mmc = mmc_get_mmc_dev(dev);
|
|
struct ftsdc010_chip *chip = mmc->priv;
|
|
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
|
return !(readl(®s->status) & FTSDC010_STATUS_CARD_DETECT);
|
|
}
|
|
|
|
static int ftsdc010_get_wp(struct udevice *dev)
|
|
{
|
|
struct mmc *mmc = mmc_get_mmc_dev(dev);
|
|
struct ftsdc010_chip *chip = mmc->priv;
|
|
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
|
if (readl(®s->status) & FTSDC010_STATUS_WRITE_PROT) {
|
|
printf("ftsdc010: write protected\n");
|
|
chip->wprot = 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ftsdc010_init(struct mmc *mmc)
|
|
{
|
|
struct ftsdc010_chip *chip = mmc->priv;
|
|
struct ftsdc010_mmc __iomem *regs = chip->regs;
|
|
uint32_t ts;
|
|
|
|
chip->fifo = (readl(®s->feature) & 0xff) << 2;
|
|
|
|
/* 1. chip reset */
|
|
writel(FTSDC010_CMD_SDC_RST, ®s->cmd);
|
|
for (ts = get_timer(0); get_timer(ts) < CFG_RST_TIMEOUT; ) {
|
|
if (readl(®s->cmd) & FTSDC010_CMD_SDC_RST)
|
|
continue;
|
|
break;
|
|
}
|
|
if (readl(®s->cmd) & FTSDC010_CMD_SDC_RST) {
|
|
printf("ftsdc010: reset failed\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* 2. enter low speed mode (400k card detection) */
|
|
ftsdc010_clkset(mmc, 400000);
|
|
|
|
/* 3. interrupt disabled */
|
|
writel(0, ®s->int_mask);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ftsdc010_probe(struct udevice *dev)
|
|
{
|
|
struct mmc *mmc = mmc_get_mmc_dev(dev);
|
|
return ftsdc010_init(mmc);
|
|
}
|
|
|
|
const struct dm_mmc_ops dm_ftsdc010_mmc_ops = {
|
|
.send_cmd = ftsdc010_request,
|
|
.set_ios = ftsdc010_set_ios,
|
|
.get_cd = ftsdc010_get_cd,
|
|
.get_wp = ftsdc010_get_wp,
|
|
};
|
|
|
|
static void ftsdc_setup_cfg(struct mmc_config *cfg, const char *name, int buswidth,
|
|
uint caps, u32 max_clk, u32 min_clk)
|
|
{
|
|
cfg->name = name;
|
|
cfg->f_min = min_clk;
|
|
cfg->f_max = max_clk;
|
|
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
|
|
cfg->host_caps = caps;
|
|
if (buswidth == 8) {
|
|
cfg->host_caps |= MMC_MODE_8BIT;
|
|
cfg->host_caps &= ~MMC_MODE_4BIT;
|
|
} else {
|
|
cfg->host_caps |= MMC_MODE_4BIT;
|
|
cfg->host_caps &= ~MMC_MODE_8BIT;
|
|
}
|
|
cfg->part_type = PART_TYPE_DOS;
|
|
cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
|
|
}
|
|
|
|
static int ftsdc010_mmc_ofdata_to_platdata(struct udevice *dev)
|
|
{
|
|
#if !CONFIG_IS_ENABLED(OF_PLATDATA)
|
|
struct ftsdc_priv *priv = dev_get_priv(dev);
|
|
struct ftsdc010_chip *chip = &priv->chip;
|
|
chip->name = dev->name;
|
|
chip->ioaddr = (void *)devfdt_get_addr(dev);
|
|
chip->buswidth = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev),
|
|
"bus-width", 4);
|
|
chip->priv = dev;
|
|
priv->fifo_depth = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev),
|
|
"fifo-depth", 0);
|
|
priv->fifo_mode = fdtdec_get_bool(gd->fdt_blob, dev_of_offset(dev),
|
|
"fifo-mode");
|
|
if (fdtdec_get_int_array(gd->fdt_blob, dev_of_offset(dev),
|
|
"clock-freq-min-max", priv->minmax, 2)) {
|
|
int val = fdtdec_get_int(gd->fdt_blob, dev_of_offset(dev),
|
|
"max-frequency", -EINVAL);
|
|
if (val < 0)
|
|
return val;
|
|
|
|
priv->minmax[0] = 400000; /* 400 kHz */
|
|
priv->minmax[1] = val;
|
|
} else {
|
|
debug("%s: 'clock-freq-min-max' property was deprecated.\n",
|
|
__func__);
|
|
}
|
|
#endif
|
|
chip->sclk = priv->minmax[1];
|
|
chip->regs = chip->ioaddr;
|
|
return 0;
|
|
}
|
|
|
|
static int ftsdc010_mmc_probe(struct udevice *dev)
|
|
{
|
|
struct ftsdc010_plat *plat = dev_get_platdata(dev);
|
|
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
|
|
struct ftsdc_priv *priv = dev_get_priv(dev);
|
|
struct ftsdc010_chip *chip = &priv->chip;
|
|
struct udevice *pwr_dev __maybe_unused;
|
|
|
|
#if CONFIG_IS_ENABLED(OF_PLATDATA)
|
|
int ret;
|
|
struct ftsdc010 *dtplat = &plat->dtplat;
|
|
chip->name = dev->name;
|
|
chip->ioaddr = map_sysmem(dtplat->reg[0], dtplat->reg[1]);
|
|
chip->buswidth = dtplat->bus_width;
|
|
chip->priv = dev;
|
|
chip->dev_index = 1;
|
|
memcpy(priv->minmax, dtplat->clock_freq_min_max, sizeof(priv->minmax));
|
|
ret = clk_get_by_index_platdata(dev, 0, dtplat->clocks, &priv->clk);
|
|
if (ret < 0)
|
|
return ret;
|
|
#endif
|
|
|
|
if (dev_read_bool(dev, "cap-mmc-highspeed") || \
|
|
dev_read_bool(dev, "cap-sd-highspeed"))
|
|
chip->caps |= MMC_MODE_HS | MMC_MODE_HS_52MHz;
|
|
|
|
ftsdc_setup_cfg(&plat->cfg, dev->name, chip->buswidth, chip->caps,
|
|
priv->minmax[1] , priv->minmax[0]);
|
|
chip->mmc = &plat->mmc;
|
|
chip->mmc->priv = &priv->chip;
|
|
chip->mmc->dev = dev;
|
|
upriv->mmc = chip->mmc;
|
|
return ftsdc010_probe(dev);
|
|
}
|
|
|
|
int ftsdc010_mmc_bind(struct udevice *dev)
|
|
{
|
|
struct ftsdc010_plat *plat = dev_get_platdata(dev);
|
|
|
|
return mmc_bind(dev, &plat->mmc, &plat->cfg);
|
|
}
|
|
|
|
static const struct udevice_id ftsdc010_mmc_ids[] = {
|
|
{ .compatible = "andestech,atsdc010" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(ftsdc010_mmc) = {
|
|
.name = "ftsdc010_mmc",
|
|
.id = UCLASS_MMC,
|
|
.of_match = ftsdc010_mmc_ids,
|
|
.ofdata_to_platdata = ftsdc010_mmc_ofdata_to_platdata,
|
|
.ops = &dm_ftsdc010_mmc_ops,
|
|
.bind = ftsdc010_mmc_bind,
|
|
.probe = ftsdc010_mmc_probe,
|
|
.priv_auto_alloc_size = sizeof(struct ftsdc_priv),
|
|
.platdata_auto_alloc_size = sizeof(struct ftsdc010_plat),
|
|
};
|