diff options
author | Oleksij Rempel <o.rempel@pengutronix.de> | 2021-04-23 16:28:24 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2021-05-03 12:46:53 +0200 |
commit | 4fc0369b1a55cc5e3068a76e0917cb5d11e13d92 (patch) | |
tree | d024d9de1e4e8d1f602a58648327118299ddf673 /drivers/mci/atmel_mci_common.c | |
parent | 7702961ce8dceed94f3efe401e39dc62ccac0e70 (diff) | |
download | barebox-4fc0369b1a55cc5e3068a76e0917cb5d11e13d92.tar.gz |
mci: atmel_mci: add PBL support
Move most of the atmel_mci code to atmel_mci_common.c and introduce
atmel_mci_pbl.c for the PBL part.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Link: https://lore.barebox.org/20210423142829.29468-2-o.rempel@pengutronix.de
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/mci/atmel_mci_common.c')
-rw-r--r-- | drivers/mci/atmel_mci_common.c | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/drivers/mci/atmel_mci_common.c b/drivers/mci/atmel_mci_common.c new file mode 100644 index 0000000000..5c9e6f9c4d --- /dev/null +++ b/drivers/mci/atmel_mci_common.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2011 Hubert Feurstein <h.feurstein@gmail.com> +// SPDX-FileCopyrightText: 2009 Ilya Yanok <yanok@emcraft.com> +// SPDX-FileCopyrightText: 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix +// SPDX-FileCopyrightText: 2006 Pavel Pisa <ppisa@pikron.com>, PiKRON + +#include <common.h> +#include <init.h> +#include <linux/clk.h> +#include <linux/iopoll.h> +#include <mci.h> + +#include "atmel-mci-regs.h" + +#ifdef __PBL__ +#define udelay early_udelay +#undef dev_err +#define dev_err(d, ...) pr_err(__VA_ARGS__) +#undef dev_warn +#define dev_warn(d, ...) pr_warn(__VA_ARGS__) +#undef dev_dbg +#define dev_dbg(d, ...) pr_debug(__VA_ARGS__) +#undef dev_info +#define dev_info(d, ...) pr_info(__VA_ARGS__) +#undef clk_enable +#define clk_enable(...) +#undef clk_disable +#define clk_disable(...) +#endif + +#define STATUS_ERROR_MASK (ATMCI_RINDE \ + | ATMCI_RDIRE \ + | ATMCI_RCRCE \ + | ATMCI_RENDE \ + | ATMCI_RTOE \ + | ATMCI_DCRCE \ + | ATMCI_DTOE \ + | ATMCI_OVRE \ + | ATMCI_UNRE) + +static void atmci_set_clk_rate(struct atmel_mci *host, + unsigned int clock_min) +{ + unsigned int clkdiv; + + if (!host->mode_reg) { + clk_enable(host->clk); + atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN); + if (host->caps.has_cfg_reg) + atmci_writel(host, ATMCI_CFG, host->cfg_reg); + } + + if (host->caps.has_odd_clk_div) { + clkdiv = DIV_ROUND_UP(host->bus_hz, clock_min) - 2; + if (clkdiv > 511) { + dev_dbg(host->hw_dev, + "clock %u too slow; using %lu\n", + clock_min, host->bus_hz / (511 + 2)); + clkdiv = 511; + } + host->mode_reg = ATMCI_MR_CLKDIV(clkdiv >> 1) + | ATMCI_MR_CLKODD(clkdiv & 1); + } else { + clkdiv = DIV_ROUND_UP(host->bus_hz, 2 * clock_min) - 1; + if (clkdiv > 255) { + dev_dbg(host->hw_dev, + "clock %u too slow; using %lu\n", + clock_min, host->bus_hz / (2 * 256)); + clkdiv = 255; + } + host->mode_reg = ATMCI_MR_CLKDIV(clkdiv); + } + + dev_dbg(host->hw_dev, "atmel_set_clk_rate: clkIn=%ld clkIos=%d divider=%d\n", + host->bus_hz, clock_min, clkdiv); + + /* + * WRPROOF and RDPROOF prevent overruns/underruns by + * stopping the clock when the FIFO is full/empty. + * This state is not expected to last for long. + */ + if (host->caps.has_rwproof) + host->mode_reg |= ATMCI_MR_RDPROOF | ATMCI_MR_WRPROOF; + + atmci_writel(host, ATMCI_MR, host->mode_reg); +} + +static int atmci_poll_status(struct atmel_mci *host, u32 mask) +{ + u32 stat; + int ret; + + ret = read_poll_timeout(atmci_readl, stat, (stat & mask), SECOND, host, + ATMCI_SR); + if (ret < 0) { + dev_err(host->hw_dev, "timeout\n"); + host->need_reset = true; + return ATMCI_RTOE | stat; + } + + if (stat & STATUS_ERROR_MASK) + return stat; + + return 0; +} + +static int atmci_pull(struct atmel_mci *host, void *_buf, int bytes) +{ + unsigned int stat; + u32 *buf = _buf; + + while (bytes > 3) { + stat = atmci_poll_status(host, ATMCI_RXRDY); + if (stat) + return stat; + + *buf++ = atmci_readl(host, ATMCI_RDR); + bytes -= 4; + } + + if (WARN_ON(bytes)) + return -EIO; + + return 0; +} + +#ifdef CONFIG_MCI_WRITE +static int atmci_push(struct atmel_mci *host, const void *_buf, int bytes) +{ + unsigned int stat; + const u32 *buf = _buf; + + while (bytes > 3) { + stat = atmci_poll_status(host, ATMCI_TXRDY); + if (stat) + return stat; + + atmci_writel(host, ATMCI_TDR, *buf++); + bytes -= 4; + } + + stat = atmci_poll_status(host, ATMCI_TXRDY); + if (stat) + return stat; + + if (WARN_ON(bytes)) + return -EIO; + + return 0; +} +#endif /* CONFIG_MCI_WRITE */ + +static int atmci_transfer_data(struct atmel_mci *host) +{ + struct mci_data *data = host->data; + int stat; + unsigned long length; + + length = data->blocks * data->blocksize; + host->datasize = 0; + + if (data->flags & MMC_DATA_READ) { + stat = atmci_pull(host, data->dest, length); + if (stat) + return stat; + + stat = atmci_poll_status(host, ATMCI_NOTBUSY); + if (stat) + return stat; + + host->datasize += length; + } else { +#ifdef CONFIG_MCI_WRITE + stat = atmci_push(host, (const void *)(data->src), length); + if (stat) + return stat; + + host->datasize += length; + stat = atmci_poll_status(host, ATMCI_NOTBUSY); + if (stat) + return stat; +#endif /* CONFIG_MCI_WRITE */ + } + return 0; +} + +static void atmci_finish_request(struct atmel_mci *host) +{ + host->cmd = NULL; + host->data = NULL; +} + +static int atmci_finish_data(struct atmel_mci *host, unsigned int stat) +{ + int data_error = 0; + + if (stat & STATUS_ERROR_MASK) { + dev_err(host->hw_dev, "request failed (status=0x%08x)\n", stat); + if (stat & ATMCI_DCRCE) + data_error = -EILSEQ; + else if (stat & (ATMCI_RTOE | ATMCI_DTOE)) + data_error = -ETIMEDOUT; + else + data_error = -EIO; + } + + host->data = NULL; + + return data_error; +} + +static void atmci_setup_data(struct atmel_mci *host, struct mci_data *data) +{ + unsigned int nob = data->blocks; + unsigned int blksz = data->blocksize; + unsigned int datasize = nob * blksz; + + BUG_ON(data->blocksize & 3); + BUG_ON(nob == 0); + + host->data = data; + + dev_dbg(host->hw_dev, "atmel_setup_data: nob=%d blksz=%d\n", + nob, blksz); + + atmci_writel(host, ATMCI_BLKR, ATMCI_BCNT(nob) + | ATMCI_BLKLEN(blksz)); + + host->datasize = datasize; +} + +static int atmci_read_response(struct atmel_mci *host, unsigned int stat) +{ + struct mci_cmd *cmd = host->cmd; + int i; + u32 *resp; + + if (!cmd) + return 0; + + resp = (u32 *)cmd->response; + + if (stat & (ATMCI_RTOE | ATMCI_DTOE)) { + dev_err(host->hw_dev, "command/data timeout\n"); + return -ETIMEDOUT; + } else if ((stat & ATMCI_RCRCE) && (cmd->resp_type & MMC_RSP_CRC)) { + dev_err(host->hw_dev, "cmd crc error\n"); + return -EILSEQ; + } + + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) { + for (i = 0; i < 4; i++) + resp[i] = atmci_readl(host, ATMCI_RSPR); + } else { + resp[0] = atmci_readl(host, ATMCI_RSPR); + } + } + + return 0; +} + +static int atmci_cmd_done(struct atmel_mci *host, unsigned int stat) +{ + int datastat; + int ret; + + ret = atmci_read_response(host, stat); + + if (ret) { + atmci_finish_request(host); + return ret; + } + + if (!host->data) { + atmci_finish_request(host); + return 0; + } + + datastat = atmci_transfer_data(host); + ret = atmci_finish_data(host, datastat); + atmci_finish_request(host); + return ret; +} + +static int atmci_start_cmd(struct atmel_mci *host, struct mci_cmd *cmd, + unsigned int cmdat) +{ + unsigned flags = 0; + unsigned cmdval = 0; + + if (host->cmd != NULL) + dev_err(host->hw_dev, "error!\n"); + + if ((atmci_readl(host, ATMCI_SR) & ATMCI_CMDRDY) == 0) { + dev_err(host->hw_dev, "mci not ready!\n"); + return -EBUSY; + } + + host->cmd = cmd; + cmdval = ATMCI_CMDR_CMDNB_MASK & cmd->cmdidx; + + switch (cmd->resp_type) { + case MMC_RSP_R1: /* short CRC, OPCODE */ + case MMC_RSP_R1b:/* short CRC, OPCODE, BUSY */ + flags |= ATMCI_CMDR_RSPTYP_48BIT; + break; + case MMC_RSP_R2: /* long 136 bit + CRC */ + flags |= ATMCI_CMDR_RSPTYP_136BIT; + break; + case MMC_RSP_R3: /* short */ + flags |= ATMCI_CMDR_RSPTYP_48BIT; + break; + case MMC_RSP_NONE: + flags |= ATMCI_CMDR_RSPTYP_NONE; + break; + default: + dev_err(host->hw_dev, "unhandled response type 0x%x\n", + cmd->resp_type); + return -EINVAL; + } + cmdval |= ATMCI_CMDR_RSPTYP & flags; + cmdval |= cmdat & ~(ATMCI_CMDR_CMDNB_MASK | ATMCI_CMDR_RSPTYP); + + atmci_writel(host, ATMCI_ARGR, cmd->cmdarg); + atmci_writel(host, ATMCI_CMDR, cmdval); + + return 0; +} + +/** init the host interface */ +int atmci_reset(struct mci_host *mci, struct device_d *mci_dev) +{ + struct atmel_mci *host = to_mci_host(mci); + + clk_enable(host->clk); + atmci_writel(host, ATMCI_DTOR, 0x7f); + clk_disable(host->clk); + + return 0; +} + +/** change host interface settings */ +void atmci_common_set_ios(struct atmel_mci *host, struct mci_ios *ios) +{ + dev_dbg(host->hw_dev, "atmel_mci_set_ios: bus_width=%d clk=%d\n", + ios->bus_width, ios->clock); + + host->sdc_reg &= ~ATMCI_SDCBUS_MASK; + switch (ios->bus_width) { + case MMC_BUS_WIDTH_4: + host->sdc_reg |= ATMCI_SDCBUS_4BIT; + break; + case MMC_BUS_WIDTH_8: + host->sdc_reg |= ATMCI_SDCBUS_8BIT; + break; + case MMC_BUS_WIDTH_1: + host->sdc_reg |= ATMCI_SDCBUS_1BIT; + break; + default: + return; + } + + if (ios->clock) { + atmci_set_clk_rate(host, ios->clock); + + if (host->caps.has_cfg_reg) { + /* setup High Speed mode in relation with card capacity */ + if (ios->timing == MMC_TIMING_SD_HS) + host->cfg_reg |= ATMCI_CFG_HSMODE; + else + host->cfg_reg &= ~ATMCI_CFG_HSMODE; + + atmci_writel(host, ATMCI_CFG, host->cfg_reg); + } + } else { + atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIDIS); + if (host->mode_reg) { + atmci_readl(host, ATMCI_MR); + clk_disable(host->clk); + } + host->mode_reg = 0; + } + + return; +} + +/** handle a command */ +int atmci_common_request(struct atmel_mci *host, struct mci_cmd *cmd, + struct mci_data *data) +{ + u32 stat, cmdat = 0; + int ret; + + if (host->need_reset || host->caps.need_reset_after_xfer) { + atmci_writel(host, ATMCI_CR, ATMCI_CR_SWRST); + atmci_writel(host, ATMCI_CR, ATMCI_CR_MCIEN); + atmci_writel(host, ATMCI_MR, host->mode_reg); + if (host->caps.has_cfg_reg) + atmci_writel(host, ATMCI_CFG, host->cfg_reg); + host->need_reset = false; + } + atmci_writel(host, ATMCI_SDCR, host->sdc_reg); + + if (cmd->resp_type != MMC_RSP_NONE) + cmdat |= ATMCI_CMDR_MAXLAT_64CYC; + + if (data) { + atmci_setup_data(host, data); + + cmdat |= ATMCI_CMDR_START_XFER | ATMCI_CMDR_MULTI_BLOCK; + + if (data->flags & MMC_DATA_READ) + cmdat |= ATMCI_CMDR_TRDIR_READ; + } + + ret = atmci_start_cmd(host, cmd, cmdat); + if (ret) { + atmci_finish_request(host); + return ret; + } + + stat = atmci_poll_status(host, ATMCI_CMDRDY); + return atmci_cmd_done(host, stat); +} + + +/* + * HSMCI (High Speed MCI) module is not fully compatible with MCI module. + * HSMCI provides DMA support and a new config register but no more supports + * PDC. + */ +void atmci_get_cap(struct atmel_mci *host) +{ + unsigned int version; + + version = atmci_readl(host, ATMCI_VERSION) & 0x00000fff; + host->version = version; + + dev_info(host->hw_dev, "version: 0x%x\n", version); + + host->caps.has_cfg_reg = 0; + host->caps.has_highspeed = 0; + host->caps.need_reset_after_xfer = 1; + + switch (version & 0xf00) { + case 0x600: + case 0x500: + host->caps.has_odd_clk_div = 1; + case 0x400: + case 0x300: + host->caps.has_cfg_reg = 1; + host->caps.has_highspeed = 1; + case 0x200: + host->caps.has_rwproof = 1; + case 0x100: + host->caps.need_reset_after_xfer = 0; + case 0x0: + break; + default: + dev_warn(host->hw_dev, + "Unmanaged mci version, set minimum capabilities\n"); + break; + } +} |