linux/drivers/mmc/host/sdhci-of-dwcmshc.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Driver for Synopsys DesignWare Cores Mobile Storage Host Controller
   4 *
   5 * Copyright (C) 2018 Synaptics Incorporated
   6 *
   7 * Author: Jisheng Zhang <jszhang@kernel.org>
   8 */
   9
  10#include <linux/clk.h>
  11#include <linux/dma-mapping.h>
  12#include <linux/kernel.h>
  13#include <linux/module.h>
  14#include <linux/of.h>
  15#include <linux/sizes.h>
  16
  17#include "sdhci-pltfm.h"
  18
  19#define BOUNDARY_OK(addr, len) \
  20        ((addr | (SZ_128M - 1)) == ((addr + len - 1) | (SZ_128M - 1)))
  21
  22struct dwcmshc_priv {
  23        struct clk      *bus_clk;
  24};
  25
  26/*
  27 * If DMA addr spans 128MB boundary, we split the DMA transfer into two
  28 * so that each DMA transfer doesn't exceed the boundary.
  29 */
  30static void dwcmshc_adma_write_desc(struct sdhci_host *host, void **desc,
  31                                    dma_addr_t addr, int len, unsigned int cmd)
  32{
  33        int tmplen, offset;
  34
  35        if (likely(!len || BOUNDARY_OK(addr, len))) {
  36                sdhci_adma_write_desc(host, desc, addr, len, cmd);
  37                return;
  38        }
  39
  40        offset = addr & (SZ_128M - 1);
  41        tmplen = SZ_128M - offset;
  42        sdhci_adma_write_desc(host, desc, addr, tmplen, cmd);
  43
  44        addr += tmplen;
  45        len -= tmplen;
  46        sdhci_adma_write_desc(host, desc, addr, len, cmd);
  47}
  48
  49static const struct sdhci_ops sdhci_dwcmshc_ops = {
  50        .set_clock              = sdhci_set_clock,
  51        .set_bus_width          = sdhci_set_bus_width,
  52        .set_uhs_signaling      = sdhci_set_uhs_signaling,
  53        .get_max_clock          = sdhci_pltfm_clk_get_max_clock,
  54        .reset                  = sdhci_reset,
  55        .adma_write_desc        = dwcmshc_adma_write_desc,
  56};
  57
  58static const struct sdhci_pltfm_data sdhci_dwcmshc_pdata = {
  59        .ops = &sdhci_dwcmshc_ops,
  60        .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
  61};
  62
  63static int dwcmshc_probe(struct platform_device *pdev)
  64{
  65        struct sdhci_pltfm_host *pltfm_host;
  66        struct sdhci_host *host;
  67        struct dwcmshc_priv *priv;
  68        int err;
  69        u32 extra;
  70
  71        host = sdhci_pltfm_init(pdev, &sdhci_dwcmshc_pdata,
  72                                sizeof(struct dwcmshc_priv));
  73        if (IS_ERR(host))
  74                return PTR_ERR(host);
  75
  76        /*
  77         * extra adma table cnt for cross 128M boundary handling.
  78         */
  79        extra = DIV_ROUND_UP_ULL(dma_get_required_mask(&pdev->dev), SZ_128M);
  80        if (extra > SDHCI_MAX_SEGS)
  81                extra = SDHCI_MAX_SEGS;
  82        host->adma_table_cnt += extra;
  83
  84        pltfm_host = sdhci_priv(host);
  85        priv = sdhci_pltfm_priv(pltfm_host);
  86
  87        pltfm_host->clk = devm_clk_get(&pdev->dev, "core");
  88        if (IS_ERR(pltfm_host->clk)) {
  89                err = PTR_ERR(pltfm_host->clk);
  90                dev_err(&pdev->dev, "failed to get core clk: %d\n", err);
  91                goto free_pltfm;
  92        }
  93        err = clk_prepare_enable(pltfm_host->clk);
  94        if (err)
  95                goto free_pltfm;
  96
  97        priv->bus_clk = devm_clk_get(&pdev->dev, "bus");
  98        if (!IS_ERR(priv->bus_clk))
  99                clk_prepare_enable(priv->bus_clk);
 100
 101        err = mmc_of_parse(host->mmc);
 102        if (err)
 103                goto err_clk;
 104
 105        sdhci_get_of_property(pdev);
 106
 107        err = sdhci_add_host(host);
 108        if (err)
 109                goto err_clk;
 110
 111        return 0;
 112
 113err_clk:
 114        clk_disable_unprepare(pltfm_host->clk);
 115        clk_disable_unprepare(priv->bus_clk);
 116free_pltfm:
 117        sdhci_pltfm_free(pdev);
 118        return err;
 119}
 120
 121static int dwcmshc_remove(struct platform_device *pdev)
 122{
 123        struct sdhci_host *host = platform_get_drvdata(pdev);
 124        struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 125        struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
 126
 127        sdhci_remove_host(host, 0);
 128
 129        clk_disable_unprepare(pltfm_host->clk);
 130        clk_disable_unprepare(priv->bus_clk);
 131
 132        sdhci_pltfm_free(pdev);
 133
 134        return 0;
 135}
 136
 137static const struct of_device_id sdhci_dwcmshc_dt_ids[] = {
 138        { .compatible = "snps,dwcmshc-sdhci" },
 139        {}
 140};
 141MODULE_DEVICE_TABLE(of, sdhci_dwcmshc_dt_ids);
 142
 143static struct platform_driver sdhci_dwcmshc_driver = {
 144        .driver = {
 145                .name   = "sdhci-dwcmshc",
 146                .of_match_table = sdhci_dwcmshc_dt_ids,
 147        },
 148        .probe  = dwcmshc_probe,
 149        .remove = dwcmshc_remove,
 150};
 151module_platform_driver(sdhci_dwcmshc_driver);
 152
 153MODULE_DESCRIPTION("SDHCI platform driver for Synopsys DWC MSHC");
 154MODULE_AUTHOR("Jisheng Zhang <jszhang@kernel.org>");
 155MODULE_LICENSE("GPL v2");
 156