linux/drivers/mmc/host/sdhci-of-aspeed.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/* Copyright (C) 2019 ASPEED Technology Inc. */
   3/* Copyright (C) 2019 IBM Corp. */
   4
   5#include <linux/clk.h>
   6#include <linux/delay.h>
   7#include <linux/device.h>
   8#include <linux/io.h>
   9#include <linux/mmc/host.h>
  10#include <linux/module.h>
  11#include <linux/of_address.h>
  12#include <linux/of.h>
  13#include <linux/of_platform.h>
  14#include <linux/platform_device.h>
  15#include <linux/spinlock.h>
  16
  17#include "sdhci-pltfm.h"
  18
  19#define ASPEED_SDC_INFO         0x00
  20#define   ASPEED_SDC_S1MMC8     BIT(25)
  21#define   ASPEED_SDC_S0MMC8     BIT(24)
  22
  23struct aspeed_sdc {
  24        struct clk *clk;
  25        struct resource *res;
  26
  27        spinlock_t lock;
  28        void __iomem *regs;
  29};
  30
  31struct aspeed_sdhci {
  32        struct aspeed_sdc *parent;
  33        u32 width_mask;
  34};
  35
  36static void aspeed_sdc_configure_8bit_mode(struct aspeed_sdc *sdc,
  37                                           struct aspeed_sdhci *sdhci,
  38                                           bool bus8)
  39{
  40        u32 info;
  41
  42        /* Set/clear 8 bit mode */
  43        spin_lock(&sdc->lock);
  44        info = readl(sdc->regs + ASPEED_SDC_INFO);
  45        if (bus8)
  46                info |= sdhci->width_mask;
  47        else
  48                info &= ~sdhci->width_mask;
  49        writel(info, sdc->regs + ASPEED_SDC_INFO);
  50        spin_unlock(&sdc->lock);
  51}
  52
  53static void aspeed_sdhci_set_clock(struct sdhci_host *host, unsigned int clock)
  54{
  55        struct sdhci_pltfm_host *pltfm_host;
  56        unsigned long parent;
  57        int div;
  58        u16 clk;
  59
  60        pltfm_host = sdhci_priv(host);
  61        parent = clk_get_rate(pltfm_host->clk);
  62
  63        sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
  64
  65        if (clock == 0)
  66                return;
  67
  68        if (WARN_ON(clock > host->max_clk))
  69                clock = host->max_clk;
  70
  71        for (div = 2; div < 256; div *= 2) {
  72                if ((parent / div) <= clock)
  73                        break;
  74        }
  75        div >>= 1;
  76
  77        clk = div << SDHCI_DIVIDER_SHIFT;
  78
  79        sdhci_enable_clk(host, clk);
  80}
  81
  82static unsigned int aspeed_sdhci_get_max_clock(struct sdhci_host *host)
  83{
  84        if (host->mmc->f_max)
  85                return host->mmc->f_max;
  86
  87        return sdhci_pltfm_clk_get_max_clock(host);
  88}
  89
  90static void aspeed_sdhci_set_bus_width(struct sdhci_host *host, int width)
  91{
  92        struct sdhci_pltfm_host *pltfm_priv;
  93        struct aspeed_sdhci *aspeed_sdhci;
  94        struct aspeed_sdc *aspeed_sdc;
  95        u8 ctrl;
  96
  97        pltfm_priv = sdhci_priv(host);
  98        aspeed_sdhci = sdhci_pltfm_priv(pltfm_priv);
  99        aspeed_sdc = aspeed_sdhci->parent;
 100
 101        /* Set/clear 8-bit mode */
 102        aspeed_sdc_configure_8bit_mode(aspeed_sdc, aspeed_sdhci,
 103                                       width == MMC_BUS_WIDTH_8);
 104
 105        /* Set/clear 1 or 4 bit mode */
 106        ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL);
 107        if (width == MMC_BUS_WIDTH_4)
 108                ctrl |= SDHCI_CTRL_4BITBUS;
 109        else
 110                ctrl &= ~SDHCI_CTRL_4BITBUS;
 111        sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL);
 112}
 113
 114static u32 aspeed_sdhci_readl(struct sdhci_host *host, int reg)
 115{
 116        u32 val = readl(host->ioaddr + reg);
 117
 118        if (unlikely(reg == SDHCI_PRESENT_STATE) &&
 119            (host->mmc->caps2 & MMC_CAP2_CD_ACTIVE_HIGH))
 120                val ^= SDHCI_CARD_PRESENT;
 121
 122        return val;
 123}
 124
 125static const struct sdhci_ops aspeed_sdhci_ops = {
 126        .read_l = aspeed_sdhci_readl,
 127        .set_clock = aspeed_sdhci_set_clock,
 128        .get_max_clock = aspeed_sdhci_get_max_clock,
 129        .set_bus_width = aspeed_sdhci_set_bus_width,
 130        .get_timeout_clock = sdhci_pltfm_clk_get_max_clock,
 131        .reset = sdhci_reset,
 132        .set_uhs_signaling = sdhci_set_uhs_signaling,
 133};
 134
 135static const struct sdhci_pltfm_data aspeed_sdhci_pdata = {
 136        .ops = &aspeed_sdhci_ops,
 137        .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
 138};
 139
 140static inline int aspeed_sdhci_calculate_slot(struct aspeed_sdhci *dev,
 141                                              struct resource *res)
 142{
 143        resource_size_t delta;
 144
 145        if (!res || resource_type(res) != IORESOURCE_MEM)
 146                return -EINVAL;
 147
 148        if (res->start < dev->parent->res->start)
 149                return -EINVAL;
 150
 151        delta = res->start - dev->parent->res->start;
 152        if (delta & (0x100 - 1))
 153                return -EINVAL;
 154
 155        return (delta / 0x100) - 1;
 156}
 157
 158static int aspeed_sdhci_probe(struct platform_device *pdev)
 159{
 160        struct sdhci_pltfm_host *pltfm_host;
 161        struct aspeed_sdhci *dev;
 162        struct sdhci_host *host;
 163        struct resource *res;
 164        int slot;
 165        int ret;
 166
 167        host = sdhci_pltfm_init(pdev, &aspeed_sdhci_pdata, sizeof(*dev));
 168        if (IS_ERR(host))
 169                return PTR_ERR(host);
 170
 171        pltfm_host = sdhci_priv(host);
 172        dev = sdhci_pltfm_priv(pltfm_host);
 173        dev->parent = dev_get_drvdata(pdev->dev.parent);
 174
 175        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 176        slot = aspeed_sdhci_calculate_slot(dev, res);
 177
 178        if (slot < 0)
 179                return slot;
 180        else if (slot >= 2)
 181                return -EINVAL;
 182
 183        dev_info(&pdev->dev, "Configuring for slot %d\n", slot);
 184        dev->width_mask = !slot ? ASPEED_SDC_S0MMC8 : ASPEED_SDC_S1MMC8;
 185
 186        sdhci_get_of_property(pdev);
 187
 188        pltfm_host->clk = devm_clk_get(&pdev->dev, NULL);
 189        if (IS_ERR(pltfm_host->clk))
 190                return PTR_ERR(pltfm_host->clk);
 191
 192        ret = clk_prepare_enable(pltfm_host->clk);
 193        if (ret) {
 194                dev_err(&pdev->dev, "Unable to enable SDIO clock\n");
 195                goto err_pltfm_free;
 196        }
 197
 198        ret = mmc_of_parse(host->mmc);
 199        if (ret)
 200                goto err_sdhci_add;
 201
 202        ret = sdhci_add_host(host);
 203        if (ret)
 204                goto err_sdhci_add;
 205
 206        return 0;
 207
 208err_sdhci_add:
 209        clk_disable_unprepare(pltfm_host->clk);
 210err_pltfm_free:
 211        sdhci_pltfm_free(pdev);
 212        return ret;
 213}
 214
 215static int aspeed_sdhci_remove(struct platform_device *pdev)
 216{
 217        struct sdhci_pltfm_host *pltfm_host;
 218        struct sdhci_host *host;
 219        int dead = 0;
 220
 221        host = platform_get_drvdata(pdev);
 222        pltfm_host = sdhci_priv(host);
 223
 224        sdhci_remove_host(host, dead);
 225
 226        clk_disable_unprepare(pltfm_host->clk);
 227
 228        sdhci_pltfm_free(pdev);
 229
 230        return 0;
 231}
 232
 233static const struct of_device_id aspeed_sdhci_of_match[] = {
 234        { .compatible = "aspeed,ast2400-sdhci", },
 235        { .compatible = "aspeed,ast2500-sdhci", },
 236        { .compatible = "aspeed,ast2600-sdhci", },
 237        { }
 238};
 239
 240static struct platform_driver aspeed_sdhci_driver = {
 241        .driver         = {
 242                .name   = "sdhci-aspeed",
 243                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
 244                .of_match_table = aspeed_sdhci_of_match,
 245        },
 246        .probe          = aspeed_sdhci_probe,
 247        .remove         = aspeed_sdhci_remove,
 248};
 249
 250static int aspeed_sdc_probe(struct platform_device *pdev)
 251
 252{
 253        struct device_node *parent, *child;
 254        struct aspeed_sdc *sdc;
 255        int ret;
 256
 257        sdc = devm_kzalloc(&pdev->dev, sizeof(*sdc), GFP_KERNEL);
 258        if (!sdc)
 259                return -ENOMEM;
 260
 261        spin_lock_init(&sdc->lock);
 262
 263        sdc->clk = devm_clk_get(&pdev->dev, NULL);
 264        if (IS_ERR(sdc->clk))
 265                return PTR_ERR(sdc->clk);
 266
 267        ret = clk_prepare_enable(sdc->clk);
 268        if (ret) {
 269                dev_err(&pdev->dev, "Unable to enable SDCLK\n");
 270                return ret;
 271        }
 272
 273        sdc->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 274        sdc->regs = devm_ioremap_resource(&pdev->dev, sdc->res);
 275        if (IS_ERR(sdc->regs)) {
 276                ret = PTR_ERR(sdc->regs);
 277                goto err_clk;
 278        }
 279
 280        dev_set_drvdata(&pdev->dev, sdc);
 281
 282        parent = pdev->dev.of_node;
 283        for_each_available_child_of_node(parent, child) {
 284                struct platform_device *cpdev;
 285
 286                cpdev = of_platform_device_create(child, NULL, &pdev->dev);
 287                if (!cpdev) {
 288                        of_node_put(child);
 289                        ret = -ENODEV;
 290                        goto err_clk;
 291                }
 292        }
 293
 294        return 0;
 295
 296err_clk:
 297        clk_disable_unprepare(sdc->clk);
 298        return ret;
 299}
 300
 301static int aspeed_sdc_remove(struct platform_device *pdev)
 302{
 303        struct aspeed_sdc *sdc = dev_get_drvdata(&pdev->dev);
 304
 305        clk_disable_unprepare(sdc->clk);
 306
 307        return 0;
 308}
 309
 310static const struct of_device_id aspeed_sdc_of_match[] = {
 311        { .compatible = "aspeed,ast2400-sd-controller", },
 312        { .compatible = "aspeed,ast2500-sd-controller", },
 313        { .compatible = "aspeed,ast2600-sd-controller", },
 314        { }
 315};
 316
 317MODULE_DEVICE_TABLE(of, aspeed_sdc_of_match);
 318
 319static struct platform_driver aspeed_sdc_driver = {
 320        .driver         = {
 321                .name   = "sd-controller-aspeed",
 322                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
 323                .pm     = &sdhci_pltfm_pmops,
 324                .of_match_table = aspeed_sdc_of_match,
 325        },
 326        .probe          = aspeed_sdc_probe,
 327        .remove         = aspeed_sdc_remove,
 328};
 329
 330static int __init aspeed_sdc_init(void)
 331{
 332        int rc;
 333
 334        rc = platform_driver_register(&aspeed_sdhci_driver);
 335        if (rc < 0)
 336                return rc;
 337
 338        rc = platform_driver_register(&aspeed_sdc_driver);
 339        if (rc < 0)
 340                platform_driver_unregister(&aspeed_sdhci_driver);
 341
 342        return rc;
 343}
 344module_init(aspeed_sdc_init);
 345
 346static void __exit aspeed_sdc_exit(void)
 347{
 348        platform_driver_unregister(&aspeed_sdc_driver);
 349        platform_driver_unregister(&aspeed_sdhci_driver);
 350}
 351module_exit(aspeed_sdc_exit);
 352
 353MODULE_DESCRIPTION("Driver for the ASPEED SD/SDIO/SDHCI Controllers");
 354MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>");
 355MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
 356MODULE_LICENSE("GPL");
 357