1e3ec3a3dSSoren Brinkmann /* 2e3ec3a3dSSoren Brinkmann * Arasan Secure Digital Host Controller Interface. 3e3ec3a3dSSoren Brinkmann * Copyright (C) 2011 - 2012 Michal Simek <monstr@monstr.eu> 4e3ec3a3dSSoren Brinkmann * Copyright (c) 2012 Wind River Systems, Inc. 5e3ec3a3dSSoren Brinkmann * Copyright (C) 2013 Pengutronix e.K. 6e3ec3a3dSSoren Brinkmann * Copyright (C) 2013 Xilinx Inc. 7e3ec3a3dSSoren Brinkmann * 8e3ec3a3dSSoren Brinkmann * Based on sdhci-of-esdhc.c 9e3ec3a3dSSoren Brinkmann * 10e3ec3a3dSSoren Brinkmann * Copyright (c) 2007 Freescale Semiconductor, Inc. 11e3ec3a3dSSoren Brinkmann * Copyright (c) 2009 MontaVista Software, Inc. 12e3ec3a3dSSoren Brinkmann * 13e3ec3a3dSSoren Brinkmann * Authors: Xiaobo Xie <X.Xie@freescale.com> 14e3ec3a3dSSoren Brinkmann * Anton Vorontsov <avorontsov@ru.mvista.com> 15e3ec3a3dSSoren Brinkmann * 16e3ec3a3dSSoren Brinkmann * This program is free software; you can redistribute it and/or modify 17e3ec3a3dSSoren Brinkmann * it under the terms of the GNU General Public License as published by 18e3ec3a3dSSoren Brinkmann * the Free Software Foundation; either version 2 of the License, or (at 19e3ec3a3dSSoren Brinkmann * your option) any later version. 20e3ec3a3dSSoren Brinkmann */ 21e3ec3a3dSSoren Brinkmann 22c390f211SDouglas Anderson #include <linux/clk-provider.h> 233ea4666eSDouglas Anderson #include <linux/mfd/syscon.h> 24e3ec3a3dSSoren Brinkmann #include <linux/module.h> 25308f3f8dSSuman Tripathi #include <linux/of_device.h> 2691aa3661SShawn Lin #include <linux/phy/phy.h> 273ea4666eSDouglas Anderson #include <linux/regmap.h> 28e3ec3a3dSSoren Brinkmann #include "sdhci-pltfm.h" 29e3ec3a3dSSoren Brinkmann 30e3ec3a3dSSoren Brinkmann #define SDHCI_ARASAN_CLK_CTRL_OFFSET 0x2c 31a05c8465SShawn Lin #define SDHCI_ARASAN_VENDOR_REGISTER 0x78 32e3ec3a3dSSoren Brinkmann 33a05c8465SShawn Lin #define VENDOR_ENHANCED_STROBE BIT(0) 34e3ec3a3dSSoren Brinkmann #define CLK_CTRL_TIMEOUT_SHIFT 16 35e3ec3a3dSSoren Brinkmann #define CLK_CTRL_TIMEOUT_MASK (0xf << CLK_CTRL_TIMEOUT_SHIFT) 36e3ec3a3dSSoren Brinkmann #define CLK_CTRL_TIMEOUT_MIN_EXP 13 37e3ec3a3dSSoren Brinkmann 383ea4666eSDouglas Anderson /* 393ea4666eSDouglas Anderson * On some SoCs the syscon area has a feature where the upper 16-bits of 403ea4666eSDouglas Anderson * each 32-bit register act as a write mask for the lower 16-bits. This allows 413ea4666eSDouglas Anderson * atomic updates of the register without locking. This macro is used on SoCs 423ea4666eSDouglas Anderson * that have that feature. 433ea4666eSDouglas Anderson */ 443ea4666eSDouglas Anderson #define HIWORD_UPDATE(val, mask, shift) \ 453ea4666eSDouglas Anderson ((val) << (shift) | (mask) << ((shift) + 16)) 463ea4666eSDouglas Anderson 473ea4666eSDouglas Anderson /** 483ea4666eSDouglas Anderson * struct sdhci_arasan_soc_ctl_field - Field used in sdhci_arasan_soc_ctl_map 493ea4666eSDouglas Anderson * 503ea4666eSDouglas Anderson * @reg: Offset within the syscon of the register containing this field 513ea4666eSDouglas Anderson * @width: Number of bits for this field 523ea4666eSDouglas Anderson * @shift: Bit offset within @reg of this field (or -1 if not avail) 533ea4666eSDouglas Anderson */ 543ea4666eSDouglas Anderson struct sdhci_arasan_soc_ctl_field { 553ea4666eSDouglas Anderson u32 reg; 563ea4666eSDouglas Anderson u16 width; 573ea4666eSDouglas Anderson s16 shift; 583ea4666eSDouglas Anderson }; 593ea4666eSDouglas Anderson 603ea4666eSDouglas Anderson /** 613ea4666eSDouglas Anderson * struct sdhci_arasan_soc_ctl_map - Map in syscon to corecfg registers 623ea4666eSDouglas Anderson * 633ea4666eSDouglas Anderson * It's up to the licensee of the Arsan IP block to make these available 643ea4666eSDouglas Anderson * somewhere if needed. Presumably these will be scattered somewhere that's 653ea4666eSDouglas Anderson * accessible via the syscon API. 663ea4666eSDouglas Anderson * 673ea4666eSDouglas Anderson * @baseclkfreq: Where to find corecfg_baseclkfreq 683ea4666eSDouglas Anderson * @hiword_update: If true, use HIWORD_UPDATE to access the syscon 693ea4666eSDouglas Anderson */ 703ea4666eSDouglas Anderson struct sdhci_arasan_soc_ctl_map { 713ea4666eSDouglas Anderson struct sdhci_arasan_soc_ctl_field baseclkfreq; 723ea4666eSDouglas Anderson bool hiword_update; 733ea4666eSDouglas Anderson }; 743ea4666eSDouglas Anderson 75e3ec3a3dSSoren Brinkmann /** 76e3ec3a3dSSoren Brinkmann * struct sdhci_arasan_data 77c390f211SDouglas Anderson * @host: Pointer to the main SDHCI host structure. 78e3ec3a3dSSoren Brinkmann * @clk_ahb: Pointer to the AHB clock 7991aa3661SShawn Lin * @phy: Pointer to the generic phy 80c390f211SDouglas Anderson * @sdcardclk_hw: Struct for the clock we might provide to a PHY. 81c390f211SDouglas Anderson * @sdcardclk: Pointer to normal 'struct clock' for sdcardclk_hw. 823ea4666eSDouglas Anderson * @soc_ctl_base: Pointer to regmap for syscon for soc_ctl registers. 833ea4666eSDouglas Anderson * @soc_ctl_map: Map to get offsets into soc_ctl registers. 84e3ec3a3dSSoren Brinkmann */ 85e3ec3a3dSSoren Brinkmann struct sdhci_arasan_data { 86c390f211SDouglas Anderson struct sdhci_host *host; 87e3ec3a3dSSoren Brinkmann struct clk *clk_ahb; 8891aa3661SShawn Lin struct phy *phy; 893ea4666eSDouglas Anderson 90c390f211SDouglas Anderson struct clk_hw sdcardclk_hw; 91c390f211SDouglas Anderson struct clk *sdcardclk; 92c390f211SDouglas Anderson 933ea4666eSDouglas Anderson struct regmap *soc_ctl_base; 943ea4666eSDouglas Anderson const struct sdhci_arasan_soc_ctl_map *soc_ctl_map; 95e3ec3a3dSSoren Brinkmann }; 96e3ec3a3dSSoren Brinkmann 973ea4666eSDouglas Anderson static const struct sdhci_arasan_soc_ctl_map rk3399_soc_ctl_map = { 983ea4666eSDouglas Anderson .baseclkfreq = { .reg = 0xf000, .width = 8, .shift = 8 }, 993ea4666eSDouglas Anderson .hiword_update = true, 1003ea4666eSDouglas Anderson }; 1013ea4666eSDouglas Anderson 1023ea4666eSDouglas Anderson /** 1033ea4666eSDouglas Anderson * sdhci_arasan_syscon_write - Write to a field in soc_ctl registers 1043ea4666eSDouglas Anderson * 1053ea4666eSDouglas Anderson * This function allows writing to fields in sdhci_arasan_soc_ctl_map. 1063ea4666eSDouglas Anderson * Note that if a field is specified as not available (shift < 0) then 1073ea4666eSDouglas Anderson * this function will silently return an error code. It will be noisy 1083ea4666eSDouglas Anderson * and print errors for any other (unexpected) errors. 1093ea4666eSDouglas Anderson * 1103ea4666eSDouglas Anderson * @host: The sdhci_host 1113ea4666eSDouglas Anderson * @fld: The field to write to 1123ea4666eSDouglas Anderson * @val: The value to write 1133ea4666eSDouglas Anderson */ 1143ea4666eSDouglas Anderson static int sdhci_arasan_syscon_write(struct sdhci_host *host, 1153ea4666eSDouglas Anderson const struct sdhci_arasan_soc_ctl_field *fld, 1163ea4666eSDouglas Anderson u32 val) 1173ea4666eSDouglas Anderson { 1183ea4666eSDouglas Anderson struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 1193ea4666eSDouglas Anderson struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host); 1203ea4666eSDouglas Anderson struct regmap *soc_ctl_base = sdhci_arasan->soc_ctl_base; 1213ea4666eSDouglas Anderson u32 reg = fld->reg; 1223ea4666eSDouglas Anderson u16 width = fld->width; 1233ea4666eSDouglas Anderson s16 shift = fld->shift; 1243ea4666eSDouglas Anderson int ret; 1253ea4666eSDouglas Anderson 1263ea4666eSDouglas Anderson /* 1273ea4666eSDouglas Anderson * Silently return errors for shift < 0 so caller doesn't have 1283ea4666eSDouglas Anderson * to check for fields which are optional. For fields that 1293ea4666eSDouglas Anderson * are required then caller needs to do something special 1303ea4666eSDouglas Anderson * anyway. 1313ea4666eSDouglas Anderson */ 1323ea4666eSDouglas Anderson if (shift < 0) 1333ea4666eSDouglas Anderson return -EINVAL; 1343ea4666eSDouglas Anderson 1353ea4666eSDouglas Anderson if (sdhci_arasan->soc_ctl_map->hiword_update) 1363ea4666eSDouglas Anderson ret = regmap_write(soc_ctl_base, reg, 1373ea4666eSDouglas Anderson HIWORD_UPDATE(val, GENMASK(width, 0), 1383ea4666eSDouglas Anderson shift)); 1393ea4666eSDouglas Anderson else 1403ea4666eSDouglas Anderson ret = regmap_update_bits(soc_ctl_base, reg, 1413ea4666eSDouglas Anderson GENMASK(shift + width, shift), 1423ea4666eSDouglas Anderson val << shift); 1433ea4666eSDouglas Anderson 1443ea4666eSDouglas Anderson /* Yell about (unexpected) regmap errors */ 1453ea4666eSDouglas Anderson if (ret) 1463ea4666eSDouglas Anderson pr_warn("%s: Regmap write fail: %d\n", 1473ea4666eSDouglas Anderson mmc_hostname(host->mmc), ret); 1483ea4666eSDouglas Anderson 1493ea4666eSDouglas Anderson return ret; 1503ea4666eSDouglas Anderson } 1513ea4666eSDouglas Anderson 152e3ec3a3dSSoren Brinkmann static unsigned int sdhci_arasan_get_timeout_clock(struct sdhci_host *host) 153e3ec3a3dSSoren Brinkmann { 154e3ec3a3dSSoren Brinkmann u32 div; 155e3ec3a3dSSoren Brinkmann unsigned long freq; 156e3ec3a3dSSoren Brinkmann struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 157e3ec3a3dSSoren Brinkmann 158e3ec3a3dSSoren Brinkmann div = readl(host->ioaddr + SDHCI_ARASAN_CLK_CTRL_OFFSET); 159e3ec3a3dSSoren Brinkmann div = (div & CLK_CTRL_TIMEOUT_MASK) >> CLK_CTRL_TIMEOUT_SHIFT; 160e3ec3a3dSSoren Brinkmann 161e3ec3a3dSSoren Brinkmann freq = clk_get_rate(pltfm_host->clk); 162e3ec3a3dSSoren Brinkmann freq /= 1 << (CLK_CTRL_TIMEOUT_MIN_EXP + div); 163e3ec3a3dSSoren Brinkmann 164e3ec3a3dSSoren Brinkmann return freq; 165e3ec3a3dSSoren Brinkmann } 166e3ec3a3dSSoren Brinkmann 167802ac39aSShawn Lin static void sdhci_arasan_set_clock(struct sdhci_host *host, unsigned int clock) 168802ac39aSShawn Lin { 169802ac39aSShawn Lin struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 170802ac39aSShawn Lin struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host); 1716fc09244SDouglas Anderson bool ctrl_phy = false; 172802ac39aSShawn Lin 1736fc09244SDouglas Anderson if (clock > MMC_HIGH_52_MAX_DTR && (!IS_ERR(sdhci_arasan->phy))) 1746fc09244SDouglas Anderson ctrl_phy = true; 175802ac39aSShawn Lin 1766fc09244SDouglas Anderson if (ctrl_phy) { 177802ac39aSShawn Lin spin_unlock_irq(&host->lock); 178802ac39aSShawn Lin phy_power_off(sdhci_arasan->phy); 179802ac39aSShawn Lin spin_lock_irq(&host->lock); 180802ac39aSShawn Lin } 181802ac39aSShawn Lin 182802ac39aSShawn Lin sdhci_set_clock(host, clock); 183802ac39aSShawn Lin 1846fc09244SDouglas Anderson if (ctrl_phy) { 185802ac39aSShawn Lin spin_unlock_irq(&host->lock); 186802ac39aSShawn Lin phy_power_on(sdhci_arasan->phy); 187802ac39aSShawn Lin spin_lock_irq(&host->lock); 188802ac39aSShawn Lin } 189802ac39aSShawn Lin } 190802ac39aSShawn Lin 191a05c8465SShawn Lin static void sdhci_arasan_hs400_enhanced_strobe(struct mmc_host *mmc, 192a05c8465SShawn Lin struct mmc_ios *ios) 193a05c8465SShawn Lin { 194a05c8465SShawn Lin u32 vendor; 195a05c8465SShawn Lin struct sdhci_host *host = mmc_priv(mmc); 196a05c8465SShawn Lin 197a05c8465SShawn Lin vendor = readl(host->ioaddr + SDHCI_ARASAN_VENDOR_REGISTER); 198a05c8465SShawn Lin if (ios->enhanced_strobe) 199a05c8465SShawn Lin vendor |= VENDOR_ENHANCED_STROBE; 200a05c8465SShawn Lin else 201a05c8465SShawn Lin vendor &= ~VENDOR_ENHANCED_STROBE; 202a05c8465SShawn Lin 203a05c8465SShawn Lin writel(vendor, host->ioaddr + SDHCI_ARASAN_VENDOR_REGISTER); 204a05c8465SShawn Lin } 205a05c8465SShawn Lin 206e3ec3a3dSSoren Brinkmann static struct sdhci_ops sdhci_arasan_ops = { 207802ac39aSShawn Lin .set_clock = sdhci_arasan_set_clock, 208e3ec3a3dSSoren Brinkmann .get_max_clock = sdhci_pltfm_clk_get_max_clock, 209e3ec3a3dSSoren Brinkmann .get_timeout_clock = sdhci_arasan_get_timeout_clock, 2102317f56cSRussell King .set_bus_width = sdhci_set_bus_width, 21103231f9bSRussell King .reset = sdhci_reset, 21296d7b78cSRussell King .set_uhs_signaling = sdhci_set_uhs_signaling, 213e3ec3a3dSSoren Brinkmann }; 214e3ec3a3dSSoren Brinkmann 215e3ec3a3dSSoren Brinkmann static struct sdhci_pltfm_data sdhci_arasan_pdata = { 216e3ec3a3dSSoren Brinkmann .ops = &sdhci_arasan_ops, 2172d532d45SSuneel Garapati .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, 2182d532d45SSuneel Garapati .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | 2192d532d45SSuneel Garapati SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, 220e3ec3a3dSSoren Brinkmann }; 221e3ec3a3dSSoren Brinkmann 222e3ec3a3dSSoren Brinkmann #ifdef CONFIG_PM_SLEEP 223e3ec3a3dSSoren Brinkmann /** 224e3ec3a3dSSoren Brinkmann * sdhci_arasan_suspend - Suspend method for the driver 225e3ec3a3dSSoren Brinkmann * @dev: Address of the device structure 226e3ec3a3dSSoren Brinkmann * Returns 0 on success and error value on error 227e3ec3a3dSSoren Brinkmann * 228e3ec3a3dSSoren Brinkmann * Put the device in a low power state. 229e3ec3a3dSSoren Brinkmann */ 230e3ec3a3dSSoren Brinkmann static int sdhci_arasan_suspend(struct device *dev) 231e3ec3a3dSSoren Brinkmann { 232e3ec3a3dSSoren Brinkmann struct platform_device *pdev = to_platform_device(dev); 233e3ec3a3dSSoren Brinkmann struct sdhci_host *host = platform_get_drvdata(pdev); 234e3ec3a3dSSoren Brinkmann struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 23589211418SJisheng Zhang struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host); 236e3ec3a3dSSoren Brinkmann int ret; 237e3ec3a3dSSoren Brinkmann 238e3ec3a3dSSoren Brinkmann ret = sdhci_suspend_host(host); 239e3ec3a3dSSoren Brinkmann if (ret) 240e3ec3a3dSSoren Brinkmann return ret; 241e3ec3a3dSSoren Brinkmann 24291aa3661SShawn Lin if (!IS_ERR(sdhci_arasan->phy)) { 24391aa3661SShawn Lin ret = phy_power_off(sdhci_arasan->phy); 24491aa3661SShawn Lin if (ret) { 24591aa3661SShawn Lin dev_err(dev, "Cannot power off phy.\n"); 24691aa3661SShawn Lin sdhci_resume_host(host); 24791aa3661SShawn Lin return ret; 24891aa3661SShawn Lin } 24991aa3661SShawn Lin } 25091aa3661SShawn Lin 251e3ec3a3dSSoren Brinkmann clk_disable(pltfm_host->clk); 252e3ec3a3dSSoren Brinkmann clk_disable(sdhci_arasan->clk_ahb); 253e3ec3a3dSSoren Brinkmann 254e3ec3a3dSSoren Brinkmann return 0; 255e3ec3a3dSSoren Brinkmann } 256e3ec3a3dSSoren Brinkmann 257e3ec3a3dSSoren Brinkmann /** 258e3ec3a3dSSoren Brinkmann * sdhci_arasan_resume - Resume method for the driver 259e3ec3a3dSSoren Brinkmann * @dev: Address of the device structure 260e3ec3a3dSSoren Brinkmann * Returns 0 on success and error value on error 261e3ec3a3dSSoren Brinkmann * 262e3ec3a3dSSoren Brinkmann * Resume operation after suspend 263e3ec3a3dSSoren Brinkmann */ 264e3ec3a3dSSoren Brinkmann static int sdhci_arasan_resume(struct device *dev) 265e3ec3a3dSSoren Brinkmann { 266e3ec3a3dSSoren Brinkmann struct platform_device *pdev = to_platform_device(dev); 267e3ec3a3dSSoren Brinkmann struct sdhci_host *host = platform_get_drvdata(pdev); 268e3ec3a3dSSoren Brinkmann struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 26989211418SJisheng Zhang struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host); 270e3ec3a3dSSoren Brinkmann int ret; 271e3ec3a3dSSoren Brinkmann 272e3ec3a3dSSoren Brinkmann ret = clk_enable(sdhci_arasan->clk_ahb); 273e3ec3a3dSSoren Brinkmann if (ret) { 274e3ec3a3dSSoren Brinkmann dev_err(dev, "Cannot enable AHB clock.\n"); 275e3ec3a3dSSoren Brinkmann return ret; 276e3ec3a3dSSoren Brinkmann } 277e3ec3a3dSSoren Brinkmann 278e3ec3a3dSSoren Brinkmann ret = clk_enable(pltfm_host->clk); 279e3ec3a3dSSoren Brinkmann if (ret) { 280e3ec3a3dSSoren Brinkmann dev_err(dev, "Cannot enable SD clock.\n"); 281e3ec3a3dSSoren Brinkmann return ret; 282e3ec3a3dSSoren Brinkmann } 283e3ec3a3dSSoren Brinkmann 28491aa3661SShawn Lin if (!IS_ERR(sdhci_arasan->phy)) { 28591aa3661SShawn Lin ret = phy_power_on(sdhci_arasan->phy); 28691aa3661SShawn Lin if (ret) { 28791aa3661SShawn Lin dev_err(dev, "Cannot power on phy.\n"); 28891aa3661SShawn Lin return ret; 28991aa3661SShawn Lin } 29091aa3661SShawn Lin } 29191aa3661SShawn Lin 292e3ec3a3dSSoren Brinkmann return sdhci_resume_host(host); 293e3ec3a3dSSoren Brinkmann } 294e3ec3a3dSSoren Brinkmann #endif /* ! CONFIG_PM_SLEEP */ 295e3ec3a3dSSoren Brinkmann 296e3ec3a3dSSoren Brinkmann static SIMPLE_DEV_PM_OPS(sdhci_arasan_dev_pm_ops, sdhci_arasan_suspend, 297e3ec3a3dSSoren Brinkmann sdhci_arasan_resume); 298e3ec3a3dSSoren Brinkmann 2993ea4666eSDouglas Anderson static const struct of_device_id sdhci_arasan_of_match[] = { 3003ea4666eSDouglas Anderson /* SoC-specific compatible strings w/ soc_ctl_map */ 3013ea4666eSDouglas Anderson { 3023ea4666eSDouglas Anderson .compatible = "rockchip,rk3399-sdhci-5.1", 3033ea4666eSDouglas Anderson .data = &rk3399_soc_ctl_map, 3043ea4666eSDouglas Anderson }, 3053ea4666eSDouglas Anderson 3063ea4666eSDouglas Anderson /* Generic compatible below here */ 3073ea4666eSDouglas Anderson { .compatible = "arasan,sdhci-8.9a" }, 3083ea4666eSDouglas Anderson { .compatible = "arasan,sdhci-5.1" }, 3093ea4666eSDouglas Anderson { .compatible = "arasan,sdhci-4.9a" }, 3103ea4666eSDouglas Anderson 3113ea4666eSDouglas Anderson { /* sentinel */ } 3123ea4666eSDouglas Anderson }; 3133ea4666eSDouglas Anderson MODULE_DEVICE_TABLE(of, sdhci_arasan_of_match); 3143ea4666eSDouglas Anderson 3153ea4666eSDouglas Anderson /** 316c390f211SDouglas Anderson * sdhci_arasan_sdcardclk_recalc_rate - Return the card clock rate 317c390f211SDouglas Anderson * 318c390f211SDouglas Anderson * Return the current actual rate of the SD card clock. This can be used 319c390f211SDouglas Anderson * to communicate with out PHY. 320c390f211SDouglas Anderson * 321c390f211SDouglas Anderson * @hw: Pointer to the hardware clock structure. 322c390f211SDouglas Anderson * @parent_rate The parent rate (should be rate of clk_xin). 323c390f211SDouglas Anderson * Returns the card clock rate. 324c390f211SDouglas Anderson */ 325c390f211SDouglas Anderson static unsigned long sdhci_arasan_sdcardclk_recalc_rate(struct clk_hw *hw, 326c390f211SDouglas Anderson unsigned long parent_rate) 327c390f211SDouglas Anderson 328c390f211SDouglas Anderson { 329c390f211SDouglas Anderson struct sdhci_arasan_data *sdhci_arasan = 330c390f211SDouglas Anderson container_of(hw, struct sdhci_arasan_data, sdcardclk_hw); 331c390f211SDouglas Anderson struct sdhci_host *host = sdhci_arasan->host; 332c390f211SDouglas Anderson 333c390f211SDouglas Anderson return host->mmc->actual_clock; 334c390f211SDouglas Anderson } 335c390f211SDouglas Anderson 336c390f211SDouglas Anderson static const struct clk_ops arasan_sdcardclk_ops = { 337c390f211SDouglas Anderson .recalc_rate = sdhci_arasan_sdcardclk_recalc_rate, 338c390f211SDouglas Anderson }; 339c390f211SDouglas Anderson 340c390f211SDouglas Anderson /** 3413ea4666eSDouglas Anderson * sdhci_arasan_update_baseclkfreq - Set corecfg_baseclkfreq 3423ea4666eSDouglas Anderson * 3433ea4666eSDouglas Anderson * The corecfg_baseclkfreq is supposed to contain the MHz of clk_xin. This 3443ea4666eSDouglas Anderson * function can be used to make that happen. 3453ea4666eSDouglas Anderson * 3463ea4666eSDouglas Anderson * NOTES: 3473ea4666eSDouglas Anderson * - Many existing devices don't seem to do this and work fine. To keep 3483ea4666eSDouglas Anderson * compatibility for old hardware where the device tree doesn't provide a 3493ea4666eSDouglas Anderson * register map, this function is a noop if a soc_ctl_map hasn't been provided 3503ea4666eSDouglas Anderson * for this platform. 3513ea4666eSDouglas Anderson * - It's assumed that clk_xin is not dynamic and that we use the SDHCI divider 3523ea4666eSDouglas Anderson * to achieve lower clock rates. That means that this function is called once 3533ea4666eSDouglas Anderson * at probe time and never called again. 3543ea4666eSDouglas Anderson * 3553ea4666eSDouglas Anderson * @host: The sdhci_host 3563ea4666eSDouglas Anderson */ 3573ea4666eSDouglas Anderson static void sdhci_arasan_update_baseclkfreq(struct sdhci_host *host) 3583ea4666eSDouglas Anderson { 3593ea4666eSDouglas Anderson struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 3603ea4666eSDouglas Anderson struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host); 3613ea4666eSDouglas Anderson const struct sdhci_arasan_soc_ctl_map *soc_ctl_map = 3623ea4666eSDouglas Anderson sdhci_arasan->soc_ctl_map; 3633ea4666eSDouglas Anderson u32 mhz = DIV_ROUND_CLOSEST(clk_get_rate(pltfm_host->clk), 1000000); 3643ea4666eSDouglas Anderson 3653ea4666eSDouglas Anderson /* Having a map is optional */ 3663ea4666eSDouglas Anderson if (!soc_ctl_map) 3673ea4666eSDouglas Anderson return; 3683ea4666eSDouglas Anderson 3693ea4666eSDouglas Anderson /* If we have a map, we expect to have a syscon */ 3703ea4666eSDouglas Anderson if (!sdhci_arasan->soc_ctl_base) { 3713ea4666eSDouglas Anderson pr_warn("%s: Have regmap, but no soc-ctl-syscon\n", 3723ea4666eSDouglas Anderson mmc_hostname(host->mmc)); 3733ea4666eSDouglas Anderson return; 3743ea4666eSDouglas Anderson } 3753ea4666eSDouglas Anderson 3763ea4666eSDouglas Anderson sdhci_arasan_syscon_write(host, &soc_ctl_map->baseclkfreq, mhz); 3773ea4666eSDouglas Anderson } 3783ea4666eSDouglas Anderson 379c390f211SDouglas Anderson /** 380c390f211SDouglas Anderson * sdhci_arasan_register_sdclk - Register the sdclk for a PHY to use 381c390f211SDouglas Anderson * 382c390f211SDouglas Anderson * Some PHY devices need to know what the actual card clock is. In order for 383c390f211SDouglas Anderson * them to find out, we'll provide a clock through the common clock framework 384c390f211SDouglas Anderson * for them to query. 385c390f211SDouglas Anderson * 386c390f211SDouglas Anderson * Note: without seriously re-architecting SDHCI's clock code and testing on 387c390f211SDouglas Anderson * all platforms, there's no way to create a totally beautiful clock here 388c390f211SDouglas Anderson * with all clock ops implemented. Instead, we'll just create a clock that can 389c390f211SDouglas Anderson * be queried and set the CLK_GET_RATE_NOCACHE attribute to tell common clock 390c390f211SDouglas Anderson * framework that we're doing things behind its back. This should be sufficient 391c390f211SDouglas Anderson * to create nice clean device tree bindings and later (if needed) we can try 392c390f211SDouglas Anderson * re-architecting SDHCI if we see some benefit to it. 393c390f211SDouglas Anderson * 394c390f211SDouglas Anderson * @sdhci_arasan: Our private data structure. 395c390f211SDouglas Anderson * @clk_xin: Pointer to the functional clock 396c390f211SDouglas Anderson * @dev: Pointer to our struct device. 397c390f211SDouglas Anderson * Returns 0 on success and error value on error 398c390f211SDouglas Anderson */ 399c390f211SDouglas Anderson static int sdhci_arasan_register_sdclk(struct sdhci_arasan_data *sdhci_arasan, 400c390f211SDouglas Anderson struct clk *clk_xin, 401c390f211SDouglas Anderson struct device *dev) 402c390f211SDouglas Anderson { 403c390f211SDouglas Anderson struct device_node *np = dev->of_node; 404c390f211SDouglas Anderson struct clk_init_data sdcardclk_init; 405c390f211SDouglas Anderson const char *parent_clk_name; 406c390f211SDouglas Anderson int ret; 407c390f211SDouglas Anderson 408c390f211SDouglas Anderson /* Providing a clock to the PHY is optional; no error if missing */ 409c390f211SDouglas Anderson if (!of_find_property(np, "#clock-cells", NULL)) 410c390f211SDouglas Anderson return 0; 411c390f211SDouglas Anderson 412c390f211SDouglas Anderson ret = of_property_read_string_index(np, "clock-output-names", 0, 413c390f211SDouglas Anderson &sdcardclk_init.name); 414c390f211SDouglas Anderson if (ret) { 415c390f211SDouglas Anderson dev_err(dev, "DT has #clock-cells but no clock-output-names\n"); 416c390f211SDouglas Anderson return ret; 417c390f211SDouglas Anderson } 418c390f211SDouglas Anderson 419c390f211SDouglas Anderson parent_clk_name = __clk_get_name(clk_xin); 420c390f211SDouglas Anderson sdcardclk_init.parent_names = &parent_clk_name; 421c390f211SDouglas Anderson sdcardclk_init.num_parents = 1; 422c390f211SDouglas Anderson sdcardclk_init.flags = CLK_GET_RATE_NOCACHE; 423c390f211SDouglas Anderson sdcardclk_init.ops = &arasan_sdcardclk_ops; 424c390f211SDouglas Anderson 425c390f211SDouglas Anderson sdhci_arasan->sdcardclk_hw.init = &sdcardclk_init; 426c390f211SDouglas Anderson sdhci_arasan->sdcardclk = 427c390f211SDouglas Anderson devm_clk_register(dev, &sdhci_arasan->sdcardclk_hw); 428c390f211SDouglas Anderson sdhci_arasan->sdcardclk_hw.init = NULL; 429c390f211SDouglas Anderson 430c390f211SDouglas Anderson ret = of_clk_add_provider(np, of_clk_src_simple_get, 431c390f211SDouglas Anderson sdhci_arasan->sdcardclk); 432c390f211SDouglas Anderson if (ret) 433c390f211SDouglas Anderson dev_err(dev, "Failed to add clock provider\n"); 434c390f211SDouglas Anderson 435c390f211SDouglas Anderson return ret; 436c390f211SDouglas Anderson } 437c390f211SDouglas Anderson 438c390f211SDouglas Anderson /** 439c390f211SDouglas Anderson * sdhci_arasan_unregister_sdclk - Undoes sdhci_arasan_register_sdclk() 440c390f211SDouglas Anderson * 441c390f211SDouglas Anderson * Should be called any time we're exiting and sdhci_arasan_register_sdclk() 442c390f211SDouglas Anderson * returned success. 443c390f211SDouglas Anderson * 444c390f211SDouglas Anderson * @dev: Pointer to our struct device. 445c390f211SDouglas Anderson */ 446c390f211SDouglas Anderson static void sdhci_arasan_unregister_sdclk(struct device *dev) 447c390f211SDouglas Anderson { 448c390f211SDouglas Anderson struct device_node *np = dev->of_node; 449c390f211SDouglas Anderson 450c390f211SDouglas Anderson if (!of_find_property(np, "#clock-cells", NULL)) 451c390f211SDouglas Anderson return; 452c390f211SDouglas Anderson 453c390f211SDouglas Anderson of_clk_del_provider(dev->of_node); 454c390f211SDouglas Anderson } 455c390f211SDouglas Anderson 456e3ec3a3dSSoren Brinkmann static int sdhci_arasan_probe(struct platform_device *pdev) 457e3ec3a3dSSoren Brinkmann { 458e3ec3a3dSSoren Brinkmann int ret; 4593ea4666eSDouglas Anderson const struct of_device_id *match; 4603ea4666eSDouglas Anderson struct device_node *node; 461e3ec3a3dSSoren Brinkmann struct clk *clk_xin; 462e3ec3a3dSSoren Brinkmann struct sdhci_host *host; 463e3ec3a3dSSoren Brinkmann struct sdhci_pltfm_host *pltfm_host; 464e3ec3a3dSSoren Brinkmann struct sdhci_arasan_data *sdhci_arasan; 465e3ec3a3dSSoren Brinkmann 46689211418SJisheng Zhang host = sdhci_pltfm_init(pdev, &sdhci_arasan_pdata, 46789211418SJisheng Zhang sizeof(*sdhci_arasan)); 46889211418SJisheng Zhang if (IS_ERR(host)) 46989211418SJisheng Zhang return PTR_ERR(host); 47089211418SJisheng Zhang 47189211418SJisheng Zhang pltfm_host = sdhci_priv(host); 47289211418SJisheng Zhang sdhci_arasan = sdhci_pltfm_priv(pltfm_host); 473c390f211SDouglas Anderson sdhci_arasan->host = host; 474e3ec3a3dSSoren Brinkmann 4753ea4666eSDouglas Anderson match = of_match_node(sdhci_arasan_of_match, pdev->dev.of_node); 4763ea4666eSDouglas Anderson sdhci_arasan->soc_ctl_map = match->data; 4773ea4666eSDouglas Anderson 4783ea4666eSDouglas Anderson node = of_parse_phandle(pdev->dev.of_node, "arasan,soc-ctl-syscon", 0); 4793ea4666eSDouglas Anderson if (node) { 4803ea4666eSDouglas Anderson sdhci_arasan->soc_ctl_base = syscon_node_to_regmap(node); 4813ea4666eSDouglas Anderson of_node_put(node); 4823ea4666eSDouglas Anderson 4833ea4666eSDouglas Anderson if (IS_ERR(sdhci_arasan->soc_ctl_base)) { 4843ea4666eSDouglas Anderson ret = PTR_ERR(sdhci_arasan->soc_ctl_base); 4853ea4666eSDouglas Anderson if (ret != -EPROBE_DEFER) 4863ea4666eSDouglas Anderson dev_err(&pdev->dev, "Can't get syscon: %d\n", 4873ea4666eSDouglas Anderson ret); 4883ea4666eSDouglas Anderson goto err_pltfm_free; 4893ea4666eSDouglas Anderson } 4903ea4666eSDouglas Anderson } 4913ea4666eSDouglas Anderson 492e3ec3a3dSSoren Brinkmann sdhci_arasan->clk_ahb = devm_clk_get(&pdev->dev, "clk_ahb"); 493e3ec3a3dSSoren Brinkmann if (IS_ERR(sdhci_arasan->clk_ahb)) { 494e3ec3a3dSSoren Brinkmann dev_err(&pdev->dev, "clk_ahb clock not found.\n"); 495278d0962SShawn Lin ret = PTR_ERR(sdhci_arasan->clk_ahb); 496278d0962SShawn Lin goto err_pltfm_free; 497e3ec3a3dSSoren Brinkmann } 498e3ec3a3dSSoren Brinkmann 499e3ec3a3dSSoren Brinkmann clk_xin = devm_clk_get(&pdev->dev, "clk_xin"); 500e3ec3a3dSSoren Brinkmann if (IS_ERR(clk_xin)) { 501e3ec3a3dSSoren Brinkmann dev_err(&pdev->dev, "clk_xin clock not found.\n"); 502278d0962SShawn Lin ret = PTR_ERR(clk_xin); 503278d0962SShawn Lin goto err_pltfm_free; 504e3ec3a3dSSoren Brinkmann } 505e3ec3a3dSSoren Brinkmann 506e3ec3a3dSSoren Brinkmann ret = clk_prepare_enable(sdhci_arasan->clk_ahb); 507e3ec3a3dSSoren Brinkmann if (ret) { 508e3ec3a3dSSoren Brinkmann dev_err(&pdev->dev, "Unable to enable AHB clock.\n"); 509278d0962SShawn Lin goto err_pltfm_free; 510e3ec3a3dSSoren Brinkmann } 511e3ec3a3dSSoren Brinkmann 512e3ec3a3dSSoren Brinkmann ret = clk_prepare_enable(clk_xin); 513e3ec3a3dSSoren Brinkmann if (ret) { 514e3ec3a3dSSoren Brinkmann dev_err(&pdev->dev, "Unable to enable SD clock.\n"); 515e3ec3a3dSSoren Brinkmann goto clk_dis_ahb; 516e3ec3a3dSSoren Brinkmann } 517e3ec3a3dSSoren Brinkmann 518e3ec3a3dSSoren Brinkmann sdhci_get_of_property(pdev); 519e3ec3a3dSSoren Brinkmann pltfm_host->clk = clk_xin; 520e3ec3a3dSSoren Brinkmann 5213ea4666eSDouglas Anderson sdhci_arasan_update_baseclkfreq(host); 5223ea4666eSDouglas Anderson 523c390f211SDouglas Anderson ret = sdhci_arasan_register_sdclk(sdhci_arasan, clk_xin, &pdev->dev); 524c390f211SDouglas Anderson if (ret) 525c390f211SDouglas Anderson goto clk_disable_all; 526c390f211SDouglas Anderson 52716b23787SMichal Simek ret = mmc_of_parse(host->mmc); 52816b23787SMichal Simek if (ret) { 52916b23787SMichal Simek dev_err(&pdev->dev, "parsing dt failed (%u)\n", ret); 530c390f211SDouglas Anderson goto unreg_clk; 53116b23787SMichal Simek } 53216b23787SMichal Simek 53391aa3661SShawn Lin sdhci_arasan->phy = ERR_PTR(-ENODEV); 53491aa3661SShawn Lin if (of_device_is_compatible(pdev->dev.of_node, 53591aa3661SShawn Lin "arasan,sdhci-5.1")) { 53691aa3661SShawn Lin sdhci_arasan->phy = devm_phy_get(&pdev->dev, 53791aa3661SShawn Lin "phy_arasan"); 53891aa3661SShawn Lin if (IS_ERR(sdhci_arasan->phy)) { 53991aa3661SShawn Lin ret = PTR_ERR(sdhci_arasan->phy); 54091aa3661SShawn Lin dev_err(&pdev->dev, "No phy for arasan,sdhci-5.1.\n"); 541c390f211SDouglas Anderson goto unreg_clk; 54291aa3661SShawn Lin } 54391aa3661SShawn Lin 54491aa3661SShawn Lin ret = phy_init(sdhci_arasan->phy); 54591aa3661SShawn Lin if (ret < 0) { 54691aa3661SShawn Lin dev_err(&pdev->dev, "phy_init err.\n"); 547c390f211SDouglas Anderson goto unreg_clk; 54891aa3661SShawn Lin } 54991aa3661SShawn Lin 5506fc09244SDouglas Anderson ret = phy_power_on(sdhci_arasan->phy); 5516fc09244SDouglas Anderson if (ret < 0) { 5526fc09244SDouglas Anderson dev_err(&pdev->dev, "phy_power_on err.\n"); 5536fc09244SDouglas Anderson goto err_phy_power; 5546fc09244SDouglas Anderson } 5556fc09244SDouglas Anderson 556a05c8465SShawn Lin host->mmc_host_ops.hs400_enhanced_strobe = 557a05c8465SShawn Lin sdhci_arasan_hs400_enhanced_strobe; 55891aa3661SShawn Lin } 55991aa3661SShawn Lin 560e3ec3a3dSSoren Brinkmann ret = sdhci_add_host(host); 561b1df9de7SMike Looijmans if (ret) 56291aa3661SShawn Lin goto err_add_host; 563e3ec3a3dSSoren Brinkmann 564e3ec3a3dSSoren Brinkmann return 0; 565e3ec3a3dSSoren Brinkmann 56691aa3661SShawn Lin err_add_host: 56791aa3661SShawn Lin if (!IS_ERR(sdhci_arasan->phy)) 5686fc09244SDouglas Anderson phy_power_off(sdhci_arasan->phy); 5696fc09244SDouglas Anderson err_phy_power: 5706fc09244SDouglas Anderson if (!IS_ERR(sdhci_arasan->phy)) 57191aa3661SShawn Lin phy_exit(sdhci_arasan->phy); 572c390f211SDouglas Anderson unreg_clk: 573c390f211SDouglas Anderson sdhci_arasan_unregister_sdclk(&pdev->dev); 574e3ec3a3dSSoren Brinkmann clk_disable_all: 575e3ec3a3dSSoren Brinkmann clk_disable_unprepare(clk_xin); 576e3ec3a3dSSoren Brinkmann clk_dis_ahb: 577e3ec3a3dSSoren Brinkmann clk_disable_unprepare(sdhci_arasan->clk_ahb); 578278d0962SShawn Lin err_pltfm_free: 579278d0962SShawn Lin sdhci_pltfm_free(pdev); 580e3ec3a3dSSoren Brinkmann return ret; 581e3ec3a3dSSoren Brinkmann } 582e3ec3a3dSSoren Brinkmann 583e3ec3a3dSSoren Brinkmann static int sdhci_arasan_remove(struct platform_device *pdev) 584e3ec3a3dSSoren Brinkmann { 5850c7fe32eSJisheng Zhang int ret; 586e3ec3a3dSSoren Brinkmann struct sdhci_host *host = platform_get_drvdata(pdev); 587e3ec3a3dSSoren Brinkmann struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 58889211418SJisheng Zhang struct sdhci_arasan_data *sdhci_arasan = sdhci_pltfm_priv(pltfm_host); 58989211418SJisheng Zhang struct clk *clk_ahb = sdhci_arasan->clk_ahb; 590e3ec3a3dSSoren Brinkmann 59191aa3661SShawn Lin if (!IS_ERR(sdhci_arasan->phy)) { 59291aa3661SShawn Lin phy_power_off(sdhci_arasan->phy); 59391aa3661SShawn Lin phy_exit(sdhci_arasan->phy); 59491aa3661SShawn Lin } 59591aa3661SShawn Lin 596c390f211SDouglas Anderson sdhci_arasan_unregister_sdclk(&pdev->dev); 597c390f211SDouglas Anderson 5980c7fe32eSJisheng Zhang ret = sdhci_pltfm_unregister(pdev); 5990c7fe32eSJisheng Zhang 60089211418SJisheng Zhang clk_disable_unprepare(clk_ahb); 601e3ec3a3dSSoren Brinkmann 6020c7fe32eSJisheng Zhang return ret; 603e3ec3a3dSSoren Brinkmann } 604e3ec3a3dSSoren Brinkmann 605e3ec3a3dSSoren Brinkmann static struct platform_driver sdhci_arasan_driver = { 606e3ec3a3dSSoren Brinkmann .driver = { 607e3ec3a3dSSoren Brinkmann .name = "sdhci-arasan", 608e3ec3a3dSSoren Brinkmann .of_match_table = sdhci_arasan_of_match, 609e3ec3a3dSSoren Brinkmann .pm = &sdhci_arasan_dev_pm_ops, 610e3ec3a3dSSoren Brinkmann }, 611e3ec3a3dSSoren Brinkmann .probe = sdhci_arasan_probe, 612e3ec3a3dSSoren Brinkmann .remove = sdhci_arasan_remove, 613e3ec3a3dSSoren Brinkmann }; 614e3ec3a3dSSoren Brinkmann 615e3ec3a3dSSoren Brinkmann module_platform_driver(sdhci_arasan_driver); 616e3ec3a3dSSoren Brinkmann 617e3ec3a3dSSoren Brinkmann MODULE_DESCRIPTION("Driver for the Arasan SDHCI Controller"); 618e3ec3a3dSSoren Brinkmann MODULE_AUTHOR("Soeren Brinkmann <soren.brinkmann@xilinx.com>"); 619e3ec3a3dSSoren Brinkmann MODULE_LICENSE("GPL"); 620