root/drivers/mmc/host/sdhci-esdhc-mcf.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Freescale eSDHC ColdFire family controller driver, platform bus.
 *
 * Copyright (c) 2020 Timesys Corporation
 *   Author: Angelo Dureghello <angelo.dureghello@timesys.it>
 */

#include <linux/module.h>
#include <linux/delay.h>
#include <linux/platform_data/mmc-esdhc-mcf.h>
#include <linux/mmc/mmc.h>
#include "sdhci-pltfm.h"
#include "sdhci-esdhc.h"

#define ESDHC_PROCTL_D3CD               0x08
#define ESDHC_SYS_CTRL_DTOCV_MASK       0x0f
#define ESDHC_DEFAULT_HOST_CONTROL      0x28

/*
 * Freescale eSDHC has DMA ERR flag at bit 28, not as std spec says, bit 25.
 */
#define ESDHC_INT_VENDOR_SPEC_DMA_ERR   BIT(28)

struct pltfm_mcf_data {
        struct clk *clk_ipg;
        struct clk *clk_ahb;
        struct clk *clk_per;
        int aside;
        int current_bus_width;
};

static inline void esdhc_mcf_buffer_swap32(u32 *buf, int len)
{
        int i;
        u32 temp;

        len = (len + 3) >> 2;

        for (i = 0; i < len;  i++) {
                temp = swab32(*buf);
                *buf++ = temp;
        }
}

static inline void esdhc_clrset_be(struct sdhci_host *host,
                                   u32 mask, u32 val, int reg)
{
        void __iomem *base = host->ioaddr + (reg & ~3);
        u8 shift = (reg & 3) << 3;

        mask <<= shift;
        val <<= shift;

        if (reg == SDHCI_HOST_CONTROL)
                val |= ESDHC_PROCTL_D3CD;

        writel((readl(base) & ~mask) | val, base);
}

/*
 * Note: mcf is big-endian, single bytes need to be accessed at big endian
 * offsets.
 */
static void esdhc_mcf_writeb_be(struct sdhci_host *host, u8 val, int reg)
{
        void __iomem *base = host->ioaddr + (reg & ~3);
        u8 shift = (reg & 3) << 3;
        u32 mask = ~(0xff << shift);

        if (reg == SDHCI_HOST_CONTROL) {
                u32 host_ctrl = ESDHC_DEFAULT_HOST_CONTROL;
                u8 dma_bits = (val & SDHCI_CTRL_DMA_MASK) >> 3;
                u8 tmp = readb(host->ioaddr + SDHCI_HOST_CONTROL + 1);

                tmp &= ~0x03;
                tmp |= dma_bits;

                /*
                 * Recomposition needed, restore always endianness and
                 * keep D3CD and AI, just setting bus width.
                 */
                host_ctrl |= val;
                host_ctrl |= (dma_bits << 8);
                writel(host_ctrl, host->ioaddr + SDHCI_HOST_CONTROL);

                return;
        }

        writel((readl(base) & mask) | (val << shift), base);
}

static void esdhc_mcf_writew_be(struct sdhci_host *host, u16 val, int reg)
{
        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
        struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);
        void __iomem *base = host->ioaddr + (reg & ~3);
        u8 shift = (reg & 3) << 3;
        u32 mask = ~(0xffff << shift);

        switch (reg) {
        case SDHCI_TRANSFER_MODE:
                mcf_data->aside = val;
                return;
        case SDHCI_COMMAND:
                if (host->cmd->opcode == MMC_STOP_TRANSMISSION)
                        val |= SDHCI_CMD_ABORTCMD;

                /*
                 * As for the fsl driver,
                 * we have to set the mode in a single write here.
                 */
                writel(val << 16 | mcf_data->aside,
                       host->ioaddr + SDHCI_TRANSFER_MODE);
                return;
        }

        writel((readl(base) & mask) | (val << shift), base);
}

static void esdhc_mcf_writel_be(struct sdhci_host *host, u32 val, int reg)
{
        writel(val, host->ioaddr + reg);
}

static u8 esdhc_mcf_readb_be(struct sdhci_host *host, int reg)
{
        if (reg == SDHCI_HOST_CONTROL) {
                u8 __iomem *base = host->ioaddr + (reg & ~3);
                u16 val = readw(base + 2);
                u8 dma_bits = (val >> 5) & SDHCI_CTRL_DMA_MASK;
                u8 host_ctrl = val & 0xff;

                host_ctrl &= ~SDHCI_CTRL_DMA_MASK;
                host_ctrl |= dma_bits;

                return host_ctrl;
        }

        return readb(host->ioaddr + (reg ^ 0x3));
}

static u16 esdhc_mcf_readw_be(struct sdhci_host *host, int reg)
{
        /*
         * For SDHCI_HOST_VERSION, sdhci specs defines 0xFE,
         * a wrong offset for us, we are at 0xFC.
         */
        if (reg == SDHCI_HOST_VERSION)
                reg -= 2;

        return readw(host->ioaddr + (reg ^ 0x2));
}

static u32 esdhc_mcf_readl_be(struct sdhci_host *host, int reg)
{
        u32 val;

        val = readl(host->ioaddr + reg);

        /*
         * RM (25.3.9) sd pin clock must never exceed 25Mhz.
         * So forcing legacy mode at 25Mhz.
         */
        if (unlikely(reg == SDHCI_CAPABILITIES))
                val &= ~SDHCI_CAN_DO_HISPD;

        if (unlikely(reg == SDHCI_INT_STATUS)) {
                if (val & ESDHC_INT_VENDOR_SPEC_DMA_ERR) {
                        val &= ~ESDHC_INT_VENDOR_SPEC_DMA_ERR;
                        val |= SDHCI_INT_ADMA_ERROR;
                }
        }

        return val;
}

static unsigned int esdhc_mcf_get_max_timeout_count(struct sdhci_host *host)
{
        return 1 << 27;
}

static void esdhc_mcf_set_timeout(struct sdhci_host *host,
                                  struct mmc_command *cmd)
{
        /* Use maximum timeout counter */
        esdhc_clrset_be(host, ESDHC_SYS_CTRL_DTOCV_MASK, 0xE,
                        SDHCI_TIMEOUT_CONTROL);
}

static void esdhc_mcf_reset(struct sdhci_host *host, u8 mask)
{
        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
        struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);

        sdhci_reset(host, mask);

        esdhc_clrset_be(host, ESDHC_CTRL_BUSWIDTH_MASK,
                        mcf_data->current_bus_width, SDHCI_HOST_CONTROL);

        sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
        sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
}

static unsigned int esdhc_mcf_pltfm_get_max_clock(struct sdhci_host *host)
{
        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);

        return pltfm_host->clock;
}

static unsigned int esdhc_mcf_pltfm_get_min_clock(struct sdhci_host *host)
{
        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);

        return pltfm_host->clock / 256 / 16;
}

static void esdhc_mcf_pltfm_set_clock(struct sdhci_host *host,
                                      unsigned int clock)
{
        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
        unsigned long *pll_dr = (unsigned long *)MCF_PLL_DR;
        u32 fvco, fsys, fesdhc, temp;
        const int sdclkfs[] = {2, 4, 8, 16, 32, 64, 128, 256};
        int delta, old_delta = clock;
        int i, q, ri, rq;

        if (clock == 0) {
                host->mmc->actual_clock = 0;
                return;
        }

        /*
         * ColdFire eSDHC clock.s
         *
         * pll -+-> / outdiv1 --> fsys
         *      +-> / outdiv3 --> eSDHC clock ---> / SDCCLKFS / DVS
         *
         * mcf5441x datasheet says:
         * (8.1.2) eSDHC should be 40 MHz max
         * (25.3.9) eSDHC input is, as example, 96 Mhz ...
         * (25.3.9) sd pin clock must never exceed 25Mhz
         *
         * fvco = fsys * outdvi1 + 1
         * fshdc = fvco / outdiv3 + 1
         */
        temp = readl(pll_dr);
        fsys = pltfm_host->clock;
        fvco = fsys * ((temp & 0x1f) + 1);
        fesdhc = fvco / (((temp >> 10) & 0x1f) + 1);

        for (i = 0; i < 8; ++i) {
                int result = fesdhc / sdclkfs[i];

                for (q = 1; q < 17; ++q) {
                        int finale = result / q;

                        delta = abs(clock - finale);

                        if (delta < old_delta) {
                                old_delta = delta;
                                ri = i;
                                rq = q;
                        }
                }
        }

        /*
         * Apply divisors and re-enable all the clocks
         */
        temp = ((sdclkfs[ri] >> 1) << 8) | ((rq - 1) << 4) |
                (ESDHC_CLOCK_IPGEN | ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN);
        esdhc_clrset_be(host, 0x0000fff7, temp, SDHCI_CLOCK_CONTROL);

        host->mmc->actual_clock = clock;

        mdelay(1);
}

static void esdhc_mcf_pltfm_set_bus_width(struct sdhci_host *host, int width)
{
        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
        struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);

        switch (width) {
        case MMC_BUS_WIDTH_4:
                mcf_data->current_bus_width = ESDHC_CTRL_4BITBUS;
                break;
        default:
                mcf_data->current_bus_width = 0;
                break;
        }

        esdhc_clrset_be(host, ESDHC_CTRL_BUSWIDTH_MASK,
                        mcf_data->current_bus_width, SDHCI_HOST_CONTROL);
}

static void esdhc_mcf_request_done(struct sdhci_host *host,
                                   struct mmc_request *mrq)
{
        struct sg_mapping_iter sgm;
        u32 *buffer;

        if (!mrq->data || !mrq->data->bytes_xfered)
                goto exit_done;

        if (mmc_get_dma_dir(mrq->data) != DMA_FROM_DEVICE)
                goto exit_done;

        /*
         * On mcf5441x there is no hw sdma option/flag to select the dma
         * transfer endiannes. A swap after the transfer is needed.
         */
        sg_miter_start(&sgm, mrq->data->sg, mrq->data->sg_len,
                       SG_MITER_ATOMIC | SG_MITER_TO_SG | SG_MITER_FROM_SG);
        while (sg_miter_next(&sgm)) {
                buffer = sgm.addr;
                esdhc_mcf_buffer_swap32(buffer, sgm.length);
        }
        sg_miter_stop(&sgm);

exit_done:
        mmc_request_done(host->mmc, mrq);
}

static void esdhc_mcf_copy_to_bounce_buffer(struct sdhci_host *host,
                                            struct mmc_data *data,
                                            unsigned int length)
{
        sg_copy_to_buffer(data->sg, data->sg_len,
                          host->bounce_buffer, length);

        esdhc_mcf_buffer_swap32((u32 *)host->bounce_buffer,
                                data->blksz * data->blocks);
}

static const struct sdhci_ops sdhci_esdhc_ops = {
        .reset = esdhc_mcf_reset,
        .set_clock = esdhc_mcf_pltfm_set_clock,
        .get_max_clock = esdhc_mcf_pltfm_get_max_clock,
        .get_min_clock = esdhc_mcf_pltfm_get_min_clock,
        .set_bus_width = esdhc_mcf_pltfm_set_bus_width,
        .get_max_timeout_count = esdhc_mcf_get_max_timeout_count,
        .set_timeout = esdhc_mcf_set_timeout,
        .write_b = esdhc_mcf_writeb_be,
        .write_w = esdhc_mcf_writew_be,
        .write_l = esdhc_mcf_writel_be,
        .read_b = esdhc_mcf_readb_be,
        .read_w = esdhc_mcf_readw_be,
        .read_l = esdhc_mcf_readl_be,
        .copy_to_bounce_buffer = esdhc_mcf_copy_to_bounce_buffer,
        .request_done = esdhc_mcf_request_done,
};

static const struct sdhci_pltfm_data sdhci_esdhc_mcf_pdata = {
        .ops = &sdhci_esdhc_ops,
        .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_FORCE_DMA,
                 /*
                  * Mandatory quirk,
                  * controller does not support cmd23,
                  * without, on > 8G cards cmd23 is used, and
                  * driver times out.
                  */
                  SDHCI_QUIRK2_HOST_NO_CMD23,
};

static int esdhc_mcf_plat_init(struct sdhci_host *host,
                               struct pltfm_mcf_data *mcf_data)
{
        struct mcf_esdhc_platform_data *plat_data;
        struct device *dev = mmc_dev(host->mmc);

        if (!dev->platform_data) {
                dev_err(dev, "no platform data!\n");
                return -EINVAL;
        }

        plat_data = (struct mcf_esdhc_platform_data *)dev->platform_data;

        /* Card_detect */
        switch (plat_data->cd_type) {
        default:
        case ESDHC_CD_CONTROLLER:
                /* We have a working card_detect back */
                host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION;
                break;
        case ESDHC_CD_PERMANENT:
                host->mmc->caps |= MMC_CAP_NONREMOVABLE;
                break;
        case ESDHC_CD_NONE:
                break;
        }

        switch (plat_data->max_bus_width) {
        case 4:
                host->mmc->caps |= MMC_CAP_4_BIT_DATA;
                break;
        case 1:
        default:
                host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA;
                break;
        }

        return 0;
}

static int sdhci_esdhc_mcf_probe(struct platform_device *pdev)
{
        struct sdhci_host *host;
        struct sdhci_pltfm_host *pltfm_host;
        struct pltfm_mcf_data *mcf_data;
        int err;

        host = sdhci_pltfm_init(pdev, &sdhci_esdhc_mcf_pdata,
                                sizeof(*mcf_data));

        if (IS_ERR(host))
                return PTR_ERR(host);

        pltfm_host = sdhci_priv(host);
        mcf_data = sdhci_pltfm_priv(pltfm_host);

        host->sdma_boundary = 0;

        host->flags |= SDHCI_AUTO_CMD12;

        mcf_data->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
        if (IS_ERR(mcf_data->clk_ipg))
                return PTR_ERR(mcf_data->clk_ipg);

        mcf_data->clk_ahb = devm_clk_get(&pdev->dev, "ahb");
        if (IS_ERR(mcf_data->clk_ahb))
                return PTR_ERR(mcf_data->clk_ahb);

        mcf_data->clk_per = devm_clk_get(&pdev->dev, "per");
        if (IS_ERR(mcf_data->clk_per))
                return PTR_ERR(mcf_data->clk_per);

        pltfm_host->clk = mcf_data->clk_per;
        pltfm_host->clock = clk_get_rate(pltfm_host->clk);
        err = clk_prepare_enable(mcf_data->clk_per);
        if (err)
                return err;

        err = clk_prepare_enable(mcf_data->clk_ipg);
        if (err)
                goto unprep_per;

        err = clk_prepare_enable(mcf_data->clk_ahb);
        if (err)
                goto unprep_ipg;

        err = esdhc_mcf_plat_init(host, mcf_data);
        if (err)
                goto unprep_ahb;

        err = sdhci_setup_host(host);
        if (err)
                goto unprep_ahb;

        if (!host->bounce_buffer) {
                dev_err(&pdev->dev, "bounce buffer not allocated");
                err = -ENOMEM;
                goto cleanup;
        }

        err = __sdhci_add_host(host);
        if (err)
                goto cleanup;

        return 0;

cleanup:
        sdhci_cleanup_host(host);
unprep_ahb:
        clk_disable_unprepare(mcf_data->clk_ahb);
unprep_ipg:
        clk_disable_unprepare(mcf_data->clk_ipg);
unprep_per:
        clk_disable_unprepare(mcf_data->clk_per);
        return err;
}

static void sdhci_esdhc_mcf_remove(struct platform_device *pdev)
{
        struct sdhci_host *host = platform_get_drvdata(pdev);
        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
        struct pltfm_mcf_data *mcf_data = sdhci_pltfm_priv(pltfm_host);

        sdhci_remove_host(host, 0);

        clk_disable_unprepare(mcf_data->clk_ipg);
        clk_disable_unprepare(mcf_data->clk_ahb);
        clk_disable_unprepare(mcf_data->clk_per);
}

static struct platform_driver sdhci_esdhc_mcf_driver = {
        .driver = {
                .name = "sdhci-esdhc-mcf",
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
        .probe = sdhci_esdhc_mcf_probe,
        .remove = sdhci_esdhc_mcf_remove,
};

module_platform_driver(sdhci_esdhc_mcf_driver);

MODULE_DESCRIPTION("SDHCI driver for Freescale ColdFire eSDHC");
MODULE_AUTHOR("Angelo Dureghello <angelo.dureghello@timesys.com>");
MODULE_LICENSE("GPL v2");