/*
* Copyright (C) 2010 Google, Inc.
*
- * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
+ * Copyright (c) 2012-2013, 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
#include <linux/mmc/sd.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>
-
+#include <linux/pm_runtime.h>
#include <asm/gpio.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
#include <mach/gpio-tegra.h>
#include <mach/sdhci.h>
#include <mach/io_dpd.h>
+#include <mach/pinmux.h>
#include "sdhci-pltfm.h"
-#define SDHCI_VENDOR_CLOCK_CNTRL 0x100
-#define SDHCI_VENDOR_CLOCK_CNTRL_SDMMC_CLK 0x1
-#define SDHCI_VENDOR_CLOCK_CNTRL_PADPIPE_CLKEN_OVERRIDE 0x8
-#define SDHCI_VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE 0x4
-#define SDHCI_VENDOR_CLOCK_CNTRL_BASE_CLK_FREQ_SHIFT 8
-#define SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT 16
-#define SDHCI_VENDOR_CLOCK_CNTRL_SDR50_TUNING 0x20
-
-#define SDHCI_VENDOR_MISC_CNTRL 0x120
-#define SDHCI_VENDOR_MISC_CNTRL_ENABLE_SDR104_SUPPORT 0x8
-#define SDHCI_VENDOR_MISC_CNTRL_ENABLE_SDR50_SUPPORT 0x10
-#define SDHCI_VENDOR_MISC_CNTRL_ENABLE_DDR50_SUPPORT 0x200
-#define SDHCI_VENDOR_MISC_CNTRL_ENABLE_SD_3_0 0x20
+#define SDHCI_VNDR_CLK_CTRL 0x100
+#define SDHCI_VNDR_CLK_CTRL_SDMMC_CLK 0x1
+#define SDHCI_VNDR_CLK_CTRL_PADPIPE_CLKEN_OVERRIDE 0x8
+#define SDHCI_VNDR_CLK_CTRL_SPI_MODE_CLKEN_OVERRIDE 0x4
+#define SDHCI_VNDR_CLK_CTRL_BASE_CLK_FREQ_SHIFT 8
+#define SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT 16
+#define SDHCI_VNDR_CLK_CTRL_TRIM_VALUE_SHIFT 24
+#define SDHCI_VNDR_CLK_CTRL_SDR50_TUNING 0x20
+
+#define SDHCI_VNDR_MISC_CTRL 0x120
+#define SDHCI_VNDR_MISC_CTRL_ENABLE_SDR104_SUPPORT 0x8
+#define SDHCI_VNDR_MISC_CTRL_ENABLE_SDR50_SUPPORT 0x10
+#define SDHCI_VNDR_MISC_CTRL_ENABLE_DDR50_SUPPORT 0x200
+#define SDHCI_VNDR_MISC_CTRL_ENABLE_SD_3_0 0x20
+#define SDHCI_VNDR_MISC_CTRL_INFINITE_ERASE_TIMEOUT 0x1
#define SDMMC_SDMEMCOMPPADCTRL 0x1E0
#define SDMMC_SDMEMCOMPPADCTRL_VREF_SEL_MASK 0xF
#define SDMMC_AUTO_CAL_CONFIG 0x1E4
+#define SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_START 0x80000000
#define SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_ENABLE 0x20000000
#define SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET_SHIFT 0x8
#define SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET 0x70
#define SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET 0x62
+#define SDMMC_AUTO_CAL_STATUS 0x1EC
+#define SDMMC_AUTO_CAL_STATUS_AUTO_CAL_ACTIVE 0x80000000
+#define SDMMC_AUTO_CAL_STATUS_PULLDOWN_OFFSET 24
+#define PULLUP_ADJUSTMENT_OFFSET 20
+
#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 TEGRA2_SDHOST_STD_FREQ 50000000
#define TEGRA3_SDHOST_STD_FREQ 104000000
-#define SD_SEND_TUNING_PATTERN 19
-#define MAX_TAP_VALUES 256
+#define MAX_DIVISOR_VALUE 128
-static unsigned int tegra_sdhost_min_freq;
-static unsigned int tegra_sdhost_std_freq;
+#define MMC_TUNING_BLOCK_SIZE_BUS_WIDTH_8 128
+#define MMC_TUNING_BLOCK_SIZE_BUS_WIDTH_4 64
+#define MAX_TAP_VALUES 255
+#define TUNING_FREQ_COUNT 2
+#define TUNING_VOLTAGES_COUNT 2
-#ifdef CONFIG_ARCH_TEGRA_3x_SOC
+static unsigned int uhs_max_freq_MHz[] = {
+ [MMC_TIMING_UHS_SDR50] = 100,
+ [MMC_TIMING_UHS_SDR104] = 208,
+ [MMC_TIMING_MMC_HS200] = 200,
+};
+
+#if defined(CONFIG_ARCH_TEGRA_3x_SOC)
static void tegra_3x_sdhci_set_card_clock(struct sdhci_host *sdhci, unsigned int clock);
-static void tegra3_sdhci_post_reset_init(struct sdhci_host *sdhci);
#endif
-#ifdef CONFIG_ARCH_TEGRA_11x_SOC
-static void tegra11x_sdhci_post_reset_init(struct sdhci_host *sdhci);
-#endif
+static unsigned int tegra_sdhost_min_freq;
+static unsigned int tegra_sdhost_std_freq;
-#ifndef CONFIG_ARCH_TEGRA_2x_SOC
-static unsigned int tegra3_sdhost_max_clk[4] = {
- 208000000, 104000000, 208000000, 104000000 };
-#endif
-struct tegra_sdhci_hw_ops{
+struct tegra_sdhci_hw_ops {
/* Set the internal clk and card clk.*/
void (*set_card_clock)(struct sdhci_host *sdhci, unsigned int clock);
- /* Post reset vendor registers configuration */
- void (*sdhost_init)(struct sdhci_host *sdhci);
};
#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
#elif defined(CONFIG_ARCH_TEGRA_3x_SOC)
static struct tegra_sdhci_hw_ops tegra_3x_sdhci_ops = {
.set_card_clock = tegra_3x_sdhci_set_card_clock,
- .sdhost_init = tegra3_sdhci_post_reset_init,
};
#else
static struct tegra_sdhci_hw_ops tegra_11x_sdhci_ops = {
- .sdhost_init = tegra11x_sdhci_post_reset_init,
};
#endif
-#define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0)
-#define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1)
+/* Erratum: Version register is invalid in HW */
+#define NVQUIRK_FORCE_SDHCI_SPEC_200 BIT(0)
+/* Erratum: Enable block gap interrupt detection */
+#define NVQUIRK_ENABLE_BLOCK_GAP_DET BIT(1)
+/* Do not enable auto calibration if the platform doesn't support */
+#define NVQUIRK_DISABLE_AUTO_CALIBRATION BIT(2)
+/* Set Calibration Offsets */
+#define NVQUIRK_SET_CALIBRATION_OFFSETS BIT(3)
+/* Set Drive Strengths */
+#define NVQUIRK_SET_DRIVE_STRENGTH BIT(4)
+/* Enable PADPIPE CLKEN */
+#define NVQUIRK_ENABLE_PADPIPE_CLKEN BIT(5)
+/* DISABLE SPI_MODE CLKEN */
+#define NVQUIRK_DISABLE_SPI_MODE_CLKEN BIT(6)
+/* Set tap delay */
+#define NVQUIRK_SET_TAP_DELAY BIT(7)
+/* Set trim delay */
+#define NVQUIRK_SET_TRIM_DELAY BIT(8)
+/* Enable SDHOST v3.0 support */
+#define NVQUIRK_ENABLE_SD_3_0 BIT(9)
+/* Enable SDR50 mode */
+#define NVQUIRK_ENABLE_SDR50 BIT(10)
+/* Enable SDR104 mode */
+#define NVQUIRK_ENABLE_SDR104 BIT(11)
+/*Enable DDR50 mode */
+#define NVQUIRK_ENABLE_DDR50 BIT(12)
+/* Enable Frequency Tuning for SDR50 mode */
+#define NVQUIRK_ENABLE_SDR50_TUNING BIT(13)
+/* Enable Infinite Erase Timeout*/
+#define NVQUIRK_INFINITE_ERASE_TIMEOUT BIT(14)
struct sdhci_tegra_soc_data {
struct sdhci_pltfm_data *pdata;
u32 nvquirks;
};
+struct sdhci_tegra_sd_stats {
+ unsigned int data_crc_count;
+ unsigned int cmd_crc_count;
+ unsigned int data_to_count;
+ unsigned int cmd_to_count;
+};
+
+enum tegra_tuning_freq {
+ TUNING_LOW_FREQ,
+ TUNING_HIGH_FREQ,
+};
+
+struct freq_tuning_params {
+ unsigned int freq_hz;
+ unsigned int nr_voltages;
+ unsigned int voltages[TUNING_VOLTAGES_COUNT];
+};
+
+static struct freq_tuning_params tuning_params[TUNING_FREQ_COUNT] = {
+ [TUNING_LOW_FREQ] = {
+ .freq_hz = 82000000,
+ .nr_voltages = 1,
+ .voltages = {ULONG_MAX},
+ },
+ [TUNING_HIGH_FREQ] = {
+ .freq_hz = 156000000,
+ .nr_voltages = 2,
+ .voltages = {ULONG_MAX, 1100000},
+ },
+};
+
+struct tap_window_data {
+ unsigned int partial_win;
+ unsigned int full_win_begin;
+ unsigned int full_win_end;
+ unsigned int tuning_ui;
+ unsigned int sampling_point;
+ bool abandon_partial_win;
+ bool abandon_full_win;
+};
+
+struct tegra_tuning_data {
+ unsigned int best_tap_value;
+ bool select_partial_win;
+ struct tap_window_data *tap_data[TUNING_VOLTAGES_COUNT];
+};
+
struct sdhci_tegra {
const struct tegra_sdhci_platform_data *plat;
const struct sdhci_tegra_soc_data *soc_data;
bool clk_enabled;
struct regulator *vdd_io_reg;
struct regulator *vdd_slot_reg;
+ struct regulator *vcore_reg;
/* Pointer to the chip specific HW ops */
struct tegra_sdhci_hw_ops *hw_ops;
/* Host controller instance */
unsigned int max_clk_limit;
/* max ddr clk supported by the platform */
unsigned int ddr_clk_limit;
+ /* SD Hot Plug in Suspend State */
+ unsigned int sd_detect_in_suspend;
struct tegra_io_dpd *dpd;
bool card_present;
bool is_rail_enabled;
struct clk *emc_clk;
unsigned int emc_max_clk;
+ struct sdhci_tegra_sd_stats *sd_stat_head;
+ unsigned int nominal_vcore_uV;
+ /* Tuning related structures and variables */
+ /* Tuning opcode to be used */
+ unsigned int tuning_opcode;
+ /* Tuning packet size */
+ unsigned int tuning_bsize;
+ /* Tuning status */
+ unsigned int tuning_status;
+#define TUNING_STATUS_DONE 1
+#define TUNING_STATUS_RETUNE 2
+ /* Freq tuning information for each sampling clock freq */
+ struct tegra_tuning_data tuning_data;
+ bool is_parent_pllc;
+};
+
+static struct clk *pll_c;
+static struct clk *pll_p;
+static unsigned long pll_c_rate;
+static unsigned long pll_p_rate;
+
+static int show_error_stats_dump(struct seq_file *s, void *data)
+{
+ struct sdhci_host *host = s->private;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ struct sdhci_tegra_sd_stats *head;
+
+ seq_printf(s, "ErrorStatistics:\n");
+ seq_printf(s, "DataCRC\tCmdCRC\tDataTimeout\tCmdTimeout\n");
+ head = tegra_host->sd_stat_head;
+ if (head != NULL)
+ seq_printf(s, "%d\t%d\t%d\t%d\n", head->data_crc_count,
+ head->cmd_crc_count, head->data_to_count,
+ head->cmd_to_count);
+ return 0;
+}
+
+static int sdhci_error_stats_dump(struct inode *inode, struct file *file)
+{
+ return single_open(file, show_error_stats_dump, inode->i_private);
+}
+
+static const struct file_operations sdhci_host_fops = {
+ .open = sdhci_error_stats_dump,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
};
static u32 tegra_sdhci_readl(struct sdhci_host *host, int reg)
{
+#ifndef CONFIG_ARCH_TEGRA_11x_SOC
u32 val;
if (unlikely(reg == SDHCI_PRESENT_STATE)) {
val = readl(host->ioaddr + reg);
return val | SDHCI_WRITE_PROTECT;
}
-
+#endif
return readl(host->ioaddr + reg);
}
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;
}
#endif
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
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;
return tegra_host->card_present;
}
+#ifndef CONFIG_ARCH_TEGRA_11x_SOC
static unsigned int tegra_sdhci_get_ro(struct sdhci_host *sdhci)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
return gpio_get_value(plat->wp_gpio);
}
-
-#ifdef CONFIG_ARCH_TEGRA_3x_SOC
-static void tegra3_sdhci_post_reset_init(struct sdhci_host *sdhci)
-{
- u16 misc_ctrl;
- u32 vendor_ctrl;
- struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
- struct sdhci_tegra *tegra_host = pltfm_host->priv;
- struct tegra_sdhci_platform_data *plat = tegra_host->plat;
-
- /* Set the base clock frequency */
- vendor_ctrl = sdhci_readl(sdhci, SDHCI_VENDOR_CLOCK_CNTRL);
- vendor_ctrl &= ~(0xFF << SDHCI_VENDOR_CLOCK_CNTRL_BASE_CLK_FREQ_SHIFT);
- vendor_ctrl |= (tegra3_sdhost_max_clk[tegra_host->instance] / 1000000) <<
- SDHCI_VENDOR_CLOCK_CNTRL_BASE_CLK_FREQ_SHIFT;
- vendor_ctrl |= SDHCI_VENDOR_CLOCK_CNTRL_PADPIPE_CLKEN_OVERRIDE;
- vendor_ctrl &= ~SDHCI_VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE;
-
- /* Set tap delay */
- if (plat->tap_delay) {
- vendor_ctrl &= ~(0xFF <<
- SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT);
- vendor_ctrl |= (plat->tap_delay <<
- SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT);
- }
- /* Enable frequency tuning for SDR50 mode */
- vendor_ctrl |= SDHCI_VENDOR_CLOCK_CNTRL_SDR50_TUNING;
- sdhci_writel(sdhci, vendor_ctrl, SDHCI_VENDOR_CLOCK_CNTRL);
-
- /* Enable SDHOST v3.0 support */
- misc_ctrl = sdhci_readw(sdhci, SDHCI_VENDOR_MISC_CNTRL);
- misc_ctrl |= SDHCI_VENDOR_MISC_CNTRL_ENABLE_SD_3_0 |
- SDHCI_VENDOR_MISC_CNTRL_ENABLE_SDR104_SUPPORT |
- SDHCI_VENDOR_MISC_CNTRL_ENABLE_SDR50_SUPPORT;
- sdhci_writew(sdhci, misc_ctrl, SDHCI_VENDOR_MISC_CNTRL);
-}
-#endif
-
-#ifdef CONFIG_ARCH_TEGRA_11x_SOC
-static void tegra11x_sdhci_post_reset_init(struct sdhci_host *sdhci)
-{
- u16 misc_ctrl;
- u32 vendor_ctrl;
- struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
- struct tegra_sdhci_host *tegra_host = pltfm_host->priv;
- struct platform_device *pdev = to_platform_device(mmc_dev(sdhci->mmc));
- struct tegra_sdhci_platform_data *plat;
-
- plat = pdev->dev.platform_data;
- /* Set the base clock frequency */
- vendor_ctrl = sdhci_readl(sdhci, SDHCI_VENDOR_CLOCK_CNTRL);
- vendor_ctrl &= ~(0xFF << SDHCI_VENDOR_CLOCK_CNTRL_BASE_CLK_FREQ_SHIFT);
- vendor_ctrl |= (tegra3_sdhost_max_clk[tegra_host->instance] / 1000000) <<
- SDHCI_VENDOR_CLOCK_CNTRL_BASE_CLK_FREQ_SHIFT;
- vendor_ctrl |= SDHCI_VENDOR_CLOCK_CNTRL_PADPIPE_CLKEN_OVERRIDE;
- /* Set tap delay */
- if (plat->tap_delay) {
- vendor_ctrl &= ~(0xFF <<
- SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT);
- vendor_ctrl |= (plat->tap_delay <<
- SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT);
- }
- sdhci_writel(sdhci, vendor_ctrl, SDHCI_VENDOR_CLOCK_CNTRL);
-
- /* Enable SDHOST v3.0 support */
- misc_ctrl = sdhci_readw(sdhci, SDHCI_VENDOR_MISC_CNTRL);
- misc_ctrl |= SDHCI_VENDOR_MISC_CNTRL_ENABLE_SDR104_SUPPORT |
- SDHCI_VENDOR_MISC_CNTRL_ENABLE_SDR50_SUPPORT |
- SDHCI_VENDOR_MISC_CNTRL_ENABLE_DDR50_SUPPORT;
- sdhci_writew(sdhci, misc_ctrl, SDHCI_VENDOR_MISC_CNTRL);
-}
#endif
static int tegra_sdhci_set_uhs_signaling(struct sdhci_host *host,
static void tegra_sdhci_reset_exit(struct sdhci_host *sdhci, u8 mask)
{
+ u16 misc_ctrl;
+ u32 vendor_ctrl;
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;
+ const struct sdhci_tegra_soc_data *soc_data = tegra_host->soc_data;
if (mask & SDHCI_RESET_ALL) {
- if (tegra_host->hw_ops->sdhost_init)
- tegra_host->hw_ops->sdhost_init(sdhci);
+ if (tegra_host->sd_stat_head != NULL) {
+ tegra_host->sd_stat_head->data_crc_count = 0;
+ tegra_host->sd_stat_head->cmd_crc_count = 0;
+ tegra_host->sd_stat_head->data_to_count = 0;
+ tegra_host->sd_stat_head->cmd_to_count = 0;
+ }
+ vendor_ctrl = sdhci_readl(sdhci, SDHCI_VNDR_CLK_CTRL);
+ if (soc_data->nvquirks & NVQUIRK_ENABLE_PADPIPE_CLKEN) {
+ vendor_ctrl |=
+ SDHCI_VNDR_CLK_CTRL_PADPIPE_CLKEN_OVERRIDE;
+ }
+ if (soc_data->nvquirks & NVQUIRK_DISABLE_SPI_MODE_CLKEN) {
+ vendor_ctrl &=
+ ~SDHCI_VNDR_CLK_CTRL_SPI_MODE_CLKEN_OVERRIDE;
+ }
+ if (soc_data->nvquirks & NVQUIRK_SET_TAP_DELAY) {
+ if ((tegra_host->tuning_status == TUNING_STATUS_DONE) &&
+ (sdhci->mmc->pm_flags & MMC_PM_KEEP_POWER)) {
+ vendor_ctrl &= ~(0xFF <<
+ SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT);
+ vendor_ctrl |=
+ (tegra_host->tuning_data.best_tap_value
+ << SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT);
+ } else {
+ if (plat->tap_delay) {
+ vendor_ctrl &= ~(0xFF <<
+ SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT);
+ vendor_ctrl |= (plat->tap_delay <<
+ SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT);
+ }
+ }
+ }
+ if (soc_data->nvquirks & NVQUIRK_SET_TRIM_DELAY) {
+ if (plat->trim_delay) {
+ vendor_ctrl &= ~(0x1F <<
+ SDHCI_VNDR_CLK_CTRL_TRIM_VALUE_SHIFT);
+ vendor_ctrl |= (plat->trim_delay <<
+ SDHCI_VNDR_CLK_CTRL_TRIM_VALUE_SHIFT);
+ }
+ }
+ if (soc_data->nvquirks & NVQUIRK_ENABLE_SDR50_TUNING)
+ vendor_ctrl |= SDHCI_VNDR_CLK_CTRL_SDR50_TUNING;
+ sdhci_writel(sdhci, vendor_ctrl, SDHCI_VNDR_CLK_CTRL);
+
+ misc_ctrl = sdhci_readw(sdhci, SDHCI_VNDR_MISC_CTRL);
+ if (soc_data->nvquirks & NVQUIRK_ENABLE_SD_3_0)
+ misc_ctrl |= SDHCI_VNDR_MISC_CTRL_ENABLE_SD_3_0;
+ if (soc_data->nvquirks & NVQUIRK_ENABLE_SDR104) {
+ misc_ctrl |=
+ SDHCI_VNDR_MISC_CTRL_ENABLE_SDR104_SUPPORT;
+ }
+ if (soc_data->nvquirks & NVQUIRK_ENABLE_SDR50) {
+ misc_ctrl |=
+ SDHCI_VNDR_MISC_CTRL_ENABLE_SDR50_SUPPORT;
+ }
+ /* Enable DDR mode support only for SDMMC4 */
+ if (soc_data->nvquirks & NVQUIRK_ENABLE_DDR50) {
+ if (tegra_host->instance == 3) {
+ misc_ctrl |=
+ SDHCI_VNDR_MISC_CTRL_ENABLE_DDR50_SUPPORT;
+ }
+ }
+ if (soc_data->nvquirks & NVQUIRK_INFINITE_ERASE_TIMEOUT) {
+ misc_ctrl |=
+ SDHCI_VNDR_MISC_CTRL_INFINITE_ERASE_TIMEOUT;
+ }
+ sdhci_writew(sdhci, misc_ctrl, SDHCI_VNDR_MISC_CTRL);
+
+ /* Mask the support for any UHS modes if specified */
+ if (plat->uhs_mask & MMC_UHS_MASK_SDR104)
+ sdhci->mmc->caps &= ~MMC_CAP_UHS_SDR104;
+
+ if (plat->uhs_mask & MMC_UHS_MASK_DDR50)
+ sdhci->mmc->caps &= ~MMC_CAP_UHS_DDR50;
+
+ if (plat->uhs_mask & MMC_UHS_MASK_SDR50)
+ sdhci->mmc->caps &= ~MMC_CAP_UHS_SDR50;
+
+ if (plat->uhs_mask & MMC_UHS_MASK_SDR25)
+ sdhci->mmc->caps &= ~MMC_CAP_UHS_SDR25;
+
+ if (plat->uhs_mask & MMC_UHS_MASK_SDR12)
+ sdhci->mmc->caps &= ~MMC_CAP_UHS_SDR12;
+
+ if (plat->uhs_mask & MMC_MASK_HS200)
+ sdhci->mmc->caps2 &= ~MMC_CAP2_HS200;
}
}
if (tegra_host->vdd_slot_reg)
regulator_disable(tegra_host->vdd_slot_reg);
tegra_host->is_rail_enabled = 0;
- }
+ }
+ /*
+ * Set retune request as tuning should be done next time
+ * a card is inserted.
+ */
+ tegra_host->tuning_status = TUNING_STATUS_RETUNE;
}
tasklet_schedule(&sdhost->card_tasklet);
return 0;
}
+/*
+* 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;
+
+ 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_c_freq;
+ unsigned long pll_p_freq;
+ int rc;
+
+ pll_c_freq = get_nearest_clock_freq(pll_c_rate, desired_rate);
+ pll_p_freq = get_nearest_clock_freq(pll_p_rate, desired_rate);
+
+ if (pll_c_freq > pll_p_freq) {
+ if (!tegra_host->is_parent_pllc) {
+ parent_clk = pll_c;
+ tegra_host->is_parent_pllc = true;
+ } else
+ return;
+ } else if (tegra_host->is_parent_pllc) {
+ parent_clk = pll_p;
+ tegra_host->is_parent_pllc = false;
+ } else
+ return;
+
+ 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)
{
unsigned int clk_rate;
unsigned int emc_clk;
- /*
- * In SDR50 mode, run the sdmmc controller at freq greater than
- * 104MHz to ensure the core voltage is at 1.2V. If the core voltage
- * is below 1.2V, CRC errors would occur during data transfers.
- */
- if (sdhci->mmc->card &&
- (mmc_card_ddr_mode(sdhci->mmc->card) ||
- (sdhci->mmc->ios.timing == MMC_TIMING_UHS_SDR50))) {
+ if (sdhci->mmc->ios.timing == MMC_TIMING_UHS_DDR50) {
/*
* In ddr mode, tegra sdmmc controller clock frequency
* should be double the card clock frequency.
} else {
clk_rate = clock * 2;
}
+ } else if (sdhci->mmc->ios.timing == MMC_TIMING_UHS_SDR50) {
+ /*
+ * In SDR50 mode, run the sdmmc controller at freq greater than
+ * 104MHz to ensure the core voltage is at 1.2V. If the core voltage
+ * is below 1.2V, CRC errors would occur during data transfers.
+ */
+ clk_rate = clock * 2;
} else {
if (clock <= tegra_sdhost_min_freq)
clk_rate = tegra_sdhost_min_freq;
(clk_rate > tegra_host->max_clk_limit))
clk_rate = tegra_host->max_clk_limit;
+ 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);
+#ifdef CONFIG_TEGRA_FPGA_PLATFORM
+ /* FPGA supports 26MHz of clock for SDMMC. */
+ sdhci->max_clk = 26000000;
+#endif
}
-
#ifdef CONFIG_ARCH_TEGRA_3x_SOC
static void tegra_3x_sdhci_set_card_clock(struct sdhci_host *sdhci, unsigned int clock)
{
out:
sdhci->clock = clock;
}
-#endif
+#endif /* #ifdef CONFIG_ARCH_TEGRA_3x_SOC */
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;
+ struct platform_device *pdev = to_platform_device(mmc_dev(sdhci->mmc));
u8 ctrl;
pr_debug("%s %s %u enabled=%u\n", __func__,
if (clock) {
/* bring out sd instance from io dpd mode */
- tegra_io_dpd_disable(tegra_host->dpd);
+ if (tegra_host->dpd) {
+ mutex_lock(&tegra_host->dpd->delay_lock);
+ cancel_delayed_work_sync(&tegra_host->dpd->delay_dpd);
+ tegra_io_dpd_disable(tegra_host->dpd);
+ mutex_unlock(&tegra_host->dpd->delay_lock);
+ }
if (!tegra_host->clk_enabled) {
- clk_enable(pltfm_host->clk);
- ctrl = sdhci_readb(sdhci, SDHCI_VENDOR_CLOCK_CNTRL);
- ctrl |= SDHCI_VENDOR_CLOCK_CNTRL_SDMMC_CLK;
- sdhci_writeb(sdhci, ctrl, SDHCI_VENDOR_CLOCK_CNTRL);
+ pm_runtime_get_sync(&pdev->dev);
+ clk_prepare_enable(pltfm_host->clk);
+ ctrl = sdhci_readb(sdhci, SDHCI_VNDR_CLK_CTRL);
+ ctrl |= SDHCI_VNDR_CLK_CTRL_SDMMC_CLK;
+ sdhci_writeb(sdhci, ctrl, SDHCI_VNDR_CLK_CTRL);
tegra_host->clk_enabled = true;
}
tegra_sdhci_set_clk_rate(sdhci, clock);
} else if (!clock && tegra_host->clk_enabled) {
if (tegra_host->hw_ops->set_card_clock)
tegra_host->hw_ops->set_card_clock(sdhci, clock);
- ctrl = sdhci_readb(sdhci, SDHCI_VENDOR_CLOCK_CNTRL);
- ctrl &= ~SDHCI_VENDOR_CLOCK_CNTRL_SDMMC_CLK;
- sdhci_writeb(sdhci, ctrl, SDHCI_VENDOR_CLOCK_CNTRL);
- clk_disable(pltfm_host->clk);
+ ctrl = sdhci_readb(sdhci, SDHCI_VNDR_CLK_CTRL);
+ ctrl &= ~SDHCI_VNDR_CLK_CTRL_SDMMC_CLK;
+ sdhci_writeb(sdhci, ctrl, SDHCI_VNDR_CLK_CTRL);
+ clk_disable_unprepare(pltfm_host->clk);
+ pm_runtime_put_sync(&pdev->dev);
tegra_host->clk_enabled = false;
/* io dpd enable call for sd instance */
- tegra_io_dpd_enable(tegra_host->dpd);
+
+ if (tegra_host->dpd) {
+ mutex_lock(&tegra_host->dpd->delay_lock);
+ if (tegra_host->dpd->need_delay_dpd) {
+ schedule_delayed_work(
+ &tegra_host->dpd->delay_dpd,
+ msecs_to_jiffies(100));
+ } else {
+ tegra_io_dpd_enable(tegra_host->dpd);
+ }
+ mutex_unlock(&tegra_host->dpd->delay_lock);
+ }
+ }
+}
+static void tegra_sdhci_do_calibration(struct sdhci_host *sdhci)
+{
+ 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;
+
+ /* No Calibration for sdmmc4 */
+ if (tegra_host->instance == 3)
+ return;
+
+ if (unlikely(soc_data->nvquirks & NVQUIRK_DISABLE_AUTO_CALIBRATION))
+ return;
+
+ val = sdhci_readl(sdhci, SDMMC_SDMEMCOMPPADCTRL);
+ val &= ~SDMMC_SDMEMCOMPPADCTRL_VREF_SEL_MASK;
+ val |= 0x7;
+ sdhci_writel(sdhci, val, SDMMC_SDMEMCOMPPADCTRL);
+
+ /* Enable Auto Calibration*/
+ val = sdhci_readl(sdhci, SDMMC_AUTO_CAL_CONFIG);
+ val |= SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_ENABLE;
+ val |= SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_START;
+ if (unlikely(soc_data->nvquirks & NVQUIRK_SET_CALIBRATION_OFFSETS)) {
+ /* Program Auto cal PD offset(bits 8:14) */
+ val &= ~(0x7F <<
+ SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET_SHIFT);
+ val |= (SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET <<
+ SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET_SHIFT);
+ /* Program Auto cal PU offset(bits 0:6) */
+ val &= ~0x7F;
+ val |= SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET;
+ }
+ sdhci_writel(sdhci, val, SDMMC_AUTO_CAL_CONFIG);
+
+ /* 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");
+
+ /* Disable Auto calibration */
+ val = sdhci_readl(sdhci, SDMMC_AUTO_CAL_CONFIG);
+ val &= ~SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_ENABLE;
+ sdhci_writel(sdhci, val, SDMMC_AUTO_CAL_CONFIG);
+
+ if (unlikely(soc_data->nvquirks & NVQUIRK_SET_DRIVE_STRENGTH)) {
+ unsigned int pulldown_code;
+ unsigned int pullup_code;
+ int pg;
+ int err;
+
+ pg = tegra_drive_get_pingroup(mmc_dev(sdhci->mmc));
+ if (pg != -1) {
+ /* Get the pull down codes from auto cal status reg */
+ pulldown_code = (
+ sdhci_readl(sdhci, SDMMC_AUTO_CAL_STATUS) >>
+ SDMMC_AUTO_CAL_STATUS_PULLDOWN_OFFSET);
+ /* Set the pull down in the pinmux reg */
+ err = tegra_drive_pinmux_set_pull_down(pg,
+ pulldown_code);
+ if (err)
+ dev_err(mmc_dev(sdhci->mmc),
+ "Failed to set pulldown codes %d err %d\n",
+ pulldown_code, err);
+
+ /* Calculate the pull up codes */
+ pullup_code = pulldown_code + PULLUP_ADJUSTMENT_OFFSET;
+ if (pullup_code >= TEGRA_MAX_PULL)
+ pullup_code = TEGRA_MAX_PULL - 1;
+ /* Set the pull up code in the pinmux reg */
+ err = tegra_drive_pinmux_set_pull_up(pg, pullup_code);
+ if (err)
+ dev_err(mmc_dev(sdhci->mmc),
+ "Failed to set pullup codes %d err %d\n",
+ pullup_code, err);
+ }
}
}
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
struct sdhci_tegra *tegra_host = pltfm_host->priv;
- unsigned int min_uV = SDHOST_HIGH_VOLT_MIN;
- unsigned int max_uV = SDHOST_HIGH_VOLT_MAX;
+ unsigned int min_uV = tegra_host->vddio_min_uv;
+ unsigned int max_uV = tegra_host->vddio_max_uv;
unsigned int rc = 0;
u16 clk, ctrl;
- unsigned int val;
+
ctrl = sdhci_readw(sdhci, SDHCI_HOST_CONTROL2);
if (signal_voltage == MMC_SIGNAL_VOLTAGE_180) {
if (rc) {
dev_err(mmc_dev(sdhci->mmc), "switching to 1.8V"
"failed . Switching back to 3.3V\n");
- regulator_set_voltage(tegra_host->vdd_io_reg,
+ rc = regulator_set_voltage(tegra_host->vdd_io_reg,
SDHOST_HIGH_VOLT_MIN,
SDHOST_HIGH_VOLT_MAX);
- goto out;
+ if (rc)
+ dev_err(mmc_dev(sdhci->mmc),
+ "switching to 3.3V also failed\n");
}
}
/* Wait for 1 msec after enabling clock */
mdelay(1);
- if (signal_voltage == MMC_SIGNAL_VOLTAGE_180) {
- /* Do Auto Calibration for 1.8V signal voltage */
- val = sdhci_readl(sdhci, SDMMC_AUTO_CAL_CONFIG);
- val |= SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_ENABLE;
- /* Program Auto cal PD offset(bits 8:14) */
- val &= ~(0x7F <<
- SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET_SHIFT);
- val |= (SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET <<
- SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET_SHIFT);
- /* Program Auto cal PU offset(bits 0:6) */
- val &= ~0x7F;
- val |= SDMMC_AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET;
- sdhci_writel(sdhci, val, SDMMC_AUTO_CAL_CONFIG);
-
- val = sdhci_readl(sdhci, SDMMC_SDMEMCOMPPADCTRL);
- val &= ~SDMMC_SDMEMCOMPPADCTRL_VREF_SEL_MASK;
- val |= 0x7;
- sdhci_writel(sdhci, val, SDMMC_SDMEMCOMPPADCTRL);
- }
-
- return rc;
-out:
- /* Enable the card clock */
- clk |= SDHCI_CLOCK_CARD_EN;
- sdhci_writew(sdhci, clk, SDHCI_CLOCK_CONTROL);
-
- /* Wait for 1 msec for the clock to stabilize */
- mdelay(1);
-
return rc;
}
timeout--;
mdelay(1);
}
+
+ tegra_sdhci_reset_exit(sdhci, mask);
}
static void sdhci_tegra_set_tap_delay(struct sdhci_host *sdhci,
/* Max tap delay value is 255 */
BUG_ON(tap_delay > MAX_TAP_VALUES);
- vendor_ctrl = sdhci_readl(sdhci, SDHCI_VENDOR_CLOCK_CNTRL);
- vendor_ctrl &= ~(0xFF << SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT);
- vendor_ctrl |= (tap_delay << SDHCI_VENDOR_CLOCK_CNTRL_TAP_VALUE_SHIFT);
- sdhci_writel(sdhci, vendor_ctrl, SDHCI_VENDOR_CLOCK_CNTRL);
+ vendor_ctrl = sdhci_readl(sdhci, SDHCI_VNDR_CLK_CTRL);
+ vendor_ctrl &= ~(0xFF << SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT);
+ vendor_ctrl |= (tap_delay << SDHCI_VNDR_CLK_CTRL_TAP_VALUE_SHIFT);
+ sdhci_writel(sdhci, vendor_ctrl, SDHCI_VNDR_CLK_CTRL);
}
-static void sdhci_tegra_clear_set_irqs(struct sdhci_host *host,
- u32 clear, u32 set)
+static int sdhci_tegra_sd_error_stats(struct sdhci_host *host, u32 int_status)
{
- u32 ier;
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ struct platform_device *pdev = to_platform_device(mmc_dev(host->mmc));
+ struct sdhci_tegra_sd_stats *head;
+
+ if (tegra_host->sd_stat_head == NULL) {
+ tegra_host->sd_stat_head = devm_kzalloc(&pdev->dev, sizeof(
+ struct sdhci_tegra_sd_stats),
+ GFP_KERNEL);
+ if (tegra_host->sd_stat_head == NULL)
+ return -ENOMEM;
+ }
+ head = tegra_host->sd_stat_head;
+ if (int_status & SDHCI_INT_DATA_CRC)
+ head->data_crc_count++;
+ if (int_status & SDHCI_INT_CRC)
+ head->cmd_crc_count++;
+ if (int_status & SDHCI_INT_TIMEOUT)
+ head->cmd_to_count++;
+ if (int_status & SDHCI_INT_DATA_TIMEOUT)
+ head->data_to_count++;
+ return 0;
+}
+
+/*
+ * Calculation of best tap value for low frequencies(82MHz).
+ * X = Partial win, Y = Full win start, Z = Full win end.
+ * UI = Z - X.
+ * Full Window = Z - Y.
+ * Taps margin = mid-point of 1/2*(curr_freq/max_frequency)*UI
+ * = (1/2)*(1/2)*(82/200)*UI
+ * = (0.1025)*UI
+ * if Partial win<(0.22)*UI
+ * best tap = Y+(0.1025*UI)
+ * else
+ * best tap = (X-(Z-Y))+(0.1025*UI)
+ * If best tap<0, best tap = 0
+ */
+static void calculate_low_freq_tap_value(struct sdhci_host *sdhci)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ unsigned int curr_clock;
+ unsigned int max_clock;
+ int best_tap_value;
+ struct tap_window_data *tap_data;
+ struct tegra_tuning_data *tuning_data;
+
+ tuning_data = &tegra_host->tuning_data;
+ tap_data = tuning_data->tap_data[0];
+
+ if (tap_data->abandon_full_win) {
+ if (tap_data->abandon_partial_win) {
+ tuning_data->best_tap_value = 0;
+ return;
+ } else {
+ tuning_data->select_partial_win = true;
+ goto calculate_best_tap;
+ }
+ }
- ier = sdhci_readl(host, SDHCI_INT_ENABLE);
- ier &= ~clear;
- ier |= set;
- sdhci_writel(host, ier, SDHCI_INT_ENABLE);
- sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE);
+ tap_data->tuning_ui = tap_data->full_win_end - tap_data->partial_win;
+
+ /* Calculate the sampling point */
+ curr_clock = sdhci->max_clk / 1000000;
+ max_clock = uhs_max_freq_MHz[sdhci->mmc->ios.timing];
+ tap_data->sampling_point = ((tap_data->tuning_ui * curr_clock) /
+ max_clock);
+ tap_data->sampling_point >>= 2;
+
+ /*
+ * Check whether partial window should be used. Use partial window
+ * if partial window > 0.22(UI).
+ */
+ if ((!tap_data->abandon_partial_win) &&
+ (tap_data->partial_win > ((22 * tap_data->tuning_ui) / 100)))
+ tuning_data->select_partial_win = true;
+
+calculate_best_tap:
+ if (tuning_data->select_partial_win) {
+ best_tap_value = (tap_data->partial_win -
+ (tap_data->full_win_end - tap_data->full_win_begin)) +
+ tap_data->sampling_point;
+ tuning_data->best_tap_value = (best_tap_value < 0) ? 0 :
+ best_tap_value;
+ } else {
+ tuning_data->best_tap_value = tap_data->full_win_begin +
+ tap_data->sampling_point;
+ }
+}
+
+/*
+ * Calculation of best tap value for high frequencies(156MHz).
+ * Tap window data at 1.25V core voltage
+ * X = Partial win, Y = Full win start, Z = Full win end.
+ * Full Window = Z-Y.
+ * UI = Z-X.
+ * Tap_margin = (0.20375)UI
+ *
+ * Tap window data at 1.1V core voltage
+ * X' = Partial win, Y' = Full win start, Z' = Full win end.
+ * UI' = Z'-X'.
+ * Full Window' = Z'-Y'.
+ * Tap_margin' = (0.20375)UI'
+ *
+ * Full_window_tap=[(Z'-0.20375UI')+(Y+0.20375UI)]/2
+ * Partial_window_tap=[(X'-0.20375UI')+(X-(Z-Y)+0x20375UI)]/2
+ * if(Partial_window_tap < 0), Partial_window_tap=0
+ *
+ * Full_window_quality=[(Z'-0.20375UI')-(Y+0.20375UI)]/2
+ * Partial_window_quality=(X'-0.20375UI')-Partial_window_tap
+ * if(Full_window_quality>Partial_window_quality) choose full window,
+ * else choose partial window.
+ * If there is no margin window for both cases,
+ * best tap=(Y+Z')/2.
+ */
+static void calculate_high_freq_tap_value(struct sdhci_host *sdhci)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ unsigned int curr_clock;
+ unsigned int max_clock;
+ struct tap_window_data *vmax_tap_data;
+ struct tap_window_data *vmid_tap_data;
+ struct tegra_tuning_data *tuning_data;
+ unsigned int full_win_tap;
+ int partial_win_start;
+ int partial_win_tap;
+ int full_win_quality;
+ int partial_win_quality;
+
+ tuning_data = &tegra_host->tuning_data;
+ vmax_tap_data = tuning_data->tap_data[0];
+ vmid_tap_data = tuning_data->tap_data[1];
+
+ curr_clock = sdhci->max_clk / 1000000;
+ max_clock = uhs_max_freq_MHz[sdhci->mmc->ios.timing];
+
+ /*
+ * Calculate the tuning_ui and sampling points for tap windows found
+ * at all core voltages.
+ */
+ vmax_tap_data->tuning_ui = vmax_tap_data->full_win_end -
+ vmax_tap_data->partial_win;
+ vmax_tap_data->sampling_point =
+ (vmax_tap_data->tuning_ui * curr_clock) / max_clock;
+ vmax_tap_data->sampling_point >>= 2;
+
+ vmid_tap_data->tuning_ui = vmid_tap_data->full_win_end -
+ vmid_tap_data->partial_win;
+ vmid_tap_data->sampling_point =
+ (vmid_tap_data->tuning_ui * curr_clock) / max_clock;
+ vmid_tap_data->sampling_point >>= 2;
+
+ full_win_tap = ((vmid_tap_data->full_win_end -
+ vmid_tap_data->sampling_point) +
+ (vmax_tap_data->full_win_begin +
+ vmax_tap_data->sampling_point));
+ full_win_tap >>= 1;
+ full_win_quality = (vmid_tap_data->full_win_end -
+ vmid_tap_data->sampling_point) -
+ (vmax_tap_data->full_win_begin +
+ vmax_tap_data->sampling_point);
+ full_win_quality >>= 1;
+
+ partial_win_start = (vmax_tap_data->partial_win -
+ (vmax_tap_data->full_win_end -
+ vmax_tap_data->full_win_begin));
+ partial_win_tap = ((vmid_tap_data->partial_win -
+ vmid_tap_data->sampling_point) +
+ (partial_win_start + vmax_tap_data->sampling_point));
+ partial_win_tap >>= 1;
+ if (partial_win_tap < 0)
+ partial_win_tap = 0;
+ partial_win_quality = (vmid_tap_data->partial_win -
+ vmid_tap_data->sampling_point) - partial_win_tap;
+
+ if ((full_win_quality <= 0) && (partial_win_quality)) {
+ dev_warn(mmc_dev(sdhci->mmc),
+ "No margin window for both windows\n");
+ tuning_data->best_tap_value = vmax_tap_data->full_win_begin +
+ vmid_tap_data->full_win_end;
+ tuning_data->best_tap_value >>= 1;
+ } else {
+ if (full_win_quality > partial_win_quality) {
+ tuning_data->best_tap_value = full_win_tap;
+ } else {
+ tuning_data->best_tap_value = partial_win_tap;
+ tuning_data->select_partial_win = true;
+ }
+ }
}
static int sdhci_tegra_run_frequency_tuning(struct sdhci_host *sdhci)
{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
int err = 0;
u8 ctrl;
- u32 ier;
u32 mask;
unsigned int timeout = 10;
int flags;
u32 intstatus;
- /*
- * As per the Host Controller spec v3.00, tuning command
- * generates Buffer Read Ready interrupt only, so enable that.
- */
- ier = sdhci_readl(sdhci, SDHCI_INT_ENABLE);
- sdhci_tegra_clear_set_irqs(sdhci, ier, SDHCI_INT_DATA_AVAIL |
- SDHCI_INT_DATA_CRC);
-
mask = SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT;
while (sdhci_readl(sdhci, SDHCI_PRESENT_STATE) & mask) {
if (timeout == 0) {
* In response to CMD19, the card sends 64 bytes of tuning
* block to the Host Controller. So we set the block size
* to 64 here.
+ * In response to CMD21, the card sends 128 bytes of tuning
+ * block for MMC_BUS_WIDTH_8 and 64 bytes for MMC_BUS_WIDTH_4
+ * to the Host Controller. So we set the block size to 64 here.
*/
- sdhci_writew(sdhci, SDHCI_MAKE_BLKSZ(7, 64), SDHCI_BLOCK_SIZE);
+ sdhci_writew(sdhci, SDHCI_MAKE_BLKSZ(7, tegra_host->tuning_bsize),
+ SDHCI_BLOCK_SIZE);
sdhci_writeb(sdhci, 0xE, SDHCI_TIMEOUT_CONTROL);
flags = SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | SDHCI_CMD_DATA;
/* Issue the command */
sdhci_writew(sdhci, SDHCI_MAKE_CMD(
- SD_SEND_TUNING_PATTERN, flags), SDHCI_COMMAND);
+ tegra_host->tuning_opcode, flags), SDHCI_COMMAND);
timeout = 5;
do {
}
mdelay(1);
out:
- sdhci_tegra_clear_set_irqs(sdhci, SDHCI_INT_DATA_AVAIL, ier);
return err;
}
-static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci)
+static int sdhci_tegra_scan_tap_values(struct sdhci_host *sdhci,
+ unsigned int starting_tap, bool expect_failure)
{
+ unsigned int tap_value = starting_tap;
+ int err;
+
+ do {
+ /* Set the tap delay */
+ sdhci_tegra_set_tap_delay(sdhci, tap_value);
+
+ /* Run frequency tuning */
+ err = sdhci_tegra_run_frequency_tuning(sdhci);
+ if ((expect_failure && !err) ||
+ (!expect_failure && err))
+ break;
+ tap_value++;
+ } while (tap_value <= MAX_TAP_VALUES);
+
+ return tap_value;
+}
+
+/*
+ * While scanning for tap values, first get the partial window followed by the
+ * full window. Note that, when scanning for full win start, tuning has to be
+ * run until a passing tap value is found. Hence, failure is expected during
+ * this process and ignored.
+ */
+static int sdhci_tegra_get_tap_window_data(struct sdhci_host *sdhci,
+ struct tap_window_data *tap_data)
+{
+ unsigned int tap_value;
+ int err = 0;
+
+ if (!tap_data) {
+ dev_err(mmc_dev(sdhci->mmc), "Invalid tap data\n");
+ return -ENODATA;
+ }
+
+ /* Get the partial window data */
+ tap_value = 0;
+ tap_value = sdhci_tegra_scan_tap_values(sdhci, tap_value, false);
+ if (!tap_value) {
+ tap_data->abandon_partial_win = true;
+ tap_data->partial_win = 0;
+ } else if (tap_value > MAX_TAP_VALUES) {
+ /*
+ * If tap value is more than 0xFF, we have hit the miracle case
+ * of all tap values passing. Discard full window as passing
+ * window has covered all taps.
+ */
+ tap_data->partial_win = MAX_TAP_VALUES;
+ tap_data->abandon_full_win = true;
+ goto out;
+ } else {
+ tap_data->partial_win = tap_value - 1;
+ if (tap_value == MAX_TAP_VALUES) {
+ /* All tap values exhausted. No full window */
+ tap_data->abandon_full_win = true;
+ goto out;
+ }
+ }
+
+ /* Get the full window start */
+ tap_value++;
+ tap_value = sdhci_tegra_scan_tap_values(sdhci, tap_value, true);
+ if (tap_value > MAX_TAP_VALUES) {
+ /* All tap values exhausted. No full window */
+ tap_data->abandon_full_win = true;
+ goto out;
+ } else {
+ tap_data->full_win_begin = tap_value;
+ /*
+ * If full win start is 0xFF, then set that as full win end
+ * and exit.
+ */
+ if (tap_value == MAX_TAP_VALUES) {
+ tap_data->full_win_end = tap_value;
+ goto out;
+ }
+ }
+
+ /* Get the full window end */
+ tap_value++;
+ tap_value = sdhci_tegra_scan_tap_values(sdhci, tap_value, false);
+ tap_data->full_win_end = tap_value - 1;
+ if (tap_value > MAX_TAP_VALUES)
+ tap_data->full_win_end = MAX_TAP_VALUES;
+out:
+ /*
+ * Mark tuning as failed if both partial and full windows are
+ * abandoned.
+ */
+ if (tap_data->abandon_partial_win && tap_data->abandon_full_win)
+ err = -EIO;
+ return err;
+}
+
+static int sdhci_tegra_execute_tuning(struct sdhci_host *sdhci, u32 opcode)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ struct tegra_tuning_data *tuning_data;
+ struct tap_window_data *tap_data;
int err;
u16 ctrl_2;
- u8 *tap_delay_status;
- unsigned int i = 0;
- unsigned int temp_low_pass_tap = 0;
- unsigned int temp_pass_window = 0;
- unsigned int best_low_pass_tap = 0;
- unsigned int best_pass_window = 0;
+ u32 ier;
+ unsigned int freq_band;
+ unsigned int i;
+ unsigned int voltage;
/* Tuning is valid only in SDR104 and SDR50 modes */
ctrl_2 = sdhci_readw(sdhci, SDHCI_HOST_CONTROL2);
(sdhci->flags & SDHCI_SDR50_NEEDS_TUNING))))
return 0;
- tap_delay_status = kzalloc(MAX_TAP_VALUES, GFP_KERNEL);
- if (tap_delay_status == NULL) {
- dev_err(mmc_dev(sdhci->mmc), "failed to allocate memory"
- "for storing tap_delay_status\n");
- err = -ENOMEM;
- goto out;
- }
+ /* Tuning should be done only for MMC_BUS_WIDTH_8 and MMC_BUS_WIDTH_4 */
+ if (sdhci->mmc->ios.bus_width == MMC_BUS_WIDTH_8)
+ tegra_host->tuning_bsize = MMC_TUNING_BLOCK_SIZE_BUS_WIDTH_8;
+ else if (sdhci->mmc->ios.bus_width == MMC_BUS_WIDTH_4)
+ tegra_host->tuning_bsize = MMC_TUNING_BLOCK_SIZE_BUS_WIDTH_4;
+ else
+ return -EINVAL;
+
+ /* Set the tuning command to be used */
+ tegra_host->tuning_opcode = opcode;
/*
- * Set each tap delay value and run frequency tuning. After each
- * run, update the tap delay status as working or not working.
+ * Disable all interrupts signalling.Enable interrupt status
+ * detection for buffer read ready and data crc. We use
+ * polling for tuning as it involves less overhead.
*/
- do {
- /* Set the tap delay */
- sdhci_tegra_set_tap_delay(sdhci, i);
-
- /* Run frequency tuning */
- err = sdhci_tegra_run_frequency_tuning(sdhci);
-
- /* Update whether the tap delay worked or not */
- tap_delay_status[i] = (err) ? 0: 1;
- i++;
- } while (i < 0xFF);
-
- /* Find the best possible tap range */
- for (i = 0; i < 0xFF; i++) {
- temp_pass_window = 0;
+ ier = sdhci_readl(sdhci, SDHCI_INT_ENABLE);
+ sdhci_writel(sdhci, 0, SDHCI_SIGNAL_ENABLE);
+ sdhci_writel(sdhci, SDHCI_INT_DATA_AVAIL |
+ SDHCI_INT_DATA_CRC, SDHCI_INT_ENABLE);
- /* Find the first passing tap in the current window */
- if (tap_delay_status[i]) {
- temp_low_pass_tap = i;
+ /*
+ * If tuning is already done and retune request is not set, then skip
+ * best tap value calculation and use the old best tap value.
+ */
+ if (tegra_host->tuning_status == TUNING_STATUS_DONE)
+ goto set_best_tap;
- /* Find the pass window */
- do {
- temp_pass_window++;
- i++;
- if (i > 0xFF)
- break;
- } while (tap_delay_status[i]);
+ if (sdhci->max_clk > tuning_params[TUNING_LOW_FREQ].freq_hz)
+ freq_band = TUNING_HIGH_FREQ;
+ else
+ freq_band = TUNING_LOW_FREQ;
- if ((temp_pass_window > best_pass_window) && (temp_pass_window > 1)){
- best_low_pass_tap = temp_low_pass_tap;
- best_pass_window = temp_pass_window;
+ /*
+ * Run tuning and get the passing tap window info for all frequencies
+ * and core voltages required to calculate the final tap value. The
+ * standard driver calls this platform specific tuning callback after
+ * holding a lock. The spinlock needs to be released when calling
+ * non-atomic context functions like regulator calls etc.
+ */
+ tuning_data = &tegra_host->tuning_data;
+ for (i = 0; i < tuning_params[freq_band].nr_voltages; i++) {
+ spin_unlock(&sdhci->lock);
+ if (!tuning_data->tap_data[i]) {
+ tuning_data->tap_data[i] = devm_kzalloc(
+ mmc_dev(sdhci->mmc),
+ sizeof(struct tap_window_data), GFP_KERNEL);
+ if (!tuning_data->tap_data[i]) {
+ err = -ENOMEM;
+ dev_err(mmc_dev(sdhci->mmc),
+ "Insufficient memory for tap window info\n");
+ spin_lock(&sdhci->lock);
+ goto out;
}
}
- }
+ tap_data = tuning_data->tap_data[i];
+
+ if (tegra_host->nominal_vcore_uV) {
+ if (!tegra_host->vcore_reg)
+ tegra_host->vcore_reg = regulator_get(
+ mmc_dev(sdhci->mmc), "vdd_core");
+ if (IS_ERR_OR_NULL(tegra_host->vcore_reg)) {
+ dev_info(mmc_dev(sdhci->mmc),
+ "No vdd_core %ld. Tuning might fail.\n",
+ PTR_ERR(tegra_host->vcore_reg));
+ tegra_host->vcore_reg = NULL;
+ } else {
+ voltage = tuning_params[freq_band].voltages[i];
+ if (voltage > tegra_host->nominal_vcore_uV)
+ voltage = tegra_host->nominal_vcore_uV;
+ err = regulator_set_voltage(
+ tegra_host->vcore_reg, voltage,
+ voltage);
+ if (err)
+ dev_err(mmc_dev(sdhci->mmc),
+ "Setting nominal core voltage failed\n");
+ }
+ }
+ spin_lock(&sdhci->lock);
+ /* Get the tuning window info */
+ err = sdhci_tegra_get_tap_window_data(sdhci, tap_data);
+ if (err) {
+ dev_err(mmc_dev(sdhci->mmc), "Failed to tuning window info\n");
+ goto out;
+ }
+ }
- pr_debug("%s: best pass tap window: start %d, end %d\n",
- mmc_hostname(sdhci->mmc), best_low_pass_tap,
- (best_low_pass_tap + best_pass_window));
+ /* Calculate best tap for current freq band */
+ if (freq_band == TUNING_LOW_FREQ)
+ calculate_low_freq_tap_value(sdhci);
+ else
+ calculate_high_freq_tap_value(sdhci);
- /* Set the best tap */
+set_best_tap:
sdhci_tegra_set_tap_delay(sdhci,
- (best_low_pass_tap + ((best_pass_window * 3) / 4)));
+ tegra_host->tuning_data.best_tap_value);
- /* Run frequency tuning */
+ /*
+ * Run tuning with the best tap value. If tuning fails, set the status
+ * for retuning next time enumeration is done.
+ */
err = sdhci_tegra_run_frequency_tuning(sdhci);
+ if (err)
+ tegra_host->tuning_status = TUNING_STATUS_RETUNE;
+ else
+ tegra_host->tuning_status = TUNING_STATUS_DONE;
out:
- if (tap_delay_status)
- kfree(tap_delay_status);
+ /* Enable the full range for core voltage if vcore_reg exists */
+ if (tegra_host->vcore_reg) {
+ spin_unlock(&sdhci->lock);
+ regulator_put(tegra_host->vcore_reg);
+ tegra_host->vcore_reg = NULL;
+ spin_lock(&sdhci->lock);
+ }
+ /* Enable interrupts. Enable full range for core voltage */
+ sdhci_writel(sdhci, ier, SDHCI_INT_ENABLE);
+ sdhci_writel(sdhci, ier, SDHCI_SIGNAL_ENABLE);
return err;
}
}
}
+ if (tegra_host->dpd) {
+ mutex_lock(&tegra_host->dpd->delay_lock);
+ tegra_host->dpd->need_delay_dpd = 1;
+ mutex_unlock(&tegra_host->dpd->delay_lock);
+ }
+
return 0;
}
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
struct sdhci_tegra *tegra_host = pltfm_host->priv;
+ struct platform_device *pdev;
+ struct tegra_sdhci_platform_data *plat;
+
+ pdev = to_platform_device(mmc_dev(sdhci->mmc));
+ plat = pdev->dev.platform_data;
+
+ if (gpio_is_valid(plat->cd_gpio))
+ tegra_host->card_present = (gpio_get_value(plat->cd_gpio) == 0);
/* Enable the power rails if any */
if (tegra_host->card_present) {
regulator_enable(tegra_host->vdd_slot_reg);
if (tegra_host->vdd_io_reg) {
regulator_enable(tegra_host->vdd_io_reg);
- tegra_sdhci_signal_voltage_switch(sdhci, MMC_SIGNAL_VOLTAGE_330);
+ if (plat->mmc_data.ocr_mask &
+ SDHOST_1V8_OCR_MASK)
+ tegra_sdhci_signal_voltage_switch(sdhci,
+ MMC_SIGNAL_VOLTAGE_180);
+ else
+ tegra_sdhci_signal_voltage_switch(sdhci,
+ MMC_SIGNAL_VOLTAGE_330);
}
tegra_host->is_rail_enabled = 1;
}
}
+
/* Setting the min identification clock of freq 400KHz */
tegra_sdhci_set_clock(sdhci, 400000);
return 0;
}
+static void tegra_sdhci_post_resume(struct sdhci_host *sdhci)
+{
+ struct sdhci_pltfm_host *pltfm_host = sdhci_priv(sdhci);
+ struct sdhci_tegra *tegra_host = pltfm_host->priv;
+
+ if (tegra_host->card_present) {
+ if (tegra_host->sd_detect_in_suspend)
+ tasklet_schedule(&sdhci->card_tasklet);
+ } else if (tegra_host->clk_enabled) {
+ /* Turn OFF the clocks if the card is not present */
+ tegra_sdhci_set_clock(sdhci, 0);
+ }
+}
+
+static void sdhci_tegra_error_stats_debugfs(struct sdhci_host *host)
+{
+ struct dentry *root;
+
+ root = debugfs_create_dir(dev_name(mmc_dev(host->mmc)), NULL);
+ if (IS_ERR(root))
+ /* Don't complain -- debugfs just isn't enabled */
+ return;
+ if (!root)
+ /* Complain -- debugfs is enabled, but it failed to
+ * create the directory. */
+ goto err_root;
+
+ host->debugfs_root = root;
+
+ if (!debugfs_create_file("error_stats", S_IRUSR, root, host,
+ &sdhci_host_fops))
+ goto err_node;
+ return;
+
+err_node:
+ debugfs_remove_recursive(root);
+ host->debugfs_root = NULL;
+err_root:
+ pr_err("%s: Failed to initialize debugfs functionality\n", __func__);
+ return;
+}
+
static struct sdhci_ops tegra_sdhci_ops = {
- .get_ro = tegra_sdhci_get_ro,
- .get_cd = tegra_sdhci_get_cd,
- .read_l = tegra_sdhci_readl,
- .read_w = tegra_sdhci_readw,
- .write_l = tegra_sdhci_writel,
- .platform_8bit_width = tegra_sdhci_8bit,
-#ifdef CONFIG_ARCH_TEGRA_3x_SOC
- .set_card_clock = tegra_3x_sdhci_set_card_clock,
+#ifndef CONFIG_ARCH_TEGRA_11x_SOC
+ .get_ro = tegra_sdhci_get_ro,
#endif
+ .get_cd = tegra_sdhci_get_cd,
+ .read_l = tegra_sdhci_readl,
+ .read_w = tegra_sdhci_readw,
+ .write_l = tegra_sdhci_writel,
+ .platform_8bit_width = tegra_sdhci_8bit,
.set_clock = tegra_sdhci_set_clock,
.suspend = tegra_sdhci_suspend,
.resume = tegra_sdhci_resume,
+ .platform_resume = tegra_sdhci_post_resume,
.platform_reset_exit = tegra_sdhci_reset_exit,
.set_uhs_signaling = tegra_sdhci_set_uhs_signaling,
.switch_signal_voltage = tegra_sdhci_signal_voltage_switch,
+ .switch_signal_voltage_exit = tegra_sdhci_do_calibration,
.execute_freq_tuning = sdhci_tegra_execute_tuning,
+ .sd_error_stats = sdhci_tegra_sd_error_stats,
};
static struct sdhci_pltfm_data sdhci_tegra20_pdata = {
#ifndef CONFIG_ARCH_TEGRA_2x_SOC
SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
SDHCI_QUIRK_NON_STD_VOLTAGE_SWITCHING |
+ SDHCI_QUIRK_NON_STANDARD_TUNING |
#endif
#ifdef CONFIG_ARCH_TEGRA_3x_SOC
SDHCI_QUIRK_NONSTANDARD_CLOCK |
- SDHCI_QUIRK_NON_STANDARD_TUNING |
-#endif
-#ifndef CONFIG_ARCH_TEGRA_11x_SOC
- SDHCI_QUIRK_BROKEN_CARD_DETECTION |
#endif
SDHCI_QUIRK_SINGLE_POWER_WRITE |
SDHCI_QUIRK_NO_HISPD_BIT |
SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC |
+ SDHCI_QUIRK_BROKEN_CARD_DETECTION |
SDHCI_QUIRK_NO_CALC_MAX_DISCARD_TO,
+ .quirks2 = SDHCI_QUIRK2_BROKEN_PRESET_VALUES,
.ops = &tegra_sdhci_ops,
};
static struct sdhci_tegra_soc_data soc_data_tegra20 = {
.pdata = &sdhci_tegra20_pdata,
.nvquirks = NVQUIRK_FORCE_SDHCI_SPEC_200 |
+#if !defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ NVQUIRK_ENABLE_PADPIPE_CLKEN |
+ NVQUIRK_DISABLE_SPI_MODE_CLKEN |
+ NVQUIRK_SET_TAP_DELAY |
+ NVQUIRK_ENABLE_SDR50_TUNING |
+ NVQUIRK_ENABLE_SDR50 |
+ NVQUIRK_ENABLE_SDR104 |
+#endif
+#if defined(CONFIG_ARCH_TEGRA_11x_SOC)
+ NVQUIRK_SET_DRIVE_STRENGTH |
+#endif
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ NVQUIRK_DISABLE_AUTO_CALIBRATION |
+#elif defined(CONFIG_ARCH_TEGRA_3x_SOC)
+ NVQUIRK_SET_CALIBRATION_OFFSETS |
+ NVQUIRK_ENABLE_SD_3_0 |
+#else
+ NVQUIRK_SET_TRIM_DELAY |
+ NVQUIRK_ENABLE_DDR50 |
+ NVQUIRK_INFINITE_ERASE_TIMEOUT |
+#endif
NVQUIRK_ENABLE_BLOCK_GAP_DET,
};
struct sdhci_pltfm_host *pltfm_host;
struct tegra_sdhci_platform_data *plat;
struct sdhci_tegra *tegra_host;
- struct clk *clk;
int rc;
match = of_match_device(sdhci_tegra_dt_match, &pdev->dev);
}
tegra_host->plat = plat;
+ tegra_host->sd_stat_head = NULL;
tegra_host->soc_data = soc_data;
pltfm_host->priv = tegra_host;
+ pll_c = clk_get_sys(NULL, "pll_c");
+ if (IS_ERR(pll_c)) {
+ rc = PTR_ERR(pll_c);
+ dev_err(mmc_dev(host->mmc),
+ "clk error in getting pll_c: %d\n", rc);
+ }
+
+ pll_p = clk_get_sys(NULL, "pll_p");
+ if (IS_ERR(pll_p)) {
+ rc = PTR_ERR(pll_p);
+ dev_err(mmc_dev(host->mmc),
+ "clk error in getting pll_p: %d\n", rc);
+ }
+
+ pll_c_rate = clk_get_rate(pll_c);
+ pll_p_rate = clk_get_rate(pll_p);
+
#ifdef CONFIG_MMC_EMBEDDED_SDIO
if (plat->mmc_data.embedded_sdio)
mmc_set_embedded_sdio_data(host->mmc,
"failed to allocate power gpio\n");
goto err_power_req;
}
- tegra_gpio_enable(plat->power_gpio);
gpio_direction_output(plat->power_gpio, 1);
}
"failed to allocate cd gpio\n");
goto err_cd_req;
}
- tegra_gpio_enable(plat->cd_gpio);
gpio_direction_input(plat->cd_gpio);
tegra_host->card_present = (gpio_get_value(plat->cd_gpio) == 0);
"failed to allocate wp gpio\n");
goto err_wp_req;
}
- tegra_gpio_enable(plat->wp_gpio);
gpio_direction_input(plat->wp_gpio);
}
if (!gpio_is_valid(plat->cd_gpio))
tegra_host->card_present = 1;
- if (!plat->mmc_data.built_in) {
- if (plat->mmc_data.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 {
- /*
- * Set the minV and maxV to default
- * voltage range of 2.7V - 3.6V
- */
- tegra_host->vddio_min_uv = SDHOST_HIGH_VOLT_MIN;
+ if (plat->mmc_data.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->mmc_data.ocr_mask & MMC_OCR_2V8_MASK) {
+ tegra_host->vddio_min_uv = SDHOST_HIGH_VOLT_2V8;
tegra_host->vddio_max_uv = SDHOST_HIGH_VOLT_MAX;
- }
- tegra_host->vdd_io_reg = regulator_get(mmc_dev(host->mmc), "vddio_sdmmc");
- if (IS_ERR_OR_NULL(tegra_host->vdd_io_reg)) {
- dev_info(mmc_dev(host->mmc), "%s regulator not found: %ld."
- "Assuming vddio_sdmmc is not required.\n",
- "vddio_sdmmc", PTR_ERR(tegra_host->vdd_io_reg));
+ } 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;
+ }
+
+ tegra_host->vdd_io_reg = regulator_get(mmc_dev(host->mmc),
+ "vddio_sdmmc");
+ if (IS_ERR_OR_NULL(tegra_host->vdd_io_reg)) {
+ dev_info(mmc_dev(host->mmc), "%s regulator not found: %ld."
+ "Assuming vddio_sdmmc is not required.\n",
+ "vddio_sdmmc", PTR_ERR(tegra_host->vdd_io_reg));
+ tegra_host->vdd_io_reg = NULL;
+ } else {
+ rc = regulator_set_voltage(tegra_host->vdd_io_reg,
+ tegra_host->vddio_min_uv,
+ tegra_host->vddio_max_uv);
+ if (rc) {
+ dev_err(mmc_dev(host->mmc), "%s regulator_set_voltage failed: %d",
+ "vddio_sdmmc", rc);
+ regulator_put(tegra_host->vdd_io_reg);
tegra_host->vdd_io_reg = NULL;
- } else {
- rc = regulator_set_voltage(tegra_host->vdd_io_reg,
- tegra_host->vddio_min_uv,
- tegra_host->vddio_max_uv);
- if (rc) {
- dev_err(mmc_dev(host->mmc), "%s regulator_set_voltage failed: %d",
- "vddio_sdmmc", rc);
- regulator_put(tegra_host->vdd_io_reg);
- tegra_host->vdd_io_reg = NULL;
- }
}
+ }
- tegra_host->vdd_slot_reg = regulator_get(mmc_dev(host->mmc), "vddio_sd_slot");
- if (IS_ERR_OR_NULL(tegra_host->vdd_slot_reg)) {
- dev_info(mmc_dev(host->mmc), "%s regulator not found: %ld."
- " Assuming vddio_sd_slot is not required.\n",
- "vddio_sd_slot", PTR_ERR(tegra_host->vdd_slot_reg));
- tegra_host->vdd_slot_reg = NULL;
- }
+ tegra_host->vdd_slot_reg = regulator_get(mmc_dev(host->mmc),
+ "vddio_sd_slot");
+ if (IS_ERR_OR_NULL(tegra_host->vdd_slot_reg)) {
+ dev_info(mmc_dev(host->mmc), "%s regulator not found: %ld."
+ " Assuming vddio_sd_slot is not required.\n",
+ "vddio_sd_slot", PTR_ERR(tegra_host->vdd_slot_reg));
+ tegra_host->vdd_slot_reg = NULL;
+ }
- if (tegra_host->card_present) {
- if (tegra_host->vdd_slot_reg)
- regulator_enable(tegra_host->vdd_slot_reg);
- if (tegra_host->vdd_io_reg)
- regulator_enable(tegra_host->vdd_io_reg);
- tegra_host->is_rail_enabled = 1;
- }
+ if (tegra_host->card_present) {
+ if (tegra_host->vdd_slot_reg)
+ regulator_enable(tegra_host->vdd_slot_reg);
+ if (tegra_host->vdd_io_reg)
+ regulator_enable(tegra_host->vdd_io_reg);
+ tegra_host->is_rail_enabled = 1;
}
- clk = clk_get(mmc_dev(host->mmc), NULL);
- if (IS_ERR(clk)) {
+ pm_runtime_enable(&pdev->dev);
+ pltfm_host->clk = clk_get(mmc_dev(host->mmc), NULL);
+ if (IS_ERR(pltfm_host->clk)) {
dev_err(mmc_dev(host->mmc), "clk err\n");
- rc = PTR_ERR(clk);
+ rc = PTR_ERR(pltfm_host->clk);
goto err_clk_get;
}
- rc = clk_enable(clk);
+
+ if (clk_get_parent(pltfm_host->clk) == pll_c)
+ tegra_host->is_parent_pllc = true;
+
+ pm_runtime_get_sync(&pdev->dev);
+ rc = clk_prepare_enable(pltfm_host->clk);
if (rc != 0)
goto err_clk_put;
clk_round_rate(tegra_host->emc_clk, ULONG_MAX);
}
- pltfm_host->clk = clk;
pltfm_host->priv = tegra_host;
tegra_host->clk_enabled = true;
tegra_host->max_clk_limit = plat->max_clk_limit;
tegra_host->ddr_clk_limit = plat->ddr_clk_limit;
+ tegra_host->sd_detect_in_suspend = plat->sd_detect_in_suspend;
tegra_host->instance = pdev->id;
tegra_host->dpd = tegra_io_dpd_get(mmc_dev(host->mmc));
host->mmc->pm_caps |= plat->pm_caps;
host->mmc->pm_flags |= plat->pm_flags;
- /*
- * tegra sd controller is not able to reset the command inhibit
- * bit for the mmc sleep/awake command
- */
- host->mmc->caps2 |= MMC_CAP2_NO_SLEEP_CMD;
-
host->mmc->caps |= MMC_CAP_ERASE;
/* enable 1/8V DDR capable */
host->mmc->caps |= MMC_CAP_1_8V_DDR;
if (plat->is_8bit)
host->mmc->caps |= MMC_CAP_8_BIT_DATA;
host->mmc->caps |= MMC_CAP_SDIO_IRQ;
-
host->mmc->pm_caps |= MMC_PM_KEEP_POWER | MMC_PM_IGNORE_PM_NOTIFY;
if (plat->mmc_data.built_in) {
host->mmc->caps |= MMC_CAP_NONREMOVABLE;
host->mmc->pm_flags |= MMC_PM_IGNORE_PM_NOTIFY;
#ifdef CONFIG_MMC_BKOPS
- host->mmc->caps |= MMC_CAP_BKOPS;
-#endif
-
-#ifdef CONFIG_MMC_EMBEDDED_SDIO
- /* Do not turn OFF embedded sdio cards as it support Wake on Wireless */
- if (plat->mmc_data.embedded_sdio)
- host->mmc->pm_flags |= MMC_PM_KEEP_POWER;
+ host->mmc->caps2 |= MMC_CAP2_BKOPS;
#endif
-
tegra_sdhost_min_freq = TEGRA_SDHOST_MIN_FREQ;
#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
tegra_host->hw_ops = &tegra_2x_sdhci_ops;
#else
tegra_host->hw_ops = &tegra_11x_sdhci_ops;
tegra_sdhost_std_freq = TEGRA3_SDHOST_STD_FREQ;
+ host->mmc->caps2 |= MMC_CAP2_HS200;
#endif
+
+ if (plat->nominal_vcore_uV)
+ tegra_host->nominal_vcore_uV = plat->nominal_vcore_uV;
+ host->edp_support = plat->edp_support ? true : false;
+ if (host->edp_support)
+ for (rc = 0; rc < SD_EDP_NUM_STATES; rc++)
+ host->edp_states[rc] = plat->edp_states[rc];
+
rc = sdhci_add_host(host);
+
+ sdhci_tegra_error_stats_debugfs(host);
if (rc)
goto err_add_host;
+ /* Enable async suspend/resume to reduce LP0 latency */
+ device_enable_async_suspend(&pdev->dev);
+
return 0;
err_add_host:
clk_put(tegra_host->emc_clk);
- clk_disable(pltfm_host->clk);
+ clk_disable_unprepare(pltfm_host->clk);
+ pm_runtime_put_sync(&pdev->dev);
err_clk_put:
clk_put(pltfm_host->clk);
err_clk_get:
- if (gpio_is_valid(plat->wp_gpio)) {
- tegra_gpio_disable(plat->wp_gpio);
+ if (gpio_is_valid(plat->wp_gpio))
gpio_free(plat->wp_gpio);
- }
err_wp_req:
if (gpio_is_valid(plat->cd_gpio))
free_irq(gpio_to_irq(plat->cd_gpio), host);
err_cd_irq_req:
- if (gpio_is_valid(plat->cd_gpio)) {
- tegra_gpio_disable(plat->cd_gpio);
+ if (gpio_is_valid(plat->cd_gpio))
gpio_free(plat->cd_gpio);
- }
err_cd_req:
- if (gpio_is_valid(plat->power_gpio)) {
- tegra_gpio_disable(plat->power_gpio);
+ if (gpio_is_valid(plat->power_gpio))
gpio_free(plat->power_gpio);
- }
err_power_req:
- kfree(tegra_host);
err_no_plat:
sdhci_pltfm_free(pdev);
return rc;
regulator_put(tegra_host->vdd_io_reg);
}
- if (gpio_is_valid(plat->wp_gpio)) {
- tegra_gpio_disable(plat->wp_gpio);
+ if (gpio_is_valid(plat->wp_gpio))
gpio_free(plat->wp_gpio);
- }
if (gpio_is_valid(plat->cd_gpio)) {
free_irq(gpio_to_irq(plat->cd_gpio), host);
- tegra_gpio_disable(plat->cd_gpio);
gpio_free(plat->cd_gpio);
}
- if (gpio_is_valid(plat->power_gpio)) {
- tegra_gpio_disable(plat->power_gpio);
+ if (gpio_is_valid(plat->power_gpio))
gpio_free(plat->power_gpio);
- }
- if (tegra_host->clk_enabled)
- clk_disable(pltfm_host->clk);
+ if (tegra_host->clk_enabled) {
+ clk_disable_unprepare(pltfm_host->clk);
+ pm_runtime_put_sync(&pdev->dev);
+ }
clk_put(pltfm_host->clk);
sdhci_pltfm_free(pdev);
- kfree(tegra_host);
return 0;
}