From d21cdea66ba57c43a85df7abc560f1ba771c191b Mon Sep 17 00:00:00 2001 From: liangshuang Date: Wed, 22 May 2024 17:27:11 +0800 Subject: [PATCH 014/219] feat:Add emmc and sdio driver. Changelogs: 1.Add emmc and sdio driver for linux-6.6. 2.enable sw tunning in dts. --- arch/riscv/boot/dts/eswin/eic7700-evb-a2.dts | 7 +- arch/riscv/boot/dts/eswin/eic7700-evb.dts | 1 + .../boot/dts/eswin/hifive-premier-550.dts | 1 + drivers/mmc/host/Kconfig | 28 + drivers/mmc/host/Makefile | 4 + drivers/mmc/host/sdhci-eswin.c | 301 +++++ drivers/mmc/host/sdhci-eswin.h | 237 ++++ drivers/mmc/host/sdhci-of-eswin-sdio.c | 1095 ++++++++++++++++ drivers/mmc/host/sdhci-of-eswin.c | 1109 +++++++++++++++++ 9 files changed, 2781 insertions(+), 2 deletions(-) create mode 100644 drivers/mmc/host/sdhci-eswin.c create mode 100644 drivers/mmc/host/sdhci-eswin.h create mode 100644 drivers/mmc/host/sdhci-of-eswin-sdio.c create mode 100644 drivers/mmc/host/sdhci-of-eswin.c diff --git a/arch/riscv/boot/dts/eswin/eic7700-evb-a2.dts b/arch/riscv/boot/dts/eswin/eic7700-evb-a2.dts index 300eed57dca6..00154660ab7c 100644 --- a/arch/riscv/boot/dts/eswin/eic7700-evb-a2.dts +++ b/arch/riscv/boot/dts/eswin/eic7700-evb-a2.dts @@ -491,17 +491,20 @@ &sdhci_emmc { &sdio0 { /* sd card */ status = "okay"; - delay_code = <0x16>; + delay_code = <0x23>; + phase_code = <0x5>; drive-impedance-ohm = <33>; enable-cmd-pullup; enable-data-pullup; + enable_sw_tuning; no-sdio; }; &sdio1 { /* wifi module */ status = "okay"; - delay_code = <0x21>; + delay_code = <0x23>; + phase_code = <0x5>; drive-impedance-ohm = <33>; enable-cmd-pullup; enable-data-pullup; diff --git a/arch/riscv/boot/dts/eswin/eic7700-evb.dts b/arch/riscv/boot/dts/eswin/eic7700-evb.dts index ba2b6a3ea3b5..7a9883a89dc3 100644 --- a/arch/riscv/boot/dts/eswin/eic7700-evb.dts +++ b/arch/riscv/boot/dts/eswin/eic7700-evb.dts @@ -466,6 +466,7 @@ &sdio0 { drive-impedance-ohm = <33>; enable-cmd-pullup; enable-data-pullup; + enable_sw_tuning; no-sdio; }; diff --git a/arch/riscv/boot/dts/eswin/hifive-premier-550.dts b/arch/riscv/boot/dts/eswin/hifive-premier-550.dts index d3baea543371..550a8cc2fbaf 100644 --- a/arch/riscv/boot/dts/eswin/hifive-premier-550.dts +++ b/arch/riscv/boot/dts/eswin/hifive-premier-550.dts @@ -400,6 +400,7 @@ &sdio0 { /* sd card */ status = "okay"; delay_code = <0x16>; + phase_code = <0x1f>; drive-impedance-ohm = <33>; enable-cmd-pullup; enable-data-pullup; diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index bc7e2ad37002..2fb029a80648 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -150,6 +150,34 @@ config MMC_SDHCI_PLTFM If unsure, say N. +config MMC_SDHCI_OF_ESWIN + tristate "SDHCI OF support for the Eswin SDHCI controllers" + depends on MMC_SDHCI_PLTFM + depends on OF + depends on COMMON_CLK + select MMC_CQHCI + help + This selects the Eswin Secure Digital Host Controller Interface + (SDHCI). + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + +config MMC_SDHCI_OF_SDIO_ESWIN + tristate "SDHCI OF SDIO support for the Eswin SDHCI controllers" + depends on MMC_SDHCI_PLTFM + depends on OF + depends on COMMON_CLK + select MMC_CQHCI + help + This selects the Eswin Secure Digital Host Controller Interface + (SDHCI-SDIO). + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_SDHCI_OF_ARASAN tristate "SDHCI OF support for the Arasan SDHCI controllers" depends on MMC_SDHCI_PLTFM diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index a693fa3d3f1c..bcab602d3ea5 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -81,6 +81,10 @@ obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o obj-$(CONFIG_MMC_SDHCI_DOVE) += sdhci-dove.o obj-$(CONFIG_MMC_SDHCI_TEGRA) += sdhci-tegra.o obj-$(CONFIG_MMC_SDHCI_OF_ARASAN) += sdhci-of-arasan.o +eswin-sdhci-emmc-objs := sdhci-of-eswin.o sdhci-eswin.o +obj-$(CONFIG_MMC_SDHCI_OF_ESWIN) += eswin-sdhci-emmc.o +eswin-sdhci-sdio-objs := sdhci-of-eswin-sdio.o sdhci-eswin.o +obj-$(CONFIG_MMC_SDHCI_OF_SDIO_ESWIN) += eswin-sdhci-sdio.o obj-$(CONFIG_MMC_SDHCI_OF_ASPEED) += sdhci-of-aspeed.o obj-$(CONFIG_MMC_SDHCI_OF_AT91) += sdhci-of-at91.o obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o diff --git a/drivers/mmc/host/sdhci-eswin.c b/drivers/mmc/host/sdhci-eswin.c new file mode 100644 index 000000000000..c823d034ce6b --- /dev/null +++ b/drivers/mmc/host/sdhci-eswin.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN Emmc Driver + * + * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved. + * SPDX-License-Identifier: GPL-2.0 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: liangshuang + */ +#include +#include +#include "sdhci-eswin.h" + +static void eswin_mshc_coreclk_config(struct sdhci_host *host, uint16_t divisor, + unsigned int flag_sel) +{ + struct sdhci_pltfm_host *pltfm_host; + struct eswin_sdhci_data *eswin_sdhci; + u32 val = 0; + u32 delay = 0xfffff; + + pltfm_host = sdhci_priv(host); + eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + + val = readl(eswin_sdhci->core_clk_reg); + val &= ~MSHC_CORE_CLK_ENABLE; + writel(val, eswin_sdhci->core_clk_reg); + while (delay--) + ; + val &= ~(MSHC_CORE_CLK_FREQ_BIT_MASK << MSHC_CORE_CLK_FREQ_BIT_SHIFT); + val |= (divisor & MSHC_CORE_CLK_FREQ_BIT_MASK) + << MSHC_CORE_CLK_FREQ_BIT_SHIFT; + val &= ~(MSHC_CORE_CLK_SEL_BIT); + val |= flag_sel; + writel(val, eswin_sdhci->core_clk_reg); + + udelay(100); + val |= MSHC_CORE_CLK_ENABLE; + writel(val, eswin_sdhci->core_clk_reg); + mdelay(1); +} + +static void eswin_mshc_coreclk_disable(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host; + struct eswin_sdhci_data *eswin_sdhci; + u32 val = 0; + + pltfm_host = sdhci_priv(host); + eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + + val = readl(eswin_sdhci->core_clk_reg); + val &= ~MSHC_CORE_CLK_ENABLE; + writel(val, eswin_sdhci->core_clk_reg); +} + +void eswin_sdhci_disable_card_clk(struct sdhci_host *host) +{ + unsigned int clk; + + /* Reset SD Clock Enable */ + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + clk &= ~SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); +} + +void eswin_sdhci_enable_card_clk(struct sdhci_host *host) +{ + ktime_t timeout; + unsigned int clk; + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + + clk |= SDHCI_CLOCK_INT_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + /* Wait max 150 ms */ + timeout = ktime_add_ms(ktime_get(), 150); + while (1) { + bool timedout = ktime_after(ktime_get(), timeout); + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + if (clk & SDHCI_CLOCK_INT_STABLE) + break; + if (timedout) { + pr_err("%s: Internal clock never stabilised.\n", + mmc_hostname(host->mmc)); + return; + } + udelay(10); + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + mdelay(1); +} + +void eswin_sdhci_set_core_clock(struct sdhci_host *host, + unsigned int clock) +{ + unsigned int div, divide; + unsigned int flag_sel, max_clk; + + if (clock == 0) { + eswin_mshc_coreclk_disable(host); + return; + } + + if (SDHCI_CLK_208M % clock == 0) { + flag_sel = 1; + max_clk = SDHCI_CLK_208M; + } else { + flag_sel = 0; + max_clk = SDHCI_CLK_200M; + } + + for (div = 1; div <= MAX_CORE_CLK_DIV; div++) { + if ((max_clk / div) <= clock) + break; + } + div--; + + if (div == 0 || div == 1) { + divide = 2; + } else { + divide = (div + 1) * 2; + } + pr_debug("%s: clock:%d timing:%d\n", mmc_hostname(host->mmc), clock, host->timing); + + eswin_sdhci_disable_card_clk(host); + eswin_mshc_coreclk_config(host, divide, flag_sel); + eswin_sdhci_enable_card_clk(host); + mdelay(2); +} + +static void eswin_sdhci_set_clk_delays(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + struct eswin_sdhci_clk_data *clk_data = &eswin_sdhci->clk_data; + + clk_set_phase(clk_data->sampleclk, + clk_data->clk_phase_in[host->timing]); + clk_set_phase(clk_data->sdcardclk, + clk_data->clk_phase_out[host->timing]); +} + +static void eswin_sdhci_dt_read_clk_phase(struct device *dev, + struct eswin_sdhci_clk_data *clk_data, + unsigned int timing, const char *prop) +{ + struct device_node *np = dev->of_node; + + int clk_phase[2] = { 0 }; + + /* + * Read Tap Delay values from DT, if the DT does not contain the + * Tap Values then use the pre-defined values. + */ + if (of_property_read_variable_u32_array(np, prop, &clk_phase[0], 2, + 0)) { + dev_dbg(dev, "Using predefined clock phase for %s = %d %d\n", + prop, clk_data->clk_phase_in[timing], + clk_data->clk_phase_out[timing]); + return; + } + + /* The values read are Input and Output Clock Delays in order */ + clk_data->clk_phase_in[timing] = clk_phase[0]; + clk_data->clk_phase_out[timing] = clk_phase[1]; +} + +/** + * eswin_dt_parse_clk_phases - Read Clock Delay values from DT + * + * @dev: Pointer to our struct device. + * @clk_data: Pointer to the Clock Data structure + * + * Called at initialization to parse the values of Clock Delays. + */ +void eswin_sdhci_dt_parse_clk_phases(struct device *dev, + struct eswin_sdhci_clk_data *clk_data) +{ + clk_data->set_clk_delays = eswin_sdhci_set_clk_delays; + + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_LEGACY, + "clk-phase-legacy"); + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_MMC_HS, + "clk-phase-mmc-hs"); + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_SD_HS, + "clk-phase-sd-hs"); + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_UHS_SDR12, + "clk-phase-uhs-sdr12"); + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_UHS_SDR25, + "clk-phase-uhs-sdr25"); + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_UHS_SDR50, + "clk-phase-uhs-sdr50"); + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_UHS_SDR104, + "clk-phase-uhs-sdr104"); + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_UHS_DDR50, + "clk-phase-uhs-ddr50"); + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_MMC_DDR52, + "clk-phase-mmc-ddr52"); + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_MMC_HS200, + "clk-phase-mmc-hs200"); + eswin_sdhci_dt_read_clk_phase(dev, clk_data, MMC_TIMING_MMC_HS400, + "clk-phase-mmc-hs400"); +} + +unsigned int eswin_convert_drive_impedance_ohm(struct platform_device *pdev, + unsigned int dr_ohm) +{ + switch (dr_ohm) { + case 100: + return PHYCTRL_DR_100OHM; + case 66: + return PHYCTRL_DR_66OHM; + case 50: + return PHYCTRL_DR_50OHM; + case 40: + return PHYCTRL_DR_40OHM; + case 33: + return PHYCTRL_DR_33OHM; + } + + dev_warn(&pdev->dev, "Invalid value %u for drive-impedance-ohm.\n", + dr_ohm); + return PHYCTRL_DR_50OHM; +} + +static void eswin_sdhci_do_reset(struct eswin_sdhci_data *eswin_sdhci) +{ + int ret; + + ret = reset_control_assert(eswin_sdhci->txrx_rst); + WARN_ON(0 != ret); + ret = reset_control_assert(eswin_sdhci->phy_rst); + WARN_ON(0 != ret); + ret = reset_control_assert(eswin_sdhci->prstn); + WARN_ON(0 != ret); + ret = reset_control_assert(eswin_sdhci->arstn); + WARN_ON(0 != ret); + + mdelay(2); + + ret = reset_control_deassert(eswin_sdhci->txrx_rst); + WARN_ON(0 != ret); + ret = reset_control_deassert(eswin_sdhci->phy_rst); + WARN_ON(0 != ret); + ret = reset_control_deassert(eswin_sdhci->prstn); + WARN_ON(0 != ret); + ret = reset_control_deassert(eswin_sdhci->arstn); + WARN_ON(0 != ret); +} + +int eswin_sdhci_reset_init(struct device *dev, + struct eswin_sdhci_data *eswin_sdhci) +{ + int ret = 0; + eswin_sdhci->txrx_rst = devm_reset_control_get_optional(dev, "txrx_rst"); + if (IS_ERR_OR_NULL(eswin_sdhci->txrx_rst)) { + dev_err_probe(dev, PTR_ERR(eswin_sdhci->txrx_rst), + "txrx_rst reset not found.\n"); + return -EFAULT; + } + + eswin_sdhci->phy_rst = devm_reset_control_get_optional(dev, "phy_rst"); + if (IS_ERR_OR_NULL(eswin_sdhci->phy_rst)) { + dev_err_probe(dev, PTR_ERR(eswin_sdhci->phy_rst), + "phy_rst reset not found.\n"); + return -EFAULT; + } + + eswin_sdhci->prstn = devm_reset_control_get_optional(dev, "prstn"); + if (IS_ERR_OR_NULL(eswin_sdhci->prstn)) { + dev_err_probe(dev, PTR_ERR(eswin_sdhci->prstn), + "prstn reset not found.\n"); + return -EFAULT; + } + + eswin_sdhci->arstn = devm_reset_control_get_optional(dev, "arstn"); + if (IS_ERR_OR_NULL(eswin_sdhci->arstn)) { + dev_err_probe(dev, PTR_ERR(eswin_sdhci->arstn), + "arstn reset not found.\n"); + return -EFAULT; + } + eswin_sdhci_do_reset(eswin_sdhci); + + return ret; +} diff --git a/drivers/mmc/host/sdhci-eswin.h b/drivers/mmc/host/sdhci-eswin.h new file mode 100644 index 000000000000..08ddfdf8932e --- /dev/null +++ b/drivers/mmc/host/sdhci-eswin.h @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN SDHCI Driver + * + * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved. + * SPDX-License-Identifier: GPL-2.0 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: liangshuang + */ +#ifndef _DRIVERS_MMC_SDHCI_ESWIN_H +#define _DRIVERS_MMC_SDHCI_ESWIN_H + +#include +#include +#include "sdhci-pltfm.h" + +#define MSHC_CARD_CLK_STABLE BIT(28) +#define MSHC_INT_BCLK_STABLE BIT(16) +#define MSHC_INT_ACLK_STABLE BIT(8) +#define MSHC_INT_TMCLK_STABLE BIT(0) +#define MSHC_INT_CLK_STABLE \ + (MSHC_CARD_CLK_STABLE | MSHC_INT_ACLK_STABLE | \ + MSHC_INT_BCLK_STABLE | MSHC_INT_TMCLK_STABLE) +#define MSHC_HOST_VAL_STABLE BIT(0) +#define EMMC0_CARD_DETECT BIT(9) +#define EMMC0_CARD_WRITE_PROT BIT(8) + +#define MSHC_CORE_CLK_ENABLE BIT(16) +#define MSHC_CORE_CLK_FREQ_BIT_SHIFT 4 +#define MSHC_CORE_CLK_FREQ_BIT_MASK 0xfffu +#define MSHC_CORE_CLK_SEL_BIT BIT(0) + +/* Controller does not have CD wired and will not function normally without */ +#define SDHCI_ESWIN_QUIRK_FORCE_CDTEST BIT(0) +/* Controller immediately reports SDHCI_CLOCK_INT_STABLE after enabling the + * internal clock even when the clock isn't stable */ +#define SDHCI_ESWIN_QUIRK_CLOCK_UNSTABLE BIT(1) + +#define ESWIN_SDHCI_CTRL_HS400 0x0007 // Non-standard, for eswin,these bits are 0x7 + +#define SDHCI_CLK_208M 208000000 +#define SDHCI_CLK_200M 200000000 + +#define AWSMMUSID GENMASK(31, 24) // The sid of write operation +#define AWSMMUSSID GENMASK(23, 16) // The ssid of write operation +#define ARSMMUSID GENMASK(15, 8) // The sid of read operation +#define ARSMMUSSID GENMASK(7, 0) // The ssid of read operation + +/* DWC_mshc_map/DWC_mshc_phy_block register */ +#define DWC_MSHC_PTR_PHY_R 0x300 +#define PHY_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x00) +#define PHY_CMDPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x04) +#define PHY_DATAPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x06) +#define PHY_CLKPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x08) +#define PHY_STBPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0a) +#define PHY_RSTNPAD_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0c) +#define PHY_PADTEST_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x0e) +#define PHY_PADTEST_OUT_R (DWC_MSHC_PTR_PHY_R + 0x10) +#define PHY_PADTEST_IN_R (DWC_MSHC_PTR_PHY_R + 0x12) +#define PHY_PRBS_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x18) +#define PHY_PHYLBK_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1a) +#define PHY_COMMDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1c) +#define PHY_SDCLKDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x1d) +#define PHY_SDCLKDL_DC_R (DWC_MSHC_PTR_PHY_R + 0x1e) +#define PHY_SMPLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x20) +#define PHY_ATDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x21) +#define PHY_DLL_CTRL_R (DWC_MSHC_PTR_PHY_R + 0x24) +#define PHY_DLL_CNFG1_R (DWC_MSHC_PTR_PHY_R + 0x25) +#define PHY_DLL_CNFG2_R (DWC_MSHC_PTR_PHY_R + 0x26) +#define PHY_DLLDL_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x28) +#define PHY_DLL_OFFST_R (DWC_MSHC_PTR_PHY_R + 0x29) +#define PHY_DLLMST_TSTDC_R (DWC_MSHC_PTR_PHY_R + 0x2a) +#define PHY_DLLBT_CNFG_R (DWC_MSHC_PTR_PHY_R + 0x2c) +#define PHY_DLL_STATUS_R (DWC_MSHC_PTR_PHY_R + 0x2e) +#define PHY_DLLDBG_MLKDC_R (DWC_MSHC_PTR_PHY_R + 0x30) +#define PHY_DLLDBG_SLKDC_R (DWC_MSHC_PTR_PHY_R + 0x32) + +#define ENABLE 1 +#define DISABLE 0 +/* strength definition */ +#define PHYCTRL_DR_33OHM 0xee +#define PHYCTRL_DR_40OHM 0xcc +#define PHYCTRL_DR_50OHM 0x88 +#define PHYCTRL_DR_66OHM 0x44 +#define PHYCTRL_DR_100OHM 0x00 + +#define PHY_PAD_MAX_DRIVE_STRENGTH 0xf +#define PHY_CLK_MAX_DELAY_MASK 0x7f +#define PHY_PAD_SP_DRIVE_SHIF 16 +#define PHY_PAD_SN_DRIVE_SHIF 20 + +#define PHY_RSTN BIT(0) +#define PHY_UPDATE_DELAY_CODE BIT(4) + +#define VENDOR_EMMC_CTRL_R 0x52c +#define EMMC_CRAD_PRESENT BIT(0) +#define EMMC_RST_N_OE BIT(3) +#define EMMC_RST_N BIT(2) + +#define PHY_SLEW_0 0x0 +#define PHY_SLEW_1 0x1 +#define PHY_SLEW_2 0x2 +#define PHY_SLEW_3 0x3 +#define PHY_TX_SLEW_CTRL_P_BIT_SHIFT 5 +#define PHY_TX_SLEW_CTRL_N_BIT_SHIFT 9 + +#define PHY_PULL_BIT_SHIF 0x3 +#define PHY_PULL_DISABLED 0x0 +#define PHY_PULL_UP 0x1 +#define PHY_PULL_DOWN 0x2 +#define PHY_PULL_MASK 0x3 + +#define PHY_PAD_RXSEL_0 0x0 +#define PHY_PAD_RXSEL_1 0x1 + +#define VENDOR_AT_CTRL_R 0x540 +#define LATENCY_LT_BIT_OFFSET 19 +#define LATENCY_LT_MASK 0x3 + +#define LATENCY_LT_1 0x0 +#define LATENCY_LT_2 0x1 +#define LATENCY_LT_3 0x2 +#define LATENCY_LT_4 0x3 +#define SW_TUNE_ENABLE BIT(4) + +#define VENDOR_AT_SATA_R 0x544 +#define MAX_PHASE_CODE 0xff + +#define DLL_ENABEL BIT(0) +#define DLL_LOCK_STS BIT(0) +#define DLL_ERROR_STS BIT(1) +#define PHY_DELAY_CODE_MASK 0x7f +#define PHY_DELAY_CODE_MAX 0x7f + +#define MAX_CORE_CLK_DIV 0xfff + +/** + * struct eswin_sdhci_clk_ops - Clock Operations for eswin SD controller + * + * @sdcardclk_ops: The output clock related operations + * @sampleclk_ops: The sample clock related operations + */ +struct eswin_sdhci_clk_ops { + const struct clk_ops *sdcardclk_ops; + const struct clk_ops *sampleclk_ops; +}; + +/** + * struct eswin_sdhci_clk_data - ESWIN Controller Clock Data. + * + * @sdcardclk_hw: Struct for the clock we might provide to a PHY. + * @sdcardclk: Pointer to normal 'struct clock' for sdcardclk_hw. + * @sampleclk_hw: Struct for the clock we might provide to a PHY. + * @sampleclk: Pointer to normal 'struct clock' for sampleclk_hw. + * @clk_phase_in: Array of Input Clock Phase Delays for all speed modes + * @clk_phase_out: Array of Output Clock Phase Delays for all speed modes + * @set_clk_delays: Function pointer for setting Clock Delays + * @clk_of_data: Platform specific runtime clock data storage pointer + */ +struct eswin_sdhci_clk_data { + struct clk_hw sdcardclk_hw; + struct clk *sdcardclk; + struct clk_hw sampleclk_hw; + struct clk *sampleclk; + int clk_phase_in[MMC_TIMING_MMC_HS400 + 1]; + int clk_phase_out[MMC_TIMING_MMC_HS400 + 1]; + void (*set_clk_delays)(struct sdhci_host *host); + void *clk_of_data; +}; + +struct eswin_sdhci_phy_data { + unsigned int drive_impedance; + unsigned int enable_strobe_pulldown; + unsigned int enable_data_pullup; + unsigned int enable_cmd_pullup; + unsigned int delay_code; +}; + +/** + * struct eswin_sdhci_data - ESWIN Controller Data + * + * @host: Pointer to the main SDHCI host structure. + * @clk_ahb: Pointer to the AHB clock + * @has_cqe: True if controller has command queuing engine. + * @clk_data: Struct for the ESWIN Controller Clock Data. + * @clk_ops: Struct for the ESWIN Controller Clock Operations. + * @soc_ctl_base: Pointer to regmap for syscon for soc_ctl registers. + * @soc_ctl_map: Map to get offsets into soc_ctl registers. + * @quirks: ESWIN deviations from spec. + * @phy: ESWIN sdhci phy configs. + * @private: private for spec driver. + */ +struct eswin_sdhci_data { + struct sdhci_host *host; + struct clk *clk_ahb; + bool has_cqe; + struct eswin_sdhci_clk_data clk_data; + const struct eswin_sdhci_clk_ops *clk_ops; + unsigned int quirks; + void __iomem *core_clk_reg; + struct reset_control *txrx_rst; + struct reset_control *phy_rst; + struct reset_control *prstn; + struct reset_control *arstn; + struct eswin_sdhci_phy_data phy; + unsigned long private[] ____cacheline_aligned; +}; + +struct eswin_sdhci_of_data { + const struct sdhci_pltfm_data *pdata; + const struct eswin_sdhci_clk_ops *clk_ops; +}; + +void eswin_sdhci_set_core_clock(struct sdhci_host *host, + unsigned int clock); +void eswin_sdhci_disable_card_clk(struct sdhci_host *host); +void eswin_sdhci_enable_card_clk(struct sdhci_host *host); +void eswin_sdhci_dt_parse_clk_phases(struct device *dev, + struct eswin_sdhci_clk_data *clk_data); +unsigned int eswin_convert_drive_impedance_ohm(struct platform_device *pdev, + unsigned int dr_ohm); +int eswin_sdhci_reset_init(struct device *dev, + struct eswin_sdhci_data *eswin_sdhci); + +#endif /* _DRIVERS_MMC_SDHCI_ESWIN_H */ diff --git a/drivers/mmc/host/sdhci-of-eswin-sdio.c b/drivers/mmc/host/sdhci-of-eswin-sdio.c new file mode 100644 index 000000000000..fa23d1ded51c --- /dev/null +++ b/drivers/mmc/host/sdhci-of-eswin-sdio.c @@ -0,0 +1,1095 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN sdio Driver + * + * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved. + * SPDX-License-Identifier: GPL-2.0 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: liangshuang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "cqhci.h" +#include "sdhci-pltfm.h" + +#include +#include +#include +#include "sdhci-eswin.h" + +#define ESWIN_SDHCI_SD_CQE_BASE_ADDR 0x180 +#define ESWIN_SDHCI_SD0_INT_STATUS 0x608 +#define ESWIN_SDHCI_SD0_PWR_CTRL 0x60c +#define ESWIN_SDHCI_SD1_INT_STATUS 0x708 +#define ESWIN_SDHCI_SD1_PWR_CTRL 0x70c + +#define DELAY_RANGE_THRESHOLD 40 + +struct eswin_sdio_private { + int phase_code; + unsigned int enable_sw_tuning; +}; + +static inline void *sdhci_sdio_priv(struct eswin_sdhci_data *sdio) +{ + return sdio->private; +} + +static void eswin_sdhci_sdio_set_clock(struct sdhci_host *host, + unsigned int clock) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci_sdio = + sdhci_pltfm_priv(pltfm_host); + struct eswin_sdhci_clk_data *clk_data = + &eswin_sdhci_sdio->clk_data; + + /* Set the Input and Output Clock Phase Delays */ + if (clk_data->set_clk_delays) { + clk_data->set_clk_delays(host); + } + + eswin_sdhci_set_core_clock(host, clock); + sdhci_set_clock(host, clock); + + if (eswin_sdhci_sdio->quirks & SDHCI_ESWIN_QUIRK_CLOCK_UNSTABLE) + /* + * Some controllers immediately report SDHCI_CLOCK_INT_STABLE + * after enabling the clock even though the clock is not + * stable. Trying to use a clock without waiting here results + * in EILSEQ while detecting some older/slower cards. The + * chosen delay is the maximum delay from sdhci_set_clock. + */ + msleep(20); +} + +static void eswin_sdhci_sdio_config_phy_delay(struct sdhci_host *host, + int delay) +{ + delay &= PHY_CLK_MAX_DELAY_MASK; + + /*phy clk delay line config*/ + sdhci_writeb(host, PHY_UPDATE_DELAY_CODE, PHY_SDCLKDL_CNFG_R); + sdhci_writeb(host, delay, PHY_SDCLKDL_DC_R); + sdhci_writeb(host, 0x0, PHY_SDCLKDL_CNFG_R); +} + +static void eswin_sdhci_sdio_config_phy(struct sdhci_host *host) +{ + unsigned int val = 0; + unsigned int drv = 0; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci = + sdhci_pltfm_priv(pltfm_host); + struct eswin_sdhci_phy_data *phy = &eswin_sdhci->phy; + + drv = phy->drive_impedance << PHY_PAD_SP_DRIVE_SHIF; + pr_debug("%s: phy drv=0x%x \n",mmc_hostname(host->mmc), drv); + + eswin_sdhci_disable_card_clk(host); + + /* reset phy,config phy's pad */ + sdhci_writel(host, drv | (~PHY_RSTN), PHY_CNFG_R); + /*CMDPAD_CNFS*/ + val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | + (phy->enable_cmd_pullup << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1; + sdhci_writew(host, val, PHY_CMDPAD_CNFG_R); + pr_debug("%s: phy cmd=0x%x\n",mmc_hostname(host->mmc), val); + + /*DATA PAD CNFG*/ + val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | + (phy->enable_data_pullup << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1; + sdhci_writew(host, val, PHY_DATAPAD_CNFG_R); + pr_debug("%s: phy data=0x%x\n",mmc_hostname(host->mmc), val); + + /*Clock PAD Setting*/ + val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | PHY_PAD_RXSEL_0; + sdhci_writew(host, val, PHY_CLKPAD_CNFG_R); + pr_debug("%s: phy clk=0x%x\n",mmc_hostname(host->mmc), val); + mdelay(2); + + /*PHY RSTN PAD setting*/ + val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | + (PHY_PULL_UP << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1; + sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R); + + sdhci_writel(host, drv | PHY_RSTN, PHY_CNFG_R); + + eswin_sdhci_sdio_config_phy_delay(host, phy->delay_code); + + eswin_sdhci_enable_card_clk(host); +} + +static void eswin_sdhci_sdio_reset(struct sdhci_host *host, u8 mask) +{ + u8 ctrl; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci_sdio = + sdhci_pltfm_priv(pltfm_host); + + sdhci_reset(host, mask); + + if (eswin_sdhci_sdio->quirks & SDHCI_ESWIN_QUIRK_FORCE_CDTEST) { + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + ctrl |= SDHCI_CTRL_CDTEST_INS | SDHCI_CTRL_CDTEST_EN; + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + } + if (mask == SDHCI_RESET_ALL) { // after reset all,the phy`s config will be clear. + eswin_sdhci_sdio_config_phy(host); + } +} + +static int eswin_sdhci_sdio_delay_tuning(struct sdhci_host *host, u32 opcode) +{ + int ret; + int delay = -1; + int i = 0; + int delay_min = -1; + int delay_max = -1; + int delay_range = -1; + + int cmd_error = 0; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci = + sdhci_pltfm_priv(pltfm_host); + + for (i = 0; i <= PHY_DELAY_CODE_MAX; i++) { + eswin_sdhci_disable_card_clk(host); + eswin_sdhci_sdio_config_phy_delay(host, i); + eswin_sdhci_enable_card_clk(host); + ret = mmc_send_tuning(host->mmc, opcode, &cmd_error); + if (ret) { + pr_debug("%s: bad delay:0x%x\n", mmc_hostname(host->mmc), i); + sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + udelay(200); + if (delay_min != -1 && delay_max != -1) { + if (delay_max - delay_min > delay_range) { + delay_range = delay_max - delay_min; + delay = (delay_min + delay_max) / 2; + if (delay_range > DELAY_RANGE_THRESHOLD) + break; + } + delay_min = -1; + delay_max = -1; + } + } else { + pr_debug("%s: ok delay:0x%x\n", mmc_hostname(host->mmc), i); + if (delay_min == -1) { + delay_min = i; + } + delay_max = i; + if (i == PHY_DELAY_CODE_MAX) { + if (delay_max - delay_min > delay_range) { + delay_range = delay_max - delay_min; + delay = (delay_min + delay_max) / 2; + } + } + continue; + } + } + + if (delay == -1) { + pr_err("%s: delay code tuning failed!\n", + mmc_hostname(host->mmc)); + eswin_sdhci_disable_card_clk(host); + eswin_sdhci_sdio_config_phy_delay(host, + eswin_sdhci->phy.delay_code); + eswin_sdhci_enable_card_clk(host); + + return ret; + } + + pr_info("%s: set delay:0x%x\n", mmc_hostname(host->mmc), delay); + eswin_sdhci_disable_card_clk(host); + eswin_sdhci_sdio_config_phy_delay(host, delay); + eswin_sdhci_enable_card_clk(host); + + return 0; +} + +static int eswin_sdhci_sdio_phase_code_tuning(struct sdhci_host *host, + u32 opcode) +{ + int cmd_error = 0; + int ret = 0; + int phase_code = 0; + int code_min = -1; + int code_max = -1; + + for (phase_code = 0; phase_code <= MAX_PHASE_CODE; phase_code++) { + eswin_sdhci_disable_card_clk(host); + sdhci_writew(host, phase_code, VENDOR_AT_SATA_R); + eswin_sdhci_enable_card_clk(host); + + ret = mmc_send_tuning(host->mmc, opcode, &cmd_error); + if (ret) { + sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + udelay(200); + if (code_min != -1 && code_max != -1) + break; + } else { + if (code_min == -1) { + code_min = phase_code; + } + code_max = phase_code; + } + } + if (code_min == -1 && code_max == -1) { + pr_err("%s: phase code tuning failed!\n", + mmc_hostname(host->mmc)); + eswin_sdhci_disable_card_clk(host); + sdhci_writew(host, 0, VENDOR_AT_SATA_R); + eswin_sdhci_enable_card_clk(host); + return -EIO; + } + + phase_code = (code_min + code_max) / 2; + pr_info("%s: set phase_code:0x%x\n", mmc_hostname(host->mmc), phase_code); + + eswin_sdhci_disable_card_clk(host); + sdhci_writew(host, phase_code, VENDOR_AT_SATA_R); + eswin_sdhci_enable_card_clk(host); + + return 0; +} + +static int eswin_sdhci_sdio_executing_tuning(struct sdhci_host *host, + u32 opcode) +{ + u32 ctrl; + u32 val; + int ret = 0; + struct sdhci_pltfm_host *pltfm_host; + struct eswin_sdhci_data *eswin_sdhci_sdio; + struct eswin_sdio_private *eswin_sdio_priv; + + pltfm_host = sdhci_priv(host); + eswin_sdhci_sdio = sdhci_pltfm_priv(pltfm_host); + eswin_sdio_priv = sdhci_sdio_priv(eswin_sdhci_sdio); + + if (!eswin_sdio_priv->enable_sw_tuning) { + if (eswin_sdio_priv->phase_code != -1) { + eswin_sdhci_disable_card_clk(host); + sdhci_writew(host, eswin_sdio_priv->phase_code, VENDOR_AT_SATA_R); + eswin_sdhci_enable_card_clk(host); + } + return 0; + } + + eswin_sdhci_disable_card_clk(host); + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl &= ~SDHCI_CTRL_TUNED_CLK; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + + val = sdhci_readl(host, VENDOR_AT_CTRL_R); + val |= SW_TUNE_ENABLE; + sdhci_writew(host, val, VENDOR_AT_CTRL_R); + sdhci_writew(host, 0, VENDOR_AT_SATA_R); + + eswin_sdhci_enable_card_clk(host); + + sdhci_writew(host, 0x0, SDHCI_CMD_DATA); + + ret = eswin_sdhci_sdio_delay_tuning(host, opcode); + if (ret < 0) { + return ret; + } + + ret = eswin_sdhci_sdio_phase_code_tuning(host, opcode); + if (ret < 0) { + return ret; + } + + return 0; +} + +static u32 eswin_sdhci_sdio_cqhci_irq(struct sdhci_host *host, u32 intmask) +{ + int cmd_error = 0; + int data_error = 0; + + if (!sdhci_cqe_irq(host, intmask, &cmd_error, &data_error)) + return intmask; + + cqhci_irq(host->mmc, intmask, cmd_error, data_error); + + return 0; +} + +static void eswin_sdhci_sdio_dumpregs(struct mmc_host *mmc) +{ + sdhci_dumpregs(mmc_priv(mmc)); +} + +static void eswin_sdhci_sdio_cqe_enable(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + u32 reg; + + reg = sdhci_readl(host, SDHCI_PRESENT_STATE); + while (reg & SDHCI_DATA_AVAILABLE) { + sdhci_readl(host, SDHCI_BUFFER); + reg = sdhci_readl(host, SDHCI_PRESENT_STATE); + } + + sdhci_cqe_enable(mmc); +} + +static const struct cqhci_host_ops eswin_sdhci_sdio_cqhci_ops = { + .enable = eswin_sdhci_sdio_cqe_enable, + .disable = sdhci_cqe_disable, + .dumpregs = eswin_sdhci_sdio_dumpregs, +}; + +static const struct sdhci_ops eswin_sdhci_sdio_cqe_ops = { + .set_clock = eswin_sdhci_sdio_set_clock, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, + .get_timeout_clock = sdhci_pltfm_clk_get_max_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = eswin_sdhci_sdio_reset, + .set_uhs_signaling = sdhci_set_uhs_signaling, + .set_power = sdhci_set_power_and_bus_voltage, + .irq = eswin_sdhci_sdio_cqhci_irq, + .platform_execute_tuning = eswin_sdhci_sdio_executing_tuning, + +}; + +static const struct sdhci_pltfm_data eswin_sdhci_sdio_cqe_pdata = { + .ops = &eswin_sdhci_sdio_cqe_ops, + .quirks = SDHCI_QUIRK_BROKEN_CQE | + SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | + SDHCI_QUIRK_BROKEN_TIMEOUT_VAL, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | + SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, +}; + +#ifdef CONFIG_PM_SLEEP +/** + * eswin_sdhci_sdio_suspend- Suspend method for the driver + * @dev: Address of the device structure + * + * Put the device in a low power state. + * + * Return: 0 on success and error value on error + */ +static int eswin_sdhci_sdio_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci_sdio = + sdhci_pltfm_priv(pltfm_host); + int ret; + + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + + if (eswin_sdhci_sdio->has_cqe) { + ret = cqhci_suspend(host->mmc); + if (ret) + return ret; + } + + ret = sdhci_suspend_host(host); + if (ret) + return ret; + + clk_disable(pltfm_host->clk); + clk_disable(eswin_sdhci_sdio->clk_ahb); + + return 0; +} + +/** + * eswin_sdhci_sdio_resume- Resume method for the driver + * @dev: Address of the device structure + * + * Resume operation after suspend + * + * Return: 0 on success and error value on error + */ +static int eswin_sdhci_sdio_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci_sdio = + sdhci_pltfm_priv(pltfm_host); + int ret; + + ret = clk_enable(eswin_sdhci_sdio->clk_ahb); + if (ret) { + dev_err(dev, "Cannot enable AHB clock.\n"); + return ret; + } + + ret = clk_enable(pltfm_host->clk); + if (ret) { + dev_err(dev, "Cannot enable SD clock.\n"); + return ret; + } + + ret = sdhci_resume_host(host); + if (ret) { + dev_err(dev, "Cannot resume host.\n"); + return ret; + } + + if (eswin_sdhci_sdio->has_cqe) + return cqhci_resume(host->mmc); + + return 0; +} +#endif /* ! CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(eswin_sdhci_sdio_dev_pm_ops, eswin_sdhci_sdio_suspend, + eswin_sdhci_sdio_resume); + +/** + * eswin_sdhci_sdio_sdcardclk_recalc_rate- Return the card clock rate + * + * @hw: Pointer to the hardware clock structure. + * @parent_rate: The parent rate (should be rate of clk_xin). + * + * Return the current actual rate of the SD card clock. This can be used + * to communicate with out PHY. + * + * Return: The card clock rate. + */ +static unsigned long +eswin_sdhci_sdio_sdcardclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct eswin_sdhci_clk_data *clk_data = container_of( + hw, struct eswin_sdhci_clk_data, sdcardclk_hw); + struct eswin_sdhci_data *eswin_sdhci_sdio = + container_of(clk_data, struct eswin_sdhci_data, clk_data); + struct sdhci_host *host = eswin_sdhci_sdio->host; + + return host->mmc->actual_clock; +} + +static const struct clk_ops eswin_sdio_sdcardclk_ops = { + .recalc_rate = eswin_sdhci_sdio_sdcardclk_recalc_rate, +}; + +/** + * eswin_sdhci_sdio_sampleclk_recalc_rate- Return the sampling clock rate + * + * @hw: Pointer to the hardware clock structure. + * @parent_rate: The parent rate (should be rate of clk_xin). + * + * Return the current actual rate of the sampling clock. This can be used + * to communicate with out PHY. + * + * Return: The sample clock rate. + */ +static unsigned long +eswin_sdhci_sdio_sampleclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct eswin_sdhci_clk_data *clk_data = container_of( + hw, struct eswin_sdhci_clk_data, sampleclk_hw); + struct eswin_sdhci_data *eswin_sdhci_sdio = + container_of(clk_data, struct eswin_sdhci_data, clk_data); + struct sdhci_host *host = eswin_sdhci_sdio->host; + + return host->mmc->actual_clock; +} + +static const struct clk_ops eswin_sdio_sampleclk_ops = { + .recalc_rate = eswin_sdhci_sdio_sampleclk_recalc_rate, +}; + +static const struct eswin_sdhci_clk_ops eswin_sdio_clk_ops = { + .sdcardclk_ops = &eswin_sdio_sdcardclk_ops, + .sampleclk_ops = &eswin_sdio_sampleclk_ops, +}; + +static struct eswin_sdhci_of_data eswin_sdhci_fu800_sdio_data = { + .pdata = &eswin_sdhci_sdio_cqe_pdata, + .clk_ops = &eswin_sdio_clk_ops, +}; + +static const struct of_device_id eswin_sdhci_sdio_of_match[] = { + /* SoC-specific compatible strings*/ + { + .compatible = "eswin,sdhci-sdio", + .data = &eswin_sdhci_fu800_sdio_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, eswin_sdhci_sdio_of_match); + +/** + * eswin_sdhci_sdio_register_sdcardclk- Register the sdcardclk for a PHY to use + * + * @sdhci_arasan: Our private data structure. + * @clk_xin: Pointer to the functional clock + * @dev: Pointer to our struct device. + * + * Some PHY devices need to know what the actual card clock is. In order for + * them to find out, we'll provide a clock through the common clock framework + * for them to query. + * + * Return: 0 on success and error value on error + */ +static int eswin_sdhci_sdio_register_sdcardclk( + struct eswin_sdhci_data *eswin_sdhci_sdio, struct clk *clk_xin, + struct device *dev) +{ + struct eswin_sdhci_clk_data *clk_data = + &eswin_sdhci_sdio->clk_data; + struct device_node *np = dev->of_node; + struct clk_init_data sdcardclk_init; + const char *parent_clk_name; + int ret; + + ret = of_property_read_string_index(np, "clock-output-names", 0, + &sdcardclk_init.name); + if (ret) { + dev_err(dev, "DT has #clock-cells but no clock-output-names\n"); + return ret; + } + + parent_clk_name = __clk_get_name(clk_xin); + sdcardclk_init.parent_names = &parent_clk_name; + sdcardclk_init.num_parents = 1; + sdcardclk_init.flags = CLK_GET_RATE_NOCACHE; + sdcardclk_init.ops = eswin_sdhci_sdio->clk_ops->sdcardclk_ops; + + clk_data->sdcardclk_hw.init = &sdcardclk_init; + clk_data->sdcardclk = devm_clk_register(dev, &clk_data->sdcardclk_hw); + if (IS_ERR(clk_data->sdcardclk)) + return PTR_ERR(clk_data->sdcardclk); + + clk_data->sdcardclk_hw.init = NULL; + + ret = of_clk_add_provider(np, of_clk_src_simple_get, + clk_data->sdcardclk); + if (ret) + dev_err(dev, "Failed to add sdcard clock provider\n"); + + return ret; +} + +/** + * eswin_sdhci_sdio_register_sampleclk - Register the sampleclk for a PHY to use + * + * @sdhci_arasan: Our private data structure. + * @clk_xin: Pointer to the functional clock + * @dev: Pointer to our struct device. + * + * Some PHY devices need to know what the actual card clock is. In order for + * them to find out, we'll provide a clock through the common clock framework + * for them to query. + * + * Return: 0 on success and error value on error + */ +static int eswin_sdhci_sdio_register_sampleclk( + struct eswin_sdhci_data *eswin_sdhci_sdio, struct clk *clk_xin, + struct device *dev) +{ + struct eswin_sdhci_clk_data *clk_data = + &eswin_sdhci_sdio->clk_data; + struct device_node *np = dev->of_node; + struct clk_init_data sampleclk_init; + const char *parent_clk_name; + int ret; + + ret = of_property_read_string_index(np, "clock-output-names", 1, + &sampleclk_init.name); + if (ret) { + dev_err(dev, "DT has #clock-cells but no clock-output-names\n"); + return ret; + } + + parent_clk_name = __clk_get_name(clk_xin); + sampleclk_init.parent_names = &parent_clk_name; + sampleclk_init.num_parents = 1; + sampleclk_init.flags = CLK_GET_RATE_NOCACHE; + sampleclk_init.ops = eswin_sdhci_sdio->clk_ops->sampleclk_ops; + + clk_data->sampleclk_hw.init = &sampleclk_init; + clk_data->sampleclk = devm_clk_register(dev, &clk_data->sampleclk_hw); + if (IS_ERR(clk_data->sampleclk)) + return PTR_ERR(clk_data->sampleclk); + clk_data->sampleclk_hw.init = NULL; + + ret = of_clk_add_provider(np, of_clk_src_simple_get, + clk_data->sampleclk); + if (ret) + dev_err(dev, "Failed to add sample clock provider\n"); + + return ret; +} + +/** + * eswin_sdhci_sdio_unregister_sdclk- Undoes sdhci_arasan_register_sdclk() + * + * @dev: Pointer to our struct device. + * + * Should be called any time we're exiting and sdhci_arasan_register_sdclk() + * returned success. + */ +static void eswin_sdhci_sdio_unregister_sdclk(struct device *dev) +{ + struct device_node *np = dev->of_node; + + if (!of_find_property(np, "#clock-cells", NULL)) + return; + + of_clk_del_provider(dev->of_node); +} + +/** + * eswin_sdhci_sdio_register_sdclk- Register the sdcardclk for a PHY to use + * + * @eswin_sdhci_sdio: Our private data structure. + * @clk_xin: Pointer to the functional clock + * @dev: Pointer to our struct device. + * + * Some PHY devices need to know what the actual card clock is. In order for + * them to find out, we'll provide a clock through the common clock framework + * for them to query. + * + * Note: without seriously re-architecting SDHCI's clock code and testing on + * all platforms, there's no way to create a totally beautiful clock here + * with all clock ops implemented. Instead, we'll just create a clock that can + * be queried and set the CLK_GET_RATE_NOCACHE attribute to tell common clock + * framework that we're doing things behind its back. This should be sufficient + * to create nice clean device tree bindings and later (if needed) we can try + * re-architecting SDHCI if we see some benefit to it. + * + * Return: 0 on success and error value on error + */ +static int +eswin_sdhci_sdio_register_sdclk(struct eswin_sdhci_data *eswin_sdhci_sdio, + struct clk *clk_xin, struct device *dev) +{ + struct device_node *np = dev->of_node; + u32 num_clks = 0; + int ret; + + /* Providing a clock to the PHY is optional; no error if missing */ + if (of_property_read_u32(np, "#clock-cells", &num_clks) < 0) + return 0; + + ret = eswin_sdhci_sdio_register_sdcardclk(eswin_sdhci_sdio, clk_xin, + dev); + if (ret) + return ret; + + if (num_clks) { + ret = eswin_sdhci_sdio_register_sampleclk(eswin_sdhci_sdio, + clk_xin, dev); + if (ret) { + eswin_sdhci_sdio_unregister_sdclk(dev); + return ret; + } + } + + return 0; +} + +static int +eswin_sdhci_sdio_add_host(struct eswin_sdhci_data *eswin_sdhci_sdio) +{ + struct sdhci_host *host = eswin_sdhci_sdio->host; + struct cqhci_host *cq_host; + bool dma64; + int ret; + + if (!eswin_sdhci_sdio->has_cqe) + return sdhci_add_host(host); + + ret = sdhci_setup_host(host); + if (ret) + return ret; + + cq_host = devm_kzalloc(host->mmc->parent, sizeof(*cq_host), GFP_KERNEL); + if (!cq_host) { + ret = -ENOMEM; + goto cleanup; + } + + cq_host->mmio = host->ioaddr + ESWIN_SDHCI_SD_CQE_BASE_ADDR; + cq_host->ops = &eswin_sdhci_sdio_cqhci_ops; + + dma64 = host->flags & SDHCI_USE_64_BIT_DMA; + if (dma64) + cq_host->caps |= CQHCI_TASK_DESC_SZ_128; + + ret = cqhci_init(cq_host, host->mmc, dma64); + if (ret) + goto cleanup; + + ret = __sdhci_add_host(host); + if (ret) + goto cleanup; + + return 0; + +cleanup: + sdhci_cleanup_host(host); + return ret; +} + +static int eswin_sdhci_sdio_sid_cfg(struct device *dev) +{ + int ret; + struct regmap *regmap; + int hsp_mmu_sdio_reg; + u32 rdwr_sid_ssid; + u32 sid; + struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); + + /* not behind smmu, use the default reset value(0x0) of the reg as streamID*/ + if (fwspec == NULL) { + dev_dbg(dev, + "dev is not behind smmu, skip configuration of sid\n"); + return 0; + } + sid = fwspec->ids[0]; + + regmap = syscon_regmap_lookup_by_phandle(dev->of_node, + "eswin,hsp_sp_csr"); + if (IS_ERR(regmap)) { + dev_dbg(dev, "No hsp_sp_csr phandle specified\n"); + return 0; + } + + ret = of_property_read_u32_index(dev->of_node, "eswin,hsp_sp_csr", 1, + &hsp_mmu_sdio_reg); + if (ret) { + dev_err(dev, "can't get sdio sid cfg reg offset (%d)\n", ret); + return ret; + } + + /* make the reading sid the same as writing sid, ssid is fixed to zero */ + rdwr_sid_ssid = FIELD_PREP(AWSMMUSID, sid); + rdwr_sid_ssid |= FIELD_PREP(ARSMMUSID, sid); + rdwr_sid_ssid |= FIELD_PREP(AWSMMUSSID, 0); + rdwr_sid_ssid |= FIELD_PREP(ARSMMUSSID, 0); + regmap_write(regmap, hsp_mmu_sdio_reg, rdwr_sid_ssid); + + ret = win2030_dynm_sid_enable(dev_to_node(dev)); + if (ret < 0) + dev_err(dev, "failed to config sdio streamID(%d)!\n", sid); + else + dev_dbg(dev, "success to config sdio streamID(%d)!\n", sid); + + return ret; +} + +static int eswin_sdhci_sdio_probe(struct platform_device *pdev) +{ + int ret; + struct clk *clk_xin; + struct clk *clk_spll2_fout3; + struct clk *clk_mux; + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct eswin_sdhci_data *eswin_sdhci_sdio; + struct eswin_sdio_private *eswin_sdio_priv; + struct regmap *regmap; + const struct eswin_sdhci_of_data *data; + unsigned int sdio_id = 0; + unsigned int val = 0; + + data = of_device_get_match_data(dev); + host = sdhci_pltfm_init(pdev, data->pdata, sizeof(*eswin_sdhci_sdio) + sizeof(*eswin_sdio_priv)); + + if (IS_ERR(host)) + return PTR_ERR(host); + + pltfm_host = sdhci_priv(host); + eswin_sdhci_sdio = sdhci_pltfm_priv(pltfm_host); + eswin_sdhci_sdio->host = host; + eswin_sdhci_sdio->has_cqe = false; + eswin_sdio_priv = sdhci_sdio_priv(eswin_sdhci_sdio); + + ret = of_property_read_u32(dev->of_node, "core-clk-reg", &val); + if (ret) { + dev_err(dev, "get core clk reg failed.\n"); + goto err_pltfm_free; + } + + eswin_sdhci_sdio->core_clk_reg = ioremap(val, 0x4); + if (!eswin_sdhci_sdio->core_clk_reg) { + dev_err(dev, "ioremap core clk reg failed.\n"); + goto err_pltfm_free; + } + + ret = of_property_read_u32(dev->of_node, "sdio-id", &sdio_id); + if (ret) { + dev_err(dev, "get sdio-id failed.\n"); + goto err_pltfm_free; + } + +#if defined(__SDIO_HAPS) || defined(__SDIO_ZEBU) +#if !defined(__SDIO_UHS) + /* This macro is only for setting 3.3v speed mode(HAPS) , you can delete it later*/ + eswin_sdhci_sdio->host->quirks2 |= SDHCI_QUIRK2_NO_1_8_V; +#endif +#if defined(__SDIO_PIO) + /* This macro is only for testing PIO , you can delete it later*/ + eswin_sdhci_sdio->host->quirks |= SDHCI_QUIRK_BROKEN_DMA | + SDHCI_QUIRK_BROKEN_ADMA; +#elif defined(__SDIO_SDMA) + /* This macro is only for testing SDMA ,you can delete it later*/ + eswin_sdhci_sdio->host->quirks |= SDHCI_QUIRK_BROKEN_ADMA; +#elif defined(__SDIO_ADMA3) + /* This macro is only for testing ADMA3 ,you can delete it later*/ + sdhci_enable_v4_mode(eswin_sdhci_sdio->host); +#endif +#endif + sdhci_get_of_property(pdev); + + eswin_sdhci_sdio->clk_ops = data->clk_ops; + eswin_sdhci_sdio->clk_ahb = devm_clk_get(dev, "clk_ahb"); + if (IS_ERR(eswin_sdhci_sdio->clk_ahb)) { + ret = dev_err_probe(dev, PTR_ERR(eswin_sdhci_sdio->clk_ahb), + "clk_ahb clock not found.\n"); + goto err_pltfm_free; + } + + clk_xin = devm_clk_get(dev, "clk_xin"); + if (IS_ERR(clk_xin)) { + ret = dev_err_probe(dev, PTR_ERR(clk_xin), + "clk_xin clock not found.\n"); + goto err_pltfm_free; + } + + clk_spll2_fout3 = devm_clk_get(dev, "clk_spll2_fout3"); + + if (IS_ERR(clk_spll2_fout3)) { + ret = dev_err_probe(dev, PTR_ERR(clk_spll2_fout3), + "clk_spll2_fout3 clock not found.\n"); + goto err_pltfm_free; + } + + if (of_device_is_compatible(np, "eswin,sdhci-sdio")) { + clk_mux = devm_clk_get(dev, "clk_mux1_1"); + if (IS_ERR(clk_mux)) { + ret = dev_err_probe(dev, PTR_ERR(clk_mux), + "clk_mux1_1 clock not found.\n"); + goto err_pltfm_free; + } + /*switch the core clk source*/ + clk_set_parent(clk_mux, clk_spll2_fout3); + } + + ret = clk_prepare_enable(eswin_sdhci_sdio->clk_ahb); + if (ret) { + dev_err(dev, "Unable to enable AHB clock.\n"); + goto err_pltfm_free; + } + /* If clock-frequency property is set, use the provided value */ + if (pltfm_host->clock && pltfm_host->clock != clk_get_rate(clk_xin)) { + ret = clk_set_rate(clk_xin, pltfm_host->clock); + if (ret) { + dev_err(&pdev->dev, "Failed to set SD clock rate\n"); + goto clk_dis_ahb; + } + } + + ret = clk_prepare_enable(clk_xin); + if (ret) { + dev_err(dev, "Unable to enable SD clock.\n"); + goto clk_dis_ahb; + } + + pltfm_host->clk = clk_xin; + ret = eswin_sdhci_sdio_register_sdclk(eswin_sdhci_sdio, clk_xin, dev); + if (ret) + goto clk_disable_all; + + ret = eswin_sdhci_reset_init(dev, eswin_sdhci_sdio); + if (ret < 0) { + dev_err(dev, "failed to reset\n"); + goto clk_disable_all; + } + + regmap = syscon_regmap_lookup_by_phandle(dev->of_node, + "eswin,hsp_sp_csr"); + if (IS_ERR(regmap)) { + dev_err(dev, "No hsp_sp_csr phandle specified\n"); + ret = -EFAULT; + goto clk_disable_all; + } + + if (sdio_id == 0) { + regmap_write(regmap, ESWIN_SDHCI_SD0_INT_STATUS, + MSHC_INT_CLK_STABLE); + regmap_write(regmap, ESWIN_SDHCI_SD0_PWR_CTRL, MSHC_HOST_VAL_STABLE); + } else { + regmap_write(regmap, ESWIN_SDHCI_SD1_INT_STATUS, + MSHC_INT_CLK_STABLE); + regmap_write(regmap, ESWIN_SDHCI_SD1_PWR_CTRL, MSHC_HOST_VAL_STABLE); + } + + ret = eswin_sdhci_sdio_sid_cfg(dev); + if (ret < 0) { + dev_err(dev, "failed to use smmu\n"); + goto clk_disable_all; + } + + if (!of_property_read_u32(dev->of_node, "delay_code", &val)) { + eswin_sdhci_sdio->phy.delay_code = val; + } + + if (!of_property_read_u32(dev->of_node, "drive-impedance-ohm", &val)) + eswin_sdhci_sdio->phy.drive_impedance = + eswin_convert_drive_impedance_ohm(pdev, val); + + if (of_property_read_bool(dev->of_node, "enable-cmd-pullup")) + eswin_sdhci_sdio->phy.enable_cmd_pullup = ENABLE; + else + eswin_sdhci_sdio->phy.enable_cmd_pullup = DISABLE; + + if (of_property_read_bool(dev->of_node, "enable-data-pullup")) + eswin_sdhci_sdio->phy.enable_data_pullup = ENABLE; + else + eswin_sdhci_sdio->phy.enable_data_pullup = DISABLE; + + if (of_property_read_bool(dev->of_node, "enable_sw_tuning")) + eswin_sdio_priv->enable_sw_tuning = ENABLE; + else + eswin_sdio_priv->enable_sw_tuning = DISABLE; + + if (!of_property_read_u32(dev->of_node, "phase_code", &val)) { + eswin_sdio_priv->phase_code = val; + } else { + eswin_sdio_priv->phase_code = -1; + } + + eswin_sdhci_dt_parse_clk_phases(dev, &eswin_sdhci_sdio->clk_data); + ret = mmc_of_parse(host->mmc); + if (ret) { + ret = dev_err_probe(dev, ret, "parsing dt failed.\n"); + goto unreg_clk; + } + + win2030_tbu_power(&pdev->dev, true); + + ret = eswin_sdhci_sdio_add_host(eswin_sdhci_sdio); + if (ret) + goto unreg_clk; + + return 0; + +unreg_clk: + eswin_sdhci_sdio_unregister_sdclk(dev); +clk_disable_all: + clk_disable_unprepare(clk_xin); +clk_dis_ahb: + clk_disable_unprepare(eswin_sdhci_sdio->clk_ahb); +err_pltfm_free: + if (eswin_sdhci_sdio->core_clk_reg) + iounmap(eswin_sdhci_sdio->core_clk_reg); + + sdhci_pltfm_free(pdev); + return ret; +} + +static int eswin_sdhci_sdio_remove(struct platform_device *pdev) +{ + int ret; + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci_sdio = + sdhci_pltfm_priv(pltfm_host); + struct clk *clk_ahb = eswin_sdhci_sdio->clk_ahb; + void __iomem *core_clk_reg = eswin_sdhci_sdio->core_clk_reg; + + sdhci_pltfm_remove(pdev); + win2030_tbu_power(&pdev->dev, false); + + if (eswin_sdhci_sdio->txrx_rst) { + ret = reset_control_assert(eswin_sdhci_sdio->txrx_rst); + WARN_ON(0 != ret); + } + + if (eswin_sdhci_sdio->phy_rst) { + ret = reset_control_assert(eswin_sdhci_sdio->phy_rst); + WARN_ON(0 != ret); + } + + if (eswin_sdhci_sdio->prstn) { + ret = reset_control_assert(eswin_sdhci_sdio->prstn); + WARN_ON(0 != ret); + } + + if (eswin_sdhci_sdio->arstn) { + ret = reset_control_assert(eswin_sdhci_sdio->arstn); + WARN_ON(0 != ret); + } + + eswin_sdhci_sdio_unregister_sdclk(&pdev->dev); + clk_disable_unprepare(clk_ahb); + iounmap(core_clk_reg); + + return 0; +} + +static struct platform_driver eswin_sdhci_sdio_driver = { + .driver = { + .name = "eswin-sdhci-sdio", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = eswin_sdhci_sdio_of_match, + .pm = &eswin_sdhci_sdio_dev_pm_ops, + }, + .probe = eswin_sdhci_sdio_probe, + .remove = eswin_sdhci_sdio_remove, +}; + +static __init int eswin_sdhci_sdio_init(void) +{ + int ret; + + ret = platform_driver_register(&eswin_sdhci_sdio_driver); + if (ret) { + pr_err("%s: failed to register platform driver\n", + __func__); + } + + return ret; +} + +static void __exit eswin_sdhci_sdio_exit(void) +{ + platform_driver_unregister(&eswin_sdhci_sdio_driver); +} + +/*Cause EMMC is often used as a system disk(mmc0), we need the SD driver to run later than the EMMC driver*/ +late_initcall(eswin_sdhci_sdio_init); +module_exit(eswin_sdhci_sdio_exit); + +MODULE_DESCRIPTION("Driver for the Eswin SDHCI Controller"); +MODULE_AUTHOR("Eswin"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/host/sdhci-of-eswin.c b/drivers/mmc/host/sdhci-of-eswin.c new file mode 100644 index 000000000000..591315f48cbc --- /dev/null +++ b/drivers/mmc/host/sdhci-of-eswin.c @@ -0,0 +1,1109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ESWIN Emmc Driver + * + * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved. + * SPDX-License-Identifier: GPL-2.0 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Authors: liangshuang + */ + +#include +#include +#include +#include +#include +#include +#include +#include "cqhci.h" +#include "sdhci-pltfm.h" +#include +#include +#include +#include +#include +#include "sdhci-eswin.h" + +#define SDHCI_EMMC0_INT_STATUS 0x508 +#define SDHCI_EMMC0_PWR_CLEAR 0x50c + +//EMMC_DWC_MSHC_CRYPTO_CFG_PTR 8 -- parameter +#define eswin_sdhci_VENDOR_REGISTER_BASEADDR 0x800 +#define eswin_sdhci_VENDOR_EMMC_CTRL_REGISTER 0x2c +#define VENDOR_ENHANCED_STROBE BIT(8) + +#define eswin_sdhci_CQE_BASE_ADDR eswin_sdhci_VENDOR_REGISTER_BASEADDR + +/* Controller does not have CD wired and will not function normally without */ +#define eswin_sdhci_QUIRK_FORCE_CDTEST BIT(0) +/* Controller immediately reports SDHCI_CLOCK_INT_STABLE after enabling the + * internal clock even when the clock isn't stable */ +#define eswin_sdhci_QUIRK_CLOCK_UNSTABLE BIT(1) + +/* + * On some SoCs the syscon area has a feature where the upper 16-bits of + * each 32-bit register act as a write mask for the lower 16-bits. This allows + * atomic updates of the register without locking. This macro is used on SoCs + * that have that feature. + */ +#define HIWORD_UPDATE(val, mask, shift) \ + ((val) << (shift) | (mask) << ((shift) + 16)) + +#define ESWIN_EMMC_CORE_CLK_REG 0x51828160 + +static void eswin_sdhci_set_clock(struct sdhci_host *host, unsigned int clock) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + struct eswin_sdhci_clk_data *clk_data = &eswin_sdhci->clk_data; + + /* Set the Input and Output Clock Phase Delays */ + if (clk_data->set_clk_delays) + clk_data->set_clk_delays(host); + + eswin_sdhci_set_core_clock(host, clock); + sdhci_set_clock(host, clock); + + /* + * Some controllers immediately report SDHCI_CLOCK_INT_STABLE + * after enabling the clock even though the clock is not + * stable. Trying to use a clock without waiting here results + * in EILSEQ while detecting some older/slower cards. The + * chosen delay is the maximum delay from sdhci_set_clock. + */ + if (eswin_sdhci->quirks & SDHCI_ESWIN_QUIRK_CLOCK_UNSTABLE) + msleep(20); +} + +#if !defined(__FPGA) && !defined(__ZEBU) +static void eswin_sdhci_hs400_enhanced_strobe(struct mmc_host *mmc, + struct mmc_ios *ios) +{ + u32 vendor; + struct sdhci_host *host = mmc_priv(mmc); + + vendor = sdhci_readl(host, eswin_sdhci_VENDOR_EMMC_CTRL_REGISTER); + if (ios->enhanced_strobe) + vendor |= VENDOR_ENHANCED_STROBE; + else + vendor &= ~VENDOR_ENHANCED_STROBE; + + sdhci_writel(host, vendor, eswin_sdhci_VENDOR_EMMC_CTRL_REGISTER); +} +#endif + +static void eswin_sdhci_config_phy_delay(struct sdhci_host *host, int delay) +{ + delay &= PHY_CLK_MAX_DELAY_MASK; + + /*phy clk delay line config*/ + sdhci_writeb(host, PHY_UPDATE_DELAY_CODE, PHY_SDCLKDL_CNFG_R); + sdhci_writeb(host, delay, PHY_SDCLKDL_DC_R); + sdhci_writeb(host, 0x0, PHY_SDCLKDL_CNFG_R); +} + +static void eswin_sdhci_config_phy(struct sdhci_host *host) +{ + unsigned int val = 0; + unsigned int drv = 0; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + struct eswin_sdhci_phy_data *phy = &eswin_sdhci->phy; + + drv = phy->drive_impedance << PHY_PAD_SP_DRIVE_SHIF; + + pr_debug("%s: phy drv=0x%x\n", mmc_hostname(host->mmc), drv); + + val = sdhci_readw(host, VENDOR_EMMC_CTRL_R); + val |= EMMC_CRAD_PRESENT; // emmc card + sdhci_writew(host, val, VENDOR_EMMC_CTRL_R); + + eswin_sdhci_disable_card_clk(host); + + /* reset phy,config phy's pad */ + sdhci_writel(host, drv | (~PHY_RSTN), PHY_CNFG_R); + /*CMDPAD_CNFS*/ + val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | + (phy->enable_cmd_pullup << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1; + sdhci_writew(host, val, PHY_CMDPAD_CNFG_R); + pr_debug("%s: phy cmd=0x%x\n", mmc_hostname(host->mmc), val); + + /*DATA PAD CNFG*/ + val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | + (phy->enable_data_pullup << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1; + sdhci_writew(host, val, PHY_DATAPAD_CNFG_R); + pr_debug("%s: phy data=0x%x\n", mmc_hostname(host->mmc), val); + + /*Clock PAD Setting*/ + val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | PHY_PAD_RXSEL_0; + sdhci_writew(host, val, PHY_CLKPAD_CNFG_R); + pr_debug("%s: phy clk=0x%x\n", mmc_hostname(host->mmc), val); + + /*PHY strobe PAD setting*/ + val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | + ((phy->enable_strobe_pulldown * PHY_PULL_DOWN) << PHY_PULL_BIT_SHIF) | + PHY_PAD_RXSEL_1; + sdhci_writew(host, val, PHY_STBPAD_CNFG_R); + pr_debug("%s: phy strobe=0x%x\n", mmc_hostname(host->mmc), val); + mdelay(2); + + /*PHY RSTN PAD setting*/ + val = (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_P_BIT_SHIFT) | + (PHY_SLEW_2 << PHY_TX_SLEW_CTRL_N_BIT_SHIFT) | + (PHY_PULL_UP << PHY_PULL_BIT_SHIF) | PHY_PAD_RXSEL_1; + sdhci_writew(host, val, PHY_RSTNPAD_CNFG_R); + pr_debug("%s: phy rstn=0x%x\n", mmc_hostname(host->mmc), val); + + sdhci_writel(host, drv | PHY_RSTN, PHY_CNFG_R); + + eswin_sdhci_config_phy_delay(host, phy->delay_code); + + eswin_sdhci_enable_card_clk(host); +} + +static void eswin_sdhci_reset(struct sdhci_host *host, u8 mask) +{ + u8 ctrl; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + + sdhci_reset(host, mask); + + if (eswin_sdhci->quirks & SDHCI_ESWIN_QUIRK_FORCE_CDTEST) { + ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + ctrl |= SDHCI_CTRL_CDTEST_INS | SDHCI_CTRL_CDTEST_EN; + sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + } + + if (mask == SDHCI_RESET_ALL) { // after reset all,the phy`s config will be clear. + eswin_sdhci_config_phy(host); + } +} + +static u32 eswin_sdhci_cqhci_irq(struct sdhci_host *host, u32 intmask) +{ + int cmd_error = 0; + int data_error = 0; + + if (!sdhci_cqe_irq(host, intmask, &cmd_error, &data_error)) + return intmask; + + cqhci_irq(host->mmc, intmask, cmd_error, data_error); + + return 0; +} + +static void eswin_sdhci_dumpregs(struct mmc_host *mmc) +{ + sdhci_dumpregs(mmc_priv(mmc)); +} + +static void eswin_sdhci_cqe_enable(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + u32 reg; + + reg = sdhci_readl(host, SDHCI_PRESENT_STATE); + while (reg & SDHCI_DATA_AVAILABLE) { + sdhci_readl(host, SDHCI_BUFFER); + reg = sdhci_readl(host, SDHCI_PRESENT_STATE); + } + + sdhci_cqe_enable(mmc); +} + +static int eswin_sdhci_delay_tuning(struct sdhci_host *host, u32 opcode) +{ + int ret; + int delay = 0; + int i = 0; + int delay_min = -1; + int delay_max = -1; + int cmd_error = 0; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + + for (i = 0; i <= PHY_DELAY_CODE_MAX; i++) { + eswin_sdhci_disable_card_clk(host); + eswin_sdhci_config_phy_delay(host, i); + eswin_sdhci_enable_card_clk(host); + ret = mmc_send_tuning(host->mmc, opcode, &cmd_error); + if (ret) { + sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + udelay(200); + if (delay_min != -1 && delay_max != -1) + break; + } else { + if (delay_min == -1) { + delay_min = i; + continue; + } else { + delay_max = i; + continue; + } + } + } + if (delay_min == -1 && delay_max == -1) { + pr_err("%s: delay code tuning failed!\n", + mmc_hostname(host->mmc)); + eswin_sdhci_disable_card_clk(host); + eswin_sdhci_config_phy_delay(host, eswin_sdhci->phy.delay_code); + eswin_sdhci_enable_card_clk(host); + + return ret; + } + + delay = (delay_min + delay_max) / 2; + pr_info("%s: set delay:0x%x\n", mmc_hostname(host->mmc), delay); + eswin_sdhci_disable_card_clk(host); + eswin_sdhci_config_phy_delay(host, delay); + eswin_sdhci_enable_card_clk(host); + + return 0; +} + +static int eswin_sdhci_phase_code_tuning(struct sdhci_host *host, u32 opcode) +{ + int cmd_error = 0; + int ret = 0; + int phase_code = 0; + int code_min = -1; + int code_max = -1; + + for (phase_code = 0; phase_code <= MAX_PHASE_CODE; phase_code++) { + eswin_sdhci_disable_card_clk(host); + sdhci_writew(host, phase_code, VENDOR_AT_SATA_R); + eswin_sdhci_enable_card_clk(host); + + ret = mmc_send_tuning(host->mmc, opcode, &cmd_error); + if (ret) { + sdhci_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + udelay(200); + if (code_min != -1 && code_max != -1) + break; + } else { + if (code_min == -1) { + code_min = phase_code; + continue; + } else { + code_max = phase_code; + continue; + } + } + } + if (code_min == -1 && code_max == -1) { + pr_err("%s: phase code tuning failed!\n", + mmc_hostname(host->mmc)); + eswin_sdhci_disable_card_clk(host); + sdhci_writew(host, 0, VENDOR_AT_SATA_R); + eswin_sdhci_enable_card_clk(host); + return -EIO; + } + + phase_code = (code_min + code_max) / 2; + pr_info("%s: set phase_code:0x%x\n", mmc_hostname(host->mmc), phase_code); + + eswin_sdhci_disable_card_clk(host); + sdhci_writew(host, phase_code, VENDOR_AT_SATA_R); + eswin_sdhci_enable_card_clk(host); + + return 0; +} + +static int eswin_sdhci_executing_tuning(struct sdhci_host *host, u32 opcode) +{ + u32 ctrl; + u32 val; + int ret = 0; + + eswin_sdhci_disable_card_clk(host); + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl &= ~SDHCI_CTRL_TUNED_CLK; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + + val = sdhci_readl(host, VENDOR_AT_CTRL_R); + val |= SW_TUNE_ENABLE; + sdhci_writew(host, val, VENDOR_AT_CTRL_R); + sdhci_writew(host, 0, VENDOR_AT_SATA_R); + + eswin_sdhci_enable_card_clk(host); + + sdhci_writew(host, 0x0, SDHCI_CMD_DATA); + + ret = eswin_sdhci_delay_tuning(host, opcode); + if (ret < 0) { + return ret; + } + ret = eswin_sdhci_phase_code_tuning(host, opcode); + if (ret < 0) { + return ret; + } + + return 0; +} + +void eswin_sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing) +{ + u32 val; + u32 status; + u32 timeout = 0; + u16 ctrl_2; + + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); + /* Select Bus Speed Mode for host */ + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; + if ((timing == MMC_TIMING_MMC_HS200) || + (timing == MMC_TIMING_UHS_SDR104)) + ctrl_2 |= SDHCI_CTRL_UHS_SDR104; + else if (timing == MMC_TIMING_UHS_SDR12) + ctrl_2 |= SDHCI_CTRL_UHS_SDR12; + else if (timing == MMC_TIMING_UHS_SDR25) + ctrl_2 |= SDHCI_CTRL_UHS_SDR25; + else if (timing == MMC_TIMING_UHS_SDR50) + ctrl_2 |= SDHCI_CTRL_UHS_SDR50; + else if ((timing == MMC_TIMING_UHS_DDR50) || + (timing == MMC_TIMING_MMC_DDR52)) + ctrl_2 |= SDHCI_CTRL_UHS_DDR50; + else if (timing == MMC_TIMING_MMC_HS400) + ctrl_2 |= ESWIN_SDHCI_CTRL_HS400; /* Non-standard */ + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); + + /* + * here need make dll locked when in hs400 at 200MHz + */ + if ((timing == MMC_TIMING_MMC_HS400) && (host->clock == 200000000)) { + eswin_sdhci_disable_card_clk(host); + + val = sdhci_readl(host, VENDOR_AT_CTRL_R); + val &= ~(LATENCY_LT_MASK << LATENCY_LT_BIT_OFFSET); + val |= (LATENCY_LT_3 << LATENCY_LT_MASK); + sdhci_writel(host, val, VENDOR_AT_CTRL_R); + + sdhci_writeb(host, 0x23, PHY_DLL_CNFG1_R); + sdhci_writeb(host, 0x02, PHY_DLL_CNFG2_R); + sdhci_writeb(host, 0x60, PHY_DLLDL_CNFG_R); + sdhci_writeb(host, 0x00, PHY_DLL_OFFST_R); + sdhci_writew(host, 0xffff, PHY_DLLBT_CNFG_R); + + eswin_sdhci_enable_card_clk(host); + sdhci_writeb(host, DLL_ENABEL, PHY_DLL_CTRL_R); + udelay(100); + + while (1) { + status = sdhci_readb(host, PHY_DLL_STATUS_R); + if (status & DLL_LOCK_STS) { + pr_debug("%s: locked status:0x%x\n", mmc_hostname(host->mmc), status); + break; + } + timeout++; + udelay(100); + if (timeout > 10000) { + pr_err("%s: DLL lock failed!status:0x%x\n", + mmc_hostname(host->mmc), status); + return; + } + } + + status = sdhci_readb(host, PHY_DLL_STATUS_R); + if (status & DLL_ERROR_STS) { + pr_err("%s: DLL lock failed!err_status:0x%x\n", + mmc_hostname(host->mmc), status); + } else { + pr_debug("%s: DLL lock is success\n", mmc_hostname(host->mmc)); + } + } +} + +static const struct cqhci_host_ops eswin_sdhci_cqhci_ops = { + .enable = eswin_sdhci_cqe_enable, + .disable = sdhci_cqe_disable, + .dumpregs = eswin_sdhci_dumpregs, +}; + +static const struct sdhci_ops eswin_sdhci_cqe_ops = { + .set_clock = eswin_sdhci_set_clock, + .get_max_clock = sdhci_pltfm_clk_get_max_clock, + .get_timeout_clock = sdhci_pltfm_clk_get_max_clock, + .set_bus_width = sdhci_set_bus_width, + .reset = eswin_sdhci_reset, + .set_uhs_signaling = eswin_sdhci_set_uhs_signaling, + .set_power = sdhci_set_power_and_bus_voltage, + .irq = eswin_sdhci_cqhci_irq, + .platform_execute_tuning = eswin_sdhci_executing_tuning, +}; + +static const struct sdhci_pltfm_data eswin_sdhci_cqe_pdata = { + .ops = &eswin_sdhci_cqe_ops, + .quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN, + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN | +#if defined(__DISABLE_HS200) + SDHCI_QUIRK2_BROKEN_HS200 | +#endif + SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN, +}; + +#ifdef CONFIG_PM_SLEEP +/** + * eswin_sdhci_suspend - Suspend method for the driver + * @dev: Address of the device structure + * + * Put the device in a low power state. + * + * Return: 0 on success and error value on error + */ +static int eswin_sdhci_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + int ret; + + if (host->tuning_mode != SDHCI_TUNING_MODE_3) + mmc_retune_needed(host->mmc); + + if (eswin_sdhci->has_cqe) { + ret = cqhci_suspend(host->mmc); + if (ret) + return ret; + } + + ret = sdhci_suspend_host(host); + if (ret) + return ret; + + clk_disable(pltfm_host->clk); + clk_disable(eswin_sdhci->clk_ahb); + + return 0; +} + +/** + * eswin_sdhci_resume - Resume method for the driver + * @dev: Address of the device structure + * + * Resume operation after suspend + * + * Return: 0 on success and error value on error + */ +static int eswin_sdhci_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + int ret; + + ret = clk_enable(eswin_sdhci->clk_ahb); + if (ret) { + dev_err(dev, "Cannot enable AHB clock.\n"); + return ret; + } + + ret = clk_enable(pltfm_host->clk); + if (ret) { + dev_err(dev, "Cannot enable SD clock.\n"); + return ret; + } + + ret = sdhci_resume_host(host); + if (ret) { + dev_err(dev, "Cannot resume host.\n"); + return ret; + } + + if (eswin_sdhci->has_cqe) + return cqhci_resume(host->mmc); + + return 0; +} +#endif /* ! CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(eswin_sdhci_dev_pm_ops, eswin_sdhci_suspend, + eswin_sdhci_resume); + +/** + * eswin_sdhci_sdcardclk_recalc_rate - Return the card clock rate + * + * @hw: Pointer to the hardware clock structure. + * @parent_rate: The parent rate (should be rate of clk_xin). + * + * Return the current actual rate of the SD card clock. This can be used + * to communicate with out PHY. + * + * Return: The card clock rate. + */ +static unsigned long +eswin_sdhci_sdcardclk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct eswin_sdhci_clk_data *clk_data = + container_of(hw, struct eswin_sdhci_clk_data, sdcardclk_hw); + struct eswin_sdhci_data *eswin_sdhci = + container_of(clk_data, struct eswin_sdhci_data, clk_data); + struct sdhci_host *host = eswin_sdhci->host; + + return host->mmc->actual_clock; +} + +static const struct clk_ops eswin_sdcardclk_ops = { + .recalc_rate = eswin_sdhci_sdcardclk_recalc_rate, +}; + +/** + * eswin_sdhci_sampleclk_recalc_rate - Return the sampling clock rate + * + * @hw: Pointer to the hardware clock structure. + * @parent_rate: The parent rate (should be rate of clk_xin). + * + * Return the current actual rate of the sampling clock. This can be used + * to communicate with out PHY. + * + * Return: The sample clock rate. + */ +static unsigned long +eswin_sdhci_sampleclk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct eswin_sdhci_clk_data *clk_data = + container_of(hw, struct eswin_sdhci_clk_data, sampleclk_hw); + struct eswin_sdhci_data *eswin_sdhci = + container_of(clk_data, struct eswin_sdhci_data, clk_data); + struct sdhci_host *host = eswin_sdhci->host; + + return host->mmc->actual_clock; +} + +static const struct clk_ops eswin_sampleclk_ops = { + .recalc_rate = eswin_sdhci_sampleclk_recalc_rate, +}; + +static const struct eswin_sdhci_clk_ops eswin_clk_ops = { + .sdcardclk_ops = &eswin_sdcardclk_ops, + .sampleclk_ops = &eswin_sampleclk_ops, +}; + +static struct eswin_sdhci_of_data eswin_sdhci_fu800_data = { + .pdata = &eswin_sdhci_cqe_pdata, + .clk_ops = &eswin_clk_ops, +}; + +static const struct of_device_id eswin_sdhci_of_match[] = { + { + .compatible = "eswin,emmc-sdhci-5.1", + .data = &eswin_sdhci_fu800_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, eswin_sdhci_of_match); + +/** + * eswin_sdhci_register_sdcardclk - Register the sdcardclk for a PHY to use + * + * @eswin_sdhci: Our private data structure. + * @clk_xin: Pointer to the functional clock + * @dev: Pointer to our struct device. + * + * Some PHY devices need to know what the actual card clock is. In order for + * them to find out, we'll provide a clock through the common clock framework + * for them to query. + * + * Return: 0 on success and error value on error + */ +static int eswin_sdhci_register_sdcardclk(struct eswin_sdhci_data *eswin_sdhci, + struct clk *clk_xin, struct device *dev) +{ + struct eswin_sdhci_clk_data *clk_data = &eswin_sdhci->clk_data; + struct device_node *np = dev->of_node; + struct clk_init_data sdcardclk_init; + const char *parent_clk_name; + int ret; + + ret = of_property_read_string_index(np, "clock-output-names", 0, + &sdcardclk_init.name); + if (ret) { + dev_err(dev, "DT has #clock-cells but no clock-output-names\n"); + return ret; + } + + parent_clk_name = __clk_get_name(clk_xin); + sdcardclk_init.parent_names = &parent_clk_name; + sdcardclk_init.num_parents = 1; + sdcardclk_init.flags = CLK_GET_RATE_NOCACHE; + sdcardclk_init.ops = eswin_sdhci->clk_ops->sdcardclk_ops; + + clk_data->sdcardclk_hw.init = &sdcardclk_init; + clk_data->sdcardclk = devm_clk_register(dev, &clk_data->sdcardclk_hw); + if (IS_ERR(clk_data->sdcardclk)) + return PTR_ERR(clk_data->sdcardclk); + clk_data->sdcardclk_hw.init = NULL; + + ret = of_clk_add_provider(np, of_clk_src_simple_get, + clk_data->sdcardclk); + if (ret) + dev_err(dev, "Failed to add sdcard clock provider\n"); + + return ret; +} + +/** + * eswin_sdhci_register_sampleclk - Register the sampleclk for a PHY to use + * + * @eswin_sdhci: Our private data structure. + * @clk_xin: Pointer to the functional clock + * @dev: Pointer to our struct device. + * + * Some PHY devices need to know what the actual card clock is. In order for + * them to find out, we'll provide a clock through the common clock framework + * for them to query. + * + * Return: 0 on success and error value on error + */ +static int eswin_sdhci_register_sampleclk(struct eswin_sdhci_data *eswin_sdhci, + struct clk *clk_xin, struct device *dev) +{ + struct eswin_sdhci_clk_data *clk_data = &eswin_sdhci->clk_data; + struct device_node *np = dev->of_node; + struct clk_init_data sampleclk_init; + const char *parent_clk_name; + int ret; + + ret = of_property_read_string_index(np, "clock-output-names", 1, + &sampleclk_init.name); + if (ret) { + dev_err(dev, "DT has #clock-cells but no clock-output-names\n"); + return ret; + } + + parent_clk_name = __clk_get_name(clk_xin); + sampleclk_init.parent_names = &parent_clk_name; + sampleclk_init.num_parents = 1; + sampleclk_init.flags = CLK_GET_RATE_NOCACHE; + sampleclk_init.ops = eswin_sdhci->clk_ops->sampleclk_ops; + + clk_data->sampleclk_hw.init = &sampleclk_init; + clk_data->sampleclk = devm_clk_register(dev, &clk_data->sampleclk_hw); + if (IS_ERR(clk_data->sampleclk)) + return PTR_ERR(clk_data->sampleclk); + clk_data->sampleclk_hw.init = NULL; + + ret = of_clk_add_provider(np, of_clk_src_simple_get, + clk_data->sampleclk); + if (ret) + dev_err(dev, "Failed to add sample clock provider\n"); + + return ret; +} + +/** + * eswin_sdhci_unregister_sdclk - Undoes eswin_sdhci_register_sdclk() + * + * @dev: Pointer to our struct device. + * + * Should be called any time we're exiting and eswin_sdhci_register_sdclk() + * returned success. + */ +static void eswin_sdhci_unregister_sdclk(struct device *dev) +{ + struct device_node *np = dev->of_node; + + if (!of_find_property(np, "#clock-cells", NULL)) + return; + + of_clk_del_provider(dev->of_node); +} + +/** + * eswin_sdhci_register_sdclk - Register the sdcardclk for a PHY to use + * + * @eswin_sdhci: Our private data structure. + * @clk_xin: Pointer to the functional clock + * @dev: Pointer to our struct device. + * + * Some PHY devices need to know what the actual card clock is. In order for + * them to find out, we'll provide a clock through the common clock framework + * for them to query. + * + * Note: without seriously re-architecting SDHCI's clock code and testing on + * all platforms, there's no way to create a totally beautiful clock here + * with all clock ops implemented. Instead, we'll just create a clock that can + * be queried and set the CLK_GET_RATE_NOCACHE attribute to tell common clock + * framework that we're doing things behind its back. This should be sufficient + * to create nice clean device tree bindings and later (if needed) we can try + * re-architecting SDHCI if we see some benefit to it. + * + * Return: 0 on success and error value on error + */ +static int eswin_sdhci_register_sdclk(struct eswin_sdhci_data *eswin_sdhci, + struct clk *clk_xin, struct device *dev) +{ + struct device_node *np = dev->of_node; + u32 num_clks = 0; + int ret; + + /* Providing a clock to the PHY is optional; no error if missing */ + if (of_property_read_u32(np, "#clock-cells", &num_clks) < 0) + return 0; + + ret = eswin_sdhci_register_sdcardclk(eswin_sdhci, clk_xin, dev); + if (ret) + return ret; + + if (num_clks) { + ret = eswin_sdhci_register_sampleclk(eswin_sdhci, clk_xin, dev); + if (ret) { + eswin_sdhci_unregister_sdclk(dev); + return ret; + } + } + + return 0; +} + +static int eswin_sdhci_add_host(struct eswin_sdhci_data *eswin_sdhci) +{ + struct sdhci_host *host = eswin_sdhci->host; + struct cqhci_host *cq_host; + bool dma64; + int ret; + + if (!eswin_sdhci->has_cqe) + return sdhci_add_host(host); + + ret = sdhci_setup_host(host); + if (ret) + return ret; + + cq_host = devm_kzalloc(host->mmc->parent, sizeof(*cq_host), GFP_KERNEL); + if (!cq_host) { + ret = -ENOMEM; + goto cleanup; + } + + cq_host->mmio = host->ioaddr + eswin_sdhci_CQE_BASE_ADDR; + cq_host->ops = &eswin_sdhci_cqhci_ops; + + dma64 = host->flags & SDHCI_USE_64_BIT_DMA; + if (dma64) + cq_host->caps |= CQHCI_TASK_DESC_SZ_128; + + ret = cqhci_init(cq_host, host->mmc, dma64); + if (ret) + goto cleanup; + + ret = __sdhci_add_host(host); + if (ret) + goto cleanup; + + return 0; + +cleanup: + sdhci_cleanup_host(host); + return ret; +} + +static int eswin_emmc_sid_cfg(struct device *dev) +{ + int ret; + struct regmap *regmap; + int hsp_mmu_emmc_reg; + u32 rdwr_sid_ssid; + u32 sid; + struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); + + /* not behind smmu, use the default reset value(0x0) of the reg as streamID*/ + if (fwspec == NULL) { + dev_dbg(dev, + "dev is not behind smmu, skip configuration of sid\n"); + return 0; + } + sid = fwspec->ids[0]; + + regmap = syscon_regmap_lookup_by_phandle(dev->of_node, + "eswin,hsp_sp_csr"); + if (IS_ERR(regmap)) { + dev_dbg(dev, "No hsp_sp_csr phandle specified\n"); + return 0; + } + + ret = of_property_read_u32_index(dev->of_node, "eswin,hsp_sp_csr", 1, + &hsp_mmu_emmc_reg); + if (ret) { + dev_err(dev, "can't get emmc sid cfg reg offset (%d)\n", ret); + return ret; + } + + /* make the reading sid the same as writing sid, ssid is fixed to zero */ + rdwr_sid_ssid = FIELD_PREP(AWSMMUSID, sid); + rdwr_sid_ssid |= FIELD_PREP(ARSMMUSID, sid); + rdwr_sid_ssid |= FIELD_PREP(AWSMMUSSID, 0); + rdwr_sid_ssid |= FIELD_PREP(ARSMMUSSID, 0); + regmap_write(regmap, hsp_mmu_emmc_reg, rdwr_sid_ssid); + + ret = win2030_dynm_sid_enable(dev_to_node(dev)); + if (ret < 0) + dev_err(dev, "failed to config emmc streamID(%d)!\n", sid); + else + dev_dbg(dev, "success to config emmc streamID(%d)!\n", sid); + + return ret; +} + +static int eswin_sdhci_probe(struct platform_device *pdev) +{ + int ret; + struct clk *clk_xin; + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host; + struct device *dev = &pdev->dev; + struct eswin_sdhci_data *eswin_sdhci; + const struct eswin_sdhci_of_data *data; + struct regmap *regmap; + unsigned int val = 0; + + data = of_device_get_match_data(dev); + host = sdhci_pltfm_init(pdev, data->pdata, sizeof(*eswin_sdhci)); + if (IS_ERR(host)) + return PTR_ERR(host); + + pltfm_host = sdhci_priv(host); + eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + eswin_sdhci->host = host; + eswin_sdhci->clk_ops = data->clk_ops; + + eswin_sdhci->core_clk_reg = ioremap(ESWIN_EMMC_CORE_CLK_REG, 0x4); + if (!eswin_sdhci->core_clk_reg) { + dev_err(dev, "ioremap core clk reg failed.\n"); + goto err_pltfm_free; + } + +#if defined(__FPGA) || defined(__ZEBU) +#if defined __SDMA + eswin_sdhci->host->quirks |= SDHCI_QUIRK_BROKEN_ADMA; +#elif defined(__ADMA2) || defined(__ADMA3) +#else + eswin_sdhci->host->quirks |= SDHCI_QUIRK_BROKEN_ADMA | + SDHCI_QUIRK_BROKEN_DMA; +#endif +#endif + +#if defined(__FORCE_1BIT) + eswin_sdhci->host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA; +#endif + + eswin_sdhci->clk_ahb = devm_clk_get(dev, "clk_ahb"); + if (IS_ERR(eswin_sdhci->clk_ahb)) { + ret = dev_err_probe(dev, PTR_ERR(eswin_sdhci->clk_ahb), + "clk_ahb clock not found.\n"); + goto err_pltfm_free; + } + + clk_xin = devm_clk_get(dev, "clk_xin"); + if (IS_ERR(clk_xin)) { + ret = dev_err_probe(dev, PTR_ERR(clk_xin), + "clk_xin clock not found.\n"); + goto err_pltfm_free; + } + + ret = clk_prepare_enable(eswin_sdhci->clk_ahb); + if (ret) { + dev_err(dev, "Unable to enable AHB clock.\n"); + goto err_pltfm_free; + } + + ret = clk_prepare_enable(clk_xin); + if (ret) { + dev_err(dev, "Unable to enable SD clock.\n"); + goto clk_dis_ahb; + } + + ret = eswin_sdhci_reset_init(dev, eswin_sdhci); + if (ret < 0) { + dev_err(dev, "failed to reset\n"); + goto clk_disable_all; + } + + win2030_tbu_power(dev, true); + + regmap = syscon_regmap_lookup_by_phandle(dev->of_node, + "eswin,hsp_sp_csr"); + if (IS_ERR(regmap)) { + dev_dbg(dev, "No hsp_sp_csr phandle specified\n"); + goto clk_disable_all; + } + + regmap_write(regmap, SDHCI_EMMC0_INT_STATUS, MSHC_INT_CLK_STABLE); + regmap_write(regmap, SDHCI_EMMC0_PWR_CLEAR, MSHC_HOST_VAL_STABLE); + + /* smmu */ + eswin_emmc_sid_cfg(dev); + + if (!of_property_read_u32(dev->of_node, "delay_code", &val)) { + eswin_sdhci->phy.delay_code = val; + } + + if (!of_property_read_u32(dev->of_node, "drive-impedance-ohm", &val)) + eswin_sdhci->phy.drive_impedance = + eswin_convert_drive_impedance_ohm(pdev, val); + + if (of_property_read_bool(dev->of_node, "enable-cmd-pullup")) + eswin_sdhci->phy.enable_cmd_pullup = ENABLE; + else + eswin_sdhci->phy.enable_cmd_pullup = DISABLE; + + if (of_property_read_bool(dev->of_node, "enable-data-pullup")) + eswin_sdhci->phy.enable_data_pullup = ENABLE; + else + eswin_sdhci->phy.enable_data_pullup = DISABLE; + + if (of_property_read_bool(dev->of_node, "enable-strobe-pulldown")) + eswin_sdhci->phy.enable_strobe_pulldown = ENABLE; + else + eswin_sdhci->phy.enable_strobe_pulldown = DISABLE; + + sdhci_get_of_property(pdev); + + pltfm_host->clk = clk_xin; + + ret = eswin_sdhci_register_sdclk(eswin_sdhci, clk_xin, dev); + if (ret) + goto clk_disable_all; + + eswin_sdhci_dt_parse_clk_phases(dev, &eswin_sdhci->clk_data); + + ret = mmc_of_parse(host->mmc); + if (ret) { + ret = dev_err_probe(dev, ret, "parsing dt failed.\n"); + goto unreg_clk; + } + +#if !defined(__FPGA) && !defined(__ZEBU) + if (of_device_is_compatible(dev->of_node, "eswin,sdhci-5.1")) { + host->mmc_host_ops.hs400_enhanced_strobe = + eswin_sdhci_hs400_enhanced_strobe; + eswin_sdhci->has_cqe = true; + host->mmc->caps2 |= MMC_CAP2_CQE; + + if (!of_property_read_bool(dev->of_node, "disable-cqe-dcmd")) + host->mmc->caps2 |= MMC_CAP2_CQE_DCMD; + } +#endif + +#if !defined(__ADMA3_DISABLE) + sdhci_enable_v4_mode(eswin_sdhci->host); +#endif + + ret = eswin_sdhci_add_host(eswin_sdhci); + if (ret) + goto unreg_clk; + + return 0; + +unreg_clk: + eswin_sdhci_unregister_sdclk(dev); +clk_disable_all: + clk_disable_unprepare(clk_xin); +clk_dis_ahb: + clk_disable_unprepare(eswin_sdhci->clk_ahb); +err_pltfm_free: + if (eswin_sdhci->core_clk_reg) + iounmap(eswin_sdhci->core_clk_reg); + sdhci_pltfm_free(pdev); + return ret; +} + +static int eswin_sdhci_remove(struct platform_device *pdev) +{ + int ret; + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct eswin_sdhci_data *eswin_sdhci = sdhci_pltfm_priv(pltfm_host); + struct clk *clk_ahb = eswin_sdhci->clk_ahb; + void __iomem *core_clk_reg = eswin_sdhci->core_clk_reg; + + sdhci_pltfm_remove(pdev); + win2030_tbu_power(&pdev->dev, false); + + if (eswin_sdhci->txrx_rst) { + ret = reset_control_assert(eswin_sdhci->txrx_rst); + WARN_ON(0 != ret); + } + + if (eswin_sdhci->phy_rst) { + ret = reset_control_assert(eswin_sdhci->phy_rst); + WARN_ON(0 != ret); + } + + if (eswin_sdhci->prstn) { + ret = reset_control_assert(eswin_sdhci->prstn); + WARN_ON(0 != ret); + } + + if (eswin_sdhci->arstn) { + ret = reset_control_assert(eswin_sdhci->arstn); + WARN_ON(0 != ret); + } + eswin_sdhci_unregister_sdclk(&pdev->dev); + clk_disable_unprepare(clk_ahb); + iounmap(core_clk_reg); + + return 0; +} + +static void emmc_hard_reset(struct sdhci_host *host) +{ + unsigned int val; + + val = sdhci_readw(host, VENDOR_EMMC_CTRL_R); + val |= EMMC_RST_N_OE; + sdhci_writew(host, val, VENDOR_EMMC_CTRL_R); + val &= ~EMMC_RST_N; + sdhci_writew(host, val, VENDOR_EMMC_CTRL_R); + mdelay(20); + val |= EMMC_RST_N; + sdhci_writew(host, val, VENDOR_EMMC_CTRL_R); +} + +static void eswin_sdhci_shutdown(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + + if (!host) + return; + + emmc_hard_reset(host); + host->ops->reset(host, SDHCI_RESET_ALL); + platform_set_drvdata(pdev, NULL); +} + +static struct platform_driver eswin_sdhci_driver = +{ + .driver = { + .name = "sdhci-eswin", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = eswin_sdhci_of_match, + .pm = &eswin_sdhci_dev_pm_ops, + }, + .probe = eswin_sdhci_probe, + .remove = eswin_sdhci_remove, + .shutdown = eswin_sdhci_shutdown, +}; + +module_platform_driver(eswin_sdhci_driver); + +MODULE_DESCRIPTION("Driver for the ESWIN SDHCI Controller"); +MODULE_AUTHOR("liangshuang@eswin.com"); +MODULE_LICENSE("GPL v2"); -- 2.47.0