Add support for the GL9750 and GL9755 chipsets. Signed-off-by: Ben Chuang <ben.chuang@xxxxxxxxxxxxxxxxxxx> Co-developed-by: Michael K Johnson <johnsonm@xxxxxxxxx> Signed-off-by: Michael K Johnson <johnsonm@xxxxxxxxx> diff --git a/drivers/mmc/host/sdhci-gli.h b/drivers/mmc/host/sdhci-gli.h new file mode 100644 index 000000000000..0acd35b6d3e2 --- /dev/null +++ b/drivers/mmc/host/sdhci-gli.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#ifndef __SDHCI_GLI_H +#define __SDHCI_GLI_H + +/* the define PCI_VENDOR_ID_GLI may put in kernel/include/linux/pci_ids.h */ +#ifndef PCI_VENDOR_ID_GLI +#define PCI_VENDOR_ID_GLI 0x17a0 +#endif + +/* Genesys Logic extra registers */ +#define SDHCI_GLI_9750_WT 0x800 +#define SDHCI_GLI_9750_DRIVING 0x860 +#define SDHCI_GLI_9750_PLL 0x864 +#define SDHCI_GLI_9750_SW_CTRL 0x874 +#define SDHCI_GLI_9750_MISC 0x878 + +#define SDHCI_GLI_9750_TUNING_CONTROL 0x540 +#define SDHCI_GLI_9750_TUNING_PARAMETERS 0x544 + +#define GLI_9755_DRIVER_VER "Genesys Logic (GL9755 v0.9.0-y190703)" +#define GLI_9750_DRIVER_VER "Genesys Logic (GL9750 v0.9.0-y190703)" + +#define GLI_MAX_TUNING_LOOP 40 + +void gli_set_9750(struct sdhci_host *host); + +#endif /* __SDHCI_GLI_H */ diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c index 4154ee11b47d..b5c28df39de1 100644 --- a/drivers/mmc/host/sdhci-pci-core.c +++ b/drivers/mmc/host/sdhci-pci-core.c @@ -35,6 +35,7 @@ #include "sdhci.h" #include "sdhci-pci.h" +#include "sdhci-gli.h" static void sdhci_pci_hw_reset(struct sdhci_host *host); @@ -1453,6 +1454,223 @@ static const struct sdhci_pci_fixes sdhci_rtsx = { .probe_slot = rtsx_probe_slot, }; +/* Genesys Logic chipset */ +static int gli_probe_slot_gl9755(struct sdhci_pci_slot *slot) +{ + struct sdhci_host *host = slot->host; + + slot->host->mmc->caps2 |= MMC_CAP2_NO_SDIO; + dev_info(&slot->chip->pdev->dev, "%s\n", GLI_9755_DRIVER_VER); + sdhci_enable_v4_mode(host); + + return 0; +} + +static void gli_set_9750_rx_inv(struct sdhci_host *host, bool b) +{ + u32 wt_value = sdhci_readl(host, SDHCI_GLI_9750_WT); + u32 misc_value = sdhci_readl(host, SDHCI_GLI_9750_MISC); + + if ((wt_value & 0x1) == 0) { + wt_value |= 0x1; + sdhci_writel(host, wt_value, SDHCI_GLI_9750_WT); + } + + misc_value = sdhci_readl(host, SDHCI_GLI_9750_MISC); + if (b) { + misc_value |= 0x8; + sdhci_writel(host, misc_value, SDHCI_GLI_9750_MISC); + } else { + misc_value &= ~0x8; + sdhci_writel(host, misc_value, SDHCI_GLI_9750_MISC); + } + + wt_value = sdhci_readl(host, SDHCI_GLI_9750_WT); + wt_value &= ~0x1; + sdhci_writel(host, wt_value, SDHCI_GLI_9750_WT); +} + +static int __sdhci_execute_tuning_9750(struct sdhci_host *host, u32 opcode) +{ + int i; + int rx_inv = 0; + + for (rx_inv = 0; rx_inv < 2; rx_inv++) { + if (rx_inv & 0x1) + gli_set_9750_rx_inv(host, true); + else + gli_set_9750_rx_inv(host, false); + + sdhci_start_tuning(host); + + for (i = 0; i < GLI_MAX_TUNING_LOOP; i++) { + u16 ctrl; + + sdhci_send_tuning(host, opcode); + + if (!host->tuning_done) { + if (rx_inv == 1) { + pr_info("%s: Tuning timeout, falling back to fixed sampling clock\n", + mmc_hostname(host->mmc)); + sdhci_abort_tuning(host, opcode); + return -ETIMEDOUT; + } + pr_info("%s: Tuning timeout, try next tuning\n", + mmc_hostname(host->mmc)); + sdhci_abort_tuning(host, opcode); + break; + } + + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + if (!(ctrl & SDHCI_CTRL_EXEC_TUNING)) { + if (ctrl & SDHCI_CTRL_TUNED_CLK) { + pr_info("%s: Tuning successful\n", + mmc_hostname(host->mmc)); + return 0; /* Success! */ + } + break; + } + } + } + + pr_info("%s: Tuning failed, falling back to fixed sampling clock\n", + mmc_hostname(host->mmc)); + sdhci_reset_tuning(host); + return -EAGAIN; +} + +static int gl9750_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct sdhci_host *host = mmc_priv(mmc); + int err = 0; + unsigned int tuning_count = 0; + bool hs400_tuning; + + hs400_tuning = host->flags & SDHCI_HS400_TUNING; + + if (host->tuning_mode == SDHCI_TUNING_MODE_1) + tuning_count = host->tuning_count; + + /* + * The Host Controller needs tuning in case of SDR104 and DDR50 + * mode, and for SDR50 mode when Use Tuning for SDR50 is set in + * the Capabilities register. + * If the Host Controller supports the HS200 mode then the + * tuning function has to be executed. + */ + switch (host->timing) { + /* HS400 tuning is done in HS200 mode */ + case MMC_TIMING_MMC_HS400: + err = -EINVAL; + goto out; + + case MMC_TIMING_MMC_HS200: + /* + * Periodic re-tuning for HS400 is not expected to be needed, so + * disable it here. + */ + if (hs400_tuning) + tuning_count = 0; + break; + + case MMC_TIMING_UHS_SDR104: + case MMC_TIMING_UHS_DDR50: + break; + + case MMC_TIMING_UHS_SDR50: + if (host->flags & SDHCI_SDR50_NEEDS_TUNING) + break; + /* FALLTHROUGH */ + + default: + goto out; + } + + if (host->ops->platform_execute_tuning) { + err = host->ops->platform_execute_tuning(host, opcode); + goto out; + } + + host->mmc->retune_period = tuning_count; + + if (host->tuning_delay < 0) + host->tuning_delay = opcode == MMC_SEND_TUNING_BLOCK; + + gli_set_9750(host); + host->tuning_err = __sdhci_execute_tuning_9750(host, opcode); + + sdhci_end_tuning(host); +out: + host->flags &= ~SDHCI_HS400_TUNING; + + return err; +} + +static int gli_probe_slot_gl9750(struct sdhci_pci_slot *slot) +{ + struct sdhci_host *host = slot->host; + struct mmc_host_ops *ops = &host->mmc_host_ops; + + u32 gli_wt = sdhci_readl(host, SDHCI_GLI_9750_WT); + + dev_info(&slot->chip->pdev->dev, "%s\n", GLI_9750_DRIVER_VER); + slot->host->mmc->caps2 |= MMC_CAP2_NO_SDIO; + + if ((gli_wt & 0x1) == 0) { + gli_wt |= 0x1; + sdhci_writel(host, gli_wt, SDHCI_GLI_9750_WT); + } + + gli_wt = sdhci_readl(host, SDHCI_GLI_9750_WT); + gli_wt &= ~0x1; + sdhci_writel(host, gli_wt, SDHCI_GLI_9750_WT); + + sdhci_enable_v4_mode(host); + + ops->execute_tuning = gl9750_execute_tuning; + + return 0; +} + +static void sdhci_gli_voltage_switch(struct sdhci_host *host) +{ + usleep_range(5000, 5500); +} + +static const struct sdhci_ops sdhci_gl9755_ops = { + .set_clock = sdhci_set_clock, + .enable_dma = sdhci_pci_enable_dma, + .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_reset, + .set_uhs_signaling = sdhci_set_uhs_signaling, + .hw_reset = sdhci_pci_hw_reset, + .voltage_switch = sdhci_gli_voltage_switch, +}; + +static const struct sdhci_pci_fixes sdhci_gl9755 = { + .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, + .quirks2 = SDHCI_QUIRK2_BROKEN_DDR50, + .probe_slot = gli_probe_slot_gl9755, + .ops = &sdhci_gl9755_ops, +}; + +static const struct sdhci_ops sdhci_gl9750_ops = { + .set_clock = sdhci_set_clock, + .enable_dma = sdhci_pci_enable_dma, + .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_reset, + .set_uhs_signaling = sdhci_set_uhs_signaling, + .hw_reset = sdhci_pci_hw_reset, + .voltage_switch = sdhci_gli_voltage_switch, +}; + +static const struct sdhci_pci_fixes sdhci_gl9750 = { + .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, + .quirks2 = SDHCI_QUIRK2_BROKEN_DDR50, + .probe_slot = gli_probe_slot_gl9750, + .ops = &sdhci_gl9750_ops, +}; + /*AMD chipset generation*/ enum amd_chipset_gen { AMD_CHIPSET_BEFORE_ML, @@ -1682,6 +1900,8 @@ static const struct pci_device_id pci_ids[] = { SDHCI_PCI_DEVICE(O2, SEABIRD1, o2), SDHCI_PCI_DEVICE(ARASAN, PHY_EMMC, arasan), SDHCI_PCI_DEVICE(SYNOPSYS, DWC_MSHC, snps), + SDHCI_PCI_DEVICE(GLI, 9755, gl9755), + SDHCI_PCI_DEVICE(GLI, 9750, gl9750), SDHCI_PCI_DEVICE_CLASS(AMD, SYSTEM_SDHCI, PCI_CLASS_MASK, amd), /* Generic SD host controller */ {PCI_DEVICE_CLASS(SYSTEM_SDHCI, PCI_CLASS_MASK)}, diff --git a/drivers/mmc/host/sdhci-pci.h b/drivers/mmc/host/sdhci-pci.h index e5dc6e44c7a4..78e6b631e252 100644 --- a/drivers/mmc/host/sdhci-pci.h +++ b/drivers/mmc/host/sdhci-pci.h @@ -65,6 +65,9 @@ #define PCI_DEVICE_ID_SYNOPSYS_DWC_MSHC 0xc202 +#define PCI_DEVICE_ID_GLI_9755 0x9755 +#define PCI_DEVICE_ID_GLI_9750 0x9750 + /* * PCI device class and mask */ diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 14957578bf2e..1ccf1b692d04 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -25,6 +25,8 @@ #include <linux/leds.h> +#include <linux/pci.h> + #include <linux/mmc/mmc.h> #include <linux/mmc/host.h> #include <linux/mmc/card.h> @@ -32,6 +34,8 @@ #include <linux/mmc/slot-gpio.h> #include "sdhci.h" +#include "sdhci-pci.h" +#include "sdhci-gli.h" #define DRIVER_NAME "sdhci" @@ -50,6 +54,8 @@ static void sdhci_finish_data(struct sdhci_host *); static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable); +static bool gli_is_9750(struct sdhci_host *); + void sdhci_dumpregs(struct sdhci_host *host) { SDHCI_DUMP("============ SDHCI REGISTER DUMP ===========\n"); @@ -328,6 +334,8 @@ static void sdhci_init(struct sdhci_host *host, int soft) host->cqe_on = false; + gli_set_9750(host); + if (soft) { /* force clock reconfiguration */ host->clock = 0; @@ -2326,7 +2334,7 @@ void sdhci_reset_tuning(struct sdhci_host *host) } EXPORT_SYMBOL_GPL(sdhci_reset_tuning); -static void sdhci_abort_tuning(struct sdhci_host *host, u32 opcode) +void sdhci_abort_tuning(struct sdhci_host *host, u32 opcode) { sdhci_reset_tuning(host); @@ -2337,6 +2345,7 @@ static void sdhci_abort_tuning(struct sdhci_host *host, u32 opcode) mmc_abort_tuning(host->mmc, opcode); } +EXPORT_SYMBOL_GPL(sdhci_abort_tuning); /* * We use sdhci_send_tuning() because mmc_send_tuning() is not a good fit. SDHCI @@ -2493,9 +2502,7 @@ int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) host->tuning_delay = opcode == MMC_SEND_TUNING_BLOCK; sdhci_start_tuning(host); - host->tuning_err = __sdhci_execute_tuning(host, opcode); - sdhci_end_tuning(host); out: host->flags &= ~SDHCI_HS400_TUNING; @@ -4090,6 +4097,12 @@ int sdhci_setup_host(struct sdhci_host *host) * value. */ max_current_caps = sdhci_readl(host, SDHCI_MAX_CURRENT); + + if (gli_is_9750(host)) { + if (!(max_current_caps & 0xff)) + max_current_caps |= 0xc8; + } + if (!max_current_caps && !IS_ERR(mmc->supply.vmmc)) { int curr = regulator_get_current_limit(mmc->supply.vmmc); if (curr > 0) { @@ -4269,6 +4282,106 @@ void sdhci_cleanup_host(struct sdhci_host *host) } EXPORT_SYMBOL_GPL(sdhci_cleanup_host); +// for Genesys Logic +void gli_set_9750(struct sdhci_host *host) +{ + u32 wt_value = 0; + u32 driving_value = 0; + u32 pll_value = 0; + u32 sw_ctrl_value = 0; + u32 misc_value = 0; + u32 parameter_value = 0; + u32 control_value = 0; + + u16 ctrl2 = 0; + + if (!gli_is_9750(host)) + return; + + wt_value = sdhci_readl(host, SDHCI_GLI_9750_WT); + if ((wt_value & 0x1) == 0) { + wt_value |= 0x1; + sdhci_writel(host, wt_value, SDHCI_GLI_9750_WT); + } + + driving_value = sdhci_readl(host, SDHCI_GLI_9750_DRIVING); + pll_value = sdhci_readl(host, SDHCI_GLI_9750_PLL); + sw_ctrl_value = sdhci_readl(host, SDHCI_GLI_9750_SW_CTRL); + misc_value = sdhci_readl(host, SDHCI_GLI_9750_MISC); + parameter_value = sdhci_readl(host, SDHCI_GLI_9750_TUNING_PARAMETERS); + control_value = sdhci_readl(host, SDHCI_GLI_9750_TUNING_CONTROL); + + driving_value &= ~(0x0C000FFF); + driving_value |= 0x0C000FFF; + sdhci_writel(host, driving_value, SDHCI_GLI_9750_DRIVING); + + sw_ctrl_value |= 0xc0; + sdhci_writel(host, sw_ctrl_value, SDHCI_GLI_9750_SW_CTRL); + + // reset the tuning flow after reinit and before starting tuning + pll_value |= 0x800000; // bit23-1 + pll_value &= ~(0x00700000); // bit22:20-0 + + misc_value &= ~(0x8); // bit3-0 + misc_value &= ~(0x4); // bit2-0 + + misc_value &= ~(0x70); // bit6:4-0 + misc_value |= 0x50; // bit6:4-5 + + parameter_value &= ~(0x7); // bit2:0-0 + parameter_value |= 0x1; // bit2:0-1 + + control_value &= ~(0x190000); // bit20:19-0, bit16-0 + control_value |= 0x110000; // bit20:19-b10, bit16-1 + + sdhci_writel(host, pll_value, SDHCI_GLI_9750_PLL); + sdhci_writel(host, misc_value, SDHCI_GLI_9750_MISC); + + // disable tuned clk + ctrl2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl2 &= ~SDHCI_CTRL_TUNED_CLK; + sdhci_writew(host, ctrl2, SDHCI_HOST_CONTROL2); + + // 540 enable tuning parameters control + control_value |= 0x10; + sdhci_writel(host, control_value, SDHCI_GLI_9750_TUNING_CONTROL); + + // write 544 tuning parameters + sdhci_writel(host, parameter_value, SDHCI_GLI_9750_TUNING_PARAMETERS); + + // 540 disable tuning parameters control + control_value &= ~0x10; + sdhci_writel(host, control_value, SDHCI_GLI_9750_TUNING_CONTROL); + + // clear tuned clk + ctrl2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); + ctrl2 &= ~SDHCI_CTRL_TUNED_CLK; + sdhci_writew(host, ctrl2, SDHCI_HOST_CONTROL2); + + udelay(1); + wt_value = sdhci_readl(host, SDHCI_GLI_9750_WT); + wt_value &= ~0x1; + sdhci_writel(host, wt_value, SDHCI_GLI_9750_WT); +} +EXPORT_SYMBOL_GPL(gli_set_9750); + +bool gli_is_9750(struct sdhci_host *host) +{ + struct sdhci_pci_slot *slot = sdhci_priv(host); + struct sdhci_pci_chip *chip; + struct pci_dev *pdev; + + chip = slot->chip; + pdev = chip->pdev; + + if (pdev->vendor == PCI_VENDOR_ID_GLI && + pdev->device == PCI_DEVICE_ID_GLI_9750) { + return true; + } + + return false; +} + int __sdhci_add_host(struct sdhci_host *host) { unsigned int flags = WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_HIGHPRI; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 72601a4d2e95..437bab3af195 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -797,5 +797,6 @@ void sdhci_start_tuning(struct sdhci_host *host); void sdhci_end_tuning(struct sdhci_host *host); void sdhci_reset_tuning(struct sdhci_host *host); void sdhci_send_tuning(struct sdhci_host *host, u32 opcode); +void sdhci_abort_tuning(struct sdhci_host *host, u32 opcode); #endif /* __SDHCI_HW_H */ diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 70e86148cb1e..e668da5c8df5 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2403,6 +2403,7 @@ #define PCI_DEVICE_ID_RDC_R6061 0x6061 #define PCI_DEVICE_ID_RDC_D1010 0x1010 +#define PCI_VENDOR_ID_GLI 0x17a0 #define PCI_VENDOR_ID_LENOVO 0x17aa #define PCI_VENDOR_ID_QCOM 0x17cb