162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * linux/drivers/mmc/host/sdhci_f_sdh30.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2013 - 2015 Fujitsu Semiconductor, Ltd 662306a36Sopenharmony_ci * Vincent Yang <vincent.yang@tw.fujitsu.com> 762306a36Sopenharmony_ci * Copyright (C) 2015 Linaro Ltd Andy Green <andy.green@linaro.org> 862306a36Sopenharmony_ci * Copyright (C) 2019 Socionext Inc. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/acpi.h> 1262306a36Sopenharmony_ci#include <linux/err.h> 1362306a36Sopenharmony_ci#include <linux/delay.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/of.h> 1662306a36Sopenharmony_ci#include <linux/property.h> 1762306a36Sopenharmony_ci#include <linux/clk.h> 1862306a36Sopenharmony_ci#include <linux/reset.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include "sdhci-pltfm.h" 2162306a36Sopenharmony_ci#include "sdhci_f_sdh30.h" 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cistruct f_sdhost_priv { 2462306a36Sopenharmony_ci struct clk *clk_iface; 2562306a36Sopenharmony_ci struct clk *clk; 2662306a36Sopenharmony_ci struct reset_control *rst; 2762306a36Sopenharmony_ci u32 vendor_hs200; 2862306a36Sopenharmony_ci struct device *dev; 2962306a36Sopenharmony_ci bool enable_cmd_dat_delay; 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic void *sdhci_f_sdhost_priv(struct sdhci_host *host) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci return sdhci_pltfm_priv(pltfm_host); 3762306a36Sopenharmony_ci} 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic void sdhci_f_sdh30_soft_voltage_switch(struct sdhci_host *host) 4062306a36Sopenharmony_ci{ 4162306a36Sopenharmony_ci struct f_sdhost_priv *priv = sdhci_f_sdhost_priv(host); 4262306a36Sopenharmony_ci u32 ctrl = 0; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci usleep_range(2500, 3000); 4562306a36Sopenharmony_ci ctrl = sdhci_readl(host, F_SDH30_IO_CONTROL2); 4662306a36Sopenharmony_ci ctrl |= F_SDH30_CRES_O_DN; 4762306a36Sopenharmony_ci sdhci_writel(host, ctrl, F_SDH30_IO_CONTROL2); 4862306a36Sopenharmony_ci ctrl |= F_SDH30_MSEL_O_1_8; 4962306a36Sopenharmony_ci sdhci_writel(host, ctrl, F_SDH30_IO_CONTROL2); 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci ctrl &= ~F_SDH30_CRES_O_DN; 5262306a36Sopenharmony_ci sdhci_writel(host, ctrl, F_SDH30_IO_CONTROL2); 5362306a36Sopenharmony_ci usleep_range(2500, 3000); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci if (priv->vendor_hs200) { 5662306a36Sopenharmony_ci dev_info(priv->dev, "%s: setting hs200\n", __func__); 5762306a36Sopenharmony_ci ctrl = sdhci_readl(host, F_SDH30_ESD_CONTROL); 5862306a36Sopenharmony_ci ctrl |= priv->vendor_hs200; 5962306a36Sopenharmony_ci sdhci_writel(host, ctrl, F_SDH30_ESD_CONTROL); 6062306a36Sopenharmony_ci } 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci ctrl = sdhci_readl(host, F_SDH30_TUNING_SETTING); 6362306a36Sopenharmony_ci ctrl |= F_SDH30_CMD_CHK_DIS; 6462306a36Sopenharmony_ci sdhci_writel(host, ctrl, F_SDH30_TUNING_SETTING); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic unsigned int sdhci_f_sdh30_get_min_clock(struct sdhci_host *host) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci return F_SDH30_MIN_CLOCK; 7062306a36Sopenharmony_ci} 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_cistatic void sdhci_f_sdh30_reset(struct sdhci_host *host, u8 mask) 7362306a36Sopenharmony_ci{ 7462306a36Sopenharmony_ci struct f_sdhost_priv *priv = sdhci_f_sdhost_priv(host); 7562306a36Sopenharmony_ci u32 ctl; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci if (sdhci_readw(host, SDHCI_CLOCK_CONTROL) == 0) 7862306a36Sopenharmony_ci sdhci_writew(host, 0xBC01, SDHCI_CLOCK_CONTROL); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci sdhci_reset(host, mask); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (priv->enable_cmd_dat_delay) { 8362306a36Sopenharmony_ci ctl = sdhci_readl(host, F_SDH30_ESD_CONTROL); 8462306a36Sopenharmony_ci ctl |= F_SDH30_CMD_DAT_DELAY; 8562306a36Sopenharmony_ci sdhci_writel(host, ctl, F_SDH30_ESD_CONTROL); 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) && 8962306a36Sopenharmony_ci !(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { 9062306a36Sopenharmony_ci ctl = sdhci_readl(host, F_SDH30_TEST); 9162306a36Sopenharmony_ci ctl |= F_SDH30_FORCE_CARD_INSERT; 9262306a36Sopenharmony_ci sdhci_writel(host, ctl, F_SDH30_TEST); 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic const struct sdhci_ops sdhci_f_sdh30_ops = { 9762306a36Sopenharmony_ci .voltage_switch = sdhci_f_sdh30_soft_voltage_switch, 9862306a36Sopenharmony_ci .get_min_clock = sdhci_f_sdh30_get_min_clock, 9962306a36Sopenharmony_ci .reset = sdhci_f_sdh30_reset, 10062306a36Sopenharmony_ci .set_clock = sdhci_set_clock, 10162306a36Sopenharmony_ci .set_bus_width = sdhci_set_bus_width, 10262306a36Sopenharmony_ci .set_uhs_signaling = sdhci_set_uhs_signaling, 10362306a36Sopenharmony_ci}; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic const struct sdhci_pltfm_data sdhci_f_sdh30_pltfm_data = { 10662306a36Sopenharmony_ci .ops = &sdhci_f_sdh30_ops, 10762306a36Sopenharmony_ci .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC 10862306a36Sopenharmony_ci | SDHCI_QUIRK_INVERTED_WRITE_PROTECT, 10962306a36Sopenharmony_ci .quirks2 = SDHCI_QUIRK2_SUPPORT_SINGLE 11062306a36Sopenharmony_ci | SDHCI_QUIRK2_TUNING_WORK_AROUND, 11162306a36Sopenharmony_ci}; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic int sdhci_f_sdh30_probe(struct platform_device *pdev) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci struct sdhci_host *host; 11662306a36Sopenharmony_ci struct device *dev = &pdev->dev; 11762306a36Sopenharmony_ci int ctrl = 0, ret = 0; 11862306a36Sopenharmony_ci struct f_sdhost_priv *priv; 11962306a36Sopenharmony_ci struct sdhci_pltfm_host *pltfm_host; 12062306a36Sopenharmony_ci u32 reg = 0; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci host = sdhci_pltfm_init(pdev, &sdhci_f_sdh30_pltfm_data, 12362306a36Sopenharmony_ci sizeof(struct f_sdhost_priv)); 12462306a36Sopenharmony_ci if (IS_ERR(host)) 12562306a36Sopenharmony_ci return PTR_ERR(host); 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci pltfm_host = sdhci_priv(host); 12862306a36Sopenharmony_ci priv = sdhci_pltfm_priv(pltfm_host); 12962306a36Sopenharmony_ci priv->dev = dev; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci priv->enable_cmd_dat_delay = device_property_read_bool(dev, 13262306a36Sopenharmony_ci "fujitsu,cmd-dat-delay-select"); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci ret = mmc_of_parse(host->mmc); 13562306a36Sopenharmony_ci if (ret) 13662306a36Sopenharmony_ci goto err; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci if (dev_of_node(dev)) { 13962306a36Sopenharmony_ci sdhci_get_of_property(pdev); 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci priv->clk_iface = devm_clk_get(&pdev->dev, "iface"); 14262306a36Sopenharmony_ci if (IS_ERR(priv->clk_iface)) { 14362306a36Sopenharmony_ci ret = PTR_ERR(priv->clk_iface); 14462306a36Sopenharmony_ci goto err; 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci ret = clk_prepare_enable(priv->clk_iface); 14862306a36Sopenharmony_ci if (ret) 14962306a36Sopenharmony_ci goto err; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci priv->clk = devm_clk_get(&pdev->dev, "core"); 15262306a36Sopenharmony_ci if (IS_ERR(priv->clk)) { 15362306a36Sopenharmony_ci ret = PTR_ERR(priv->clk); 15462306a36Sopenharmony_ci goto err_clk; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci ret = clk_prepare_enable(priv->clk); 15862306a36Sopenharmony_ci if (ret) 15962306a36Sopenharmony_ci goto err_clk; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci priv->rst = devm_reset_control_get_optional_shared(dev, NULL); 16262306a36Sopenharmony_ci if (IS_ERR(priv->rst)) { 16362306a36Sopenharmony_ci ret = PTR_ERR(priv->rst); 16462306a36Sopenharmony_ci goto err_rst; 16562306a36Sopenharmony_ci } 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci ret = reset_control_deassert(priv->rst); 16862306a36Sopenharmony_ci if (ret) 16962306a36Sopenharmony_ci goto err_rst; 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* init vendor specific regs */ 17362306a36Sopenharmony_ci ctrl = sdhci_readw(host, F_SDH30_AHB_CONFIG); 17462306a36Sopenharmony_ci ctrl |= F_SDH30_SIN | F_SDH30_AHB_INCR_16 | F_SDH30_AHB_INCR_8 | 17562306a36Sopenharmony_ci F_SDH30_AHB_INCR_4; 17662306a36Sopenharmony_ci ctrl &= ~(F_SDH30_AHB_BIGED | F_SDH30_BUSLOCK_EN); 17762306a36Sopenharmony_ci sdhci_writew(host, ctrl, F_SDH30_AHB_CONFIG); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci reg = sdhci_readl(host, F_SDH30_ESD_CONTROL); 18062306a36Sopenharmony_ci sdhci_writel(host, reg & ~F_SDH30_EMMC_RST, F_SDH30_ESD_CONTROL); 18162306a36Sopenharmony_ci msleep(20); 18262306a36Sopenharmony_ci sdhci_writel(host, reg | F_SDH30_EMMC_RST, F_SDH30_ESD_CONTROL); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci reg = sdhci_readl(host, SDHCI_CAPABILITIES); 18562306a36Sopenharmony_ci if (reg & SDHCI_CAN_DO_8BIT) 18662306a36Sopenharmony_ci priv->vendor_hs200 = F_SDH30_EMMC_HS200; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci if (!(reg & SDHCI_TIMEOUT_CLK_MASK)) 18962306a36Sopenharmony_ci host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci ret = sdhci_add_host(host); 19262306a36Sopenharmony_ci if (ret) 19362306a36Sopenharmony_ci goto err_add_host; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci return 0; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cierr_add_host: 19862306a36Sopenharmony_ci reset_control_assert(priv->rst); 19962306a36Sopenharmony_cierr_rst: 20062306a36Sopenharmony_ci clk_disable_unprepare(priv->clk); 20162306a36Sopenharmony_cierr_clk: 20262306a36Sopenharmony_ci clk_disable_unprepare(priv->clk_iface); 20362306a36Sopenharmony_cierr: 20462306a36Sopenharmony_ci sdhci_pltfm_free(pdev); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci return ret; 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_cistatic void sdhci_f_sdh30_remove(struct platform_device *pdev) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci struct sdhci_host *host = platform_get_drvdata(pdev); 21262306a36Sopenharmony_ci struct f_sdhost_priv *priv = sdhci_f_sdhost_priv(host); 21362306a36Sopenharmony_ci struct clk *clk_iface = priv->clk_iface; 21462306a36Sopenharmony_ci struct reset_control *rst = priv->rst; 21562306a36Sopenharmony_ci struct clk *clk = priv->clk; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci sdhci_pltfm_remove(pdev); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci reset_control_assert(rst); 22062306a36Sopenharmony_ci clk_disable_unprepare(clk); 22162306a36Sopenharmony_ci clk_disable_unprepare(clk_iface); 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci#ifdef CONFIG_OF 22562306a36Sopenharmony_cistatic const struct of_device_id f_sdh30_dt_ids[] = { 22662306a36Sopenharmony_ci { .compatible = "fujitsu,mb86s70-sdhci-3.0" }, 22762306a36Sopenharmony_ci { .compatible = "socionext,f-sdh30-e51-mmc" }, 22862306a36Sopenharmony_ci { /* sentinel */ } 22962306a36Sopenharmony_ci}; 23062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, f_sdh30_dt_ids); 23162306a36Sopenharmony_ci#endif 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci#ifdef CONFIG_ACPI 23462306a36Sopenharmony_cistatic const struct acpi_device_id f_sdh30_acpi_ids[] = { 23562306a36Sopenharmony_ci { "SCX0002" }, 23662306a36Sopenharmony_ci { /* sentinel */ } 23762306a36Sopenharmony_ci}; 23862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, f_sdh30_acpi_ids); 23962306a36Sopenharmony_ci#endif 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic struct platform_driver sdhci_f_sdh30_driver = { 24262306a36Sopenharmony_ci .driver = { 24362306a36Sopenharmony_ci .name = "f_sdh30", 24462306a36Sopenharmony_ci .probe_type = PROBE_PREFER_ASYNCHRONOUS, 24562306a36Sopenharmony_ci .of_match_table = of_match_ptr(f_sdh30_dt_ids), 24662306a36Sopenharmony_ci .acpi_match_table = ACPI_PTR(f_sdh30_acpi_ids), 24762306a36Sopenharmony_ci .pm = &sdhci_pltfm_pmops, 24862306a36Sopenharmony_ci }, 24962306a36Sopenharmony_ci .probe = sdhci_f_sdh30_probe, 25062306a36Sopenharmony_ci .remove_new = sdhci_f_sdh30_remove, 25162306a36Sopenharmony_ci}; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cimodule_platform_driver(sdhci_f_sdh30_driver); 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ciMODULE_DESCRIPTION("F_SDH30 SD Card Controller driver"); 25662306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 25762306a36Sopenharmony_ciMODULE_AUTHOR("FUJITSU SEMICONDUCTOR LTD., Socionext Inc."); 25862306a36Sopenharmony_ciMODULE_ALIAS("platform:f_sdh30"); 259