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 = 1; 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 const struct sdhci_ops aspeed_sdhci_ops = {
 115        .set_clock = aspeed_sdhci_set_clock,
 116        .get_max_clock = aspeed_sdhci_get_max_clock,
 117        .set_bus_width = aspeed_sdhci_set_bus_width,
 118        .get_timeout_clock = sdhci_pltfm_clk_get_max_clock,
 119        .reset = sdhci_reset,
 120        .set_uhs_signaling = sdhci_set_uhs_signaling,
 121};
 122
 123static const struct sdhci_pltfm_data aspeed_sdhci_pdata = {
 124        .ops = &aspeed_sdhci_ops,
 125        .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
 126};
 127
 128static inline int aspeed_sdhci_calculate_slot(struct aspeed_sdhci *dev,
 129                                              struct resource *res)
 130{
 131        resource_size_t delta;
 132
 133        if (!res || resource_type(res) != IORESOURCE_MEM)
 134                return -EINVAL;
 135
 136        if (res->start < dev->parent->res->start)
 137                return -EINVAL;
 138
 139        delta = res->start - dev->parent->res->start;
 140        if (delta & (0x100 - 1))
 141                return -EINVAL;
 142
 143        return (delta / 0x100) - 1;
 144}
 145
 146static int aspeed_sdhci_probe(struct platform_device *pdev)
 147{
 148        struct sdhci_pltfm_host *pltfm_host;
 149        struct aspeed_sdhci *dev;
 150        struct sdhci_host *host;
 151        struct resource *res;
 152        int slot;
 153        int ret;
 154
 155        host = sdhci_pltfm_init(pdev, &aspeed_sdhci_pdata, sizeof(*dev));
 156        if (IS_ERR(host))
 157                return PTR_ERR(host);
 158
 159        pltfm_host = sdhci_priv(host);
 160        dev = sdhci_pltfm_priv(pltfm_host);
 161        dev->parent = dev_get_drvdata(pdev->dev.parent);
 162
 163        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 164        slot = aspeed_sdhci_calculate_slot(dev, res);
 165
 166        if (slot < 0)
 167                return slot;
 168        else if (slot >= 2)
 169                return -EINVAL;
 170
 171        dev_info(&pdev->dev, "Configuring for slot %d\n", slot);
 172        dev->width_mask = !slot ? ASPEED_SDC_S0MMC8 : ASPEED_SDC_S1MMC8;
 173
 174        sdhci_get_of_property(pdev);
 175
 176        pltfm_host->clk = devm_clk_get(&pdev->dev, NULL);
 177        if (IS_ERR(pltfm_host->clk))
 178                return PTR_ERR(pltfm_host->clk);
 179
 180        ret = clk_prepare_enable(pltfm_host->clk);
 181        if (ret) {
 182                dev_err(&pdev->dev, "Unable to enable SDIO clock\n");
 183                goto err_pltfm_free;
 184        }
 185
 186        ret = mmc_of_parse(host->mmc);
 187        if (ret)
 188                goto err_sdhci_add;
 189
 190        ret = sdhci_add_host(host);
 191        if (ret)
 192                goto err_sdhci_add;
 193
 194        return 0;
 195
 196err_sdhci_add:
 197        clk_disable_unprepare(pltfm_host->clk);
 198err_pltfm_free:
 199        sdhci_pltfm_free(pdev);
 200        return ret;
 201}
 202
 203static int aspeed_sdhci_remove(struct platform_device *pdev)
 204{
 205        struct sdhci_pltfm_host *pltfm_host;
 206        struct sdhci_host *host;
 207        int dead = 0;
 208
 209        host = platform_get_drvdata(pdev);
 210        pltfm_host = sdhci_priv(host);
 211
 212        sdhci_remove_host(host, dead);
 213
 214        clk_disable_unprepare(pltfm_host->clk);
 215
 216        sdhci_pltfm_free(pdev);
 217
 218        return 0;
 219}
 220
 221static const struct of_device_id aspeed_sdhci_of_match[] = {
 222        { .compatible = "aspeed,ast2400-sdhci", },
 223        { .compatible = "aspeed,ast2500-sdhci", },
 224        { .compatible = "aspeed,ast2600-sdhci", },
 225        { }
 226};
 227
 228static struct platform_driver aspeed_sdhci_driver = {
 229        .driver         = {
 230                .name   = "sdhci-aspeed",
 231                .of_match_table = aspeed_sdhci_of_match,
 232        },
 233        .probe          = aspeed_sdhci_probe,
 234        .remove         = aspeed_sdhci_remove,
 235};
 236
 237static int aspeed_sdc_probe(struct platform_device *pdev)
 238
 239{
 240        struct device_node *parent, *child;
 241        struct aspeed_sdc *sdc;
 242        int ret;
 243
 244        sdc = devm_kzalloc(&pdev->dev, sizeof(*sdc), GFP_KERNEL);
 245        if (!sdc)
 246                return -ENOMEM;
 247
 248        spin_lock_init(&sdc->lock);
 249
 250        sdc->clk = devm_clk_get(&pdev->dev, NULL);
 251        if (IS_ERR(sdc->clk))
 252                return PTR_ERR(sdc->clk);
 253
 254        ret = clk_prepare_enable(sdc->clk);
 255        if (ret) {
 256                dev_err(&pdev->dev, "Unable to enable SDCLK\n");
 257                return ret;
 258        }
 259
 260        sdc->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 261        sdc->regs = devm_ioremap_resource(&pdev->dev, sdc->res);
 262        if (IS_ERR(sdc->regs)) {
 263                ret = PTR_ERR(sdc->regs);
 264                goto err_clk;
 265        }
 266
 267        dev_set_drvdata(&pdev->dev, sdc);
 268
 269        parent = pdev->dev.of_node;
 270        for_each_available_child_of_node(parent, child) {
 271                struct platform_device *cpdev;
 272
 273                cpdev = of_platform_device_create(child, NULL, &pdev->dev);
 274                if (!cpdev) {
 275                        of_node_put(child);
 276                        ret = -ENODEV;
 277                        goto err_clk;
 278                }
 279        }
 280
 281        return 0;
 282
 283err_clk:
 284        clk_disable_unprepare(sdc->clk);
 285        return ret;
 286}
 287
 288static int aspeed_sdc_remove(struct platform_device *pdev)
 289{
 290        struct aspeed_sdc *sdc = dev_get_drvdata(&pdev->dev);
 291
 292        clk_disable_unprepare(sdc->clk);
 293
 294        return 0;
 295}
 296
 297static const struct of_device_id aspeed_sdc_of_match[] = {
 298        { .compatible = "aspeed,ast2400-sd-controller", },
 299        { .compatible = "aspeed,ast2500-sd-controller", },
 300        { .compatible = "aspeed,ast2600-sd-controller", },
 301        { }
 302};
 303
 304MODULE_DEVICE_TABLE(of, aspeed_sdc_of_match);
 305
 306static struct platform_driver aspeed_sdc_driver = {
 307        .driver         = {
 308                .name   = "sd-controller-aspeed",
 309                .pm     = &sdhci_pltfm_pmops,
 310                .of_match_table = aspeed_sdc_of_match,
 311        },
 312        .probe          = aspeed_sdc_probe,
 313        .remove         = aspeed_sdc_remove,
 314};
 315
 316static int __init aspeed_sdc_init(void)
 317{
 318        int rc;
 319
 320        rc = platform_driver_register(&aspeed_sdhci_driver);
 321        if (rc < 0)
 322                return rc;
 323
 324        rc = platform_driver_register(&aspeed_sdc_driver);
 325        if (rc < 0)
 326                platform_driver_unregister(&aspeed_sdhci_driver);
 327
 328        return rc;
 329}
 330module_init(aspeed_sdc_init);
 331
 332static void __exit aspeed_sdc_exit(void)
 333{
 334        platform_driver_unregister(&aspeed_sdc_driver);
 335        platform_driver_unregister(&aspeed_sdhci_driver);
 336}
 337module_exit(aspeed_sdc_exit);
 338
 339MODULE_DESCRIPTION("Driver for the ASPEED SD/SDIO/SDHCI Controllers");
 340MODULE_AUTHOR("Ryan Chen <ryan_chen@aspeedtech.com>");
 341MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>");
 342MODULE_LICENSE("GPL");
 343