diff options
Diffstat (limited to 'drivers/ufs/host')
-rw-r--r-- | drivers/ufs/host/Kconfig | 19 | ||||
-rw-r--r-- | drivers/ufs/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/ufs/host/ufs-exynos.c | 10 | ||||
-rw-r--r-- | drivers/ufs/host/ufs-qcom.c | 548 | ||||
-rw-r--r-- | drivers/ufs/host/ufs-qcom.h | 97 | ||||
-rw-r--r-- | drivers/ufs/host/ufs-sprd.c | 458 | ||||
-rw-r--r-- | drivers/ufs/host/ufs-sprd.h | 85 |
7 files changed, 1005 insertions, 213 deletions
diff --git a/drivers/ufs/host/Kconfig b/drivers/ufs/host/Kconfig index 4cc2dbd79ed0..663881437921 100644 --- a/drivers/ufs/host/Kconfig +++ b/drivers/ufs/host/Kconfig @@ -57,8 +57,9 @@ config SCSI_UFS_DWC_TC_PLATFORM config SCSI_UFS_QCOM tristate "QCOM specific hooks to UFS controller platform driver" depends on SCSI_UFSHCD_PLATFORM && ARCH_QCOM + depends on GENERIC_MSI_IRQ + depends on RESET_CONTROLLER select QCOM_SCM if SCSI_UFS_CRYPTO - select RESET_CONTROLLER help This selects the QCOM specific additions to UFSHCD platform driver. UFS host on QCOM needs some vendor specific configuration before @@ -124,3 +125,19 @@ config SCSI_UFS_EXYNOS Select this if you have UFS host controller on Samsung Exynos SoC. If unsure, say N. + +config SCSI_UFS_VARIABLE_SG_ENTRY_SIZE + bool + default y if SCSI_UFS_EXYNOS && SCSI_UFS_CRYPTO + +config SCSI_UFS_SPRD + tristate "Unisoc specific hooks to UFS controller platform driver" + depends on SCSI_UFSHCD_PLATFORM && (ARCH_SPRD || COMPILE_TEST) + help + This selects the Unisoc specific additions to UFSHCD platform driver. + UFS host on Unisoc needs some vendor specific configuration before + accessing the hardware which includes PHY configuration and vendor + specific registers. + + Select this if you have UFS controller on Unisoc chipset. + If unsure, say N. diff --git a/drivers/ufs/host/Makefile b/drivers/ufs/host/Makefile index 7717ca93e7d5..d7c5bf7fa512 100644 --- a/drivers/ufs/host/Makefile +++ b/drivers/ufs/host/Makefile @@ -12,4 +12,5 @@ obj-$(CONFIG_SCSI_UFSHCD_PLATFORM) += ufshcd-pltfrm.o obj-$(CONFIG_SCSI_UFS_HISI) += ufs-hisi.o obj-$(CONFIG_SCSI_UFS_MEDIATEK) += ufs-mediatek.o obj-$(CONFIG_SCSI_UFS_RENESAS) += ufs-renesas.o +obj-$(CONFIG_SCSI_UFS_SPRD) += ufs-sprd.o obj-$(CONFIG_SCSI_UFS_TI_J721E) += ti-j721e-ufs.o diff --git a/drivers/ufs/host/ufs-exynos.c b/drivers/ufs/host/ufs-exynos.c index c3628a8645a5..7c985fc38db1 100644 --- a/drivers/ufs/host/ufs-exynos.c +++ b/drivers/ufs/host/ufs-exynos.c @@ -1300,6 +1300,14 @@ static int exynos_ufs_hce_enable_notify(struct ufs_hba *hba, switch (status) { case PRE_CHANGE: + /* + * The maximum segment size must be set after scsi_host_alloc() + * has been called and before LUN scanning starts + * (ufshcd_async_scan()). Note: this callback may also be called + * from other functions than ufshcd_init(). + */ + hba->host->max_segment_size = 4096; + if (ufs->drv_data->pre_hce_enable) { ret = ufs->drv_data->pre_hce_enable(ufs); if (ret) @@ -1673,7 +1681,7 @@ static const struct exynos_ufs_drv_data exynos_ufs_drvs = { UFSHCD_QUIRK_BROKEN_OCS_FATAL_ERROR | UFSHCI_QUIRK_SKIP_MANUAL_WB_FLUSH_CTRL | UFSHCD_QUIRK_SKIP_DEF_UNIPRO_TIMEOUT_SETTING | - UFSHCD_QUIRK_ALIGN_SG_WITH_PAGE_SIZE, + UFSHCD_QUIRK_4KB_DMA_ALIGNMENT, .opts = EXYNOS_UFS_OPT_HAS_APB_CLK_CTRL | EXYNOS_UFS_OPT_BROKEN_AUTO_CLK_CTRL | EXYNOS_UFS_OPT_BROKEN_RX_SEL_IDX | diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c index 8ad1415e10b6..34fc453f3eb1 100644 --- a/drivers/ufs/host/ufs-qcom.c +++ b/drivers/ufs/host/ufs-qcom.c @@ -22,8 +22,11 @@ #include <ufs/ufshci.h> #include <ufs/ufs_quirks.h> -#define UFS_QCOM_DEFAULT_DBG_PRINT_EN \ - (UFS_QCOM_DBG_PRINT_REGS_EN | UFS_QCOM_DBG_PRINT_TEST_BUS_EN) +#define MCQ_QCFGPTR_MASK GENMASK(7, 0) +#define MCQ_QCFGPTR_UNIT 0x200 +#define MCQ_SQATTR_OFFSET(c) \ + ((((c) >> 16) & MCQ_QCFGPTR_MASK) * MCQ_QCFGPTR_UNIT) +#define MCQ_QCFG_SIZE 0x40 enum { TSTBUS_UAWM, @@ -52,12 +55,6 @@ static struct ufs_qcom_host *rcdev_to_ufs_host(struct reset_controller_dev *rcd) return container_of(rcd, struct ufs_qcom_host, rcdev); } -static void ufs_qcom_dump_regs_wrapper(struct ufs_hba *hba, int offset, int len, - const char *prefix, void *priv) -{ - ufshcd_dump_regs(hba, offset, len * 4, prefix); -} - static int ufs_qcom_host_clk_get(struct device *dev, const char *name, struct clk **clk_out, bool optional) { @@ -110,7 +107,7 @@ static void ufs_qcom_disable_lane_clks(struct ufs_qcom_host *host) static int ufs_qcom_enable_lane_clks(struct ufs_qcom_host *host) { - int err = 0; + int err; struct device *dev = host->hba->dev; if (host->is_lane_clks_enabled) @@ -119,7 +116,7 @@ static int ufs_qcom_enable_lane_clks(struct ufs_qcom_host *host) err = ufs_qcom_host_clk_enable(dev, "rx_lane0_sync_clk", host->rx_l0_sync_clk); if (err) - goto out; + return err; err = ufs_qcom_host_clk_enable(dev, "tx_lane0_sync_clk", host->tx_l0_sync_clk); @@ -137,7 +134,8 @@ static int ufs_qcom_enable_lane_clks(struct ufs_qcom_host *host) goto disable_rx_l1; host->is_lane_clks_enabled = true; - goto out; + + return 0; disable_rx_l1: clk_disable_unprepare(host->rx_l1_sync_clk); @@ -145,7 +143,7 @@ disable_tx_l0: clk_disable_unprepare(host->tx_l0_sync_clk); disable_rx_l0: clk_disable_unprepare(host->rx_l0_sync_clk); -out: + return err; } @@ -160,25 +158,25 @@ static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host) err = ufs_qcom_host_clk_get(dev, "rx_lane0_sync_clk", &host->rx_l0_sync_clk, false); if (err) - goto out; + return err; err = ufs_qcom_host_clk_get(dev, "tx_lane0_sync_clk", &host->tx_l0_sync_clk, false); if (err) - goto out; + return err; /* In case of single lane per direction, don't read lane1 clocks */ if (host->hba->lanes_per_direction > 1) { err = ufs_qcom_host_clk_get(dev, "rx_lane1_sync_clk", &host->rx_l1_sync_clk, false); if (err) - goto out; + return err; err = ufs_qcom_host_clk_get(dev, "tx_lane1_sync_clk", &host->tx_l1_sync_clk, true); } -out: - return err; + + return 0; } static int ufs_qcom_check_hibern8(struct ufs_hba *hba) @@ -226,6 +224,10 @@ static void ufs_qcom_select_unipro_mode(struct ufs_qcom_host *host) ufshcd_rmwl(host->hba, QUNIPRO_SEL, ufs_qcom_cap_qunipro(host) ? QUNIPRO_SEL : 0, REG_UFS_CFG1); + + if (host->hw_ver.major == 0x05) + ufshcd_rmwl(host->hba, QUNIPRO_G4_SEL, 0, REG_UFS_CFG0); + /* make sure above configuration is applied before we return */ mb(); } @@ -241,7 +243,7 @@ static int ufs_qcom_host_reset(struct ufs_hba *hba) if (!host->core_reset) { dev_warn(hba->dev, "%s: reset control not set\n", __func__); - goto out; + return 0; } reenable_intr = hba->is_irq_enabled; @@ -252,7 +254,7 @@ static int ufs_qcom_host_reset(struct ufs_hba *hba) if (ret) { dev_err(hba->dev, "%s: core_reset assert failed, err = %d\n", __func__, ret); - goto out; + return ret; } /* @@ -274,16 +276,35 @@ static int ufs_qcom_host_reset(struct ufs_hba *hba) hba->is_irq_enabled = true; } -out: - return ret; + return 0; +} + +static u32 ufs_qcom_get_hs_gear(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = ufshcd_get_variant(hba); + + if (host->hw_ver.major == 0x1) { + /* + * HS-G3 operations may not reliably work on legacy QCOM + * UFS host controller hardware even though capability + * exchange during link startup phase may end up + * negotiating maximum supported gear as G3. + * Hence downgrade the maximum supported gear to HS-G2. + */ + return UFS_HS_G2; + } else if (host->hw_ver.major >= 0x4) { + return UFS_QCOM_MAX_GEAR(ufshcd_readl(hba, REG_UFS_PARAM0)); + } + + /* Default is HS-G3 */ + return UFS_HS_G3; } static int ufs_qcom_power_up_sequence(struct ufs_hba *hba) { struct ufs_qcom_host *host = ufshcd_get_variant(hba); struct phy *phy = host->generic_phy; - int ret = 0; - bool is_rate_B = UFS_QCOM_LIMIT_HS_RATE == PA_HS_MODE_B; + int ret; /* Reset UFS Host Controller and PHY */ ret = ufs_qcom_host_reset(hba); @@ -291,17 +312,16 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba) dev_warn(hba->dev, "%s: host reset returned %d\n", __func__, ret); - if (is_rate_B) - phy_set_mode(phy, PHY_MODE_UFS_HS_B); - /* phy initialization - calibrate the phy */ ret = phy_init(phy); if (ret) { dev_err(hba->dev, "%s: phy init failed, ret = %d\n", __func__, ret); - goto out; + return ret; } + phy_set_mode_ext(phy, PHY_MODE_UFS_HS_B, host->hs_gear); + /* power on phy - start serdes and phy's power and clocks */ ret = phy_power_on(phy); if (ret) { @@ -316,7 +336,7 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba) out_disable_phy: phy_exit(phy); -out: + return ret; } @@ -374,7 +394,6 @@ static int ufs_qcom_hce_enable_notify(struct ufs_hba *hba, static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, u32 hs, u32 rate, bool update_link_startup_timer) { - int ret = 0; struct ufs_qcom_host *host = ufshcd_get_variant(hba); struct ufs_clk_info *clki; u32 core_clk_period_in_ns; @@ -409,11 +428,11 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, * Aggregation logic. */ if (ufs_qcom_cap_qunipro(host) && !ufshcd_is_intr_aggr_allowed(hba)) - goto out; + return 0; if (gear == 0) { dev_err(hba->dev, "%s: invalid gear = %d\n", __func__, gear); - goto out_error; + return -EINVAL; } list_for_each_entry(clki, &hba->clk_list_head, list) { @@ -436,7 +455,7 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, } if (ufs_qcom_cap_qunipro(host)) - goto out; + return 0; core_clk_period_in_ns = NSEC_PER_SEC / core_clk_rate; core_clk_period_in_ns <<= OFFSET_CLK_NS_REG; @@ -451,7 +470,7 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, "%s: index %d exceeds table size %zu\n", __func__, gear, ARRAY_SIZE(hs_fr_table_rA)); - goto out_error; + return -EINVAL; } tx_clk_cycles_per_us = hs_fr_table_rA[gear-1][1]; } else if (rate == PA_HS_MODE_B) { @@ -460,13 +479,13 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, "%s: index %d exceeds table size %zu\n", __func__, gear, ARRAY_SIZE(hs_fr_table_rB)); - goto out_error; + return -EINVAL; } tx_clk_cycles_per_us = hs_fr_table_rB[gear-1][1]; } else { dev_err(hba->dev, "%s: invalid rate = %d\n", __func__, rate); - goto out_error; + return -EINVAL; } break; case SLOWAUTO_MODE: @@ -476,14 +495,14 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, "%s: index %d exceeds table size %zu\n", __func__, gear, ARRAY_SIZE(pwm_fr_table)); - goto out_error; + return -EINVAL; } tx_clk_cycles_per_us = pwm_fr_table[gear-1][1]; break; case UNCHANGED: default: dev_err(hba->dev, "%s: invalid mode = %d\n", __func__, hs); - goto out_error; + return -EINVAL; } if (ufshcd_readl(hba, REG_UFS_TX_SYMBOL_CLK_NS_US) != @@ -498,21 +517,17 @@ static int ufs_qcom_cfg_timers(struct ufs_hba *hba, u32 gear, mb(); } - if (update_link_startup_timer) { + if (update_link_startup_timer && host->hw_ver.major != 0x5) { ufshcd_writel(hba, ((core_clk_rate / MSEC_PER_SEC) * 100), - REG_UFS_PA_LINK_STARTUP_TIMER); + REG_UFS_CFG0); /* * make sure that this configuration is applied before * we return */ mb(); } - goto out; -out_error: - ret = -EINVAL; -out: - return ret; + return 0; } static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, @@ -527,8 +542,7 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, 0, true)) { dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n", __func__); - err = -EINVAL; - goto out; + return -EINVAL; } if (ufs_qcom_cap_qunipro(host)) @@ -554,7 +568,6 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, break; } -out: return err; } @@ -691,8 +704,7 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba, if (!dev_req_params) { pr_err("%s: incoming dev_req_params is NULL\n", __func__); - ret = -EINVAL; - goto out; + return -EINVAL; } switch (status) { @@ -700,29 +712,21 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba, ufshcd_init_pwr_dev_param(&ufs_qcom_cap); ufs_qcom_cap.hs_rate = UFS_QCOM_LIMIT_HS_RATE; - if (host->hw_ver.major == 0x1) { - /* - * HS-G3 operations may not reliably work on legacy QCOM - * UFS host controller hardware even though capability - * exchange during link startup phase may end up - * negotiating maximum supported gear as G3. - * Hence downgrade the maximum supported gear to HS-G2. - */ - if (ufs_qcom_cap.hs_tx_gear > UFS_HS_G2) - ufs_qcom_cap.hs_tx_gear = UFS_HS_G2; - if (ufs_qcom_cap.hs_rx_gear > UFS_HS_G2) - ufs_qcom_cap.hs_rx_gear = UFS_HS_G2; - } + /* This driver only supports symmetic gear setting i.e., hs_tx_gear == hs_rx_gear */ + ufs_qcom_cap.hs_tx_gear = ufs_qcom_cap.hs_rx_gear = ufs_qcom_get_hs_gear(hba); ret = ufshcd_get_pwr_dev_param(&ufs_qcom_cap, dev_max_params, dev_req_params); if (ret) { - pr_err("%s: failed to determine capabilities\n", + dev_err(hba->dev, "%s: failed to determine capabilities\n", __func__); - goto out; + return ret; } + /* Use the agreed gear */ + host->hs_gear = dev_req_params->gear_tx; + /* enable the device ref clock before changing to HS mode */ if (!ufshcd_is_hs_mode(&hba->pwr_info) && ufshcd_is_hs_mode(dev_req_params)) @@ -761,7 +765,7 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba, ret = -EINVAL; break; } -out: + return ret; } @@ -773,14 +777,11 @@ static int ufs_qcom_quirk_host_pa_saveconfigtime(struct ufs_hba *hba) err = ufshcd_dme_get(hba, UIC_ARG_MIB(PA_VS_CONFIG_REG1), &pa_vs_config_reg1); if (err) - goto out; + return err; /* Allow extension of MSB bits of PA_SaveConfigTime attribute */ - err = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_VS_CONFIG_REG1), + return ufshcd_dme_set(hba, UIC_ARG_MIB(PA_VS_CONFIG_REG1), (pa_vs_config_reg1 | (1 << 12))); - -out: - return err; } static int ufs_qcom_apply_dev_quirks(struct ufs_hba *hba) @@ -839,6 +840,9 @@ static void ufs_qcom_advertise_quirks(struct ufs_hba *hba) | UFSHCD_QUIRK_DME_PEER_ACCESS_AUTO_MODE | UFSHCD_QUIRK_BROKEN_PA_RXHSUNTERMCAP); } + + if (host->hw_ver.major > 0x3) + hba->quirks |= UFSHCD_QUIRK_REINIT_AFTER_MAX_GEAR_SWITCH; } static void ufs_qcom_set_caps(struct ufs_hba *hba) @@ -906,8 +910,6 @@ ufs_qcom_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) { struct ufs_qcom_host *host = rcdev_to_ufs_host(rcdev); - /* Currently this code only knows about a single reset. */ - WARN_ON(id); ufs_qcom_assert_reset(host->hba); /* provide 1ms delay to let the reset pulse propagate. */ usleep_range(1000, 1100); @@ -919,8 +921,6 @@ ufs_qcom_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) { struct ufs_qcom_host *host = rcdev_to_ufs_host(rcdev); - /* Currently this code only knows about a single reset. */ - WARN_ON(id); ufs_qcom_deassert_reset(host->hba); /* @@ -957,9 +957,8 @@ static int ufs_qcom_init(struct ufs_hba *hba) host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); if (!host) { - err = -ENOMEM; dev_err(dev, "%s: no memory for qcom ufs host\n", __func__); - goto out; + return -ENOMEM; } /* Make a two way bind between the qcom host and the hba */ @@ -980,10 +979,8 @@ static int ufs_qcom_init(struct ufs_hba *hba) host->rcdev.owner = dev->driver->owner; host->rcdev.nr_resets = 1; err = devm_reset_controller_register(dev, &host->rcdev); - if (err) { + if (err) dev_warn(dev, "Failed to register reset controller\n"); - err = 0; - } if (!has_acpi_companion(dev)) { host->generic_phy = devm_phy_get(dev, "ufsphy"); @@ -1046,20 +1043,24 @@ static int ufs_qcom_init(struct ufs_hba *hba) if (hba->dev->id < MAX_UFS_QCOM_HOSTS) ufs_qcom_hosts[hba->dev->id] = host; - host->dbg_print_en |= UFS_QCOM_DEFAULT_DBG_PRINT_EN; ufs_qcom_get_default_testbus_cfg(host); err = ufs_qcom_testbus_config(host); - if (err) { + if (err) + /* Failure is non-fatal */ dev_warn(dev, "%s: failed to configure the testbus %d\n", __func__, err); - err = 0; - } - goto out; + /* + * Power up the PHY using the minimum supported gear (UFS_HS_G2). + * Switching to max gear will be performed during reinit if supported. + */ + host->hs_gear = UFS_HS_G2; + + return 0; out_variant_clear: ufshcd_set_variant(hba, NULL); -out: + return err; } @@ -1085,7 +1086,7 @@ static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba, UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL), &core_clk_ctrl_reg); if (err) - goto out; + return err; core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_MAX_CORE_CLK_1US_CYCLES_MASK; core_clk_ctrl_reg |= clk_cycles; @@ -1093,11 +1094,9 @@ static int ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(struct ufs_hba *hba, /* Clear CORE_CLK_DIV_EN */ core_clk_ctrl_reg &= ~DME_VS_CORE_CLK_CTRL_CORE_CLK_DIV_EN_BIT; - err = ufshcd_dme_set(hba, + return ufshcd_dme_set(hba, UIC_ARG_MIB(DME_VS_CORE_CLK_CTRL), core_clk_ctrl_reg); -out: - return err; } static int ufs_qcom_clk_scale_up_pre_change(struct ufs_hba *hba) @@ -1180,7 +1179,7 @@ static int ufs_qcom_clk_scale_notify(struct ufs_hba *hba, if (err || !dev_req_params) { ufshcd_uic_hibern8_exit(hba); - goto out; + return err; } ufs_qcom_cfg_timers(hba, @@ -1191,81 +1190,14 @@ static int ufs_qcom_clk_scale_notify(struct ufs_hba *hba, ufshcd_uic_hibern8_exit(hba); } -out: - return err; -} - -static void ufs_qcom_print_hw_debug_reg_all(struct ufs_hba *hba, - void *priv, void (*print_fn)(struct ufs_hba *hba, - int offset, int num_regs, const char *str, void *priv)) -{ - u32 reg; - struct ufs_qcom_host *host; - - if (unlikely(!hba)) { - pr_err("%s: hba is NULL\n", __func__); - return; - } - if (unlikely(!print_fn)) { - dev_err(hba->dev, "%s: print_fn is NULL\n", __func__); - return; - } - - host = ufshcd_get_variant(hba); - if (!(host->dbg_print_en & UFS_QCOM_DBG_PRINT_REGS_EN)) - return; - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_REG_OCSC); - print_fn(hba, reg, 44, "UFS_UFS_DBG_RD_REG_OCSC ", priv); - - reg = ufshcd_readl(hba, REG_UFS_CFG1); - reg |= UTP_DBG_RAMS_EN; - ufshcd_writel(hba, reg, REG_UFS_CFG1); - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_EDTL_RAM); - print_fn(hba, reg, 32, "UFS_UFS_DBG_RD_EDTL_RAM ", priv); - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_DESC_RAM); - print_fn(hba, reg, 128, "UFS_UFS_DBG_RD_DESC_RAM ", priv); - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_PRDT_RAM); - print_fn(hba, reg, 64, "UFS_UFS_DBG_RD_PRDT_RAM ", priv); - - /* clear bit 17 - UTP_DBG_RAMS_EN */ - ufshcd_rmwl(hba, UTP_DBG_RAMS_EN, 0, REG_UFS_CFG1); - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_UAWM); - print_fn(hba, reg, 4, "UFS_DBG_RD_REG_UAWM ", priv); - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_UARM); - print_fn(hba, reg, 4, "UFS_DBG_RD_REG_UARM ", priv); - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TXUC); - print_fn(hba, reg, 48, "UFS_DBG_RD_REG_TXUC ", priv); - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_RXUC); - print_fn(hba, reg, 27, "UFS_DBG_RD_REG_RXUC ", priv); - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_DFC); - print_fn(hba, reg, 19, "UFS_DBG_RD_REG_DFC ", priv); - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TRLUT); - print_fn(hba, reg, 34, "UFS_DBG_RD_REG_TRLUT ", priv); - - reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TMRLUT); - print_fn(hba, reg, 9, "UFS_DBG_RD_REG_TMRLUT ", priv); + return 0; } static void ufs_qcom_enable_test_bus(struct ufs_qcom_host *host) { - if (host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN) { - ufshcd_rmwl(host->hba, UFS_REG_TEST_BUS_EN, - UFS_REG_TEST_BUS_EN, REG_UFS_CFG1); - ufshcd_rmwl(host->hba, TEST_BUS_EN, TEST_BUS_EN, REG_UFS_CFG1); - } else { - ufshcd_rmwl(host->hba, UFS_REG_TEST_BUS_EN, 0, REG_UFS_CFG1); - ufshcd_rmwl(host->hba, TEST_BUS_EN, 0, REG_UFS_CFG1); - } + ufshcd_rmwl(host->hba, UFS_REG_TEST_BUS_EN, + UFS_REG_TEST_BUS_EN, REG_UFS_CFG1); + ufshcd_rmwl(host->hba, TEST_BUS_EN, TEST_BUS_EN, REG_UFS_CFG1); } static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host) @@ -1374,10 +1306,53 @@ int ufs_qcom_testbus_config(struct ufs_qcom_host *host) static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba) { + u32 reg; + struct ufs_qcom_host *host; + + host = ufshcd_get_variant(hba); + ufshcd_dump_regs(hba, REG_UFS_SYS1CLK_1US, 16 * 4, "HCI Vendor Specific Registers "); - ufs_qcom_print_hw_debug_reg_all(hba, NULL, ufs_qcom_dump_regs_wrapper); + reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_REG_OCSC); + ufshcd_dump_regs(hba, reg, 44 * 4, "UFS_UFS_DBG_RD_REG_OCSC "); + + reg = ufshcd_readl(hba, REG_UFS_CFG1); + reg |= UTP_DBG_RAMS_EN; + ufshcd_writel(hba, reg, REG_UFS_CFG1); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_EDTL_RAM); + ufshcd_dump_regs(hba, reg, 32 * 4, "UFS_UFS_DBG_RD_EDTL_RAM "); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_DESC_RAM); + ufshcd_dump_regs(hba, reg, 128 * 4, "UFS_UFS_DBG_RD_DESC_RAM "); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_PRDT_RAM); + ufshcd_dump_regs(hba, reg, 64 * 4, "UFS_UFS_DBG_RD_PRDT_RAM "); + + /* clear bit 17 - UTP_DBG_RAMS_EN */ + ufshcd_rmwl(hba, UTP_DBG_RAMS_EN, 0, REG_UFS_CFG1); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_UAWM); + ufshcd_dump_regs(hba, reg, 4 * 4, "UFS_DBG_RD_REG_UAWM "); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_UARM); + ufshcd_dump_regs(hba, reg, 4 * 4, "UFS_DBG_RD_REG_UARM "); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TXUC); + ufshcd_dump_regs(hba, reg, 48 * 4, "UFS_DBG_RD_REG_TXUC "); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_RXUC); + ufshcd_dump_regs(hba, reg, 27 * 4, "UFS_DBG_RD_REG_RXUC "); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_DFC); + ufshcd_dump_regs(hba, reg, 19 * 4, "UFS_DBG_RD_REG_DFC "); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TRLUT); + ufshcd_dump_regs(hba, reg, 34 * 4, "UFS_DBG_RD_REG_TRLUT "); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TMRLUT); + ufshcd_dump_regs(hba, reg, 9 * 4, "UFS_DBG_RD_REG_TMRLUT "); } /** @@ -1424,6 +1399,236 @@ static void ufs_qcom_config_scaling_param(struct ufs_hba *hba, } #endif +static void ufs_qcom_reinit_notify(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = ufshcd_get_variant(hba); + + phy_power_off(host->generic_phy); +} + +/* Resources */ +static const struct ufshcd_res_info ufs_res_info[RES_MAX] = { + {.name = "ufs_mem",}, + {.name = "mcq",}, + /* Submission Queue DAO */ + {.name = "mcq_sqd",}, + /* Submission Queue Interrupt Status */ + {.name = "mcq_sqis",}, + /* Completion Queue DAO */ + {.name = "mcq_cqd",}, + /* Completion Queue Interrupt Status */ + {.name = "mcq_cqis",}, + /* MCQ vendor specific */ + {.name = "mcq_vs",}, +}; + +static int ufs_qcom_mcq_config_resource(struct ufs_hba *hba) +{ + struct platform_device *pdev = to_platform_device(hba->dev); + struct ufshcd_res_info *res; + struct resource *res_mem, *res_mcq; + int i, ret = 0; + + memcpy(hba->res, ufs_res_info, sizeof(ufs_res_info)); + + for (i = 0; i < RES_MAX; i++) { + res = &hba->res[i]; + res->resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + res->name); + if (!res->resource) { + dev_info(hba->dev, "Resource %s not provided\n", res->name); + if (i == RES_UFS) + return -ENOMEM; + continue; + } else if (i == RES_UFS) { + res_mem = res->resource; + res->base = hba->mmio_base; + continue; + } + + res->base = devm_ioremap_resource(hba->dev, res->resource); + if (IS_ERR(res->base)) { + dev_err(hba->dev, "Failed to map res %s, err=%d\n", + res->name, (int)PTR_ERR(res->base)); + res->base = NULL; + ret = PTR_ERR(res->base); + return ret; + } + } + + /* MCQ resource provided in DT */ + res = &hba->res[RES_MCQ]; + /* Bail if MCQ resource is provided */ + if (res->base) + goto out; + + /* Explicitly allocate MCQ resource from ufs_mem */ + res_mcq = devm_kzalloc(hba->dev, sizeof(*res_mcq), GFP_KERNEL); + if (!res_mcq) + return ret; + + res_mcq->start = res_mem->start + + MCQ_SQATTR_OFFSET(hba->mcq_capabilities); + res_mcq->end = res_mcq->start + hba->nr_hw_queues * MCQ_QCFG_SIZE - 1; + res_mcq->flags = res_mem->flags; + res_mcq->name = "mcq"; + + ret = insert_resource(&iomem_resource, res_mcq); + if (ret) { + dev_err(hba->dev, "Failed to insert MCQ resource, err=%d\n", + ret); + goto insert_res_err; + } + + res->base = devm_ioremap_resource(hba->dev, res_mcq); + if (IS_ERR(res->base)) { + dev_err(hba->dev, "MCQ registers mapping failed, err=%d\n", + (int)PTR_ERR(res->base)); + ret = PTR_ERR(res->base); + goto ioremap_err; + } + +out: + hba->mcq_base = res->base; + return 0; +ioremap_err: + res->base = NULL; + remove_resource(res_mcq); +insert_res_err: + devm_kfree(hba->dev, res_mcq); + return ret; +} + +static int ufs_qcom_op_runtime_config(struct ufs_hba *hba) +{ + struct ufshcd_res_info *mem_res, *sqdao_res; + struct ufshcd_mcq_opr_info_t *opr; + int i; + + mem_res = &hba->res[RES_UFS]; + sqdao_res = &hba->res[RES_MCQ_SQD]; + + if (!mem_res->base || !sqdao_res->base) + return -EINVAL; + + for (i = 0; i < OPR_MAX; i++) { + opr = &hba->mcq_opr[i]; + opr->offset = sqdao_res->resource->start - + mem_res->resource->start + 0x40 * i; + opr->stride = 0x100; + opr->base = sqdao_res->base + 0x40 * i; + } + + return 0; +} + +static int ufs_qcom_get_hba_mac(struct ufs_hba *hba) +{ + /* Qualcomm HC supports up to 64 */ + return MAX_SUPP_MAC; +} + +static int ufs_qcom_get_outstanding_cqs(struct ufs_hba *hba, + unsigned long *ocqs) +{ + struct ufshcd_res_info *mcq_vs_res = &hba->res[RES_MCQ_VS]; + + if (!mcq_vs_res->base) + return -EINVAL; + + *ocqs = readl(mcq_vs_res->base + UFS_MEM_CQIS_VS); + + return 0; +} + +static void ufs_qcom_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg) +{ + struct device *dev = msi_desc_to_dev(desc); + struct ufs_hba *hba = dev_get_drvdata(dev); + + ufshcd_mcq_config_esi(hba, msg); +} + +static irqreturn_t ufs_qcom_mcq_esi_handler(int irq, void *__hba) +{ + struct ufs_hba *hba = __hba; + struct ufs_qcom_host *host = ufshcd_get_variant(hba); + u32 id = irq - host->esi_base; + struct ufs_hw_queue *hwq = &hba->uhq[id]; + + ufshcd_mcq_write_cqis(hba, 0x1, id); + ufshcd_mcq_poll_cqe_nolock(hba, hwq); + + return IRQ_HANDLED; +} + +static int ufs_qcom_config_esi(struct ufs_hba *hba) +{ + struct ufs_qcom_host *host = ufshcd_get_variant(hba); + struct msi_desc *desc; + struct msi_desc *failed_desc = NULL; + int nr_irqs, ret; + + if (host->esi_enabled) + return 0; + else if (host->esi_base < 0) + return -EINVAL; + + /* + * 1. We only handle CQs as of now. + * 2. Poll queues do not need ESI. + */ + nr_irqs = hba->nr_hw_queues - hba->nr_queues[HCTX_TYPE_POLL]; + ret = platform_msi_domain_alloc_irqs(hba->dev, nr_irqs, + ufs_qcom_write_msi_msg); + if (ret) + goto out; + + msi_for_each_desc(desc, hba->dev, MSI_DESC_ALL) { + if (!desc->msi_index) + host->esi_base = desc->irq; + + ret = devm_request_irq(hba->dev, desc->irq, + ufs_qcom_mcq_esi_handler, + IRQF_SHARED, "qcom-mcq-esi", hba); + if (ret) { + dev_err(hba->dev, "%s: Fail to request IRQ for %d, err = %d\n", + __func__, desc->irq, ret); + failed_desc = desc; + break; + } + } + + if (ret) { + /* Rewind */ + msi_for_each_desc(desc, hba->dev, MSI_DESC_ALL) { + if (desc == failed_desc) + break; + devm_free_irq(hba->dev, desc->irq, hba); + } + platform_msi_domain_free_irqs(hba->dev); + } else { + if (host->hw_ver.major == 6 && host->hw_ver.minor == 0 && + host->hw_ver.step == 0) { + ufshcd_writel(hba, + ufshcd_readl(hba, REG_UFS_CFG3) | 0x1F000, + REG_UFS_CFG3); + } + ufshcd_mcq_enable_esi(hba); + } + +out: + if (ret) { + host->esi_base = -1; + dev_warn(hba->dev, "Failed to request Platform MSI %d\n", ret); + } else { + host->esi_enabled = true; + } + + return ret; +} + /* * struct ufs_hba_qcom_vops - UFS QCOM specific variant operations * @@ -1447,6 +1652,12 @@ static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = { .device_reset = ufs_qcom_device_reset, .config_scaling_param = ufs_qcom_config_scaling_param, .program_key = ufs_qcom_ice_program_key, + .reinit_notify = ufs_qcom_reinit_notify, + .mcq_config_resource = ufs_qcom_mcq_config_resource, + .get_hba_mac = ufs_qcom_get_hba_mac, + .op_runtime_config = ufs_qcom_op_runtime_config, + .get_outstanding_cqs = ufs_qcom_get_outstanding_cqs, + .config_esi = ufs_qcom_config_esi, }; /** @@ -1463,9 +1674,9 @@ static int ufs_qcom_probe(struct platform_device *pdev) /* Perform generic probe */ err = ufshcd_pltfrm_init(pdev, &ufs_hba_qcom_vops); if (err) - dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err); + return dev_err_probe(dev, err, "ufshcd_pltfrm_init() failed\n"); - return err; + return 0; } /** @@ -1480,6 +1691,7 @@ static int ufs_qcom_remove(struct platform_device *pdev) pm_runtime_get_sync(&(pdev)->dev); ufshcd_remove(hba); + platform_msi_domain_free_irqs(hba->dev); return 0; } @@ -1498,10 +1710,16 @@ MODULE_DEVICE_TABLE(acpi, ufs_qcom_acpi_match); #endif static const struct dev_pm_ops ufs_qcom_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume) SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL) .prepare = ufshcd_suspend_prepare, .complete = ufshcd_resume_complete, +#ifdef CONFIG_PM_SLEEP + .suspend = ufshcd_system_suspend, + .resume = ufshcd_system_resume, + .freeze = ufshcd_system_freeze, + .restore = ufshcd_system_restore, + .thaw = ufshcd_system_thaw, +#endif }; static struct platform_driver ufs_qcom_pltform = { diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h index 44466a395bb5..39e774254fb2 100644 --- a/drivers/ufs/host/ufs-qcom.h +++ b/drivers/ufs/host/ufs-qcom.h @@ -16,13 +16,11 @@ #define HBRN8_POLL_TOUT_MS 100 #define DEFAULT_CLK_RATE_HZ 1000000 #define BUS_VECTOR_NAME_LEN 32 +#define MAX_SUPP_MAC 64 -#define UFS_HW_VER_MAJOR_SHFT (28) -#define UFS_HW_VER_MAJOR_MASK (0x000F << UFS_HW_VER_MAJOR_SHFT) -#define UFS_HW_VER_MINOR_SHFT (16) -#define UFS_HW_VER_MINOR_MASK (0x0FFF << UFS_HW_VER_MINOR_SHFT) -#define UFS_HW_VER_STEP_SHFT (0) -#define UFS_HW_VER_STEP_MASK (0xFFFF << UFS_HW_VER_STEP_SHFT) +#define UFS_HW_VER_MAJOR_MASK GENMASK(31, 28) +#define UFS_HW_VER_MINOR_MASK GENMASK(27, 16) +#define UFS_HW_VER_STEP_MASK GENMASK(15, 0) /* vendor specific pre-defined parameters */ #define SLOW 1 @@ -36,8 +34,10 @@ enum { REG_UFS_TX_SYMBOL_CLK_NS_US = 0xC4, REG_UFS_LOCAL_PORT_ID_REG = 0xC8, REG_UFS_PA_ERR_CODE = 0xCC, - REG_UFS_RETRY_TIMER_REG = 0xD0, - REG_UFS_PA_LINK_STARTUP_TIMER = 0xD8, + /* On older UFS revisions, this register is called "RETRY_TIMER_REG" */ + REG_UFS_PARAM0 = 0xD0, + /* On older UFS revisions, this register is called "REG_UFS_PA_LINK_STARTUP_TIMER" */ + REG_UFS_CFG0 = 0xD8, REG_UFS_CFG1 = 0xDC, REG_UFS_CFG2 = 0xE0, REG_UFS_HW_VERSION = 0xE4, @@ -53,6 +53,8 @@ enum { * added in HW Version 3.0.0 */ UFS_AH8_CFG = 0xFC, + + REG_UFS_CFG3 = 0x271C, }; /* QCOM UFS host controller vendor specific debug registers */ @@ -72,28 +74,43 @@ enum { UFS_UFS_DBG_RD_EDTL_RAM = 0x1900, }; +enum { + UFS_MEM_CQIS_VS = 0x8, +}; + #define UFS_CNTLR_2_x_x_VEN_REGS_OFFSET(x) (0x000 + x) #define UFS_CNTLR_3_x_x_VEN_REGS_OFFSET(x) (0x400 + x) +/* bit definitions for REG_UFS_CFG0 register */ +#define QUNIPRO_G4_SEL BIT(5) + /* bit definitions for REG_UFS_CFG1 register */ -#define QUNIPRO_SEL 0x1 -#define UTP_DBG_RAMS_EN 0x20000 +#define QUNIPRO_SEL BIT(0) +#define UFS_PHY_SOFT_RESET BIT(1) +#define UTP_DBG_RAMS_EN BIT(17) #define TEST_BUS_EN BIT(18) #define TEST_BUS_SEL GENMASK(22, 19) #define UFS_REG_TEST_BUS_EN BIT(30) +#define UFS_PHY_RESET_ENABLE 1 +#define UFS_PHY_RESET_DISABLE 0 + /* bit definitions for REG_UFS_CFG2 register */ -#define UAWM_HW_CGC_EN (1 << 0) -#define UARM_HW_CGC_EN (1 << 1) -#define TXUC_HW_CGC_EN (1 << 2) -#define RXUC_HW_CGC_EN (1 << 3) -#define DFC_HW_CGC_EN (1 << 4) -#define TRLUT_HW_CGC_EN (1 << 5) -#define TMRLUT_HW_CGC_EN (1 << 6) -#define OCSC_HW_CGC_EN (1 << 7) +#define UAWM_HW_CGC_EN BIT(0) +#define UARM_HW_CGC_EN BIT(1) +#define TXUC_HW_CGC_EN BIT(2) +#define RXUC_HW_CGC_EN BIT(3) +#define DFC_HW_CGC_EN BIT(4) +#define TRLUT_HW_CGC_EN BIT(5) +#define TMRLUT_HW_CGC_EN BIT(6) +#define OCSC_HW_CGC_EN BIT(7) + +/* bit definitions for REG_UFS_PARAM0 */ +#define MAX_HS_GEAR_MASK GENMASK(6, 4) +#define UFS_QCOM_MAX_GEAR(x) FIELD_GET(MAX_HS_GEAR_MASK, (x)) /* bit definition for UFS_UFS_TEST_BUS_CTRL_n */ -#define TEST_BUS_SUB_SEL_MASK 0x1F /* All XXX_SEL fields are 5 bits wide */ +#define TEST_BUS_SUB_SEL_MASK GENMASK(4, 0) /* All XXX_SEL fields are 5 bits wide */ #define REG_UFS_CFG2_CGC_EN_ALL (UAWM_HW_CGC_EN | UARM_HW_CGC_EN |\ TXUC_HW_CGC_EN | RXUC_HW_CGC_EN |\ @@ -101,26 +118,11 @@ enum { TMRLUT_HW_CGC_EN | OCSC_HW_CGC_EN) /* bit offset */ -enum { - OFFSET_UFS_PHY_SOFT_RESET = 1, - OFFSET_CLK_NS_REG = 10, -}; +#define OFFSET_CLK_NS_REG 0xa /* bit masks */ -enum { - MASK_UFS_PHY_SOFT_RESET = 0x2, - MASK_TX_SYMBOL_CLK_1US_REG = 0x3FF, - MASK_CLK_NS_REG = 0xFFFC00, -}; - -/* QCOM UFS debug print bit mask */ -#define UFS_QCOM_DBG_PRINT_REGS_EN BIT(0) -#define UFS_QCOM_DBG_PRINT_ICE_REGS_EN BIT(1) -#define UFS_QCOM_DBG_PRINT_TEST_BUS_EN BIT(2) - -#define UFS_QCOM_DBG_PRINT_ALL \ - (UFS_QCOM_DBG_PRINT_REGS_EN | UFS_QCOM_DBG_PRINT_ICE_REGS_EN | \ - UFS_QCOM_DBG_PRINT_TEST_BUS_EN) +#define MASK_TX_SYMBOL_CLK_1US_REG GENMASK(9, 0) +#define MASK_CLK_NS_REG GENMASK(23, 10) /* QUniPro Vendor specific attributes */ #define PA_VS_CONFIG_REG1 0x9000 @@ -135,15 +137,15 @@ ufs_qcom_get_controller_revision(struct ufs_hba *hba, { u32 ver = ufshcd_readl(hba, REG_UFS_HW_VERSION); - *major = (ver & UFS_HW_VER_MAJOR_MASK) >> UFS_HW_VER_MAJOR_SHFT; - *minor = (ver & UFS_HW_VER_MINOR_MASK) >> UFS_HW_VER_MINOR_SHFT; - *step = (ver & UFS_HW_VER_STEP_MASK) >> UFS_HW_VER_STEP_SHFT; + *major = FIELD_GET(UFS_HW_VER_MAJOR_MASK, ver); + *minor = FIELD_GET(UFS_HW_VER_MINOR_MASK, ver); + *step = FIELD_GET(UFS_HW_VER_STEP_MASK, ver); }; static inline void ufs_qcom_assert_reset(struct ufs_hba *hba) { - ufshcd_rmwl(hba, MASK_UFS_PHY_SOFT_RESET, - 1 << OFFSET_UFS_PHY_SOFT_RESET, REG_UFS_CFG1); + ufshcd_rmwl(hba, UFS_PHY_SOFT_RESET, FIELD_PREP(UFS_PHY_SOFT_RESET, UFS_PHY_RESET_ENABLE), + REG_UFS_CFG1); /* * Make sure assertion of ufs phy reset is written to @@ -154,8 +156,8 @@ static inline void ufs_qcom_assert_reset(struct ufs_hba *hba) static inline void ufs_qcom_deassert_reset(struct ufs_hba *hba) { - ufshcd_rmwl(hba, MASK_UFS_PHY_SOFT_RESET, - 0 << OFFSET_UFS_PHY_SOFT_RESET, REG_UFS_CFG1); + ufshcd_rmwl(hba, UFS_PHY_SOFT_RESET, FIELD_PREP(UFS_PHY_SOFT_RESET, UFS_PHY_RESET_DISABLE), + REG_UFS_CFG1); /* * Make sure de-assertion of ufs phy reset is written to @@ -212,8 +214,6 @@ struct ufs_qcom_host { u32 dev_ref_clk_en_mask; - /* Bitmask for enabling debug prints */ - u32 dbg_print_en; struct ufs_qcom_testbus testbus; /* Reset control of HCI */ @@ -221,6 +221,11 @@ struct ufs_qcom_host { struct reset_controller_dev rcdev; struct gpio_desc *device_reset; + + u32 hs_gear; + + int esi_base; + bool esi_enabled; }; static inline u32 diff --git a/drivers/ufs/host/ufs-sprd.c b/drivers/ufs/host/ufs-sprd.c new file mode 100644 index 000000000000..051f3f40d92c --- /dev/null +++ b/drivers/ufs/host/ufs-sprd.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * UNISOC UFS Host Controller driver + * + * Copyright (C) 2022 Unisoc, Inc. + * Author: Zhe Wang <zhe.wang1@unisoc.com> + */ + +#include <linux/arm-smccc.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/regulator/consumer.h> + +#include <ufs/ufshcd.h> +#include "ufshcd-pltfrm.h" +#include "ufs-sprd.h" + +static const struct of_device_id ufs_sprd_of_match[]; + +static struct ufs_sprd_priv *ufs_sprd_get_priv_data(struct ufs_hba *hba) +{ + struct ufs_sprd_host *host = ufshcd_get_variant(hba); + + WARN_ON(!host->priv); + return host->priv; +} + +static void ufs_sprd_regmap_update(struct ufs_sprd_priv *priv, unsigned int index, + unsigned int reg, unsigned int bits, unsigned int val) +{ + regmap_update_bits(priv->sysci[index].regmap, reg, bits, val); +} + +static void ufs_sprd_regmap_read(struct ufs_sprd_priv *priv, unsigned int index, + unsigned int reg, unsigned int *val) +{ + regmap_read(priv->sysci[index].regmap, reg, val); +} + +static void ufs_sprd_get_unipro_ver(struct ufs_hba *hba) +{ + struct ufs_sprd_host *host = ufshcd_get_variant(hba); + + if (ufshcd_dme_get(hba, UIC_ARG_MIB(PA_LOCALVERINFO), &host->unipro_ver)) + host->unipro_ver = 0; +} + +static void ufs_sprd_ctrl_uic_compl(struct ufs_hba *hba, bool enable) +{ + u32 set = ufshcd_readl(hba, REG_INTERRUPT_ENABLE); + + if (enable == true) + set |= UIC_COMMAND_COMPL; + else + set &= ~UIC_COMMAND_COMPL; + ufshcd_writel(hba, set, REG_INTERRUPT_ENABLE); +} + +static int ufs_sprd_get_reset_ctrl(struct device *dev, struct ufs_sprd_rst *rci) +{ + rci->rc = devm_reset_control_get(dev, rci->name); + if (IS_ERR(rci->rc)) { + dev_err(dev, "failed to get reset ctrl:%s\n", rci->name); + return PTR_ERR(rci->rc); + } + + return 0; +} + +static int ufs_sprd_get_syscon_reg(struct device *dev, struct ufs_sprd_syscon *sysci) +{ + sysci->regmap = syscon_regmap_lookup_by_phandle(dev->of_node, sysci->name); + if (IS_ERR(sysci->regmap)) { + dev_err(dev, "failed to get ufs syscon:%s\n", sysci->name); + return PTR_ERR(sysci->regmap); + } + + return 0; +} + +static int ufs_sprd_get_vreg(struct device *dev, struct ufs_sprd_vreg *vregi) +{ + vregi->vreg = devm_regulator_get(dev, vregi->name); + if (IS_ERR(vregi->vreg)) { + dev_err(dev, "failed to get vreg:%s\n", vregi->name); + return PTR_ERR(vregi->vreg); + } + + return 0; +} + +static int ufs_sprd_parse_dt(struct device *dev, struct ufs_hba *hba, struct ufs_sprd_host *host) +{ + u32 i; + struct ufs_sprd_priv *priv = host->priv; + int ret = 0; + + /* Parse UFS reset ctrl info */ + for (i = 0; i < SPRD_UFS_RST_MAX; i++) { + if (!priv->rci[i].name) + continue; + ret = ufs_sprd_get_reset_ctrl(dev, &priv->rci[i]); + if (ret) + goto out; + } + + /* Parse UFS syscon reg info */ + for (i = 0; i < SPRD_UFS_SYSCON_MAX; i++) { + if (!priv->sysci[i].name) + continue; + ret = ufs_sprd_get_syscon_reg(dev, &priv->sysci[i]); + if (ret) + goto out; + } + + /* Parse UFS vreg info */ + for (i = 0; i < SPRD_UFS_VREG_MAX; i++) { + if (!priv->vregi[i].name) + continue; + ret = ufs_sprd_get_vreg(dev, &priv->vregi[i]); + if (ret) + goto out; + } + +out: + return ret; +} + +static int ufs_sprd_common_init(struct ufs_hba *hba) +{ + struct device *dev = hba->dev; + struct ufs_sprd_host *host; + struct platform_device __maybe_unused *pdev = to_platform_device(dev); + const struct of_device_id *of_id; + int ret = 0; + + host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + of_id = of_match_node(ufs_sprd_of_match, pdev->dev.of_node); + if (of_id->data != NULL) + host->priv = container_of(of_id->data, struct ufs_sprd_priv, + ufs_hba_sprd_vops); + + host->hba = hba; + ufshcd_set_variant(hba, host); + + hba->caps |= UFSHCD_CAP_CLK_GATING | + UFSHCD_CAP_CRYPTO | + UFSHCD_CAP_WB_EN; + hba->quirks |= UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS; + + ret = ufs_sprd_parse_dt(dev, hba, host); + + return ret; +} + +static int sprd_ufs_pwr_change_notify(struct ufs_hba *hba, + enum ufs_notify_change_status status, + struct ufs_pa_layer_attr *dev_max_params, + struct ufs_pa_layer_attr *dev_req_params) +{ + struct ufs_sprd_host *host = ufshcd_get_variant(hba); + + if (status == PRE_CHANGE) { + memcpy(dev_req_params, dev_max_params, + sizeof(struct ufs_pa_layer_attr)); + if (host->unipro_ver >= UFS_UNIPRO_VER_1_8) + ufshcd_dme_configure_adapt(hba, dev_req_params->gear_tx, + PA_INITIAL_ADAPT); + } + + return 0; +} + +static int ufs_sprd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op, + enum ufs_notify_change_status status) +{ + unsigned long flags; + + if (status == PRE_CHANGE) { + if (ufshcd_is_auto_hibern8_supported(hba)) { + spin_lock_irqsave(hba->host->host_lock, flags); + ufshcd_writel(hba, 0, REG_AUTO_HIBERNATE_IDLE_TIMER); + spin_unlock_irqrestore(hba->host->host_lock, flags); + } + } + + return 0; +} + +static void ufs_sprd_n6_host_reset(struct ufs_hba *hba) +{ + struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba); + + dev_info(hba->dev, "ufs host reset!\n"); + + reset_control_assert(priv->rci[SPRD_UFSHCI_SOFT_RST].rc); + usleep_range(1000, 1100); + reset_control_deassert(priv->rci[SPRD_UFSHCI_SOFT_RST].rc); +} + +static int ufs_sprd_n6_device_reset(struct ufs_hba *hba) +{ + struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba); + + dev_info(hba->dev, "ufs device reset!\n"); + + reset_control_assert(priv->rci[SPRD_UFS_DEV_RST].rc); + usleep_range(1000, 1100); + reset_control_deassert(priv->rci[SPRD_UFS_DEV_RST].rc); + + return 0; +} + +static void ufs_sprd_n6_key_acc_enable(struct ufs_hba *hba) +{ + u32 val; + u32 retry = 10; + struct arm_smccc_res res; + +check_hce: + /* Key access only can be enabled under HCE enable */ + val = ufshcd_readl(hba, REG_CONTROLLER_ENABLE); + if (!(val & CONTROLLER_ENABLE)) { + ufs_sprd_n6_host_reset(hba); + val |= CONTROLLER_ENABLE; + ufshcd_writel(hba, val, REG_CONTROLLER_ENABLE); + usleep_range(1000, 1100); + if (retry) { + retry--; + goto check_hce; + } + goto disable_crypto; + } + + arm_smccc_smc(SPRD_SIP_SVC_STORAGE_UFS_CRYPTO_ENABLE, + 0, 0, 0, 0, 0, 0, 0, &res); + if (!res.a0) + return; + +disable_crypto: + dev_err(hba->dev, "key reg access enable fail, disable crypto\n"); + hba->caps &= ~UFSHCD_CAP_CRYPTO; +} + +static int ufs_sprd_n6_init(struct ufs_hba *hba) +{ + struct ufs_sprd_priv *priv; + int ret = 0; + + ret = ufs_sprd_common_init(hba); + if (ret != 0) + return ret; + + priv = ufs_sprd_get_priv_data(hba); + + ret = regulator_enable(priv->vregi[SPRD_UFS_VDD_MPHY].vreg); + if (ret) + return -ENODEV; + + if (hba->caps & UFSHCD_CAP_CRYPTO) + ufs_sprd_n6_key_acc_enable(hba); + + return 0; +} + +static int ufs_sprd_n6_phy_init(struct ufs_hba *hba) +{ + int ret = 0; + uint32_t val = 0; + uint32_t retry = 10; + uint32_t offset; + struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba); + + ufshcd_dme_set(hba, UIC_ARG_MIB(CBREFCLKCTRL2), 0x90); + ufshcd_dme_set(hba, UIC_ARG_MIB(CBCRCTRL), 0x01); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RXSQCONTROL, + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), 0x01); + ufshcd_dme_set(hba, UIC_ARG_MIB_SEL(RXSQCONTROL, + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(1)), 0x01); + ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01); + ufshcd_dme_set(hba, UIC_ARG_MIB(CBRATESEL), 0x01); + + do { + /* phy_sram_init_done */ + ufs_sprd_regmap_read(priv, SPRD_UFS_ANLG, 0xc, &val); + if ((val & 0x1) == 0x1) { + for (offset = 0x40; offset < 0x42; offset++) { + /* Lane afe calibration */ + ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGADDRLSB), 0x1c); + ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGADDRMSB), offset); + ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGWRLSB), 0x04); + ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGWRMSB), 0x00); + ufshcd_dme_set(hba, UIC_ARG_MIB(CBCREGRDWRSEL), 0x01); + ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01); + } + + goto update_phy; + } + udelay(1000); + retry--; + } while (retry > 0); + + ret = -ETIMEDOUT; + goto out; + +update_phy: + /* phy_sram_ext_ld_done */ + ufs_sprd_regmap_update(priv, SPRD_UFS_ANLG, 0xc, 0x2, 0); + ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYCFGUPDT), 0x01); + ufshcd_dme_set(hba, UIC_ARG_MIB(VS_MPHYDISABLE), 0x0); +out: + return ret; +} + + +static int sprd_ufs_n6_hce_enable_notify(struct ufs_hba *hba, + enum ufs_notify_change_status status) +{ + int err = 0; + struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba); + + if (status == PRE_CHANGE) { + /* phy_sram_ext_ld_done */ + ufs_sprd_regmap_update(priv, SPRD_UFS_ANLG, 0xc, 0x2, 0x2); + /* phy_sram_bypass */ + ufs_sprd_regmap_update(priv, SPRD_UFS_ANLG, 0xc, 0x4, 0x4); + + ufs_sprd_n6_host_reset(hba); + + if (hba->caps & UFSHCD_CAP_CRYPTO) + ufs_sprd_n6_key_acc_enable(hba); + } + + if (status == POST_CHANGE) { + err = ufs_sprd_n6_phy_init(hba); + if (err) { + dev_err(hba->dev, "Phy setup failed (%d)\n", err); + goto out; + } + + ufs_sprd_get_unipro_ver(hba); + } +out: + return err; +} + +static void sprd_ufs_n6_h8_notify(struct ufs_hba *hba, + enum uic_cmd_dme cmd, + enum ufs_notify_change_status status) +{ + struct ufs_sprd_priv *priv = ufs_sprd_get_priv_data(hba); + + if (status == PRE_CHANGE) { + if (cmd == UIC_CMD_DME_HIBER_ENTER) + /* + * Disable UIC COMPL INTR to prevent access to UFSHCI after + * checking HCS.UPMCRS + */ + ufs_sprd_ctrl_uic_compl(hba, false); + + if (cmd == UIC_CMD_DME_HIBER_EXIT) { + ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_UFSDEV_REG, + APB_UFSDEV_REFCLK_EN, APB_UFSDEV_REFCLK_EN); + ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_USB31PLL_CTRL, + APB_USB31PLLV_REF2MPHY, APB_USB31PLLV_REF2MPHY); + } + } + + if (status == POST_CHANGE) { + if (cmd == UIC_CMD_DME_HIBER_EXIT) + ufs_sprd_ctrl_uic_compl(hba, true); + + if (cmd == UIC_CMD_DME_HIBER_ENTER) { + ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_UFSDEV_REG, + APB_UFSDEV_REFCLK_EN, 0); + ufs_sprd_regmap_update(priv, SPRD_UFS_AON_APB, APB_USB31PLL_CTRL, + APB_USB31PLLV_REF2MPHY, 0); + } + } +} + +static struct ufs_sprd_priv n6_ufs = { + .rci[SPRD_UFSHCI_SOFT_RST] = { .name = "controller", }, + .rci[SPRD_UFS_DEV_RST] = { .name = "device", }, + + .sysci[SPRD_UFS_ANLG] = { .name = "sprd,ufs-anlg-syscon", }, + .sysci[SPRD_UFS_AON_APB] = { .name = "sprd,aon-apb-syscon", }, + + .vregi[SPRD_UFS_VDD_MPHY] = { .name = "vdd-mphy", }, + + .ufs_hba_sprd_vops = { + .name = "sprd,ums9620-ufs", + .init = ufs_sprd_n6_init, + .hce_enable_notify = sprd_ufs_n6_hce_enable_notify, + .pwr_change_notify = sprd_ufs_pwr_change_notify, + .hibern8_notify = sprd_ufs_n6_h8_notify, + .device_reset = ufs_sprd_n6_device_reset, + .suspend = ufs_sprd_suspend, + }, +}; + +static const struct of_device_id __maybe_unused ufs_sprd_of_match[] = { + { .compatible = "sprd,ums9620-ufs", .data = &n6_ufs.ufs_hba_sprd_vops}, + {}, +}; +MODULE_DEVICE_TABLE(of, ufs_sprd_of_match); + +static int ufs_sprd_probe(struct platform_device *pdev) +{ + int err; + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + + of_id = of_match_node(ufs_sprd_of_match, dev->of_node); + err = ufshcd_pltfrm_init(pdev, of_id->data); + if (err) + dev_err(dev, "ufshcd_pltfrm_init() failed %d\n", err); + + return err; +} + +static int ufs_sprd_remove(struct platform_device *pdev) +{ + struct ufs_hba *hba = platform_get_drvdata(pdev); + + pm_runtime_get_sync(&(pdev)->dev); + ufshcd_remove(hba); + return 0; +} + +static const struct dev_pm_ops ufs_sprd_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ufshcd_system_suspend, ufshcd_system_resume) + SET_RUNTIME_PM_OPS(ufshcd_runtime_suspend, ufshcd_runtime_resume, NULL) + .prepare = ufshcd_suspend_prepare, + .complete = ufshcd_resume_complete, +}; + +static struct platform_driver ufs_sprd_pltform = { + .probe = ufs_sprd_probe, + .remove = ufs_sprd_remove, + .shutdown = ufshcd_pltfrm_shutdown, + .driver = { + .name = "ufshcd-sprd", + .pm = &ufs_sprd_pm_ops, + .of_match_table = of_match_ptr(ufs_sprd_of_match), + }, +}; +module_platform_driver(ufs_sprd_pltform); + +MODULE_AUTHOR("Zhe Wang <zhe.wang1@unisoc.com>"); +MODULE_DESCRIPTION("Unisoc UFS Host Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/ufs/host/ufs-sprd.h b/drivers/ufs/host/ufs-sprd.h new file mode 100644 index 000000000000..26ad5c3af4c1 --- /dev/null +++ b/drivers/ufs/host/ufs-sprd.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * UNISOC UFS Host Controller driver + * + * Copyright (C) 2022 Unisoc, Inc. + * Author: Zhe Wang <zhe.wang1@unisoc.com> + */ + +#ifndef _UFS_SPRD_H_ +#define _UFS_SPRD_H_ + +/* Vendor specific attributes */ +#define RXSQCONTROL 0x8009 +#define CBRATESEL 0x8114 +#define CBCREGADDRLSB 0x8116 +#define CBCREGADDRMSB 0x8117 +#define CBCREGWRLSB 0x8118 +#define CBCREGWRMSB 0x8119 +#define CBCREGRDWRSEL 0x811C +#define CBCRCTRL 0x811F +#define CBREFCLKCTRL2 0x8132 +#define VS_MPHYDISABLE 0xD0C1 + +#define APB_UFSDEV_REG 0xCE8 +#define APB_UFSDEV_REFCLK_EN 0x2 +#define APB_USB31PLL_CTRL 0xCFC +#define APB_USB31PLLV_REF2MPHY 0x1 + +#define SPRD_SIP_SVC_STORAGE_UFS_CRYPTO_ENABLE \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_32, \ + ARM_SMCCC_OWNER_SIP, \ + 0x0301) + +enum SPRD_UFS_RST_INDEX { + SPRD_UFSHCI_SOFT_RST, + SPRD_UFS_DEV_RST, + + SPRD_UFS_RST_MAX +}; + +enum SPRD_UFS_SYSCON_INDEX { + SPRD_UFS_ANLG, + SPRD_UFS_AON_APB, + + SPRD_UFS_SYSCON_MAX +}; + +enum SPRD_UFS_VREG_INDEX { + SPRD_UFS_VDD_MPHY, + + SPRD_UFS_VREG_MAX +}; + +struct ufs_sprd_rst { + const char *name; + struct reset_control *rc; +}; + +struct ufs_sprd_syscon { + const char *name; + struct regmap *regmap; +}; + +struct ufs_sprd_vreg { + const char *name; + struct regulator *vreg; +}; + +struct ufs_sprd_priv { + struct ufs_sprd_rst rci[SPRD_UFS_RST_MAX]; + struct ufs_sprd_syscon sysci[SPRD_UFS_SYSCON_MAX]; + struct ufs_sprd_vreg vregi[SPRD_UFS_VREG_MAX]; + const struct ufs_hba_variant_ops ufs_hba_sprd_vops; +}; + +struct ufs_sprd_host { + struct ufs_hba *hba; + struct ufs_sprd_priv *priv; + void __iomem *ufs_dbg_mmio; + + enum ufs_unipro_ver unipro_ver; +}; + +#endif /* _UFS_SPRD_H_ */ |