static int sdhci_tegra_probe()

in host/sdhci-tegra.c [1619:1778]


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;
	struct clk *clk;
	int rc;

	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, sizeof(*tegra_host));
	if (IS_ERR(host))
		return PTR_ERR(host);
	pltfm_host = sdhci_priv(host);

	tegra_host = sdhci_pltfm_priv(pltfm_host);
	tegra_host->ddr_signaling = false;
	tegra_host->pad_calib_required = false;
	tegra_host->pad_control_available = false;
	tegra_host->soc_data = soc_data;

	if (soc_data->nvquirks & NVQUIRK_HAS_ANDROID_GPT_SECTOR)
		host->mmc->caps2 |= MMC_CAP2_ALT_GPT_TEGRA;

	if (soc_data->nvquirks & NVQUIRK_NEEDS_PAD_CONTROL) {
		rc = tegra_sdhci_init_pinctrl_info(&pdev->dev, tegra_host);
		if (rc == 0)
			host->mmc_host_ops.start_signal_voltage_switch =
				sdhci_tegra_start_signal_voltage_switch;
	}

	/* Hook to periodically rerun pad calibration */
	if (soc_data->nvquirks & NVQUIRK_HAS_PADCALIB)
		host->mmc_host_ops.request = tegra_sdhci_request;

	host->mmc_host_ops.hs400_enhanced_strobe =
			tegra_sdhci_hs400_enhanced_strobe;

	if (!host->ops->platform_execute_tuning)
		host->mmc_host_ops.execute_tuning =
				tegra_sdhci_execute_hw_tuning;

	rc = mmc_of_parse(host->mmc);
	if (rc)
		goto err_parse_dt;

	if (tegra_host->soc_data->nvquirks & NVQUIRK_ENABLE_DDR50)
		host->mmc->caps |= MMC_CAP_1_8V_DDR;

	/* HW busy detection is supported, but R1B responses are required. */
	host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_NEED_RSP_BUSY;

	tegra_sdhci_parse_dt(host);

	tegra_host->power_gpio = devm_gpiod_get_optional(&pdev->dev, "power",
							 GPIOD_OUT_HIGH);
	if (IS_ERR(tegra_host->power_gpio)) {
		rc = PTR_ERR(tegra_host->power_gpio);
		goto err_power_req;
	}

	/*
	 * Tegra210 has a separate SDMMC_LEGACY_TM clock used for host
	 * timeout clock and SW can choose TMCLK or SDCLK for hardware
	 * data timeout through the bit USE_TMCLK_FOR_DATA_TIMEOUT of
	 * the register SDHCI_TEGRA_VENDOR_SYS_SW_CTRL.
	 *
	 * USE_TMCLK_FOR_DATA_TIMEOUT bit default is set to 1 and SDMMC uses
	 * 12Mhz TMCLK which is advertised in host capability register.
	 * With TMCLK of 12Mhz provides maximum data timeout period that can
	 * be achieved is 11s better than using SDCLK for data timeout.
	 *
	 * So, TMCLK is set to 12Mhz and kept enabled all the time on SoC's
	 * supporting separate TMCLK.
	 */

	if (soc_data->nvquirks & NVQUIRK_HAS_TMCLK) {
		clk = devm_clk_get(&pdev->dev, "tmclk");
		if (IS_ERR(clk)) {
			rc = PTR_ERR(clk);
			if (rc == -EPROBE_DEFER)
				goto err_power_req;

			dev_warn(&pdev->dev, "failed to get tmclk: %d\n", rc);
			clk = NULL;
		}

		clk_set_rate(clk, 12000000);
		rc = clk_prepare_enable(clk);
		if (rc) {
			dev_err(&pdev->dev,
				"failed to enable tmclk: %d\n", rc);
			goto err_power_req;
		}

		tegra_host->tmclk = clk;
	}

	clk = devm_clk_get(mmc_dev(host->mmc), NULL);
	if (IS_ERR(clk)) {
		rc = dev_err_probe(&pdev->dev, PTR_ERR(clk),
				   "failed to get clock\n");
		goto err_clk_get;
	}
	pltfm_host->clk = clk;

	tegra_host->rst = devm_reset_control_get_exclusive(&pdev->dev,
							   "sdhci");
	if (IS_ERR(tegra_host->rst)) {
		rc = PTR_ERR(tegra_host->rst);
		dev_err(&pdev->dev, "failed to get reset control: %d\n", rc);
		goto err_rst_get;
	}

	rc = devm_tegra_core_dev_init_opp_table_common(&pdev->dev);
	if (rc)
		goto err_rst_get;

	pm_runtime_enable(&pdev->dev);
	rc = pm_runtime_resume_and_get(&pdev->dev);
	if (rc)
		goto err_pm_get;

	rc = reset_control_assert(tegra_host->rst);
	if (rc)
		goto err_rst_assert;

	usleep_range(2000, 4000);

	rc = reset_control_deassert(tegra_host->rst);
	if (rc)
		goto err_rst_assert;

	usleep_range(2000, 4000);

	rc = sdhci_tegra_add_host(host);
	if (rc)
		goto err_add_host;

	return 0;

err_add_host:
	reset_control_assert(tegra_host->rst);
err_rst_assert:
	pm_runtime_put_sync_suspend(&pdev->dev);
err_pm_get:
	pm_runtime_disable(&pdev->dev);
err_rst_get:
err_clk_get:
	clk_disable_unprepare(tegra_host->tmclk);
err_power_req:
err_parse_dt:
	sdhci_pltfm_free(pdev);
	return rc;
}