diff options
Diffstat (limited to 'drivers/scsi/ufs/ufs-mediatek.c')
-rw-r--r-- | drivers/scsi/ufs/ufs-mediatek.c | 140 |
1 files changed, 112 insertions, 28 deletions
diff --git a/drivers/scsi/ufs/ufs-mediatek.c b/drivers/scsi/ufs/ufs-mediatek.c index d56ce8d97d4e..29cd017c1aa0 100644 --- a/drivers/scsi/ufs/ufs-mediatek.c +++ b/drivers/scsi/ufs/ufs-mediatek.c @@ -12,9 +12,11 @@ #include <linux/of_address.h> #include <linux/phy/phy.h> #include <linux/platform_device.h> +#include <linux/regulator/consumer.h> #include <linux/soc/mediatek/mtk_sip_svc.h> #include "ufshcd.h" +#include "ufshcd-crypto.h" #include "ufshcd-pltfrm.h" #include "ufs_quirks.h" #include "unipro.h" @@ -24,6 +26,9 @@ arm_smccc_smc(MTK_SIP_UFS_CONTROL, \ cmd, val, 0, 0, 0, 0, 0, &(res)) +#define ufs_mtk_crypto_ctrl(res, enable) \ + ufs_mtk_smc(UFS_MTK_SIP_CRYPTO_CTRL, enable, res) + #define ufs_mtk_ref_clk_notify(on, res) \ ufs_mtk_smc(UFS_MTK_SIP_REF_CLK_NOTIFICATION, on, res) @@ -31,6 +36,8 @@ ufs_mtk_smc(UFS_MTK_SIP_DEVICE_RESET, high, res) static struct ufs_dev_fix ufs_mtk_dev_fixups[] = { + UFS_FIX(UFS_VENDOR_MICRON, UFS_ANY_MODEL, + UFS_DEVICE_QUIRK_DELAY_AFTER_LPM), UFS_FIX(UFS_VENDOR_SKHYNIX, "H9HQ21AFAMZDAR", UFS_DEVICE_QUIRK_SUPPORT_EXTENDED_FEATURES), END_FIX @@ -72,6 +79,18 @@ static void ufs_mtk_cfg_unipro_cg(struct ufs_hba *hba, bool enable) } } +static void ufs_mtk_crypto_enable(struct ufs_hba *hba) +{ + struct arm_smccc_res res; + + ufs_mtk_crypto_ctrl(res, 1); + if (res.a0) { + dev_info(hba->dev, "%s: crypto enable failed, err: %lu\n", + __func__, res.a0); + hba->caps &= ~UFSHCD_CAP_CRYPTO; + } +} + static int ufs_mtk_hce_enable_notify(struct ufs_hba *hba, enum ufs_notify_change_status status) { @@ -82,6 +101,9 @@ static int ufs_mtk_hce_enable_notify(struct ufs_hba *hba, hba->vps->hba_enable_delay_us = 0; else hba->vps->hba_enable_delay_us = 600; + + if (hba->caps & UFSHCD_CAP_CRYPTO) + ufs_mtk_crypto_enable(hba); } return 0; @@ -112,6 +134,12 @@ static int ufs_mtk_bind_mphy(struct ufs_hba *hba) if (err) host->mphy = NULL; + /* + * Allow unbound mphy because not every platform needs specific + * mphy control. + */ + if (err == -ENODEV) + err = 0; return err; } @@ -120,7 +148,7 @@ static int ufs_mtk_setup_ref_clk(struct ufs_hba *hba, bool on) { struct ufs_mtk_host *host = ufshcd_get_variant(hba); struct arm_smccc_res res; - unsigned long timeout; + ktime_t timeout, time_checked; u32 value; if (host->ref_clk_enabled == on) @@ -135,8 +163,9 @@ static int ufs_mtk_setup_ref_clk(struct ufs_hba *hba, bool on) } /* Wait for ack */ - timeout = jiffies + msecs_to_jiffies(REFCLK_REQ_TIMEOUT_MS); + timeout = ktime_add_us(ktime_get(), REFCLK_REQ_TIMEOUT_US); do { + time_checked = ktime_get(); value = ufshcd_readl(hba, REG_UFS_REFCLK_CTRL); /* Wait until ack bit equals to req bit */ @@ -144,7 +173,7 @@ static int ufs_mtk_setup_ref_clk(struct ufs_hba *hba, bool on) goto out; usleep_range(100, 200); - } while (time_before(jiffies, timeout)); + } while (ktime_before(time_checked, timeout)); dev_err(hba->dev, "missing ack of refclk req, reg: 0x%x\n", value); @@ -177,15 +206,47 @@ static void ufs_mtk_setup_ref_clk_wait_us(struct ufs_hba *hba, host->ref_clk_ungating_wait_us = ungating_us; } -static u32 ufs_mtk_link_get_state(struct ufs_hba *hba) +static int ufs_mtk_wait_link_state(struct ufs_hba *hba, u32 state, + unsigned long max_wait_ms) { + ktime_t timeout, time_checked; u32 val; - ufshcd_writel(hba, 0x20, REG_UFS_DEBUG_SEL); - val = ufshcd_readl(hba, REG_UFS_PROBE); - val = val >> 28; + timeout = ktime_add_us(ktime_get(), ms_to_ktime(max_wait_ms)); + do { + time_checked = ktime_get(); + ufshcd_writel(hba, 0x20, REG_UFS_DEBUG_SEL); + val = ufshcd_readl(hba, REG_UFS_PROBE); + val = val >> 28; + + if (val == state) + return 0; + + /* Sleep for max. 200us */ + usleep_range(100, 200); + } while (ktime_before(time_checked, timeout)); - return val; + if (val == state) + return 0; + + return -ETIMEDOUT; +} + +static void ufs_mtk_mphy_power_on(struct ufs_hba *hba, bool on) +{ + struct ufs_mtk_host *host = ufshcd_get_variant(hba); + struct phy *mphy = host->mphy; + + if (!mphy) + return; + + if (on && !host->mphy_powered_on) + phy_power_on(mphy); + else if (!on && host->mphy_powered_on) + phy_power_off(mphy); + else + return; + host->mphy_powered_on = on; } /** @@ -201,6 +262,7 @@ static int ufs_mtk_setup_clocks(struct ufs_hba *hba, bool on, { struct ufs_mtk_host *host = ufshcd_get_variant(hba); int ret = 0; + bool clk_pwr_off = false; /* * In case ufs_mtk_init() is not yet done, simply ignore. @@ -211,22 +273,29 @@ static int ufs_mtk_setup_clocks(struct ufs_hba *hba, bool on, return 0; if (!on && status == PRE_CHANGE) { - if (!ufshcd_is_link_active(hba)) { - ufs_mtk_setup_ref_clk(hba, on); - ret = phy_power_off(host->mphy); - } else { + if (ufshcd_is_link_off(hba)) { + clk_pwr_off = true; + } else if (ufshcd_is_link_hibern8(hba) || + (!ufshcd_can_hibern8_during_gating(hba) && + ufshcd_is_auto_hibern8_enabled(hba))) { /* - * Gate ref-clk if link state is in Hibern8 - * triggered by Auto-Hibern8. + * Gate ref-clk and poweroff mphy if link state is in + * OFF or Hibern8 by either Auto-Hibern8 or + * ufshcd_link_state_transition(). */ - if (!ufshcd_can_hibern8_during_gating(hba) && - ufshcd_is_auto_hibern8_enabled(hba) && - ufs_mtk_link_get_state(hba) == - VS_LINK_HIBERN8) - ufs_mtk_setup_ref_clk(hba, on); + ret = ufs_mtk_wait_link_state(hba, + VS_LINK_HIBERN8, + 15); + if (!ret) + clk_pwr_off = true; + } + + if (clk_pwr_off) { + ufs_mtk_setup_ref_clk(hba, on); + ufs_mtk_mphy_power_on(hba, on); } } else if (on && status == POST_CHANGE) { - ret = phy_power_on(host->mphy); + ufs_mtk_mphy_power_on(hba, on); ufs_mtk_setup_ref_clk(hba, on); } @@ -269,6 +338,9 @@ static int ufs_mtk_init(struct ufs_hba *hba) /* Enable clock-gating */ hba->caps |= UFSHCD_CAP_CLK_GATING; + /* Enable inline encryption */ + hba->caps |= UFSHCD_CAP_CRYPTO; + /* Enable WriteBooster */ hba->caps |= UFSHCD_CAP_WB_EN; hba->vps->wb_flush_threshold = UFS_WB_BUF_REMAIN_PERCENT(80); @@ -502,10 +574,22 @@ static int ufs_mtk_link_set_lpm(struct ufs_hba *hba) return 0; } +static void ufs_mtk_vreg_set_lpm(struct ufs_hba *hba, bool lpm) +{ + if (!hba->vreg_info.vccq2 || !hba->vreg_info.vcc) + return; + + if (lpm & !hba->vreg_info.vcc->enabled) + regulator_set_mode(hba->vreg_info.vccq2->reg, + REGULATOR_MODE_IDLE); + else if (!lpm) + regulator_set_mode(hba->vreg_info.vccq2->reg, + REGULATOR_MODE_NORMAL); +} + static int ufs_mtk_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) { int err; - struct ufs_mtk_host *host = ufshcd_get_variant(hba); if (ufshcd_is_link_hibern8(hba)) { err = ufs_mtk_link_set_lpm(hba); @@ -518,23 +602,23 @@ static int ufs_mtk_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) ufshcd_set_link_off(hba); return -EAGAIN; } + /* + * Make sure no error will be returned to prevent + * ufshcd_suspend() re-enabling regulators while vreg is still + * in low-power mode. + */ + ufs_mtk_vreg_set_lpm(hba, true); } - if (!ufshcd_is_link_active(hba)) - phy_power_off(host->mphy); - return 0; } static int ufs_mtk_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) { - struct ufs_mtk_host *host = ufshcd_get_variant(hba); int err; - if (!ufshcd_is_link_active(hba)) - phy_power_on(host->mphy); - if (ufshcd_is_link_hibern8(hba)) { + ufs_mtk_vreg_set_lpm(hba, false); err = ufs_mtk_link_set_hpm(hba); if (err) { err = ufshcd_link_recovery(hba); |