linux/drivers/mmc/host/sdhci-milbeaut.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (C) 2013 - 2015 Fujitsu Semiconductor, Ltd
   4 *              Vincent Yang <vincent.yang@tw.fujitsu.com>
   5 * Copyright (C) 2015 Linaro Ltd  Andy Green <andy.green@linaro.org>
   6 * Copyright (C) 2019 Socionext Inc.
   7 *              Takao Orito <orito.takao@socionext.com>
   8 */
   9
  10#include <linux/bits.h>
  11#include <linux/clk.h>
  12#include <linux/delay.h>
  13#include <linux/err.h>
  14#include <linux/gpio/consumer.h>
  15#include <linux/module.h>
  16#include <linux/of.h>
  17#include <linux/property.h>
  18
  19#include "sdhci-pltfm.h"
  20#include "sdhci_f_sdh30.h"
  21
  22/* milbeaut bridge controller register */
  23#define MLB_SOFT_RESET          0x0200
  24#define  MLB_SOFT_RESET_RSTX            BIT(0)
  25
  26#define MLB_WP_CD_LED_SET       0x0210
  27#define  MLB_WP_CD_LED_SET_LED_INV  BIT(2)
  28
  29#define MLB_CR_SET                      0x0220
  30#define  MLB_CR_SET_CR_TOCLKUNIT       BIT(24)
  31#define  MLB_CR_SET_CR_TOCLKFREQ_SFT   (16)
  32#define  MLB_CR_SET_CR_TOCLKFREQ_MASK  (0x3F << MLB_CR_SET_CR_TOCLKFREQ_SFT)
  33#define  MLB_CR_SET_CR_BCLKFREQ_SFT    (8)
  34#define  MLB_CR_SET_CR_BCLKFREQ_MASK   (0xFF << MLB_CR_SET_CR_BCLKFREQ_SFT)
  35#define  MLB_CR_SET_CR_RTUNTIMER_SFT   (4)
  36#define  MLB_CR_SET_CR_RTUNTIMER_MASK  (0xF << MLB_CR_SET_CR_RTUNTIMER_SFT)
  37
  38#define MLB_SD_TOCLK_I_DIV  16
  39#define MLB_TOCLKFREQ_UNIT_THRES    16000000
  40#define MLB_CAL_TOCLKFREQ_MHZ(rate) (rate / MLB_SD_TOCLK_I_DIV / 1000000)
  41#define MLB_CAL_TOCLKFREQ_KHZ(rate) (rate / MLB_SD_TOCLK_I_DIV / 1000)
  42#define MLB_TOCLKFREQ_MAX   63
  43#define MLB_TOCLKFREQ_MIN    1
  44
  45#define MLB_SD_BCLK_I_DIV   4
  46#define MLB_CAL_BCLKFREQ(rate)  (rate / MLB_SD_BCLK_I_DIV / 1000000)
  47#define MLB_BCLKFREQ_MAX        255
  48#define MLB_BCLKFREQ_MIN          1
  49
  50#define MLB_CDR_SET                     0x0230
  51#define MLB_CDR_SET_CLK2POW16   3
  52
  53struct f_sdhost_priv {
  54        struct clk *clk_iface;
  55        struct clk *clk;
  56        struct device *dev;
  57        bool enable_cmd_dat_delay;
  58};
  59
  60static void sdhci_milbeaut_soft_voltage_switch(struct sdhci_host *host)
  61{
  62        u32 ctrl = 0;
  63
  64        usleep_range(2500, 3000);
  65        ctrl = sdhci_readl(host, F_SDH30_IO_CONTROL2);
  66        ctrl |= F_SDH30_CRES_O_DN;
  67        sdhci_writel(host, ctrl, F_SDH30_IO_CONTROL2);
  68        ctrl |= F_SDH30_MSEL_O_1_8;
  69        sdhci_writel(host, ctrl, F_SDH30_IO_CONTROL2);
  70
  71        ctrl &= ~F_SDH30_CRES_O_DN;
  72        sdhci_writel(host, ctrl, F_SDH30_IO_CONTROL2);
  73        usleep_range(2500, 3000);
  74
  75        ctrl = sdhci_readl(host, F_SDH30_TUNING_SETTING);
  76        ctrl |= F_SDH30_CMD_CHK_DIS;
  77        sdhci_writel(host, ctrl, F_SDH30_TUNING_SETTING);
  78}
  79
  80static unsigned int sdhci_milbeaut_get_min_clock(struct sdhci_host *host)
  81{
  82        return F_SDH30_MIN_CLOCK;
  83}
  84
  85static void sdhci_milbeaut_reset(struct sdhci_host *host, u8 mask)
  86{
  87        struct f_sdhost_priv *priv = sdhci_priv(host);
  88        u16 clk;
  89        u32 ctl;
  90        ktime_t timeout;
  91
  92        clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
  93        clk = (clk & ~SDHCI_CLOCK_CARD_EN) | SDHCI_CLOCK_INT_EN;
  94        sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
  95
  96        sdhci_reset(host, mask);
  97
  98        clk |= SDHCI_CLOCK_CARD_EN;
  99        sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL);
 100
 101        timeout = ktime_add_ms(ktime_get(), 10);
 102        while (1) {
 103                bool timedout = ktime_after(ktime_get(), timeout);
 104
 105                clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
 106                if (clk & SDHCI_CLOCK_INT_STABLE)
 107                        break;
 108                if (timedout) {
 109                        pr_err("%s: Internal clock never stabilised.\n",
 110                                mmc_hostname(host->mmc));
 111                        sdhci_dumpregs(host);
 112                        return;
 113                }
 114                udelay(10);
 115        }
 116
 117        if (priv->enable_cmd_dat_delay) {
 118                ctl = sdhci_readl(host, F_SDH30_ESD_CONTROL);
 119                ctl |= F_SDH30_CMD_DAT_DELAY;
 120                sdhci_writel(host, ctl, F_SDH30_ESD_CONTROL);
 121        }
 122}
 123
 124static const struct sdhci_ops sdhci_milbeaut_ops = {
 125        .voltage_switch = sdhci_milbeaut_soft_voltage_switch,
 126        .get_min_clock = sdhci_milbeaut_get_min_clock,
 127        .reset = sdhci_milbeaut_reset,
 128        .set_clock = sdhci_set_clock,
 129        .set_bus_width = sdhci_set_bus_width,
 130        .set_uhs_signaling = sdhci_set_uhs_signaling,
 131        .set_power = sdhci_set_power_and_bus_voltage,
 132};
 133
 134static void sdhci_milbeaut_bridge_reset(struct sdhci_host *host,
 135                                                int reset_flag)
 136{
 137        if (reset_flag)
 138                sdhci_writel(host, 0, MLB_SOFT_RESET);
 139        else
 140                sdhci_writel(host, MLB_SOFT_RESET_RSTX, MLB_SOFT_RESET);
 141}
 142
 143static void sdhci_milbeaut_bridge_init(struct sdhci_host *host,
 144                                                int rate)
 145{
 146        u32 val, clk;
 147
 148        /* IO_SDIO_CR_SET should be set while reset */
 149        val = sdhci_readl(host, MLB_CR_SET);
 150        val &= ~(MLB_CR_SET_CR_TOCLKFREQ_MASK | MLB_CR_SET_CR_TOCLKUNIT |
 151                        MLB_CR_SET_CR_BCLKFREQ_MASK);
 152        if (rate >= MLB_TOCLKFREQ_UNIT_THRES) {
 153                clk = MLB_CAL_TOCLKFREQ_MHZ(rate);
 154                clk = min_t(u32, MLB_TOCLKFREQ_MAX, clk);
 155                val |= MLB_CR_SET_CR_TOCLKUNIT |
 156                        (clk << MLB_CR_SET_CR_TOCLKFREQ_SFT);
 157        } else {
 158                clk = MLB_CAL_TOCLKFREQ_KHZ(rate);
 159                clk = min_t(u32, MLB_TOCLKFREQ_MAX, clk);
 160                clk = max_t(u32, MLB_TOCLKFREQ_MIN, clk);
 161                val |= clk << MLB_CR_SET_CR_TOCLKFREQ_SFT;
 162        }
 163
 164        clk = MLB_CAL_BCLKFREQ(rate);
 165        clk = min_t(u32, MLB_BCLKFREQ_MAX, clk);
 166        clk = max_t(u32, MLB_BCLKFREQ_MIN, clk);
 167        val |=  clk << MLB_CR_SET_CR_BCLKFREQ_SFT;
 168        val &= ~MLB_CR_SET_CR_RTUNTIMER_MASK;
 169        sdhci_writel(host, val, MLB_CR_SET);
 170
 171        sdhci_writel(host, MLB_CDR_SET_CLK2POW16, MLB_CDR_SET);
 172
 173        sdhci_writel(host, MLB_WP_CD_LED_SET_LED_INV, MLB_WP_CD_LED_SET);
 174}
 175
 176static void sdhci_milbeaut_vendor_init(struct sdhci_host *host)
 177{
 178        struct f_sdhost_priv *priv = sdhci_priv(host);
 179        u32 ctl;
 180
 181        ctl = sdhci_readl(host, F_SDH30_IO_CONTROL2);
 182        ctl |= F_SDH30_CRES_O_DN;
 183        sdhci_writel(host, ctl, F_SDH30_IO_CONTROL2);
 184        ctl &= ~F_SDH30_MSEL_O_1_8;
 185        sdhci_writel(host, ctl, F_SDH30_IO_CONTROL2);
 186        ctl &= ~F_SDH30_CRES_O_DN;
 187        sdhci_writel(host, ctl, F_SDH30_IO_CONTROL2);
 188
 189        ctl = sdhci_readw(host, F_SDH30_AHB_CONFIG);
 190        ctl |= F_SDH30_SIN | F_SDH30_AHB_INCR_16 | F_SDH30_AHB_INCR_8 |
 191               F_SDH30_AHB_INCR_4;
 192        ctl &= ~(F_SDH30_AHB_BIGED | F_SDH30_BUSLOCK_EN);
 193        sdhci_writew(host, ctl, F_SDH30_AHB_CONFIG);
 194
 195        if (priv->enable_cmd_dat_delay) {
 196                ctl = sdhci_readl(host, F_SDH30_ESD_CONTROL);
 197                ctl |= F_SDH30_CMD_DAT_DELAY;
 198                sdhci_writel(host, ctl, F_SDH30_ESD_CONTROL);
 199        }
 200}
 201
 202static const struct of_device_id mlb_dt_ids[] = {
 203        {
 204                .compatible = "socionext,milbeaut-m10v-sdhci-3.0",
 205        },
 206        { /* sentinel */ }
 207};
 208MODULE_DEVICE_TABLE(of, mlb_dt_ids);
 209
 210static void sdhci_milbeaut_init(struct sdhci_host *host)
 211{
 212        struct f_sdhost_priv *priv = sdhci_priv(host);
 213        int rate = clk_get_rate(priv->clk);
 214        u16 ctl;
 215
 216        sdhci_milbeaut_bridge_reset(host, 0);
 217
 218        ctl = sdhci_readw(host, SDHCI_CLOCK_CONTROL);
 219        ctl &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
 220        sdhci_writew(host, ctl, SDHCI_CLOCK_CONTROL);
 221
 222        sdhci_milbeaut_bridge_reset(host, 1);
 223
 224        sdhci_milbeaut_bridge_init(host, rate);
 225        sdhci_milbeaut_bridge_reset(host, 0);
 226
 227        sdhci_milbeaut_vendor_init(host);
 228}
 229
 230static int sdhci_milbeaut_probe(struct platform_device *pdev)
 231{
 232        struct sdhci_host *host;
 233        struct device *dev = &pdev->dev;
 234        int irq, ret = 0;
 235        struct f_sdhost_priv *priv;
 236
 237        irq = platform_get_irq(pdev, 0);
 238        if (irq < 0)
 239                return irq;
 240
 241        host = sdhci_alloc_host(dev, sizeof(struct f_sdhost_priv));
 242        if (IS_ERR(host))
 243                return PTR_ERR(host);
 244
 245        priv = sdhci_priv(host);
 246        priv->dev = dev;
 247
 248        host->quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
 249                           SDHCI_QUIRK_INVERTED_WRITE_PROTECT |
 250                           SDHCI_QUIRK_CLOCK_BEFORE_RESET |
 251                           SDHCI_QUIRK_DELAY_AFTER_POWER;
 252        host->quirks2 = SDHCI_QUIRK2_SUPPORT_SINGLE |
 253                        SDHCI_QUIRK2_TUNING_WORK_AROUND |
 254                        SDHCI_QUIRK2_PRESET_VALUE_BROKEN;
 255
 256        priv->enable_cmd_dat_delay = device_property_read_bool(dev,
 257                                                "fujitsu,cmd-dat-delay-select");
 258
 259        ret = mmc_of_parse(host->mmc);
 260        if (ret)
 261                goto err;
 262
 263        platform_set_drvdata(pdev, host);
 264
 265        host->hw_name = "f_sdh30";
 266        host->ops = &sdhci_milbeaut_ops;
 267        host->irq = irq;
 268
 269        host->ioaddr = devm_platform_ioremap_resource(pdev, 0);
 270        if (IS_ERR(host->ioaddr)) {
 271                ret = PTR_ERR(host->ioaddr);
 272                goto err;
 273        }
 274
 275        if (dev_of_node(dev)) {
 276                sdhci_get_of_property(pdev);
 277
 278                priv->clk_iface = devm_clk_get(&pdev->dev, "iface");
 279                if (IS_ERR(priv->clk_iface)) {
 280                        ret = PTR_ERR(priv->clk_iface);
 281                        goto err;
 282                }
 283
 284                ret = clk_prepare_enable(priv->clk_iface);
 285                if (ret)
 286                        goto err;
 287
 288                priv->clk = devm_clk_get(&pdev->dev, "core");
 289                if (IS_ERR(priv->clk)) {
 290                        ret = PTR_ERR(priv->clk);
 291                        goto err_clk;
 292                }
 293
 294                ret = clk_prepare_enable(priv->clk);
 295                if (ret)
 296                        goto err_clk;
 297        }
 298
 299        sdhci_milbeaut_init(host);
 300
 301        ret = sdhci_add_host(host);
 302        if (ret)
 303                goto err_add_host;
 304
 305        return 0;
 306
 307err_add_host:
 308        clk_disable_unprepare(priv->clk);
 309err_clk:
 310        clk_disable_unprepare(priv->clk_iface);
 311err:
 312        sdhci_free_host(host);
 313        return ret;
 314}
 315
 316static int sdhci_milbeaut_remove(struct platform_device *pdev)
 317{
 318        struct sdhci_host *host = platform_get_drvdata(pdev);
 319        struct f_sdhost_priv *priv = sdhci_priv(host);
 320
 321        sdhci_remove_host(host, readl(host->ioaddr + SDHCI_INT_STATUS) ==
 322                          0xffffffff);
 323
 324        clk_disable_unprepare(priv->clk_iface);
 325        clk_disable_unprepare(priv->clk);
 326
 327        sdhci_free_host(host);
 328        platform_set_drvdata(pdev, NULL);
 329
 330        return 0;
 331}
 332
 333static struct platform_driver sdhci_milbeaut_driver = {
 334        .driver = {
 335                .name = "sdhci-milbeaut",
 336                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
 337                .of_match_table = of_match_ptr(mlb_dt_ids),
 338        },
 339        .probe  = sdhci_milbeaut_probe,
 340        .remove = sdhci_milbeaut_remove,
 341};
 342
 343module_platform_driver(sdhci_milbeaut_driver);
 344
 345MODULE_DESCRIPTION("MILBEAUT SD Card Controller driver");
 346MODULE_AUTHOR("Takao Orito <orito.takao@socionext.com>");
 347MODULE_LICENSE("GPL v2");
 348MODULE_ALIAS("platform:sdhci-milbeaut");
 349