| /* |
| * Copyright (C) 2010 Google, Inc. |
| * |
| * Copyright (c) 2012-2016, NVIDIA CORPORATION. All rights reserved. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #include <linux/err.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/platform_device.h> |
| #include <linux/clk.h> |
| #include <linux/io.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/gpio.h> |
| #include <linux/reset.h> |
| #include <linux/mmc/card.h> |
| #include <linux/mmc/host.h> |
| #include <linux/mmc/slot-gpio.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/delay.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/platform/tegra/emc_bwmgr.h> |
| #include <linux/clk/tegra.h> |
| #include <linux/tegra_prod.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/platform_data/mmc-sdhci-tegra.h> |
| #include <linux/padctrl/padctrl.h> |
| #include <linux/mmc/cmdq_hci.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/tegra-soc.h> |
| #include <linux/ktime.h> |
| |
| #include "sdhci-pltfm.h" |
| |
| #define SDHCI_RTPM_MSEC_TMOUT 10 |
| #define SDMMC_TEGRA_FALLBACK_CLK_HZ 400000 |
| #define SAVE_TUNED_TAP 0 |
| |
| /* Tegra SDHOST controller vendor register definitions */ |
| #define SDHCI_VNDR_CLK_CTRL 0x100 |
| #define SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT 16 |
| #define SDHCI_VNDR_CLK_CTRL_TAP_VALUE_MASK 0xFF |
| #define SDHCI_VNDR_CLK_CTRL_TRIM_VALUE_SHIFT 24 |
| #define SDHCI_VNDR_CLK_CTRL_TRIM_VALUE_MASK 0x1F |
| #define SDHCI_VNDR_CLK_CTRL_SDMMC_CLK 0x1 |
| |
| #define SDHCI_VNDR_SYS_SW_CTRL 0x104 |
| #define SDHCI_VNDR_SYS_SW_CTRL_STROBE_SHIFT 31 |
| |
| #define SDHCI_VNDR_CAP_OVERRIDES_0 0x10c |
| #define SDHCI_VNDR_CAP_OVERRIDES_0_DQS_TRIM_SHIFT 8 |
| #define SDHCI_VNDR_CAP_OVERRIDES_0_DQS_TRIM_MASK 0x3F |
| |
| #define SDMMC_VNDR_IO_TRIM_CTRL_0 0x1AC |
| #define SDMMC_VNDR_IO_TRIM_CTRL_0_SEL_VREG_MASK 0x4 |
| |
| #define SDHCI_VNDR_DLLCAL_CFG 0x1b0 |
| #define SDHCI_VNDR_DLLCAL_CFG_EN_CALIBRATE 0x80000000 |
| |
| #define SDHCI_VNDR_DLLCAL_CFG_STATUS 0x1bc |
| #define SDHCI_VNDR_DLLCAL_CFG_STATUS_DLL_ACTIVE 0x80000000 |
| |
| #define SDHCI_VNDR_TUN_CTRL0_0 0x1c0 |
| #define SDHCI_VNDR_TUN_CTRL0_TUN_HW_TAP 0x20000 |
| |
| #define SDHCI_VNDR_TUN_STATUS0_0 0x1c8 |
| #define TUNING_WORD_SEL_MASK 0x7 |
| |
| #define SDMMC_SDMEMCOMPPADCTRL 0x1E0 |
| #define SDMMC_SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD_MASK 0x80000000 |
| |
| #define SDMMC_AUTO_CAL_CONFIG 0x1E4 |
| #define SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_START 0x80000000 |
| |
| #define SDHCI_TEGRA_VENDOR_MISC_CTRL 0x120 |
| #define SDHCI_MISC_CTRL_ENABLE_SDR104 0x8 |
| #define SDHCI_MISC_CTRL_ENABLE_SDR50 0x10 |
| #define SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300 0x20 |
| #define SDHCI_MISC_CTRL_ENABLE_DDR50 0x200 |
| |
| #define SDMMC_AUTO_CAL_STATUS 0x1EC |
| #define SDMMC_AUTO_CAL_STATUS_AUTO_CAL_ACTIVE 0x80000000 |
| |
| #define SDMMC_VENDOR_ERR_INTR_STATUS_0 0x108 |
| |
| #define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0) |
| #define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1) |
| #define NVQUIRK_ENABLE_SDHCI_SPEC_300 BIT(2) |
| #define NVQUIRK_DISABLE_SDR50 BIT(3) |
| #define NVQUIRK_DISABLE_SDR104 BIT(4) |
| #define NVQUIRK_DISABLE_DDR50 BIT(5) |
| /* Do not enable auto calibration if the platform doesn't support */ |
| #define NVQUIRK_DISABLE_AUTO_CALIBRATION BIT(6) |
| #define NVQUIRK2_SET_PLL_CLK_PARENT BIT(0) |
| /* Tegra register write WAR - needs follow on register read */ |
| #define NVQUIRK2_TEGRA_WRITE_REG BIT(1) |
| /* Disable card clk before changing tap value and issue dat+cmd |
| * reset after sending each tuning command or after setting |
| * tap_delay for T210 platforms. |
| */ |
| #define NVQUIRK2_DISABLE_CARD_CLK BIT(2) |
| |
| /* Common quirks for Tegra 12x and later versions of sdmmc controllers */ |
| #define TEGRA_SDHCI_QUIRKS (SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | \ |
| SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | \ |
| SDHCI_QUIRK_SINGLE_POWER_WRITE | \ |
| SDHCI_QUIRK_NO_HISPD_BIT | \ |
| SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC | \ |
| SDHCI_QUIRK_BROKEN_CARD_DETECTION | \ |
| SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC) |
| |
| #define TEGRA_SDHCI_QUIRKS2 (SDHCI_QUIRK2_PRESET_VALUE_BROKEN | \ |
| SDHCI_QUIRK2_NON_STD_TUNING_LOOP_CNTR | \ |
| SDHCI_QUIRK2_HOST_OFF_CARD_ON) |
| |
| /* max limit defines */ |
| #define SDHCI_TEGRA_MAX_TAP_VALUES 0xFF |
| #define SDHCI_TEGRA_MAX_TRIM_VALUES 0x1F |
| #define SDHCI_TEGRA_MAX_DQS_TRIM_VALUES 0x3F |
| #define MAX_DIVISOR_VALUE 128 |
| #define DEFAULT_SDHOST_FREQ 50000000 |
| #define SDHOST_MIN_FREQ 6000000 |
| #define SDMMC_EMC_MAX_FREQ 150000000 |
| #define TEGRA_SDHCI_MAX_PLL_SOURCE 2 |
| |
| /* Interface voltages */ |
| #define SDHOST_1V8_OCR_MASK 0x8 |
| #define SDHOST_HIGH_VOLT_MIN 2700000 |
| #define SDHOST_HIGH_VOLT_MAX 3600000 |
| #define SDHOST_HIGH_VOLT_2V8 2800000 |
| #define SDHOST_LOW_VOLT_MIN 1800000 |
| #define SDHOST_LOW_VOLT_MAX 1800000 |
| #define SDHOST_HIGH_VOLT_3V2 3200000 |
| #define SDHOST_HIGH_VOLT_3V3 3300000 |
| #define SDHOST_MAX_VOLT_SUPPORT 3000000 |
| |
| static unsigned int sdmmc_emc_clinet_id[] = { |
| TEGRA_BWMGR_CLIENT_SDMMC1, |
| TEGRA_BWMGR_CLIENT_SDMMC2, |
| TEGRA_BWMGR_CLIENT_SDMMC3, |
| TEGRA_BWMGR_CLIENT_SDMMC4 |
| }; |
| |
| enum tegra_regulator_config_ops { |
| CONFIG_REG_GET, |
| CONFIG_REG_EN, |
| CONFIG_REG_DIS, |
| CONFIG_REG_SET_VOLT, |
| }; |
| |
| struct sdhci_tegra_soc_data { |
| const struct sdhci_pltfm_data *pdata; |
| u32 nvquirks; |
| u32 nvquirks2; |
| const char *parent_clk_list[TEGRA_SDHCI_MAX_PLL_SOURCE]; |
| }; |
| |
| struct sdhci_tegra_pll_parent { |
| struct clk *pll; |
| unsigned long pll_rate; |
| }; |
| |
| struct sdhci_tegra { |
| const struct sdhci_tegra_soc_data *soc_data; |
| const struct tegra_sdhci_platform_data *plat; |
| struct tegra_bwmgr_client *emc_clk; |
| bool clk_enabled; |
| /* ensure atomic set clock calls */ |
| struct mutex set_clock_mutex; |
| /* max clk supported by the platform */ |
| unsigned int max_clk_limit; |
| /* max ddr clk supported by the platform */ |
| unsigned int ddr_clk_limit; |
| struct reset_control *rstc; |
| unsigned int tuned_tap_delay; |
| unsigned int tuning_status; |
| #define TUNING_STATUS_DONE 1 |
| #define TUNING_STATUS_RETUNE 2 |
| struct sdhci_tegra_pll_parent pll_source[TEGRA_SDHCI_MAX_PLL_SOURCE]; |
| bool is_parent_pll_source_1; |
| struct regulator *vdd_io_reg; |
| struct regulator *vdd_slot_reg; |
| unsigned int vddio_min_uv; |
| unsigned int vddio_max_uv; |
| bool check_pad_ctrl_setting; |
| struct tegra_prod *prods; |
| int vddio_prev; |
| bool is_rail_enabled; |
| bool set_1v8_status; |
| struct padctrl *sdmmc_padctrl; |
| bool calib_1v8_offsets_done; |
| unsigned char timing; |
| unsigned int cd_irq; |
| bool wake_enable_failed; |
| ktime_t timestamp; |
| }; |
| |
| static void tegra_sdhci_do_calibration(struct sdhci_host *sdhci, |
| unsigned char signal_voltage); |
| /* Module Params declarations */ |
| static unsigned int en_boot_part_access; |
| |
| static int tegra_sdhci_configure_regulators(struct sdhci_host *sdhci, |
| u8 option, int min_uV, int max_uV); |
| static inline int sdhci_tegra_set_tap_delay(struct sdhci_host *sdhci, |
| int tap_delay, int type); |
| static inline int sdhci_tegra_set_dqs_trim_delay(struct sdhci_host *sdhci, |
| int dqs_trim_delay); |
| static void vendor_trim_clear_sel_vreg(struct sdhci_host *host, bool enable); |
| |
| static u16 tegra_sdhci_readw(struct sdhci_host *host, int reg) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; |
| |
| if (unlikely((soc_data->nvquirks & NVQUIRK_FORCE_SDHCI_SPEC_200) && |
| (reg == SDHCI_HOST_VERSION))) { |
| /* Erratum: Version register is invalid in HW. */ |
| return SDHCI_SPEC_200; |
| } |
| |
| return readw(host->ioaddr + reg); |
| } |
| |
| static void tegra_sdhci_dumpregs(struct sdhci_host *sdhci) |
| { |
| u32 tap_delay; |
| u32 trim_delay; |
| u32 reg, val; |
| int i; |
| |
| /* print tuning windows */ |
| for (i = 0; i <= TUNING_WORD_SEL_MASK; i++) { |
| reg = sdhci_readl(sdhci, SDHCI_VNDR_TUN_CTRL0_0); |
| reg &= ~TUNING_WORD_SEL_MASK; |
| reg |= i; |
| sdhci_writel(sdhci, reg, SDHCI_VNDR_TUN_CTRL0_0); |
| val = sdhci_readl(sdhci, SDHCI_VNDR_TUN_STATUS0_0); |
| pr_info("%s: tuning_window[%d]: %#x\n", |
| mmc_hostname(sdhci->mmc), i, val); |
| } |
| tap_delay = sdhci_readl(sdhci, SDHCI_VNDR_CLK_CTRL); |
| trim_delay = tap_delay; |
| tap_delay >>= SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT; |
| tap_delay &= SDHCI_VNDR_CLK_CTRL_TAP_VALUE_MASK; |
| trim_delay >>= SDHCI_VNDR_CLK_CTRL_TRIM_VALUE_SHIFT; |
| trim_delay &= SDHCI_VNDR_CLK_CTRL_TRIM_VALUE_MASK; |
| pr_info("sdhci: Tap value: %u | Trim value: %u\n", tap_delay, |
| trim_delay); |
| pr_info("sdhci: SDMMC_VENDOR_INTR_STATUS[0x%x]: 0x%x\n", |
| SDMMC_VENDOR_ERR_INTR_STATUS_0, |
| sdhci_readl(sdhci, SDMMC_VENDOR_ERR_INTR_STATUS_0)); |
| } |
| |
| static bool tegra_sdhci_is_tuning_done(struct sdhci_host *sdhci) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| |
| if (tegra_host->tuning_status == TUNING_STATUS_DONE) { |
| dev_info(mmc_dev(sdhci->mmc), |
| "Tuning already done, restoring the best tap value : %u\n", |
| tegra_host->tuned_tap_delay); |
| sdhci_tegra_set_tap_delay(sdhci, tegra_host->tuned_tap_delay, |
| SET_TUNED_TAP); |
| return true; |
| } |
| return false; |
| } |
| |
| static int sdhci_tegra_get_max_tuning_loop_counter(struct sdhci_host *sdhci) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| int err = 0; |
| |
| if (sdhci->mmc->ios.timing == MMC_TIMING_UHS_SDR50) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "tun-iterations-sdr50", tegra_host->prods); |
| else if (sdhci->mmc->ios.timing == MMC_TIMING_UHS_SDR104) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "tun-iterations-sdr104", tegra_host->prods); |
| else if (sdhci->mmc->caps2 & MMC_CAP2_HS533) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "tun-iterations-hs533", tegra_host->prods); |
| else if (sdhci->mmc->ios.timing == MMC_TIMING_MMC_HS400) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "tun-iterations-hs400", tegra_host->prods); |
| else if (sdhci->mmc->ios.timing == MMC_TIMING_MMC_HS200) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "tun-iterations-hs200", tegra_host->prods); |
| if (err) |
| dev_err(mmc_dev(sdhci->mmc), |
| "%s: error %d in tuning iteration update\n", |
| __func__, err); |
| |
| return 257; |
| } |
| |
| static void tegra_sdhci_writew(struct sdhci_host *host, u16 val, int reg) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; |
| |
| switch (reg) { |
| case SDHCI_TRANSFER_MODE: |
| /* |
| * Postpone this write, we must do it together with a |
| * command write that is down below. |
| */ |
| pltfm_host->xfer_mode_shadow = val; |
| return; |
| case SDHCI_COMMAND: |
| writel((val << 16) | pltfm_host->xfer_mode_shadow, |
| host->ioaddr + SDHCI_TRANSFER_MODE); |
| if (soc_data->nvquirks2 & NVQUIRK2_TEGRA_WRITE_REG) |
| readl(host->ioaddr + SDHCI_TRANSFER_MODE); |
| return; |
| } |
| |
| writew(val, host->ioaddr + reg); |
| if (soc_data->nvquirks2 & NVQUIRK2_TEGRA_WRITE_REG) |
| readw(host->ioaddr + reg); |
| } |
| |
| static void tegra_sdhci_writeb(struct sdhci_host *host, u8 val, int reg) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; |
| |
| writeb(val, host->ioaddr + reg); |
| if (soc_data->nvquirks2 & NVQUIRK2_TEGRA_WRITE_REG) |
| readb(host->ioaddr + reg); |
| } |
| |
| static void tegra_sdhci_writel(struct sdhci_host *host, u32 val, int reg) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; |
| |
| /* Seems like we're getting spurious timeout and crc errors, so |
| * disable signalling of them. In case of real errors software |
| * timers should take care of eventually detecting them. |
| */ |
| if (unlikely(reg == SDHCI_SIGNAL_ENABLE)) |
| val &= ~(SDHCI_INT_TIMEOUT|SDHCI_INT_CRC); |
| |
| writel(val, host->ioaddr + reg); |
| if (soc_data->nvquirks2 & NVQUIRK2_TEGRA_WRITE_REG) |
| readl(host->ioaddr + reg); |
| |
| if (unlikely((soc_data->nvquirks & NVQUIRK_ENABLE_BLOCK_GAP_DET) && |
| (reg == SDHCI_INT_ENABLE))) { |
| /* Erratum: Must enable block gap interrupt detection */ |
| u8 gap_ctrl = readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); |
| if (val & SDHCI_INT_CARD_INT) |
| gap_ctrl |= 0x8; |
| else |
| gap_ctrl &= ~0x8; |
| writeb(gap_ctrl, host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); |
| if (soc_data->nvquirks2 & NVQUIRK2_TEGRA_WRITE_REG) |
| readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL); |
| } |
| } |
| |
| static void sdhci_tegra_card_event(struct sdhci_host *sdhci) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| int present = sdhci->mmc->rem_card_present; |
| int err; |
| |
| if (!present) { |
| /* turn off voltage rregulators */ |
| err = tegra_sdhci_configure_regulators(sdhci, |
| CONFIG_REG_DIS, 0, 0); |
| |
| sdhci->is_calibration_done = false; |
| /* |
| * Set retune request as tuning should be done next time |
| * a card is inserted. |
| */ |
| tegra_host->tuning_status = TUNING_STATUS_RETUNE; |
| } else { |
| /* turn on voltage regulators */ |
| err = tegra_sdhci_configure_regulators(sdhci, |
| CONFIG_REG_EN, 0, 0); |
| tegra_host->calib_1v8_offsets_done = false; |
| } |
| } |
| |
| static unsigned int tegra_sdhci_get_ro(struct sdhci_host *host) |
| { |
| return mmc_gpio_get_ro(host->mmc); |
| } |
| static inline int sdhci_tegra_set_dqs_trim_delay(struct sdhci_host *sdhci, |
| int dqs_trim_delay) |
| { |
| u32 vend_ovrds; |
| |
| if ((dqs_trim_delay > SDHCI_TEGRA_MAX_DQS_TRIM_VALUES) || |
| (dqs_trim_delay < 0)) { |
| dev_err(mmc_dev(sdhci->mmc), "Invalid dqs trim value\n"); |
| return -1; |
| } |
| vend_ovrds = sdhci_readl(sdhci, SDHCI_VNDR_CAP_OVERRIDES_0); |
| vend_ovrds &= ~(SDHCI_VNDR_CAP_OVERRIDES_0_DQS_TRIM_MASK << |
| SDHCI_VNDR_CAP_OVERRIDES_0_DQS_TRIM_SHIFT); |
| vend_ovrds |= (dqs_trim_delay << |
| SDHCI_VNDR_CAP_OVERRIDES_0_DQS_TRIM_SHIFT); |
| sdhci_writel(sdhci, vend_ovrds, SDHCI_VNDR_CAP_OVERRIDES_0); |
| |
| return 0; |
| } |
| |
| static inline int sdhci_tegra_set_tap_delay(struct sdhci_host *sdhci, |
| int tap_delay, int type) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; |
| u16 clk; |
| u32 ctrl; |
| bool card_clk_enabled; |
| int err; |
| if ((tap_delay > SDHCI_TEGRA_MAX_TAP_VALUES) || (tap_delay < 0)){ |
| dev_err(mmc_dev(sdhci->mmc), "Invalid tap value\n"); |
| return -1; |
| } |
| |
| clk = sdhci_readw(sdhci, SDHCI_CLOCK_CONTROL); |
| card_clk_enabled = clk & SDHCI_CLOCK_CARD_EN; |
| |
| if ((soc_data->nvquirks2 & NVQUIRK2_DISABLE_CARD_CLK) && |
| (card_clk_enabled)) { |
| clk &= ~SDHCI_CLOCK_CARD_EN; |
| sdhci_writew(sdhci, clk, SDHCI_CLOCK_CONTROL); |
| } |
| |
| ctrl = sdhci_readl(sdhci, SDHCI_VNDR_TUN_CTRL0_0); |
| ctrl &= ~SDHCI_VNDR_TUN_CTRL0_TUN_HW_TAP; |
| sdhci_writel(sdhci, ctrl, SDHCI_VNDR_TUN_CTRL0_0); |
| |
| if (type & (SET_DDR_TAP | SET_DEFAULT_TAP)) { |
| if (type == SET_DDR_TAP) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "tap-delay-ddr", tegra_host->prods); |
| else |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "tap-delay-default", tegra_host->prods); |
| if (err < 0) |
| dev_err(mmc_dev(sdhci->mmc), |
| "%s: error %d in prod settings\n", |
| __func__, err); |
| } else { |
| ctrl = sdhci_readl(sdhci, SDHCI_VNDR_CLK_CTRL); |
| ctrl &= ~(SDHCI_VNDR_CLK_CTRL_TAP_VALUE_MASK << |
| SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT); |
| ctrl |= (tap_delay << |
| SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT); |
| sdhci_writel(sdhci, ctrl, SDHCI_VNDR_CLK_CTRL); |
| } |
| |
| ctrl = sdhci_readl(sdhci, SDHCI_VNDR_TUN_CTRL0_0); |
| ctrl |= SDHCI_VNDR_TUN_CTRL0_TUN_HW_TAP; |
| sdhci_writel(sdhci, ctrl, SDHCI_VNDR_TUN_CTRL0_0); |
| |
| if ((soc_data->nvquirks2 & NVQUIRK2_DISABLE_CARD_CLK) && |
| (card_clk_enabled)) { |
| udelay(1); |
| sdhci_reset(sdhci, SDHCI_RESET_CMD | SDHCI_RESET_DATA); |
| clk |= SDHCI_CLOCK_CARD_EN; |
| sdhci_writew(sdhci, clk, SDHCI_CLOCK_CONTROL); |
| } |
| |
| return 0; |
| } |
| |
| static void tegra_sdhci_reset(struct sdhci_host *host, u8 mask) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; |
| const struct tegra_sdhci_platform_data *plat = tegra_host->plat; |
| u32 misc_ctrl; |
| int err; |
| |
| sdhci_reset(host, mask); |
| |
| if (!(mask & SDHCI_RESET_ALL)) |
| return; |
| |
| err = tegra_prod_set_by_name(&host->ioaddr, "prod-reset", |
| tegra_host->prods); |
| if (err) |
| dev_err(mmc_dev(host->mmc), |
| "%s: error %d in prod-reset update\n", |
| __func__, err); |
| |
| /* Set the tap delay value */ |
| if (!tegra_sdhci_is_tuning_done(host)) |
| sdhci_tegra_set_tap_delay(host, plat->tap_delay, |
| SET_DEFAULT_TAP); |
| |
| misc_ctrl = sdhci_readl(host, SDHCI_TEGRA_VENDOR_MISC_CTRL); |
| /* Erratum: Enable SDHCI spec v3.00 support */ |
| if (soc_data->nvquirks & NVQUIRK_ENABLE_SDHCI_SPEC_300) |
| misc_ctrl |= SDHCI_MISC_CTRL_ENABLE_SDHCI_SPEC_300; |
| /* Don't advertise UHS modes which aren't supported yet */ |
| if (soc_data->nvquirks & NVQUIRK_DISABLE_SDR50) |
| misc_ctrl &= ~SDHCI_MISC_CTRL_ENABLE_SDR50; |
| if (soc_data->nvquirks & NVQUIRK_DISABLE_DDR50) |
| misc_ctrl &= ~SDHCI_MISC_CTRL_ENABLE_DDR50; |
| if (soc_data->nvquirks & NVQUIRK_DISABLE_SDR104) |
| misc_ctrl &= ~SDHCI_MISC_CTRL_ENABLE_SDR104; |
| sdhci_writew(host, misc_ctrl, SDHCI_TEGRA_VENDOR_MISC_CTRL); |
| |
| /* SEL_VREG should be 0 for all modes*/ |
| vendor_trim_clear_sel_vreg(host, true); |
| |
| /* Mask any bus speed modes if set in platform data */ |
| if (plat->uhs_mask & MMC_UHS_MASK_SDR12) |
| host->mmc->caps &= ~MMC_CAP_UHS_SDR12; |
| |
| if (plat->uhs_mask & MMC_UHS_MASK_SDR25) |
| host->mmc->caps &= ~MMC_CAP_UHS_SDR25; |
| |
| if (plat->uhs_mask & MMC_UHS_MASK_SDR50) |
| host->mmc->caps &= ~MMC_CAP_UHS_SDR50; |
| |
| if (plat->uhs_mask & MMC_UHS_MASK_SDR104) |
| host->mmc->caps &= ~MMC_CAP_UHS_SDR104; |
| |
| |
| if (plat->uhs_mask & MMC_UHS_MASK_DDR50) { |
| host->mmc->caps &= ~MMC_CAP_UHS_DDR50; |
| host->mmc->caps &= ~MMC_CAP_1_8V_DDR; |
| } |
| |
| if (plat->uhs_mask & MMC_MASK_HS200) { |
| host->mmc->caps2 &= ~MMC_CAP2_HS200; |
| host->mmc->caps2 &= ~MMC_CAP2_HS400; |
| } |
| |
| if (plat->uhs_mask & MMC_MASK_HS400) |
| host->mmc->caps2 &= ~MMC_CAP2_HS400; |
| } |
| |
| /* |
| * Calculation of nearest clock frequency for desired rate: |
| * Get the divisor value, div = p / d_rate |
| * 1. If it is nearer to ceil(p/d_rate) then increment the div value by 0.5 and |
| * nearest_rate, i.e. result = p / (div + 0.5) = (p << 1)/((div << 1) + 1). |
| * 2. If not, result = p / div |
| * As the nearest clk freq should be <= to desired_rate, |
| * 3. If result > desired_rate then increment the div by 0.5 |
| * and do, (p << 1)/((div << 1) + 1) |
| * 4. Else return result |
| * Here, If condtions 1 & 3 are both satisfied then to keep track of div value, |
| * defined index variable. |
| */ |
| static unsigned long get_nearest_clock_freq(unsigned long pll_rate, |
| unsigned long desired_rate) |
| { |
| unsigned long result; |
| int div; |
| int index = 1; |
| |
| if (pll_rate <= desired_rate) |
| return pll_rate; |
| |
| div = pll_rate / desired_rate; |
| if (div > MAX_DIVISOR_VALUE) { |
| div = MAX_DIVISOR_VALUE; |
| result = pll_rate / div; |
| } else { |
| if ((pll_rate % desired_rate) >= (desired_rate / 2)) |
| result = (pll_rate << 1) / ((div << 1) + index++); |
| else |
| result = pll_rate / div; |
| |
| if (desired_rate < result) { |
| /* |
| * Trying to get lower clock freq than desired clock, |
| * by increasing the divisor value by 0.5 |
| */ |
| result = (pll_rate << 1) / ((div << 1) + index); |
| } |
| } |
| |
| return result; |
| } |
| |
| static void tegra_sdhci_clock_set_parent(struct sdhci_host *host, |
| unsigned long desired_rate) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| struct clk *parent_clk; |
| unsigned long pll_source_1_freq; |
| unsigned long pll_source_2_freq; |
| struct sdhci_tegra_pll_parent *pll_source = tegra_host->pll_source; |
| int rc; |
| |
| #ifdef CONFIG_TEGRA_FPGA_PLATFORM |
| return; |
| #endif |
| |
| /* |
| * Currently pll_p and pll_c are used as clock sources for SDMMC. If clk |
| * rate is missing for either of them, then no selection is needed and |
| * the default parent is used. |
| */ |
| if (!pll_source[0].pll_rate || !pll_source[1].pll_rate) |
| return; |
| |
| if ((tegra_host->soc_data->nvquirks2 & NVQUIRK2_SET_PLL_CLK_PARENT) && |
| pll_source[0].pll_rate && !pll_source[1].pll_rate) { |
| rc = clk_set_rate(pltfm_host->clk, desired_rate); |
| if (!tegra_host->is_parent_pll_source_1) { |
| rc = clk_set_parent(pltfm_host->clk, pll_source[0].pll); |
| if (rc) |
| pr_err("%s: failed to set pll parent clock %d\n", |
| mmc_hostname(host->mmc), rc); |
| tegra_host->is_parent_pll_source_1 = true; |
| } |
| return; |
| } |
| |
| pll_source_1_freq = get_nearest_clock_freq(pll_source[0].pll_rate, |
| desired_rate); |
| pll_source_2_freq = get_nearest_clock_freq(pll_source[1].pll_rate, |
| desired_rate); |
| |
| /* |
| * For low freq requests, both the desired rates might be higher than |
| * the requested clock frequency. In such cases, select the parent |
| * with the lower frequency rate. |
| */ |
| if ((pll_source_1_freq > desired_rate) |
| && (pll_source_2_freq > desired_rate)) { |
| if (pll_source_2_freq <= pll_source_1_freq) { |
| desired_rate = pll_source_2_freq; |
| pll_source_1_freq = 0; |
| parent_clk = pll_source[1].pll; |
| } else { |
| desired_rate = pll_source_1_freq; |
| pll_source_2_freq = 0; |
| parent_clk = pll_source[0].pll; |
| } |
| rc = clk_set_rate(pltfm_host->clk, desired_rate); |
| goto set_parent_clk; |
| } |
| |
| if (pll_source_1_freq > pll_source_2_freq) { |
| if (!tegra_host->is_parent_pll_source_1) { |
| parent_clk = pll_source[0].pll; |
| tegra_host->is_parent_pll_source_1 = true; |
| clk_set_rate(pltfm_host->clk, DEFAULT_SDHOST_FREQ); |
| } else |
| return; |
| } else if (tegra_host->is_parent_pll_source_1) { |
| parent_clk = pll_source[1].pll; |
| tegra_host->is_parent_pll_source_1 = false; |
| clk_set_rate(pltfm_host->clk, DEFAULT_SDHOST_FREQ); |
| } else |
| return; |
| |
| set_parent_clk: |
| rc = clk_set_parent(pltfm_host->clk, parent_clk); |
| if (rc) |
| pr_err("%s: failed to set pll parent clock %d\n", |
| mmc_hostname(host->mmc), rc); |
| } |
| |
| static void tegra_sdhci_set_clk_rate(struct sdhci_host *sdhci, |
| unsigned int clock) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| unsigned int clk_rate; |
| |
| clk_rate = clk_get_rate(pltfm_host->clk); |
| |
| if (clk_rate == clock) |
| return; |
| |
| /* |
| * In ddr mode, tegra sdmmc controller clock frequency |
| * should be double the card clock frequency. |
| */ |
| if ((sdhci->mmc->ios.timing == MMC_TIMING_UHS_DDR50) || |
| (sdhci->mmc->ios.timing == MMC_TIMING_MMC_DDR52)) { |
| if (tegra_host->ddr_clk_limit && |
| (tegra_host->ddr_clk_limit < clock)) |
| clk_rate = tegra_host->ddr_clk_limit * 2; |
| else |
| clk_rate = clock * 2; |
| } else |
| clk_rate = clock; |
| |
| if (tegra_host->max_clk_limit && (clk_rate > tegra_host->max_clk_limit)) |
| clk_rate = tegra_host->max_clk_limit; |
| |
| if (clk_rate < SDHOST_MIN_FREQ) |
| clk_rate = SDHOST_MIN_FREQ; |
| |
| tegra_sdhci_clock_set_parent(sdhci, clk_rate); |
| clk_set_rate(pltfm_host->clk, clk_rate); |
| sdhci->max_clk = clk_get_rate(pltfm_host->clk); |
| } |
| |
| static void tegra_sdhci_set_clock(struct sdhci_host *sdhci, unsigned int clock) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| const struct tegra_sdhci_platform_data *plat = tegra_host->plat; |
| u8 vendor_ctrl; |
| int ret = 0; |
| ktime_t cur_time; |
| s64 period_time; |
| |
| if (tegra_platform_is_vdk()) |
| return; |
| |
| mutex_lock(&tegra_host->set_clock_mutex); |
| pr_debug("%s %s %u enabled=%u\n", __func__, |
| mmc_hostname(sdhci->mmc), clock, tegra_host->clk_enabled); |
| if (clock) { |
| if (!tegra_host->clk_enabled) { |
| ret = clk_prepare_enable(pltfm_host->clk); |
| if (ret) { |
| dev_err(mmc_dev(sdhci->mmc), |
| "clock enable is failed, ret: %d\n", ret); |
| mutex_unlock(&tegra_host->set_clock_mutex); |
| return; |
| } |
| tegra_host->clk_enabled = true; |
| vendor_ctrl = sdhci_readb(sdhci, SDHCI_VNDR_CLK_CTRL); |
| vendor_ctrl |= SDHCI_VNDR_CLK_CTRL_SDMMC_CLK; |
| sdhci_writeb(sdhci, vendor_ctrl, SDHCI_VNDR_CLK_CTRL); |
| /* power up / active state */ |
| vendor_trim_clear_sel_vreg(sdhci, true); |
| if (tegra_host->emc_clk) { |
| ret = tegra_bwmgr_set_emc(tegra_host->emc_clk, |
| SDMMC_EMC_MAX_FREQ, TEGRA_BWMGR_SET_EMC_SHARED_BW); |
| if (ret) |
| dev_err(mmc_dev(sdhci->mmc), |
| "enabling eMC clock failed, ret: %d\n", |
| ret); |
| } |
| } |
| tegra_sdhci_set_clk_rate(sdhci, clock); |
| if (plat->en_periodic_calib && |
| sdhci->is_calibration_done) { |
| cur_time = ktime_get(); |
| period_time = ktime_to_ms(ktime_sub(cur_time, |
| tegra_host->timestamp)); |
| if (period_time >= SDHCI_PERIODIC_CALIB_TIMEOUT) |
| tegra_sdhci_do_calibration(sdhci, |
| sdhci->mmc->ios.signal_voltage); |
| } |
| sdhci_set_clock(sdhci, clock); |
| } else if (!clock && tegra_host->clk_enabled) { |
| /* power down / idle state */ |
| vendor_trim_clear_sel_vreg(sdhci, false); |
| vendor_ctrl = sdhci_readb(sdhci, SDHCI_VNDR_CLK_CTRL); |
| vendor_ctrl &= ~SDHCI_VNDR_CLK_CTRL_SDMMC_CLK; |
| sdhci_writeb(sdhci, vendor_ctrl, SDHCI_VNDR_CLK_CTRL); |
| clk_disable_unprepare(pltfm_host->clk); |
| tegra_host->clk_enabled = false; |
| if (tegra_host->emc_clk) { |
| ret = tegra_bwmgr_set_emc(tegra_host->emc_clk, 0, |
| TEGRA_BWMGR_SET_EMC_SHARED_BW); |
| if (ret) |
| dev_err(mmc_dev(sdhci->mmc), |
| "disabling eMC clock failed, ret: %d\n", ret); |
| } |
| } |
| mutex_unlock(&tegra_host->set_clock_mutex); |
| } |
| |
| static void tegra_sdhci_set_bus_width(struct sdhci_host *host, int bus_width) |
| { |
| u32 ctrl; |
| |
| ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); |
| if ((host->mmc->caps & MMC_CAP_8_BIT_DATA) && |
| (bus_width == MMC_BUS_WIDTH_8)) { |
| ctrl &= ~SDHCI_CTRL_4BITBUS; |
| ctrl |= SDHCI_CTRL_8BITBUS; |
| } else { |
| ctrl &= ~SDHCI_CTRL_8BITBUS; |
| if (bus_width == MMC_BUS_WIDTH_4) |
| ctrl |= SDHCI_CTRL_4BITBUS; |
| else |
| ctrl &= ~SDHCI_CTRL_4BITBUS; |
| } |
| sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); |
| } |
| |
| static void tegra_sdhci_set_uhs_signaling(struct sdhci_host *host, |
| unsigned int timing) |
| { |
| u16 clk; |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| const struct tegra_sdhci_platform_data *plat = tegra_host->plat; |
| bool set_1v8_calib_offsets = false; |
| unsigned int dqs_trim_delay; |
| int err; |
| /* Set the UHS signaling mode */ |
| sdhci_set_uhs_signaling(host, timing); |
| |
| switch (timing) { |
| case MMC_TIMING_UHS_DDR50: |
| case MMC_TIMING_UHS_SDR12: |
| case MMC_TIMING_UHS_SDR25: |
| case MMC_TIMING_UHS_SDR50: |
| case MMC_TIMING_UHS_SDR104: |
| case MMC_TIMING_MMC_HS200: |
| case MMC_TIMING_MMC_HS400: |
| set_1v8_calib_offsets = true; |
| |
| break; |
| } |
| |
| if (timing == MMC_TIMING_MMC_HS400) { |
| dqs_trim_delay = plat->dqs_trim_delay; |
| sdhci_tegra_set_dqs_trim_delay(host, dqs_trim_delay); |
| } |
| |
| /* |
| * Tegra SDMMC controllers support only a clock divisor of 2 in DDR |
| * mode. No other divisors are supported. |
| * Set the best tap value based on timing. |
| * Set trim delay if required. |
| */ |
| if ((timing == MMC_TIMING_UHS_DDR50) || |
| (timing == MMC_TIMING_MMC_DDR52)) { |
| clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); |
| clk &= ~(0xFF << SDHCI_DIVIDER_SHIFT); |
| clk |= 1 << SDHCI_DIVIDER_SHIFT; |
| sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); |
| sdhci_tegra_set_tap_delay(host, plat->ddr_tap_delay, |
| SET_DDR_TAP); |
| err = tegra_prod_set_by_name(&host->ioaddr, |
| "trim-delay-ddr", tegra_host->prods); |
| if (err < 0) |
| dev_err(mmc_dev(host->mmc), |
| "%s: error %d in prod settings\n", |
| __func__, err); |
| } else if (((timing == MMC_TIMING_MMC_HS200) || |
| (timing == MMC_TIMING_UHS_SDR104) || |
| (timing == MMC_TIMING_MMC_HS400) || |
| (timing == MMC_TIMING_UHS_SDR50)) && |
| (tegra_host->tuning_status == TUNING_STATUS_DONE)) { |
| sdhci_tegra_set_tap_delay(host, tegra_host->tuned_tap_delay, |
| SET_TUNED_TAP); |
| } else { |
| sdhci_tegra_set_tap_delay(host, tegra_host->plat->tap_delay, |
| SET_DEFAULT_TAP); |
| } |
| |
| if (timing == MMC_TIMING_MMC_HS200) { |
| err = tegra_prod_set_by_name(&host->ioaddr, |
| "trim-delay-hs200", tegra_host->prods); |
| if (err < 0) |
| dev_dbg(mmc_dev(host->mmc), |
| "%s: error %d in prod settings\n", |
| __func__, err); |
| } |
| |
| if (set_1v8_calib_offsets && ((timing > tegra_host->timing) || |
| (!tegra_host->calib_1v8_offsets_done))) { |
| tegra_sdhci_do_calibration(host, |
| host->mmc->ios.signal_voltage); |
| tegra_host->calib_1v8_offsets_done = true; |
| tegra_host->timing = timing; |
| } |
| } |
| |
| static void vendor_trim_clear_sel_vreg(struct sdhci_host *host, bool enable) |
| { |
| unsigned int misc_ctrl; |
| |
| misc_ctrl = sdhci_readl(host, SDMMC_VNDR_IO_TRIM_CTRL_0); |
| if (enable) { |
| misc_ctrl &= ~(SDMMC_VNDR_IO_TRIM_CTRL_0_SEL_VREG_MASK); |
| sdhci_writel(host, misc_ctrl, SDMMC_VNDR_IO_TRIM_CTRL_0); |
| udelay(3); |
| sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); |
| } else { |
| misc_ctrl |= (SDMMC_VNDR_IO_TRIM_CTRL_0_SEL_VREG_MASK); |
| sdhci_writel(host, misc_ctrl, SDMMC_VNDR_IO_TRIM_CTRL_0); |
| udelay(1); |
| } |
| } |
| |
| static void tegra_sdhci_en_strobe(struct sdhci_host *host) |
| { |
| u32 vndr_ctrl; |
| |
| vndr_ctrl = sdhci_readl(host, SDHCI_VNDR_SYS_SW_CTRL); |
| vndr_ctrl |= (1 << |
| SDHCI_VNDR_SYS_SW_CTRL_STROBE_SHIFT); |
| sdhci_writel(host, vndr_ctrl, SDHCI_VNDR_SYS_SW_CTRL); |
| } |
| |
| /* Execute DLL calibration once for MMC device if it is |
| * enumerated in HS400 mode at 200MHz clock freq before |
| * starting any data transfer. |
| */ |
| static void tegra_sdhci_post_init(struct sdhci_host *sdhci) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| u32 dll_cfg; |
| unsigned timeout = 5; |
| |
| if ((sdhci->mmc->card->ext_csd.strobe_support) && |
| (sdhci->mmc->caps2 & MMC_CAP2_EN_STROBE) && |
| tegra_host->plat->en_strobe) |
| tegra_sdhci_en_strobe(sdhci); |
| |
| dll_cfg = sdhci_readl(sdhci, SDHCI_VNDR_DLLCAL_CFG); |
| dll_cfg |= SDHCI_VNDR_DLLCAL_CFG_EN_CALIBRATE; |
| sdhci_writel(sdhci, dll_cfg, SDHCI_VNDR_DLLCAL_CFG); |
| |
| mdelay(1); |
| |
| /* Wait until the dll calibration is done */ |
| do { |
| if (!(sdhci_readl(sdhci, SDHCI_VNDR_DLLCAL_CFG_STATUS) & |
| SDHCI_VNDR_DLLCAL_CFG_STATUS_DLL_ACTIVE)) |
| break; |
| |
| mdelay(1); |
| timeout--; |
| } while (timeout); |
| |
| if (!timeout) { |
| dev_err(mmc_dev(sdhci->mmc), "DLL calibration is failed\n"); |
| } |
| } |
| |
| static void tegra_sdhci_configure_e_input(struct sdhci_host *sdhci, bool enable) |
| { |
| u32 val; |
| |
| val = sdhci_readl(sdhci, SDMMC_SDMEMCOMPPADCTRL); |
| if (enable) |
| val |= SDMMC_SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD_MASK; |
| else |
| val &= ~SDMMC_SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD_MASK; |
| sdhci_writel(sdhci, val, SDMMC_SDMEMCOMPPADCTRL); |
| udelay(1); |
| return; |
| } |
| |
| static int tegra_sdhci_configure_regulators(struct sdhci_host *sdhci, |
| u8 option, int min_uV, int max_uV) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| int rc = 0; |
| |
| switch (option) { |
| case CONFIG_REG_GET: |
| tegra_host->vdd_io_reg = regulator_get_optional( |
| mmc_dev(sdhci->mmc), "vddio_sdmmc"); |
| if (IS_ERR(tegra_host->vdd_io_reg)) { |
| dev_dbg(mmc_dev(sdhci->mmc), |
| "%s regulator not found: %ld", "vddio_sdmmc", |
| PTR_ERR(tegra_host->vdd_io_reg)); |
| tegra_host->vdd_io_reg = NULL; |
| } |
| tegra_host->vdd_slot_reg = regulator_get_optional( |
| mmc_dev(sdhci->mmc), "vdd_sd_slot"); |
| if (IS_ERR(tegra_host->vdd_slot_reg)) { |
| dev_dbg(mmc_dev(sdhci->mmc), |
| "%s regulator not found: %ld", "vdd_sd_slot", |
| PTR_ERR(tegra_host->vdd_slot_reg)); |
| tegra_host->vdd_slot_reg = NULL; |
| } |
| break; |
| case CONFIG_REG_EN: |
| if (!tegra_host->is_rail_enabled) { |
| tegra_sdhci_configure_e_input(sdhci, true); |
| if (tegra_host->vdd_slot_reg) |
| rc = regulator_enable(tegra_host->vdd_slot_reg); |
| if (tegra_host->vdd_io_reg) |
| rc = regulator_enable(tegra_host->vdd_io_reg); |
| tegra_host->is_rail_enabled = true; |
| } |
| break; |
| case CONFIG_REG_DIS: |
| if (tegra_host->is_rail_enabled) { |
| if (tegra_host->vdd_io_reg) |
| rc = regulator_disable(tegra_host->vdd_io_reg); |
| if (tegra_host->vdd_slot_reg) |
| rc = regulator_disable( |
| tegra_host->vdd_slot_reg); |
| tegra_host->is_rail_enabled = false; |
| } |
| break; |
| case CONFIG_REG_SET_VOLT: |
| |
| if (tegra_host->vdd_io_reg) |
| rc = regulator_set_voltage(tegra_host->vdd_io_reg, |
| min_uV, max_uV); |
| break; |
| default: |
| pr_err("Invalid argument passed to reg config %d\n", option); |
| } |
| |
| return rc; |
| } |
| |
| static void tegra_sdhci_do_calibration(struct sdhci_host *sdhci, |
| unsigned char signal_voltage) |
| { |
| unsigned int val; |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data; |
| unsigned int timeout = 10; |
| u16 clk; |
| bool card_clk_enabled; |
| int err = 0; |
| unsigned int timing = sdhci->mmc->ios.timing; |
| |
| if (tegra_host->plat->disable_auto_cal) |
| return; |
| |
| /* |
| * Do not enable auto calibration if the platform doesn't |
| * support it. |
| */ |
| if (unlikely(soc_data->nvquirks & NVQUIRK_DISABLE_AUTO_CALIBRATION)) |
| return; |
| |
| clk = sdhci_readw(sdhci, SDHCI_CLOCK_CONTROL); |
| card_clk_enabled = clk & SDHCI_CLOCK_CARD_EN; |
| if (card_clk_enabled) { |
| clk &= ~SDHCI_CLOCK_CARD_EN; |
| sdhci_writew(sdhci, clk, SDHCI_CLOCK_CONTROL); |
| } |
| |
| tegra_sdhci_configure_e_input(sdhci, true); |
| if (signal_voltage == MMC_SIGNAL_VOLTAGE_330) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "comp-vref-3v3", tegra_host->prods); |
| else if (signal_voltage == MMC_SIGNAL_VOLTAGE_180) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "comp-vref-1v8", tegra_host->prods); |
| if (err < 0) |
| dev_err(mmc_dev(sdhci->mmc), |
| "%s: error %d in comp vref settings\n", |
| __func__, err); |
| |
| /* Wait for 1us after e_input is enabled*/ |
| udelay(1); |
| |
| /* Enable Auto Calibration*/ |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "autocal-en", tegra_host->prods); |
| if (err < 0) |
| dev_err(mmc_dev(sdhci->mmc), |
| "%s: error %d in autocal-en settings\n", |
| __func__, err); |
| |
| val = sdhci_readl(sdhci, SDMMC_AUTO_CAL_CONFIG); |
| val |= SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_START; |
| sdhci_writel(sdhci, val, SDMMC_AUTO_CAL_CONFIG); |
| |
| switch (signal_voltage) { |
| case MMC_SIGNAL_VOLTAGE_180: |
| if (timing == MMC_TIMING_MMC_HS400) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "autocal-pu-pd-offset-hs400-1v8", |
| tegra_host->prods); |
| else if (timing == MMC_TIMING_MMC_HS200) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "autocal-pu-pd-offset-hs200-1v8", |
| tegra_host->prods); |
| else if (timing == MMC_TIMING_UHS_SDR104) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "autocal-pu-pd-offset-sdr104-1v8", |
| tegra_host->prods); |
| else if (timing == MMC_TIMING_UHS_SDR50) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "autocal-pu-pd-offset-sdr50-1v8", |
| tegra_host->prods); |
| else if (timing == MMC_TIMING_UHS_SDR25) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "autocal-pu-pd-offset-hs-1v8", |
| tegra_host->prods); |
| else |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "autocal-pu-pd-offset-default-1v8", |
| tegra_host->prods); |
| break; |
| case MMC_SIGNAL_VOLTAGE_330: |
| if ((timing == MMC_TIMING_SD_HS) || |
| (timing == MMC_TIMING_MMC_HS)) |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "autocal-pu-pd-offset-hs-3v3", |
| tegra_host->prods); |
| else |
| err = tegra_prod_set_by_name(&sdhci->ioaddr, |
| "autocal-pu-pd-offset-default-3v3", |
| tegra_host->prods); |
| break; |
| } |
| if (err < 0) |
| dev_err(mmc_dev(sdhci->mmc), |
| "error %d in autocal-pu-pd-offset settings\n", err); |
| |
| /* Wait for 2us after auto calibration is enabled*/ |
| udelay(2); |
| |
| /* Wait until the calibration is done */ |
| do { |
| if (!(sdhci_readl(sdhci, SDMMC_AUTO_CAL_STATUS) & |
| SDMMC_AUTO_CAL_STATUS_AUTO_CAL_ACTIVE)) |
| break; |
| |
| mdelay(1); |
| timeout--; |
| } while (timeout); |
| |
| if (!timeout) |
| dev_err(mmc_dev(sdhci->mmc), "Auto calibration failed\n"); |
| |
| tegra_sdhci_configure_e_input(sdhci, false); |
| |
| if (card_clk_enabled) { |
| clk |= SDHCI_CLOCK_CARD_EN; |
| sdhci_writew(sdhci, clk, SDHCI_CLOCK_CONTROL); |
| } |
| if (tegra_host->plat->en_periodic_calib) { |
| tegra_host->timestamp = ktime_get(); |
| sdhci->timestamp = ktime_get(); |
| sdhci->is_calibration_done = true; |
| } |
| } |
| |
| static void tegra_sdhci_set_padctrl(struct sdhci_host *sdhci, int voltage) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| const struct tegra_sdhci_platform_data *plat = tegra_host->plat; |
| int ret; |
| |
| if (plat->pwrdet_support && tegra_host->sdmmc_padctrl) { |
| ret = padctrl_set_voltage(tegra_host->sdmmc_padctrl, voltage); |
| if (ret) |
| dev_err(mmc_dev(sdhci->mmc), |
| "padctrl set volt failed %d\n", ret); |
| } |
| } |
| |
| static int tegra_sdhci_signal_voltage_switch(struct sdhci_host *sdhci, |
| unsigned int signal_voltage) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| unsigned int min_uV = tegra_host->vddio_min_uv; |
| unsigned int max_uV = tegra_host->vddio_max_uv; |
| unsigned int rc = 0; |
| u16 ctrl; |
| unsigned int clock; |
| |
| if (!tegra_host->vdd_io_reg) |
| return -ENOTSUPP; |
| |
| ctrl = sdhci_readw(sdhci, SDHCI_HOST_CONTROL2); |
| if (signal_voltage == MMC_SIGNAL_VOLTAGE_180) { |
| ctrl |= SDHCI_CTRL_VDD_180; |
| min_uV = SDHOST_LOW_VOLT_MIN; |
| max_uV = SDHOST_LOW_VOLT_MAX; |
| } else if (signal_voltage == MMC_SIGNAL_VOLTAGE_330) { |
| if (ctrl & SDHCI_CTRL_VDD_180) |
| ctrl &= ~SDHCI_CTRL_VDD_180; |
| } |
| |
| /* Check if the slot can support the required voltage */ |
| if (min_uV > tegra_host->vddio_max_uv) |
| return 0; |
| |
| clock = sdhci->clock; |
| sdhci_set_clock(sdhci, 0); |
| |
| /* Set/clear the 1.8V signalling */ |
| sdhci_writew(sdhci, ctrl, SDHCI_HOST_CONTROL2); |
| |
| tegra_sdhci_configure_e_input(sdhci, true); |
| |
| /* Switch the I/O rail voltage */ |
| dev_info(mmc_dev(sdhci->mmc), |
| "setting min_voltage: %u, max_voltage: %u\n", min_uV, max_uV); |
| rc = tegra_sdhci_configure_regulators(sdhci, CONFIG_REG_SET_VOLT, |
| min_uV, max_uV); |
| if (rc && (signal_voltage == MMC_SIGNAL_VOLTAGE_180)) { |
| dev_err(mmc_dev(sdhci->mmc), |
| "setting 1.8V failed %d. Revert to 3.3V\n", rc); |
| tegra_host->set_1v8_status = false; |
| signal_voltage = MMC_SIGNAL_VOLTAGE_330; |
| rc = tegra_sdhci_configure_regulators(sdhci, |
| CONFIG_REG_SET_VOLT, tegra_host->vddio_min_uv, |
| tegra_host->vddio_max_uv); |
| } else if ((!rc) && (signal_voltage == MMC_SIGNAL_VOLTAGE_180)) |
| tegra_host->set_1v8_status = true; |
| |
| /* Wait for the voltage to stabilize */ |
| mdelay(10); |
| |
| sdhci_set_clock(sdhci, clock); |
| |
| /* Wait 1 msec for clock to stabilize */ |
| mdelay(1); |
| |
| tegra_host->check_pad_ctrl_setting = true; |
| |
| return rc; |
| } |
| |
| static void tegra_sdhci_pre_voltage_switch(struct sdhci_host *sdhci, |
| unsigned char signal_voltage) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| unsigned int min_uV; |
| |
| if (!tegra_host || |
| (!tegra_host->vdd_io_reg && |
| IS_ERR_OR_NULL(sdhci->mmc->supply.vqmmc))) |
| return; |
| |
| min_uV = tegra_host->vddio_min_uv; |
| if (signal_voltage == MMC_SIGNAL_VOLTAGE_180) |
| min_uV = SDHOST_LOW_VOLT_MIN; |
| |
| if (tegra_host->vdd_io_reg) |
| tegra_host->vddio_prev = |
| regulator_get_voltage(tegra_host->vdd_io_reg); |
| else |
| tegra_host->vddio_prev = |
| regulator_get_voltage(sdhci->mmc->supply.vqmmc); |
| |
| /* set pwrdet sdmmc1 before set 3.3 V */ |
| if (signal_voltage == MMC_SIGNAL_VOLTAGE_330) { |
| if ((tegra_host->vddio_prev < min_uV) && |
| (min_uV >= SDHOST_HIGH_VOLT_2V8)) { |
| tegra_sdhci_set_padctrl(sdhci, |
| SDHOST_HIGH_VOLT_3V3); |
| } |
| } |
| tegra_host->check_pad_ctrl_setting = true; |
| } |
| |
| static void tegra_sdhci_post_voltage_switch(struct sdhci_host *sdhci, |
| unsigned char signal_voltage, int voltage_switch_status) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| int voltage; |
| bool set, change_padctrl; |
| |
| if (!tegra_host || |
| (!tegra_host->vdd_io_reg && |
| IS_ERR_OR_NULL(sdhci->mmc->supply.vqmmc))) |
| return; |
| |
| if (tegra_host->check_pad_ctrl_setting) { |
| if (tegra_host->vdd_io_reg) { |
| voltage = regulator_get_voltage(tegra_host->vdd_io_reg); |
| change_padctrl = tegra_host->set_1v8_status; |
| } else { |
| voltage = regulator_get_voltage( |
| sdhci->mmc->supply.vqmmc); |
| change_padctrl = voltage_switch_status; |
| } |
| set = (signal_voltage == MMC_SIGNAL_VOLTAGE_180) ? true : false; |
| if (set && (voltage <= tegra_host->vddio_prev) && |
| change_padctrl) { |
| tegra_sdhci_set_padctrl(sdhci, SDHOST_LOW_VOLT_MAX); |
| } |
| } |
| return; |
| } |
| |
| static int tegra_sdhci_suspend(struct sdhci_host *sdhci) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| struct platform_device *pdev = to_platform_device(mmc_dev(sdhci->mmc)); |
| |
| int ret = 0; |
| |
| ret = tegra_sdhci_configure_regulators(sdhci, CONFIG_REG_DIS, 0, 0); |
| |
| sdhci->is_calibration_done = false; |
| |
| /* Enable wake irq at end of suspend */ |
| if (device_may_wakeup(&pdev->dev)) { |
| if (enable_irq_wake(tegra_host->cd_irq)) { |
| dev_err(mmc_dev(sdhci->mmc), |
| "enable wake irq=%u failed, err: %d\n", |
| tegra_host->cd_irq, ret); |
| tegra_host->wake_enable_failed = true; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int tegra_sdhci_resume(struct sdhci_host *sdhci) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| struct platform_device *pdev = to_platform_device(mmc_dev(sdhci->mmc)); |
| int ret = 0; |
| int signal_voltage = MMC_SIGNAL_VOLTAGE_330; |
| |
| /* Disable wake capability at start of resume */ |
| if (device_may_wakeup(&pdev->dev)) { |
| if (!tegra_host->wake_enable_failed) |
| ret = disable_irq_wake(tegra_host->cd_irq); |
| if (ret) |
| dev_err(mmc_dev(sdhci->mmc), |
| "disable wake irq=%u failed, err: %d\n", |
| tegra_host->cd_irq, ret); |
| } |
| |
| sdhci->mmc->rem_card_present = (mmc_gpio_get_cd(sdhci->mmc) == 0); |
| |
| /* Setting the min identification clock of freq 400KHz */ |
| tegra_sdhci_set_clock(sdhci, 400000); |
| if (tegra_host->vddio_max_uv < SDHOST_HIGH_VOLT_MIN) |
| signal_voltage = MMC_SIGNAL_VOLTAGE_180; |
| ret = tegra_sdhci_configure_regulators(sdhci, CONFIG_REG_EN, 0, 0); |
| if (!ret) { |
| tegra_sdhci_pre_voltage_switch(sdhci, signal_voltage); |
| ret = tegra_sdhci_signal_voltage_switch(sdhci, signal_voltage); |
| /* |
| * Ignore Not supported error as this means vmmc,vqmmc |
| * regulators are being used. |
| */ |
| if ((ret == -ENOTSUPP) && !tegra_host->vdd_io_reg) |
| ret = 0; |
| tegra_sdhci_post_voltage_switch(sdhci, signal_voltage, |
| ret ? 0 : 1); |
| } |
| |
| return ret; |
| } |
| |
| static int tegra_sdhci_runtime_suspend(struct sdhci_host *sdhci) |
| { |
| /* disable clock */ |
| tegra_sdhci_set_clock(sdhci, 0); |
| return 0; |
| } |
| |
| static int tegra_sdhci_runtime_resume(struct sdhci_host *sdhci) |
| { |
| unsigned int clk; |
| |
| /* enable clock */ |
| if (sdhci->clock) |
| clk = sdhci->clock; |
| else if (sdhci->mmc->ios.clock) |
| clk = sdhci->mmc->ios.clock; |
| else |
| clk = SDMMC_TEGRA_FALLBACK_CLK_HZ; |
| |
| tegra_sdhci_set_clock(sdhci, clk); |
| |
| return 0; |
| } |
| |
| static void tegra_sdhci_post_resume(struct sdhci_host *sdhci) |
| { |
| bool dll_calib_req = false; |
| |
| dll_calib_req = (sdhci->mmc->card && |
| (sdhci->mmc->card->type == MMC_TYPE_MMC) && |
| (sdhci->mmc->ios.timing == MMC_TIMING_MMC_HS400)); |
| if (dll_calib_req) |
| tegra_sdhci_post_init(sdhci); |
| } |
| |
| static void tegra_sdhci_config_tap(struct sdhci_host *sdhci, u8 option) |
| { |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| u32 tap_delay; |
| |
| switch (option) { |
| case SAVE_TUNED_TAP: |
| tap_delay = sdhci_readl(sdhci, SDHCI_VNDR_CLK_CTRL); |
| tap_delay >>= SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT; |
| tap_delay &= SDHCI_VNDR_CLK_CTRL_TAP_VALUE_MASK; |
| tegra_host->tuned_tap_delay = tap_delay; |
| tegra_host->tuning_status = TUNING_STATUS_DONE; |
| pr_info("%s tuning done saved tap delay=%d\n", |
| mmc_hostname(sdhci->mmc), tegra_host->tuned_tap_delay); |
| break; |
| case SET_DEFAULT_TAP: |
| sdhci_tegra_set_tap_delay(sdhci, tegra_host->plat->tap_delay, |
| SET_DEFAULT_TAP); |
| break; |
| case SET_TUNED_TAP: |
| sdhci_tegra_set_tap_delay(sdhci, tegra_host->tuned_tap_delay, |
| SET_TUNED_TAP); |
| break; |
| default: |
| dev_err(mmc_dev(sdhci->mmc), |
| "Invalid argument passed to tap config\n"); |
| } |
| } |
| |
| static void tegra_sdhci_post_tuning(struct sdhci_host *sdhci) |
| { |
| tegra_sdhci_config_tap(sdhci, SAVE_TUNED_TAP); |
| } |
| |
| static int sdhci_tegra_get_pll_from_dt(struct platform_device *pdev, |
| const char **parent_clk_list, int size) |
| { |
| struct device_node *np = pdev->dev.of_node; |
| const char *pll_str; |
| int i, cnt; |
| |
| if (!np) |
| return -EINVAL; |
| |
| if (!of_find_property(np, "pll_source", NULL)) |
| return -ENXIO; |
| |
| cnt = of_property_count_strings(np, "pll_source"); |
| if (!cnt) |
| return -EINVAL; |
| |
| if (cnt > size) { |
| dev_warn(&pdev->dev, |
| "pll list provide in DT exceeds max supported\n"); |
| cnt = size; |
| } |
| |
| for (i = 0; i < cnt; i++) { |
| of_property_read_string_index(np, "pll_source", i, &pll_str); |
| parent_clk_list[i] = pll_str; |
| } |
| return 0; |
| } |
| |
| static void tegra_sdhci_pre_regulator_config(struct sdhci_host *sdhci, int vdd) |
| { |
| if (!vdd) |
| return; |
| |
| tegra_sdhci_configure_e_input(sdhci, true); |
| } |
| |
| static int tegra_sdhci_enable_dma(struct sdhci_host *host) |
| { |
| /* |
| * QUIRK2_BROKEN_64_BIT_DMA is to be taken care by the driver. Handle it |
| * here so that we don't fall back to PIO mode. |
| */ |
| if (host->quirks2 & SDHCI_QUIRK2_BROKEN_64_BIT_DMA) |
| host->flags &= (~SDHCI_USE_64_BIT_DMA); |
| |
| return 0; |
| } |
| |
| static const struct sdhci_ops tegra_sdhci_ops = { |
| .get_ro = tegra_sdhci_get_ro, |
| .card_event = sdhci_tegra_card_event, |
| .read_w = tegra_sdhci_readw, |
| .write_b = tegra_sdhci_writeb, |
| .write_w = tegra_sdhci_writew, |
| .write_l = tegra_sdhci_writel, |
| .set_clock = tegra_sdhci_set_clock, |
| .set_bus_width = tegra_sdhci_set_bus_width, |
| .reset = tegra_sdhci_reset, |
| .set_uhs_signaling = tegra_sdhci_set_uhs_signaling, |
| .get_max_clock = sdhci_pltfm_clk_get_max_clock, |
| .post_init = tegra_sdhci_post_init, |
| .switch_signal_voltage = tegra_sdhci_signal_voltage_switch, |
| .switch_signal_voltage_exit = tegra_sdhci_post_voltage_switch, |
| .switch_signal_voltage_enter = tegra_sdhci_pre_voltage_switch, |
| .suspend = tegra_sdhci_suspend, |
| .resume = tegra_sdhci_resume, |
| .runtime_suspend = tegra_sdhci_runtime_suspend, |
| .runtime_resume = tegra_sdhci_runtime_resume, |
| .platform_resume = tegra_sdhci_post_resume, |
| .dump_host_cust_regs = tegra_sdhci_dumpregs, |
| .get_max_tuning_loop_counter = sdhci_tegra_get_max_tuning_loop_counter, |
| .post_tuning = tegra_sdhci_post_tuning, |
| .is_tuning_done = tegra_sdhci_is_tuning_done, |
| .pre_regulator_config = tegra_sdhci_pre_regulator_config, |
| .enable_dma = tegra_sdhci_enable_dma, |
| .do_calibration = tegra_sdhci_do_calibration, |
| }; |
| |
| static const struct sdhci_pltfm_data sdhci_tegra20_pdata = { |
| .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | |
| SDHCI_QUIRK_SINGLE_POWER_WRITE | |
| SDHCI_QUIRK_NO_HISPD_BIT | |
| SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC | |
| SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, |
| .ops = &tegra_sdhci_ops, |
| }; |
| |
| static struct sdhci_tegra_soc_data soc_data_tegra20 = { |
| .pdata = &sdhci_tegra20_pdata, |
| .nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 | |
| NVQUIRK_ENABLE_BLOCK_GAP_DET, |
| }; |
| |
| static const struct sdhci_pltfm_data sdhci_tegra30_pdata = { |
| .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | |
| SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | |
| SDHCI_QUIRK_SINGLE_POWER_WRITE | |
| SDHCI_QUIRK_NO_HISPD_BIT | |
| SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC | |
| SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, |
| .ops = &tegra_sdhci_ops, |
| }; |
| |
| static struct sdhci_tegra_soc_data soc_data_tegra30 = { |
| .pdata = &sdhci_tegra30_pdata, |
| .nvquirks = NVQUIRK_ENABLE_SDHCI_SPEC_300 | |
| NVQUIRK_DISABLE_SDR50 | |
| NVQUIRK_DISABLE_SDR104, |
| }; |
| |
| static const struct sdhci_ops tegra114_sdhci_ops = { |
| .get_ro = tegra_sdhci_get_ro, |
| .read_w = tegra_sdhci_readw, |
| .write_w = tegra_sdhci_writew, |
| .write_l = tegra_sdhci_writel, |
| .set_clock = sdhci_set_clock, |
| .set_bus_width = tegra_sdhci_set_bus_width, |
| .reset = tegra_sdhci_reset, |
| .set_uhs_signaling = sdhci_set_uhs_signaling, |
| .get_max_clock = sdhci_pltfm_clk_get_max_clock, |
| }; |
| |
| static const struct sdhci_pltfm_data sdhci_tegra114_pdata = { |
| .quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | |
| SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | |
| SDHCI_QUIRK_SINGLE_POWER_WRITE | |
| SDHCI_QUIRK_NO_HISPD_BIT | |
| SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC | |
| SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, |
| .ops = &tegra114_sdhci_ops, |
| }; |
| |
| static struct sdhci_tegra_soc_data soc_data_tegra114 = { |
| .pdata = &sdhci_tegra114_pdata, |
| .nvquirks = NVQUIRK_DISABLE_SDR50 | |
| NVQUIRK_DISABLE_DDR50 | |
| NVQUIRK_DISABLE_SDR104, |
| }; |
| static const struct sdhci_pltfm_data sdhci_tegra186_pdata = { |
| .quirks = TEGRA_SDHCI_QUIRKS, |
| .quirks2 = TEGRA_SDHCI_QUIRKS2 | |
| SDHCI_QUIRK2_USE_64BIT_ADDR | |
| SDHCI_QUIRK2_DDR_FIXED_DIVISOR | |
| SDHCI_QUIRK2_SEL_SDR104_UHS_MODE_IN_SDR50 | |
| SDHCI_QUIRK2_SKIP_TUNING, |
| .ops = &tegra_sdhci_ops, |
| }; |
| |
| static const struct sdhci_pltfm_data sdhci_tegra194_pdata = { |
| .quirks = TEGRA_SDHCI_QUIRKS, |
| .quirks2 = TEGRA_SDHCI_QUIRKS2 | |
| SDHCI_QUIRK2_USE_64BIT_ADDR, |
| .ops = &tegra_sdhci_ops, |
| }; |
| |
| static struct sdhci_tegra_soc_data soc_data_tegra186 = { |
| .pdata = &sdhci_tegra186_pdata, |
| .nvquirks2 = NVQUIRK2_SET_PLL_CLK_PARENT, |
| }; |
| |
| static struct sdhci_tegra_soc_data soc_data_tegra194 = { |
| .pdata = &sdhci_tegra194_pdata, |
| }; |
| |
| static const struct sdhci_pltfm_data sdhci_tegra210_pdata = { |
| .quirks = TEGRA_SDHCI_QUIRKS, |
| .quirks2 = TEGRA_SDHCI_QUIRKS2 | |
| SDHCI_QUIRK2_USE_64BIT_ADDR | |
| SDHCI_QUIRK2_DDR_FIXED_DIVISOR | |
| SDHCI_QUIRK2_SEL_SDR104_UHS_MODE_IN_SDR50 | |
| SDHCI_QUIRK2_SKIP_TUNING, |
| .ops = &tegra_sdhci_ops, |
| }; |
| |
| static const struct sdhci_tegra_soc_data soc_data_tegra210 = { |
| .pdata = &sdhci_tegra210_pdata, |
| .nvquirks2 = NVQUIRK2_TEGRA_WRITE_REG | |
| NVQUIRK2_DISABLE_CARD_CLK, |
| NVQUIRK2_SET_PLL_CLK_PARENT, |
| }; |
| |
| static const struct of_device_id sdhci_tegra_dt_match[] = { |
| { .compatible = "nvidia,tegra194-sdhci", .data = &soc_data_tegra194 }, |
| { .compatible = "nvidia,tegra186-sdhci", .data = &soc_data_tegra186 }, |
| { .compatible = "nvidia,tegra210-sdhci", .data = &soc_data_tegra210 }, |
| { .compatible = "nvidia,tegra124-sdhci", .data = &soc_data_tegra114 }, |
| { .compatible = "nvidia,tegra114-sdhci", .data = &soc_data_tegra114 }, |
| { .compatible = "nvidia,tegra30-sdhci", .data = &soc_data_tegra30 }, |
| { .compatible = "nvidia,tegra20-sdhci", .data = &soc_data_tegra20 }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(of, sdhci_tegra_dt_match); |
| |
| static int sdhci_tegra_parse_dt(struct device *dev) |
| { |
| struct device_node *np = dev->of_node; |
| struct sdhci_host *host = dev_get_drvdata(dev); |
| struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); |
| struct sdhci_tegra *tegra_host = pltfm_host->priv; |
| struct tegra_sdhci_platform_data *plat; |
| int val; |
| |
| if (!np) |
| return -EINVAL; |
| |
| plat = devm_kzalloc(dev, sizeof(*plat), GFP_KERNEL); |
| of_property_read_u32(np, "uhs-mask", &plat->uhs_mask); |
| of_property_read_u32(np, "dqs-trim-delay", &plat->dqs_trim_delay); |
| of_property_read_u32(np, "nvidia,ddr-tap-delay", &plat->ddr_tap_delay); |
| of_property_read_u32(np, "nvidia,ddr-trim-delay", &plat->ddr_trim_delay); |
| plat->pwrdet_support = of_property_read_bool(np, "pwrdet-support"); |
| plat->cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); |
| plat->disable_rtpm = of_property_read_bool(np, "nvidia,disable-rtpm"); |
| plat->instance = of_alias_get_id(np, "sdhci"); |
| of_property_read_u32(np, "tap-delay", &plat->tap_delay); |
| of_property_read_u32(np, "trim-delay", &plat->trim_delay); |
| of_property_read_u32(np, "max-clk-limit", &plat->max_clk_limit); |
| plat->en_strobe = |
| of_property_read_bool(np, "nvidia,enable-strobe-mode"); |
| |
| if (!of_property_read_u32(np, "mmc-ocr-mask", &val)) { |
| if (val == 0) |
| plat->ocr_mask = MMC_OCR_1V8_MASK; |
| else if (val == 1) |
| plat->ocr_mask = MMC_OCR_2V8_MASK; |
| else if (val == 2) |
| plat->ocr_mask = MMC_OCR_3V2_MASK; |
| else if (val == 3) |
| plat->ocr_mask = MMC_OCR_3V3_MASK; |
| } |
| #ifdef CONFIG_MMC_CQ_HCI |
| plat->enable_hw_cq = |
| of_property_read_bool(np, "nvidia,enable-hwcq"); |
| #endif |
| plat->en_periodic_cflush = of_property_read_bool(np, |
| "nvidia,en-periodic-cflush"); |
| if (plat->en_periodic_cflush) { |
| val = 0; |
| of_property_read_u32(np, "nvidia,periodic-cflush-to", &val); |
| host->mmc->flush_timeout = val; |
| if (val == 0) { |
| plat->en_periodic_cflush = false; |
| dev_warn(dev, "Periodic cache flush feature disabled," |
| "since flush timeout value is zero.\n"); |
| } |
| } |
| |
| plat->cd_wakeup_capable = of_property_read_bool(np, "nvidia,cd-wakeup-capable"); |
| plat->en_periodic_calib = of_property_read_bool(np, |
| "nvidia,en-periodic-calib"); |
| |
| tegra_host->plat = plat; |
| return mmc_of_parse(host->mmc); |
| } |
| |
| static int sdhci_tegra_probe(struct platform_device *pdev) |
| { |
| const struct of_device_id *match; |
| const struct sdhci_tegra_soc_data *soc_data; |
| struct sdhci_host *host; |
| struct sdhci_pltfm_host *pltfm_host; |
| struct sdhci_tegra *tegra_host; |
| const struct tegra_sdhci_platform_data *plat; |
| struct clk *clk; |
| struct clk *sdmmc_default_parent; |
| const char *parent_clk_list[TEGRA_SDHCI_MAX_PLL_SOURCE]; |
| int rc, i; |
| int signal_voltage = MMC_SIGNAL_VOLTAGE_330; |
| |
| for (i = 0; i < ARRAY_SIZE(parent_clk_list); i++) |
| parent_clk_list[i] = NULL; |
| match = of_match_device(sdhci_tegra_dt_match, &pdev->dev); |
| if (!match) |
| return -EINVAL; |
| soc_data = match->data; |
| |
| host = sdhci_pltfm_init(pdev, soc_data->pdata, 0); |
| if (IS_ERR(host)) |
| return PTR_ERR(host); |
| pltfm_host = sdhci_priv(host); |
| |
| /* FIXME: This is for until dma-mask binding is supported in DT. |
| * Set coherent_dma_mask for each Tegra SKUs. |
| * If dma_mask is NULL, set it to coherent_dma_mask. */ |
| pdev->dev.coherent_dma_mask = DMA_BIT_MASK(64); |
| |
| if (!pdev->dev.dma_mask) |
| pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; |
| |
| tegra_host = devm_kzalloc(&pdev->dev, sizeof(*tegra_host), GFP_KERNEL); |
| if (!tegra_host) { |
| dev_err(mmc_dev(host->mmc), "failed to allocate tegra_host\n"); |
| rc = -ENOMEM; |
| goto err_alloc_tegra_host; |
| } |
| tegra_host->soc_data = soc_data; |
| pltfm_host->priv = tegra_host; |
| rc = sdhci_tegra_parse_dt(&pdev->dev); |
| if (rc) |
| goto err_alloc_tegra_host; |
| |
| plat = tegra_host->plat; |
| |
| /* check if DT provide list possible pll parents */ |
| if (sdhci_tegra_get_pll_from_dt(pdev, |
| &parent_clk_list[0], ARRAY_SIZE(parent_clk_list))) { |
| parent_clk_list[0] = soc_data->parent_clk_list[0]; |
| parent_clk_list[1] = soc_data->parent_clk_list[1]; |
| } |
| for (i = 0; i < ARRAY_SIZE(parent_clk_list); i++) { |
| if (!parent_clk_list[i]) |
| continue; |
| |
| tegra_host->pll_source[i].pll = devm_clk_get(&pdev->dev, |
| parent_clk_list[i]); |
| if (IS_ERR(tegra_host->pll_source[i].pll)) { |
| rc = PTR_ERR(tegra_host->pll_source[i].pll); |
| dev_err(mmc_dev(host->mmc), |
| "clk[%d] error in getting %s: %d\n", |
| i, parent_clk_list[i], rc); |
| goto err_alloc_tegra_host; |
| } |
| tegra_host->pll_source[i].pll_rate = |
| clk_get_rate(tegra_host->pll_source[i].pll); |
| |
| dev_info(mmc_dev(host->mmc), "Parent select= %s rate=%ld\n", |
| parent_clk_list[i], |
| tegra_host->pll_source[i].pll_rate); |
| } |
| |
| tegra_host->prods = devm_tegra_prod_get(&pdev->dev); |
| if (IS_ERR_OR_NULL(tegra_host->prods)) { |
| dev_info(&pdev->dev, "Prod-setting not available\n"); |
| tegra_host->prods = NULL; |
| } |
| if (plat->pwrdet_support) { |
| tegra_host->sdmmc_padctrl = devm_padctrl_get(&pdev->dev, "sdmmc"); |
| if (IS_ERR(tegra_host->sdmmc_padctrl)) { |
| rc = PTR_ERR(tegra_host->sdmmc_padctrl);\ |
| tegra_host->sdmmc_padctrl = NULL; |
| dev_err(&pdev->dev, "pad control get failed, error:%d\n", rc); |
| } |
| } |
| |
| if (gpio_is_valid(plat->cd_gpio) && plat->cd_wakeup_capable) { |
| tegra_host->cd_irq = gpio_to_irq(plat->cd_gpio); |
| if (tegra_host->cd_irq <= 0) { |
| dev_err(&pdev->dev, "gpio_to_irq failed, err:%d\n", |
| tegra_host->cd_irq); |
| tegra_host->cd_irq = 0; |
| } else { |
| device_init_wakeup(&pdev->dev, 1); |
| dev_info(&pdev->dev, "wakeup init done, cd_irq: %d\n", |
| tegra_host->cd_irq); |
| } |
| } |
| |
| host->mmc->rem_card_present = (mmc_gpio_get_cd(host->mmc) == 0); |
| |
| /* |
| * If there is no card detect gpio, assume that the |
| * card is always present. |
| */ |
| if (!gpio_is_valid(plat->cd_gpio)) |
| host->mmc->rem_card_present = 1; |
| |
| /* set_clock call from runtime resume uses mutex */ |
| mutex_init(&tegra_host->set_clock_mutex); |
| clk = devm_clk_get(&pdev->dev, "sdmmc"); |
| if (IS_ERR(clk)) { |
| dev_err(mmc_dev(host->mmc), "clk err\n"); |
| rc = PTR_ERR(clk); |
| if (!tegra_platform_is_vdk()) |
| goto err_alloc_tegra_host; |
| } |
| |
| tegra_host->emc_clk = |
| tegra_bwmgr_register(sdmmc_emc_clinet_id[plat->instance]); |
| |
| if (IS_ERR_OR_NULL(tegra_host->emc_clk)) |
| dev_err(mmc_dev(host->mmc), |
| "Client registration for eMC failed\n"); |
| else |
| dev_info(mmc_dev(host->mmc), |
| "Client registration for eMC Successful\n"); |
| |
| pltfm_host->clk = clk; |
| if (!tegra_platform_is_vdk()) { |
| if (clk_get_parent(pltfm_host->clk) |
| == tegra_host->pll_source[0].pll) |
| tegra_host->is_parent_pll_source_1 = true; |
| } |
| |
| if (tegra_host->soc_data->nvquirks2 & NVQUIRK2_SET_PLL_CLK_PARENT) { |
| sdmmc_default_parent = devm_clk_get(&pdev->dev, "pll_p"); |
| clk_set_parent(pltfm_host->clk, sdmmc_default_parent); |
| if (strcmp(parent_clk_list[0], "pll_p")) |
| tegra_host->is_parent_pll_source_1 = true; |
| } |
| |
| /* Reset the sdhci controller to clear all previous status.*/ |
| |
| tegra_host->rstc = devm_reset_control_get(&pdev->dev, "sdmmc"); |
| if (IS_ERR(tegra_host->rstc)) |
| pr_err("Reset for %s is failed\n", dev_name(&pdev->dev)); |
| else |
| reset_control_reset(tegra_host->rstc); |
| |
| if(plat->disable_rtpm) { |
| /* enable clocks first time */ |
| rc = clk_prepare_enable(pltfm_host->clk); |
| if (rc != 0) |
| goto err_alloc_tegra_host; |
| } else { |
| |
| /* clock enable call is removed but the below runtime call sequence |
| * is sensitive. Beware that change in order of calls such as |
| * pm_runtime_set_active call before pm_runtime_get_sync |
| * may hang due to sdmmc clock staying off during sdhci access |
| */ |
| |
| pm_runtime_enable(mmc_dev(host->mmc)); |
| pm_runtime_set_autosuspend_delay(mmc_dev(host->mmc), |
| SDHCI_RTPM_MSEC_TMOUT); |
| pm_runtime_use_autosuspend(mmc_dev(host->mmc)); |
| pm_runtime_get_sync(mmc_dev(host->mmc)); |
| pm_runtime_set_active(mmc_dev(host->mmc)); |
| } |
| |
| pltfm_host->priv = tegra_host; |
| |
| tegra_host->max_clk_limit = plat->max_clk_limit; |
| host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY; |
| |
| /* |
| * We don't support Extended GP in VDK even if the EXT_CSD might |
| * indicate that it does. Explicitly mark no support in case we |
| * are running on VDK. |
| */ |
| if (tegra_platform_is_vdk()) { |
| host->mmc->caps2 |= MMC_CAP2_NO_EXTENDED_GP; |
| } |
| |
| if (plat->ocr_mask & SDHOST_1V8_OCR_MASK) { |
| tegra_host->vddio_min_uv = SDHOST_LOW_VOLT_MIN; |
| tegra_host->vddio_max_uv = SDHOST_LOW_VOLT_MAX; |
| } else if (plat->ocr_mask & MMC_OCR_2V8_MASK) { |
| tegra_host->vddio_min_uv = SDHOST_HIGH_VOLT_2V8; |
| tegra_host->vddio_max_uv = SDHOST_HIGH_VOLT_MAX; |
| } else if (plat->ocr_mask & MMC_OCR_3V2_MASK) { |
| tegra_host->vddio_min_uv = SDHOST_HIGH_VOLT_3V2; |
| tegra_host->vddio_max_uv = SDHOST_HIGH_VOLT_MAX; |
| } else if (plat->ocr_mask & MMC_OCR_3V3_MASK) { |
| tegra_host->vddio_min_uv = SDHOST_HIGH_VOLT_3V3; |
| tegra_host->vddio_max_uv = SDHOST_HIGH_VOLT_MAX; |
| } else { |
| /* |
| * Set the minV and maxV to default |
| * voltage range of 2.7V - 3.6V |
| */ |
| tegra_host->vddio_min_uv = SDHOST_HIGH_VOLT_MIN; |
| tegra_host->vddio_max_uv = SDHOST_HIGH_VOLT_MAX; |
| } |
| |
| if (plat->ocr_mask & SDHOST_1V8_OCR_MASK) |
| host->ocr_mask = MMC_VDD_165_195; |
| else |
| host->ocr_mask = MMC_VDD_27_36 | MMC_VDD_165_195; |
| |
| rc = tegra_sdhci_configure_regulators(host, CONFIG_REG_GET, 0, 0); |
| if (!rc) { |
| tegra_sdhci_pre_voltage_switch(host, MMC_SIGNAL_VOLTAGE_330); |
| if (tegra_host->vddio_max_uv < SDHOST_HIGH_VOLT_MIN) |
| signal_voltage = MMC_SIGNAL_VOLTAGE_180; |
| rc = tegra_sdhci_signal_voltage_switch(host, signal_voltage); |
| if (rc) { |
| dev_err(&pdev->dev, |
| " voltage switch failed in probe, err: %d\n" |
| , rc); |
| } else { |
| if (host->mmc->rem_card_present) |
| rc = tegra_sdhci_configure_regulators(host, |
| CONFIG_REG_EN, 0, 0); |
| if (rc) |
| dev_err(&pdev->dev, |
| " voltage enable failed in probe, err: %d\n" |
| , rc); |
| tegra_sdhci_post_voltage_switch(host, signal_voltage, |
| rc); |
| } |
| } |
| if (plat->en_strobe) |
| host->mmc->caps2 |= MMC_CAP2_EN_STROBE; |
| |
| if (!en_boot_part_access) |
| host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC; |
| |
| if (plat->en_periodic_cflush) |
| host->mmc->caps2 |= MMC_CAP2_PERIODIC_CACHE_FLUSH; |
| |
| #ifdef CONFIG_MMC_CQ_HCI |
| if (plat->enable_hw_cq) { |
| host->mmc->caps2 |= MMC_CAP2_HW_CQ; |
| host->cq_host = cmdq_pltfm_init(pdev); |
| if (IS_ERR(host->cq_host)) |
| pr_err("CMDQ: Error in cmdq_platfm_init function\n"); |
| else |
| pr_info("CMDQ: cmdq_platfm_init successful\n"); |
| } |
| #endif |
| if (plat->en_periodic_calib) |
| host->quirks2 |= SDHCI_QUIRK2_PERIODIC_CALIBRATION; |
| |
| rc = sdhci_add_host(host); |
| |
| if(!plat->disable_rtpm) { |
| /* end host register access in probe */ |
| pm_runtime_mark_last_busy(mmc_dev(host->mmc)); |
| pm_runtime_put_autosuspend(mmc_dev(host->mmc)); |
| } |
| |
| if (rc) |
| goto err_add_host; |
| |
| return 0; |
| |
| err_add_host: |
| if(!plat->disable_rtpm) |
| pm_runtime_disable(mmc_dev(host->mmc)); |
| clk_disable_unprepare(pltfm_host->clk); |
| err_alloc_tegra_host: |
| sdhci_pltfm_free(pdev); |
| return rc; |
| } |
| |
| static struct platform_driver sdhci_tegra_driver = { |
| .driver = { |
| .name = "sdhci-tegra", |
| .of_match_table = sdhci_tegra_dt_match, |
| .pm = SDHCI_PLTFM_PMOPS, |
| .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
| }, |
| .probe = sdhci_tegra_probe, |
| .remove = sdhci_pltfm_unregister, |
| }; |
| |
| module_platform_driver(sdhci_tegra_driver); |
| |
| module_param(en_boot_part_access, uint, 0444); |
| |
| MODULE_DESCRIPTION("SDHCI driver for Tegra"); |
| MODULE_AUTHOR("Google, Inc."); |
| MODULE_LICENSE("GPL v2"); |
| |
| MODULE_PARM_DESC(en_boot_part_access, "Allow boot partitions access on device."); |