u-boot/drivers/mmc/arm_pl180_mmci.c
Linus Walleij ee6cee125a mmc: arm_pl180_mmci: Rely on DM
The PL180/MMCI driver is implied to use CONFIG_DM and the ARM
defconfigs such as configs/vexpress_ca9x4_defconfig will get it
as well.

With a simple oneline to default to not being the v2 variant,
the original ARM MMCI variant works fine with the driver as well.
The IP version actually needs to be read out from a register on
the ARM versions, but we will simply assume we are running on the
original hardware if arm,primecell-periphid is not explicitly
specified in the device tree.

Drop the !CONFIG_DM code and depend on DM_MMC.

Tested on the Versatile Express CA9x4 board.

Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
2024-04-15 15:19:12 +09:00

503 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* ARM PrimeCell MultiMedia Card Interface - PL180
*
* Copyright (C) ST-Ericsson SA 2010
*
* Author: Ulf Hansson <ulf.hansson@stericsson.com>
* Author: Martin Lundholm <martin.xa.lundholm@stericsson.com>
* Ported to drivers/mmc/ by: Matt Waddel <matt.waddel@linaro.org>
*/
/* #define DEBUG */
#include "common.h"
#include <clk.h>
#include <errno.h>
#include <log.h>
#include <malloc.h>
#include <mmc.h>
#include <dm/device_compat.h>
#include <dm.h>
#include <asm/io.h>
#include <asm-generic/gpio.h>
#include "arm_pl180_mmci.h"
#include <linux/delay.h>
#define MMC_CLOCK_MAX 48000000
#define MMC_CLOCK_MIN 400000
struct arm_pl180_mmc_plat {
struct mmc_config cfg;
struct mmc mmc;
};
static int wait_for_command_end(struct mmc *dev, struct mmc_cmd *cmd)
{
u32 hoststatus, statusmask;
struct pl180_mmc_host *host = dev->priv;
statusmask = SDI_STA_CTIMEOUT | SDI_STA_CCRCFAIL;
if ((cmd->resp_type & MMC_RSP_PRESENT))
statusmask |= SDI_STA_CMDREND;
else
statusmask |= SDI_STA_CMDSENT;
do
hoststatus = readl(&host->base->status) & statusmask;
while (!hoststatus);
writel(statusmask, &host->base->status_clear);
if (hoststatus & SDI_STA_CTIMEOUT) {
debug("CMD%d time out\n", cmd->cmdidx);
return -ETIMEDOUT;
} else if ((hoststatus & SDI_STA_CCRCFAIL) &&
(cmd->resp_type & MMC_RSP_CRC)) {
printf("CMD%d CRC error\n", cmd->cmdidx);
return -EILSEQ;
}
if (cmd->resp_type & MMC_RSP_PRESENT) {
cmd->response[0] = readl(&host->base->response0);
cmd->response[1] = readl(&host->base->response1);
cmd->response[2] = readl(&host->base->response2);
cmd->response[3] = readl(&host->base->response3);
debug("CMD%d response[0]:0x%08X, response[1]:0x%08X, "
"response[2]:0x%08X, response[3]:0x%08X\n",
cmd->cmdidx, cmd->response[0], cmd->response[1],
cmd->response[2], cmd->response[3]);
}
return 0;
}
/* send command to the mmc card and wait for results */
static int do_command(struct mmc *dev, struct mmc_cmd *cmd)
{
int result;
u32 sdi_cmd = 0;
struct pl180_mmc_host *host = dev->priv;
sdi_cmd = ((cmd->cmdidx & SDI_CMD_CMDINDEX_MASK) | SDI_CMD_CPSMEN);
if (cmd->resp_type) {
sdi_cmd |= SDI_CMD_WAITRESP;
if (cmd->resp_type & MMC_RSP_136)
sdi_cmd |= SDI_CMD_LONGRESP;
}
writel((u32)cmd->cmdarg, &host->base->argument);
udelay(COMMAND_REG_DELAY);
writel(sdi_cmd, &host->base->command);
result = wait_for_command_end(dev, cmd);
/* After CMD2 set RCA to a none zero value. */
if ((result == 0) && (cmd->cmdidx == MMC_CMD_ALL_SEND_CID))
dev->rca = 10;
/* After CMD3 open drain is switched off and push pull is used. */
if ((result == 0) && (cmd->cmdidx == MMC_CMD_SET_RELATIVE_ADDR)) {
u32 sdi_pwr = readl(&host->base->power) & ~SDI_PWR_OPD;
writel(sdi_pwr, &host->base->power);
}
return result;
}
static int read_bytes(struct mmc *dev, u32 *dest, u32 blkcount, u32 blksize)
{
u32 *tempbuff = dest;
u64 xfercount = blkcount * blksize;
struct pl180_mmc_host *host = dev->priv;
u32 status, status_err;
debug("read_bytes: blkcount=%u blksize=%u\n", blkcount, blksize);
status = readl(&host->base->status);
status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT |
SDI_STA_RXOVERR);
while ((!status_err) && (xfercount >= sizeof(u32))) {
if (status & SDI_STA_RXDAVL) {
*(tempbuff) = readl(&host->base->fifo);
tempbuff++;
xfercount -= sizeof(u32);
}
status = readl(&host->base->status);
status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT |
SDI_STA_RXOVERR);
}
status_err = status &
(SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND |
SDI_STA_RXOVERR);
while (!status_err) {
status = readl(&host->base->status);
status_err = status &
(SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND |
SDI_STA_RXOVERR);
}
if (status & SDI_STA_DTIMEOUT) {
printf("Read data timed out, xfercount: %llu, status: 0x%08X\n",
xfercount, status);
return -ETIMEDOUT;
} else if (status & SDI_STA_DCRCFAIL) {
printf("Read data bytes CRC error: 0x%x\n", status);
return -EILSEQ;
} else if (status & SDI_STA_RXOVERR) {
printf("Read data RX overflow error\n");
return -EIO;
}
writel(SDI_ICR_MASK, &host->base->status_clear);
if (xfercount) {
printf("Read data error, xfercount: %llu\n", xfercount);
return -ENOBUFS;
}
return 0;
}
static int write_bytes(struct mmc *dev, u32 *src, u32 blkcount, u32 blksize)
{
u32 *tempbuff = src;
int i;
u64 xfercount = blkcount * blksize;
struct pl180_mmc_host *host = dev->priv;
u32 status, status_err;
debug("write_bytes: blkcount=%u blksize=%u\n", blkcount, blksize);
status = readl(&host->base->status);
status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT);
while (!status_err && xfercount) {
if (status & SDI_STA_TXFIFOBW) {
if (xfercount >= SDI_FIFO_BURST_SIZE * sizeof(u32)) {
for (i = 0; i < SDI_FIFO_BURST_SIZE; i++)
writel(*(tempbuff + i),
&host->base->fifo);
tempbuff += SDI_FIFO_BURST_SIZE;
xfercount -= SDI_FIFO_BURST_SIZE * sizeof(u32);
} else {
while (xfercount >= sizeof(u32)) {
writel(*(tempbuff), &host->base->fifo);
tempbuff++;
xfercount -= sizeof(u32);
}
}
}
status = readl(&host->base->status);
status_err = status & (SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT);
}
status_err = status &
(SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND);
while (!status_err) {
status = readl(&host->base->status);
status_err = status &
(SDI_STA_DCRCFAIL | SDI_STA_DTIMEOUT | SDI_STA_DBCKEND);
}
if (status & SDI_STA_DTIMEOUT) {
printf("Write data timed out, xfercount:%llu,status:0x%08X\n",
xfercount, status);
return -ETIMEDOUT;
} else if (status & SDI_STA_DCRCFAIL) {
printf("Write data CRC error\n");
return -EILSEQ;
}
writel(SDI_ICR_MASK, &host->base->status_clear);
if (xfercount) {
printf("Write data error, xfercount:%llu", xfercount);
return -ENOBUFS;
}
return 0;
}
static int do_data_transfer(struct mmc *dev,
struct mmc_cmd *cmd,
struct mmc_data *data)
{
int error = -ETIMEDOUT;
struct pl180_mmc_host *host = dev->priv;
u32 blksz = 0;
u32 data_ctrl = 0;
u32 data_len = (u32) (data->blocks * data->blocksize);
if (!host->version2) {
blksz = (ffs(data->blocksize) - 1);
data_ctrl |= ((blksz << 4) & SDI_DCTRL_DBLKSIZE_MASK);
} else {
blksz = data->blocksize;
data_ctrl |= (blksz << SDI_DCTRL_DBLOCKSIZE_V2_SHIFT);
}
data_ctrl |= SDI_DCTRL_DTEN | SDI_DCTRL_BUSYMODE;
writel(SDI_DTIMER_DEFAULT, &host->base->datatimer);
writel(data_len, &host->base->datalength);
udelay(DATA_REG_DELAY);
if (data->flags & MMC_DATA_READ) {
data_ctrl |= SDI_DCTRL_DTDIR_IN;
writel(data_ctrl, &host->base->datactrl);
error = do_command(dev, cmd);
if (error)
return error;
error = read_bytes(dev, (u32 *)data->dest, (u32)data->blocks,
(u32)data->blocksize);
} else if (data->flags & MMC_DATA_WRITE) {
error = do_command(dev, cmd);
if (error)
return error;
writel(data_ctrl, &host->base->datactrl);
error = write_bytes(dev, (u32 *)data->src, (u32)data->blocks,
(u32)data->blocksize);
}
return error;
}
static int host_request(struct mmc *dev,
struct mmc_cmd *cmd,
struct mmc_data *data)
{
int result;
if (data)
result = do_data_transfer(dev, cmd, data);
else
result = do_command(dev, cmd);
return result;
}
static int check_peripheral_id(struct pl180_mmc_host *host, u32 periph_id)
{
return readl(&host->base->periph_id0) == (periph_id & 0xFF) &&
readl(&host->base->periph_id1) == ((periph_id >> 8) & 0xFF) &&
readl(&host->base->periph_id2) == ((periph_id >> 16) & 0xFF) &&
readl(&host->base->periph_id3) == ((periph_id >> 24) & 0xFF);
}
static int host_set_ios(struct mmc *dev)
{
struct pl180_mmc_host *host = dev->priv;
u32 sdi_clkcr;
sdi_clkcr = readl(&host->base->clock);
/* Ramp up the clock rate */
if (dev->clock) {
u32 clkdiv = 0;
u32 tmp_clock;
if (dev->clock >= dev->cfg->f_max) {
clkdiv = 0;
dev->clock = dev->cfg->f_max;
} else {
clkdiv = (host->clock_in / dev->clock) - 2;
}
tmp_clock = host->clock_in / (clkdiv + 2);
while (tmp_clock > dev->clock) {
clkdiv++;
tmp_clock = host->clock_in / (clkdiv + 2);
}
if (clkdiv > SDI_CLKCR_CLKDIV_MASK)
clkdiv = SDI_CLKCR_CLKDIV_MASK;
tmp_clock = host->clock_in / (clkdiv + 2);
dev->clock = tmp_clock;
sdi_clkcr &= ~(SDI_CLKCR_CLKDIV_MASK);
sdi_clkcr |= clkdiv;
}
/* Set the bus width */
if (dev->bus_width) {
u32 buswidth = 0;
switch (dev->bus_width) {
case 1:
buswidth |= SDI_CLKCR_WIDBUS_1;
break;
case 4:
buswidth |= SDI_CLKCR_WIDBUS_4;
break;
case 8:
buswidth |= SDI_CLKCR_WIDBUS_8;
break;
default:
printf("Invalid bus width: %d\n", dev->bus_width);
break;
}
sdi_clkcr &= ~(SDI_CLKCR_WIDBUS_MASK);
sdi_clkcr |= buswidth;
}
/* For MMCs' with peripheral id 0x02041180 and 0x03041180, H/W flow control
* needs to be enabled for multi block writes (MMC CMD 18).
*/
if (check_peripheral_id(host, 0x02041180) ||
check_peripheral_id(host, 0x03041180))
sdi_clkcr |= SDI_CLKCR_HWFCEN;
writel(sdi_clkcr, &host->base->clock);
udelay(CLK_CHANGE_DELAY);
return 0;
}
static void arm_pl180_mmc_init(struct pl180_mmc_host *host)
{
u32 sdi_u32;
writel(host->pwr_init, &host->base->power);
writel(host->clkdiv_init, &host->base->clock);
udelay(CLK_CHANGE_DELAY);
/* Disable mmc interrupts */
sdi_u32 = readl(&host->base->mask0) & ~SDI_MASK0_MASK;
writel(sdi_u32, &host->base->mask0);
}
static int arm_pl180_mmc_probe(struct udevice *dev)
{
struct arm_pl180_mmc_plat *pdata = dev_get_plat(dev);
struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
struct mmc *mmc = &pdata->mmc;
struct pl180_mmc_host *host = dev_get_priv(dev);
struct mmc_config *cfg = &pdata->cfg;
struct clk clk;
u32 periphid;
int ret;
ret = clk_get_by_index(dev, 0, &clk);
if (ret < 0)
return ret;
ret = clk_enable(&clk);
if (ret) {
dev_err(dev, "failed to enable clock\n");
return ret;
}
host->pwr_init = INIT_PWR;
host->clkdiv_init = SDI_CLKCR_CLKDIV_INIT_V1 | SDI_CLKCR_CLKEN |
SDI_CLKCR_HWFC_EN;
host->clock_in = clk_get_rate(&clk);
cfg->name = dev->name;
cfg->voltages = VOLTAGE_WINDOW_SD;
cfg->host_caps = 0;
cfg->f_min = host->clock_in / (2 * (SDI_CLKCR_CLKDIV_INIT_V1 + 1));
cfg->f_max = MMC_CLOCK_MAX;
cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
periphid = dev_read_u32_default(dev, "arm,primecell-periphid", 0);
switch (periphid) {
case STM32_MMCI_ID: /* stm32 variant */
host->version2 = false;
break;
case UX500V2_MMCI_ID:
host->pwr_init = SDI_PWR_OPD | SDI_PWR_PWRCTRL_ON;
host->clkdiv_init = SDI_CLKCR_CLKDIV_INIT_V2 | SDI_CLKCR_CLKEN |
SDI_CLKCR_HWFC_EN;
cfg->voltages = VOLTAGE_WINDOW_MMC;
cfg->f_min = host->clock_in / (2 + SDI_CLKCR_CLKDIV_INIT_V2);
host->version2 = true;
break;
default:
host->version2 = false; /* ARM variant */
}
gpio_request_by_name(dev, "cd-gpios", 0, &host->cd_gpio, GPIOD_IS_IN);
ret = mmc_of_parse(dev, cfg);
if (ret)
return ret;
arm_pl180_mmc_init(host);
mmc->priv = host;
mmc->dev = dev;
upriv->mmc = mmc;
return 0;
}
int arm_pl180_mmc_bind(struct udevice *dev)
{
struct arm_pl180_mmc_plat *plat = dev_get_plat(dev);
return mmc_bind(dev, &plat->mmc, &plat->cfg);
}
static int dm_host_request(struct udevice *dev, struct mmc_cmd *cmd,
struct mmc_data *data)
{
struct mmc *mmc = mmc_get_mmc_dev(dev);
return host_request(mmc, cmd, data);
}
static int dm_host_set_ios(struct udevice *dev)
{
struct mmc *mmc = mmc_get_mmc_dev(dev);
return host_set_ios(mmc);
}
static int dm_mmc_getcd(struct udevice *dev)
{
struct pl180_mmc_host *host = dev_get_priv(dev);
int value = 1;
if (dm_gpio_is_valid(&host->cd_gpio))
value = dm_gpio_get_value(&host->cd_gpio);
return value;
}
static const struct dm_mmc_ops arm_pl180_dm_mmc_ops = {
.send_cmd = dm_host_request,
.set_ios = dm_host_set_ios,
.get_cd = dm_mmc_getcd,
};
static int arm_pl180_mmc_of_to_plat(struct udevice *dev)
{
struct pl180_mmc_host *host = dev_get_priv(dev);
host->base = dev_read_addr_ptr(dev);
if (!host->base)
return -EINVAL;
return 0;
}
static const struct udevice_id arm_pl180_mmc_match[] = {
{ .compatible = "arm,pl180" },
{ .compatible = "arm,pl18x" },
{ /* sentinel */ }
};
U_BOOT_DRIVER(arm_pl180_mmc) = {
.name = "arm_pl180_mmc",
.id = UCLASS_MMC,
.of_match = arm_pl180_mmc_match,
.ops = &arm_pl180_dm_mmc_ops,
.probe = arm_pl180_mmc_probe,
.of_to_plat = arm_pl180_mmc_of_to_plat,
.bind = arm_pl180_mmc_bind,
.priv_auto = sizeof(struct pl180_mmc_host),
.plat_auto = sizeof(struct arm_pl180_mmc_plat),
};