Loading drivers/scsi/ufs/ufs-msm.c +276 −25 Original line number Diff line number Diff line Loading @@ -1309,6 +1309,107 @@ static struct msm_ufs_phy_calibration phy_cal_table_rate_B[] = { }, }; static struct msm_ufs_phy_calibration cached_phy_regs[] = { {QSERDES_COM_PLL_CRCTRL}, {QSERDES_COM_PLL_CNTRL}, {QSERDES_COM_SYSCLK_EN_SEL}, {QSERDES_COM_SYS_CLK_CTRL}, {QSERDES_COM_PLL_CLKEPDIV}, {QSERDES_COM_DEC_START1}, {QSERDES_COM_DEC_START2}, {QSERDES_COM_DIV_FRAC_START1}, {QSERDES_COM_DIV_FRAC_START2}, {QSERDES_COM_DIV_FRAC_START3}, {QSERDES_COM_PLLLOCK_CMP1}, {QSERDES_COM_PLLLOCK_CMP2}, {QSERDES_COM_PLLLOCK_CMP3}, {QSERDES_COM_PLLLOCK_CMP_EN}, {QSERDES_COM_RESETSM_CNTRL}, {QSERDES_COM_PLL_RXTXEPCLK_EN}, {QSERDES_RX_PWM_CNTRL1(0)}, {QSERDES_RX_PWM_CNTRL1(1)}, {QSERDES_RX_CDR_CONTROL(0)}, {QSERDES_RX_CDR_CONTROL_HALF(0)}, {QSERDES_RX_CDR_CONTROL_QUARTER(0)}, {QSERDES_RX_CDR_CONTROL(1)}, {QSERDES_RX_CDR_CONTROL_HALF(1)}, {QSERDES_RX_CDR_CONTROL_QUARTER(1)}, {QSERDES_RX_SIGDET_CNTRL(0)}, {QSERDES_RX_SIGDET_CNTRL(1)}, {QSERDES_RX_SIGDET_CNTRL2(0)}, {QSERDES_RX_SIGDET_CNTRL2(1)}, {QSERDES_RX_RX_EQ_GAIN1(0)}, {QSERDES_RX_RX_EQ_GAIN2(0)}, {QSERDES_RX_RX_EQ_GAIN1(1)}, {QSERDES_RX_RX_EQ_GAIN2(1)}, {QSERDES_COM_PLL_IP_SETI}, {QSERDES_COM_PLL_CP_SETI}, {QSERDES_COM_PLL_IP_SETP}, {QSERDES_COM_PLL_CP_SETP}, {UFS_PHY_PWM_G1_CLK_DIVIDER}, {UFS_PHY_PWM_G2_CLK_DIVIDER}, {UFS_PHY_PWM_G3_CLK_DIVIDER}, {UFS_PHY_PWM_G4_CLK_DIVIDER}, {UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER}, {UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER}, {UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER}, {UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER}, {UFS_PHY_OMC_STATUS_RDVAL}, {UFS_PHY_LINE_RESET_TIME}, {UFS_PHY_LINE_RESET_GRANULARITY}, {UFS_PHY_TSYNC_RSYNC_CNTL}, {UFS_PHY_PLL_CNTL}, {UFS_PHY_TX_LARGE_AMP_DRV_LVL}, {UFS_PHY_TX_SMALL_AMP_DRV_LVL}, {UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL}, {UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL}, {UFS_PHY_CFG_CHANGE_CNT_VAL}, {UFS_PHY_RX_SYNC_WAIT_TIME}, {UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY}, {UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY}, {UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY}, {UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY}, {UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY}, {UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY}, {UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY}, {UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY}, {QSERDES_RX_CDR_CONTROL3(0)}, {QSERDES_RX_CDR_CONTROL3(1)}, {QSERDES_COM_RES_TRIM_OFFSET}, {QSERDES_COM_BGTC}, {QSERDES_COM_PLL_AMP_OS}, }; static struct msm_ufs_stored_attributes cached_phy_attr[] = { {TX_MODE}, {TX_HSRATE_SERIES}, {TX_HSGEAR}, {TX_PWMGEAR}, {TX_AMPLITUDE}, {TX_HS_SLEWRATE}, {TX_SYNC_SOURCE}, {TX_HS_PREPARE_LENGTH}, {TX_LS_PREPARE_LENGTH}, {TX_LCC_ENABLE}, {TX_PWM_BURST_CLOSURE_EXTENSION}, {TX_BYPASS_8B10B_ENABLE}, {TX_DRIVER_POLARITY}, {TX_HS_UNTERMINATED_LINE_DRIVE_ENABLE}, {TX_LS_TERMINATED_LINE_DRIVE_ENABLE}, {TX_LCC_SEQUENCER}, {TX_MIN_ACTIVATETIME}, {TX_PWM_G6_G7_SYNC_LENGTH}, {RX_MODE}, {RX_HSRATE_SERIES}, {RX_HSGEAR}, {RX_PWMGEAR}, {RX_LS_TERMINATED_ENABLE}, {RX_HS_UNTERMINATED_ENABLE}, {RX_ENTER_HIBERN8}, {RX_BYPASS_8B10B_ENABLE}, {RX_TERMINATION_FORCE_ENABLE}, }; static struct msm_ufs_phy *msm_get_ufs_phy(struct device *dev) { int err = -EPROBE_DEFER; Loading Loading @@ -1793,6 +1894,90 @@ static int msm_ufs_phy_power_off(struct msm_ufs_phy *phy) return 0; } static u32 msm_ufs_read_phy_attr(struct ufs_hba *hba, u32 attr) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; u32 l0, l1; writel_relaxed(attr, phy->mmio + UFS_PHY_RMMI_ATTRID); /* Read attribute value for both Lanes */ writel_relaxed((UFS_PHY_RMMI_CFGRD_L0 | UFS_PHY_RMMI_CFGRD_L1), phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); l0 = readl_relaxed(phy->mmio + UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS); l1 = readl_relaxed(phy->mmio + UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS); /* Both lanes should have the same value for same attribute type */ if (unlikely(l0 != l1)) dev_warn(phy->dev, "%s: attr 0x%x values are not same for Lane-0 and Lane-1, l0=0x%x, l1=0x%x", __func__, attr, l0, l1); /* must clear now */ writel_relaxed(0x00, phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); return l0; } static void msm_ufs_write_phy_attr(struct ufs_hba *hba, u32 attr, u32 val) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; writel_relaxed(attr, phy->mmio + UFS_PHY_RMMI_ATTRID); writel_relaxed(val, phy->mmio + UFS_PHY_RMMI_ATTRWRVAL); /* update attribute for both Lanes */ writel_relaxed((UFS_PHY_RMMI_CFGWR_L0 | UFS_PHY_RMMI_CFGWR_L1), phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); if (is_mphy_tx_attr(attr)) writel_relaxed((UFS_PHY_RMMI_TX_CFGUPDT_L0 | UFS_PHY_RMMI_TX_CFGUPDT_L1), phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); else writel_relaxed((UFS_PHY_RMMI_RX_CFGUPDT_L0 | UFS_PHY_RMMI_RX_CFGUPDT_L1), phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); /* must clear now */ writel_relaxed(0x00, phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); } static void msm_ufs_save_phy_configuration(struct ufs_hba *hba) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; int i; for (i = 0; i < ARRAY_SIZE(cached_phy_regs); i++) cached_phy_regs[i].cfg_value = readl_relaxed(phy->mmio + cached_phy_regs[i].reg_offset); for (i = 0; i < ARRAY_SIZE(cached_phy_attr); i++) cached_phy_attr[i].value = msm_ufs_read_phy_attr(hba, cached_phy_attr[i].att); } static void msm_ufs_restore_phy_swi_regs(struct msm_ufs_phy *phy) { int i; for (i = 0; i < ARRAY_SIZE(cached_phy_regs); i++) writel_relaxed(cached_phy_regs[i].cfg_value, phy->mmio + cached_phy_regs[i].reg_offset); /* flush buffered writes */ mb(); } static void msm_ufs_restore_phy_attrs(struct ufs_hba *hba) { int i; for (i = 0; i < ARRAY_SIZE(cached_phy_attr); i++) msm_ufs_write_phy_attr(hba, cached_phy_attr[i].att, cached_phy_attr[i].value); } static inline void msm_ufs_assert_reset(struct ufs_hba *hba) { ufshcd_rmwl(hba, MASK_UFS_PHY_SOFT_RESET, Loading @@ -1807,22 +1992,30 @@ static inline void msm_ufs_deassert_reset(struct ufs_hba *hba) mb(); } static int msm_ufs_hce_enable_notify(struct ufs_hba *hba, bool status) static int msm_ufs_power_up_sequence(struct ufs_hba *hba, enum msm_ufs_phy_init_type type) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; u32 val; int err = -EINVAL; int err = 0; switch (status) { case PRE_CHANGE: switch (type) { case UFS_PHY_INIT_FULL: /* Assert PHY reset and apply PHY calibration values */ msm_ufs_assert_reset(hba); /* provide 1ms delay to let the reset pulse propagate */ usleep_range(1000, 1100); msm_ufs_phy_calibrate(hba); break; case UFS_PHY_INIT_CFG_RESTORE: msm_ufs_restore_phy_swi_regs(phy); break; default: dev_err(phy->dev, "%s: Unknown calibration type, %d\n", __func__, type); return -EINVAL; } /* De-assert PHY reset and start serdes */ msm_ufs_deassert_reset(hba); Loading @@ -1835,14 +2028,26 @@ static int msm_ufs_hce_enable_notify(struct ufs_hba *hba, bool status) msm_ufs_phy_start_serdes(phy); if (type == UFS_PHY_INIT_CFG_RESTORE) msm_ufs_restore_phy_attrs(hba); /* poll for PCS_READY for max. 1sec */ err = readl_poll_timeout(phy->mmio + UFS_PHY_PCS_READY_STATUS, val, (val & MASK_PCS_READY), 10, 1000000); if (err) { dev_err(phy->dev, "%s: phy init failed, %d\n", __func__, err); break; if (err) dev_err(phy->dev, "%s: phy init failed, %d\n", __func__, err); return err; } static int msm_ufs_hce_enable_notify(struct ufs_hba *hba, bool status) { struct msm_ufs_host *host = hba->priv; int err = 0; switch (status) { case PRE_CHANGE: msm_ufs_power_up_sequence(hba, UFS_PHY_INIT_FULL); /* * The PHY PLL output is the source of tx/rx lane symbol clocks. * Hence, enable the lane clocks only after PHY is initialized. Loading @@ -1853,6 +2058,8 @@ static int msm_ufs_hce_enable_notify(struct ufs_hba *hba, bool status) /* check if UFS PHY moved from DISABLED to HIBERN8 */ err = msm_ufs_check_hibern8(hba); default: dev_err(hba->dev, "%s: invalid status %d\n", __func__, status); err = -EINVAL; break; } Loading Loading @@ -2019,6 +2226,10 @@ static int msm_ufs_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) * rail and low noise analog power rail for PLL can be switched off. */ if (!ufshcd_is_link_active(hba)) { if ((phy->quirks & MSM_UFS_PHY_QUIRK_CFG_RESTORE) && ufshcd_is_link_hibern8(hba)) msm_ufs_save_phy_configuration(hba); msm_ufs_disable_phy_ref_clk(phy); writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); /* Loading @@ -2034,15 +2245,50 @@ out: return ret; } static bool msm_ufs_is_phy_config_restore_required(struct ufs_hba *hba) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; return (phy->quirks & MSM_UFS_PHY_QUIRK_CFG_RESTORE) && ufshcd_is_link_hibern8(hba) && hba->is_sys_suspended; } static int msm_ufs_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; int err; if (!phy) return 0; return msm_ufs_phy_power_on(phy); if (msm_ufs_is_phy_config_restore_required(hba)) { msm_ufs_assert_reset(hba); /* provide 1ms delay to let the reset pulse propagate */ usleep_range(1000, 1100); } err = msm_ufs_phy_power_on(phy); if (err) { dev_err(hba->dev, "%s: failed enabling regs, err = %d\n", __func__, err); goto out; } if (msm_ufs_is_phy_config_restore_required(hba)) { err = msm_ufs_power_up_sequence(hba, UFS_PHY_INIT_CFG_RESTORE); if (err) { dev_err(hba->dev, "%s: phy power up sequence failed err = %d\n", __func__, err); goto out; } hba->is_sys_suspended = false; } out: return err; } struct ufs_msm_dev_params { Loading Loading @@ -2302,6 +2548,8 @@ out: */ static void msm_ufs_advertise_quirks(struct ufs_hba *hba) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; u8 major; u16 minor, step; Loading @@ -2311,7 +2559,7 @@ static void msm_ufs_advertise_quirks(struct ufs_hba *hba) * Interrupt aggregation and HIBERN8 on UFS HW controller revision 1.1.0 * is broken. */ if ((major == 0x1) && (minor == 0x001) && (step == 0x0000)) if ((major == 0x1) && (minor == 0x001) && (step == 0x0000)) { hba->quirks |= (UFSHCD_QUIRK_BROKEN_INTR_AGGR | UFSHCD_QUIRK_BROKEN_HIBERN8 | UFSHCD_QUIRK_BROKEN_VER_REG_1_1 Loading @@ -2320,13 +2568,16 @@ static void msm_ufs_advertise_quirks(struct ufs_hba *hba) | UFSHCD_QUIRK_BROKEN_2_TX_LANES | UFSHCD_QUIRK_BROKEN_SUSPEND | UFSHCD_BROKEN_LCC); else if ((major == 0x1) && (minor == 0x001) && (step == 0x0001)) } else if ((major == 0x1) && (minor == 0x001) && (step == 0x0001)) { hba->quirks |= (UFSHCD_QUIRK_BROKEN_HIBERN8 | UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS | UFSHCD_QUIRK_BROKEN_INTR_AGGR | UFSHCD_QUIRK_BROKEN_SUSPEND | UFSHCD_BROKEN_GEAR_CHANGE_INTO_HS | UFSHCD_BROKEN_LCC); phy->quirks = MSM_UFS_PHY_QUIRK_CFG_RESTORE; } } static int msm_ufs_get_bus_vote(struct msm_ufs_host *host, Loading drivers/scsi/ufs/ufs-msm.h +57 −1 Original line number Diff line number Diff line /* Copyright (c) 2013, The Linux Foundation. All rights reserved. /* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading Loading @@ -54,6 +54,16 @@ struct msm_ufs_phy_calibration { u32 cfg_value; }; struct msm_ufs_stored_attributes { u32 att; u32 value; }; enum msm_ufs_phy_init_type { UFS_PHY_INIT_FULL, UFS_PHY_INIT_CFG_RESTORE, }; struct msm_ufs_phy_vreg { const char *name; struct regulator *reg; Loading @@ -76,6 +86,39 @@ struct msm_ufs_phy { bool is_ref_clk_enabled; struct msm_ufs_phy_vreg vdda_pll; struct msm_ufs_phy_vreg vdda_phy; unsigned int quirks; /* * As part of UFS power management, UFS link would be put in hibernate * and UFS device would be put in SLEEP mode as part of runtime/system * suspend callback. But when system goes into suspend with VDD * minimization, UFS PHY states are being reset which means UFS link * hibernate exit command on system resume would fail. * If this quirk is enabled then above issue is workaround by saving * the UFS PHY state information before system goes into suspend and * restoring the saved state information during system resume but * before executing the hibern8 exit command. * Note that this quirk will help restoring the PHY state if even when * link in not kept in hibern8 during suspend. * * Here is the list of steps to save/restore the configuration: * Before entering into system suspend: * 1. Read Critical PCS SWI Registers + less critical PHY CSR * 2. Read RMMI Attributes * Enter into system suspend * After exiting from system suspend: * 1. Set UFS_PHY_SOFT_RESET bit in UFS_CFG1 register of the UFS * Controller * 2. Write 0x01 to the UFS_PHY_POWER_DOWN_CONTROL register in the * UFS PHY * 3. Write back the values of the PHY SWI registers * 4. Clear UFS_PHY_SOFT_RESET bit in UFS_CFG1 register of the UFS * Controller * 5. Write 0x01 to the UFS_PHY_PHY_START in the UFS PHY. This will * start the PLL calibration and bring-up of the PHY. * 6. Write back the values to the PHY RMMI Attributes * 7. Wait for UFS_PHY_PCS_READY_STATUS[0] to be '1' */ #define MSM_UFS_PHY_QUIRK_CFG_RESTORE (1 << 0) }; struct msm_ufs_bus_vote { Loading Loading @@ -248,6 +291,19 @@ static int msm_ufs_update_bus_bw_vote(struct msm_ufs_host *host); #define UFS_PHY_DEBUG_BUS_1_STATUS PHY_OFF(0x154) #define UFS_PHY_DEBUG_BUS_2_STATUS PHY_OFF(0x158) #define UFS_PHY_DEBUG_BUS_3_STATUS PHY_OFF(0x15C) #define UFS_PHY_RMMI_ATTR_CTRL PHY_OFF(0x16C) #define UFS_PHY_RMMI_RX_CFGUPDT_L1 (1 << 7) #define UFS_PHY_RMMI_TX_CFGUPDT_L1 (1 << 6) #define UFS_PHY_RMMI_CFGWR_L1 (1 << 5) #define UFS_PHY_RMMI_CFGRD_L1 (1 << 4) #define UFS_PHY_RMMI_RX_CFGUPDT_L0 (1 << 3) #define UFS_PHY_RMMI_TX_CFGUPDT_L0 (1 << 2) #define UFS_PHY_RMMI_CFGWR_L0 (1 << 1) #define UFS_PHY_RMMI_CFGRD_L0 (1 << 0) #define UFS_PHY_RMMI_ATTRID PHY_OFF(0x170) #define UFS_PHY_RMMI_ATTRWRVAL PHY_OFF(0x174) #define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS PHY_OFF(0x178) #define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS PHY_OFF(0x17C) /* TX LANE n (0, 1) registers */ #define QSERDES_TX_BIST_MODE_LANENO(n) TX_OFF(n, 0x00) Loading drivers/scsi/ufs/ufshcd.c +2 −0 Original line number Diff line number Diff line Loading @@ -5608,6 +5608,8 @@ int ufshcd_system_suspend(struct ufs_hba *hba) out: trace_ufshcd_system_suspend(dev_name(hba->dev), ret, ktime_to_us(ktime_sub(ktime_get(), start))); if (!ret) hba->is_sys_suspended = true; return ret; } EXPORT_SYMBOL(ufshcd_system_suspend); Loading drivers/scsi/ufs/ufshcd.h +1 −0 Original line number Diff line number Diff line Loading @@ -550,6 +550,7 @@ struct ufs_hba { struct ufs_stats ufs_stats; struct debugfs_files debugfs_files; #endif bool is_sys_suspended; }; /* Returns true if clocks can be gated. Otherwise false */ Loading drivers/scsi/ufs/unipro.h +34 −1 Original line number Diff line number Diff line Loading @@ -15,8 +15,41 @@ /* * M-TX Configuration Attributes */ #define TX_LCC_ENABLE 0x2C #define TX_MODE 0x0021 #define TX_HSRATE_SERIES 0x0022 #define TX_HSGEAR 0x0023 #define TX_PWMGEAR 0x0024 #define TX_AMPLITUDE 0x0025 #define TX_HS_SLEWRATE 0x0026 #define TX_SYNC_SOURCE 0x0027 #define TX_HS_SYNC_LENGTH 0x0028 #define TX_HS_PREPARE_LENGTH 0x0029 #define TX_LS_PREPARE_LENGTH 0x002A #define TX_HIBERN8_CONTROL 0x002B #define TX_LCC_ENABLE 0x002C #define TX_PWM_BURST_CLOSURE_EXTENSION 0x002D #define TX_BYPASS_8B10B_ENABLE 0x002E #define TX_DRIVER_POLARITY 0x002F #define TX_HS_UNTERMINATED_LINE_DRIVE_ENABLE 0x0030 #define TX_LS_TERMINATED_LINE_DRIVE_ENABLE 0x0031 #define TX_LCC_SEQUENCER 0x0032 #define TX_MIN_ACTIVATETIME 0x0033 #define TX_PWM_G6_G7_SYNC_LENGTH 0x0034 /* * M-RX Configuration Attributes */ #define RX_MODE 0x00A1 #define RX_HSRATE_SERIES 0x00A2 #define RX_HSGEAR 0x00A3 #define RX_PWMGEAR 0x00A4 #define RX_LS_TERMINATED_ENABLE 0x00A5 #define RX_HS_UNTERMINATED_ENABLE 0x00A6 #define RX_ENTER_HIBERN8 0x00A7 #define RX_BYPASS_8B10B_ENABLE 0x00A8 #define RX_TERMINATION_FORCE_ENABLE 0x0089 #define is_mphy_tx_attr(attr) (attr < RX_MODE) /* * PHY Adpater attributes */ Loading Loading
drivers/scsi/ufs/ufs-msm.c +276 −25 Original line number Diff line number Diff line Loading @@ -1309,6 +1309,107 @@ static struct msm_ufs_phy_calibration phy_cal_table_rate_B[] = { }, }; static struct msm_ufs_phy_calibration cached_phy_regs[] = { {QSERDES_COM_PLL_CRCTRL}, {QSERDES_COM_PLL_CNTRL}, {QSERDES_COM_SYSCLK_EN_SEL}, {QSERDES_COM_SYS_CLK_CTRL}, {QSERDES_COM_PLL_CLKEPDIV}, {QSERDES_COM_DEC_START1}, {QSERDES_COM_DEC_START2}, {QSERDES_COM_DIV_FRAC_START1}, {QSERDES_COM_DIV_FRAC_START2}, {QSERDES_COM_DIV_FRAC_START3}, {QSERDES_COM_PLLLOCK_CMP1}, {QSERDES_COM_PLLLOCK_CMP2}, {QSERDES_COM_PLLLOCK_CMP3}, {QSERDES_COM_PLLLOCK_CMP_EN}, {QSERDES_COM_RESETSM_CNTRL}, {QSERDES_COM_PLL_RXTXEPCLK_EN}, {QSERDES_RX_PWM_CNTRL1(0)}, {QSERDES_RX_PWM_CNTRL1(1)}, {QSERDES_RX_CDR_CONTROL(0)}, {QSERDES_RX_CDR_CONTROL_HALF(0)}, {QSERDES_RX_CDR_CONTROL_QUARTER(0)}, {QSERDES_RX_CDR_CONTROL(1)}, {QSERDES_RX_CDR_CONTROL_HALF(1)}, {QSERDES_RX_CDR_CONTROL_QUARTER(1)}, {QSERDES_RX_SIGDET_CNTRL(0)}, {QSERDES_RX_SIGDET_CNTRL(1)}, {QSERDES_RX_SIGDET_CNTRL2(0)}, {QSERDES_RX_SIGDET_CNTRL2(1)}, {QSERDES_RX_RX_EQ_GAIN1(0)}, {QSERDES_RX_RX_EQ_GAIN2(0)}, {QSERDES_RX_RX_EQ_GAIN1(1)}, {QSERDES_RX_RX_EQ_GAIN2(1)}, {QSERDES_COM_PLL_IP_SETI}, {QSERDES_COM_PLL_CP_SETI}, {QSERDES_COM_PLL_IP_SETP}, {QSERDES_COM_PLL_CP_SETP}, {UFS_PHY_PWM_G1_CLK_DIVIDER}, {UFS_PHY_PWM_G2_CLK_DIVIDER}, {UFS_PHY_PWM_G3_CLK_DIVIDER}, {UFS_PHY_PWM_G4_CLK_DIVIDER}, {UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER}, {UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER}, {UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER}, {UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER}, {UFS_PHY_OMC_STATUS_RDVAL}, {UFS_PHY_LINE_RESET_TIME}, {UFS_PHY_LINE_RESET_GRANULARITY}, {UFS_PHY_TSYNC_RSYNC_CNTL}, {UFS_PHY_PLL_CNTL}, {UFS_PHY_TX_LARGE_AMP_DRV_LVL}, {UFS_PHY_TX_SMALL_AMP_DRV_LVL}, {UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL}, {UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL}, {UFS_PHY_CFG_CHANGE_CNT_VAL}, {UFS_PHY_RX_SYNC_WAIT_TIME}, {UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY}, {UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY}, {UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY}, {UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY}, {UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY}, {UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY}, {UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY}, {UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY}, {QSERDES_RX_CDR_CONTROL3(0)}, {QSERDES_RX_CDR_CONTROL3(1)}, {QSERDES_COM_RES_TRIM_OFFSET}, {QSERDES_COM_BGTC}, {QSERDES_COM_PLL_AMP_OS}, }; static struct msm_ufs_stored_attributes cached_phy_attr[] = { {TX_MODE}, {TX_HSRATE_SERIES}, {TX_HSGEAR}, {TX_PWMGEAR}, {TX_AMPLITUDE}, {TX_HS_SLEWRATE}, {TX_SYNC_SOURCE}, {TX_HS_PREPARE_LENGTH}, {TX_LS_PREPARE_LENGTH}, {TX_LCC_ENABLE}, {TX_PWM_BURST_CLOSURE_EXTENSION}, {TX_BYPASS_8B10B_ENABLE}, {TX_DRIVER_POLARITY}, {TX_HS_UNTERMINATED_LINE_DRIVE_ENABLE}, {TX_LS_TERMINATED_LINE_DRIVE_ENABLE}, {TX_LCC_SEQUENCER}, {TX_MIN_ACTIVATETIME}, {TX_PWM_G6_G7_SYNC_LENGTH}, {RX_MODE}, {RX_HSRATE_SERIES}, {RX_HSGEAR}, {RX_PWMGEAR}, {RX_LS_TERMINATED_ENABLE}, {RX_HS_UNTERMINATED_ENABLE}, {RX_ENTER_HIBERN8}, {RX_BYPASS_8B10B_ENABLE}, {RX_TERMINATION_FORCE_ENABLE}, }; static struct msm_ufs_phy *msm_get_ufs_phy(struct device *dev) { int err = -EPROBE_DEFER; Loading Loading @@ -1793,6 +1894,90 @@ static int msm_ufs_phy_power_off(struct msm_ufs_phy *phy) return 0; } static u32 msm_ufs_read_phy_attr(struct ufs_hba *hba, u32 attr) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; u32 l0, l1; writel_relaxed(attr, phy->mmio + UFS_PHY_RMMI_ATTRID); /* Read attribute value for both Lanes */ writel_relaxed((UFS_PHY_RMMI_CFGRD_L0 | UFS_PHY_RMMI_CFGRD_L1), phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); l0 = readl_relaxed(phy->mmio + UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS); l1 = readl_relaxed(phy->mmio + UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS); /* Both lanes should have the same value for same attribute type */ if (unlikely(l0 != l1)) dev_warn(phy->dev, "%s: attr 0x%x values are not same for Lane-0 and Lane-1, l0=0x%x, l1=0x%x", __func__, attr, l0, l1); /* must clear now */ writel_relaxed(0x00, phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); return l0; } static void msm_ufs_write_phy_attr(struct ufs_hba *hba, u32 attr, u32 val) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; writel_relaxed(attr, phy->mmio + UFS_PHY_RMMI_ATTRID); writel_relaxed(val, phy->mmio + UFS_PHY_RMMI_ATTRWRVAL); /* update attribute for both Lanes */ writel_relaxed((UFS_PHY_RMMI_CFGWR_L0 | UFS_PHY_RMMI_CFGWR_L1), phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); if (is_mphy_tx_attr(attr)) writel_relaxed((UFS_PHY_RMMI_TX_CFGUPDT_L0 | UFS_PHY_RMMI_TX_CFGUPDT_L1), phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); else writel_relaxed((UFS_PHY_RMMI_RX_CFGUPDT_L0 | UFS_PHY_RMMI_RX_CFGUPDT_L1), phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); /* must clear now */ writel_relaxed(0x00, phy->mmio + UFS_PHY_RMMI_ATTR_CTRL); } static void msm_ufs_save_phy_configuration(struct ufs_hba *hba) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; int i; for (i = 0; i < ARRAY_SIZE(cached_phy_regs); i++) cached_phy_regs[i].cfg_value = readl_relaxed(phy->mmio + cached_phy_regs[i].reg_offset); for (i = 0; i < ARRAY_SIZE(cached_phy_attr); i++) cached_phy_attr[i].value = msm_ufs_read_phy_attr(hba, cached_phy_attr[i].att); } static void msm_ufs_restore_phy_swi_regs(struct msm_ufs_phy *phy) { int i; for (i = 0; i < ARRAY_SIZE(cached_phy_regs); i++) writel_relaxed(cached_phy_regs[i].cfg_value, phy->mmio + cached_phy_regs[i].reg_offset); /* flush buffered writes */ mb(); } static void msm_ufs_restore_phy_attrs(struct ufs_hba *hba) { int i; for (i = 0; i < ARRAY_SIZE(cached_phy_attr); i++) msm_ufs_write_phy_attr(hba, cached_phy_attr[i].att, cached_phy_attr[i].value); } static inline void msm_ufs_assert_reset(struct ufs_hba *hba) { ufshcd_rmwl(hba, MASK_UFS_PHY_SOFT_RESET, Loading @@ -1807,22 +1992,30 @@ static inline void msm_ufs_deassert_reset(struct ufs_hba *hba) mb(); } static int msm_ufs_hce_enable_notify(struct ufs_hba *hba, bool status) static int msm_ufs_power_up_sequence(struct ufs_hba *hba, enum msm_ufs_phy_init_type type) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; u32 val; int err = -EINVAL; int err = 0; switch (status) { case PRE_CHANGE: switch (type) { case UFS_PHY_INIT_FULL: /* Assert PHY reset and apply PHY calibration values */ msm_ufs_assert_reset(hba); /* provide 1ms delay to let the reset pulse propagate */ usleep_range(1000, 1100); msm_ufs_phy_calibrate(hba); break; case UFS_PHY_INIT_CFG_RESTORE: msm_ufs_restore_phy_swi_regs(phy); break; default: dev_err(phy->dev, "%s: Unknown calibration type, %d\n", __func__, type); return -EINVAL; } /* De-assert PHY reset and start serdes */ msm_ufs_deassert_reset(hba); Loading @@ -1835,14 +2028,26 @@ static int msm_ufs_hce_enable_notify(struct ufs_hba *hba, bool status) msm_ufs_phy_start_serdes(phy); if (type == UFS_PHY_INIT_CFG_RESTORE) msm_ufs_restore_phy_attrs(hba); /* poll for PCS_READY for max. 1sec */ err = readl_poll_timeout(phy->mmio + UFS_PHY_PCS_READY_STATUS, val, (val & MASK_PCS_READY), 10, 1000000); if (err) { dev_err(phy->dev, "%s: phy init failed, %d\n", __func__, err); break; if (err) dev_err(phy->dev, "%s: phy init failed, %d\n", __func__, err); return err; } static int msm_ufs_hce_enable_notify(struct ufs_hba *hba, bool status) { struct msm_ufs_host *host = hba->priv; int err = 0; switch (status) { case PRE_CHANGE: msm_ufs_power_up_sequence(hba, UFS_PHY_INIT_FULL); /* * The PHY PLL output is the source of tx/rx lane symbol clocks. * Hence, enable the lane clocks only after PHY is initialized. Loading @@ -1853,6 +2058,8 @@ static int msm_ufs_hce_enable_notify(struct ufs_hba *hba, bool status) /* check if UFS PHY moved from DISABLED to HIBERN8 */ err = msm_ufs_check_hibern8(hba); default: dev_err(hba->dev, "%s: invalid status %d\n", __func__, status); err = -EINVAL; break; } Loading Loading @@ -2019,6 +2226,10 @@ static int msm_ufs_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op) * rail and low noise analog power rail for PLL can be switched off. */ if (!ufshcd_is_link_active(hba)) { if ((phy->quirks & MSM_UFS_PHY_QUIRK_CFG_RESTORE) && ufshcd_is_link_hibern8(hba)) msm_ufs_save_phy_configuration(hba); msm_ufs_disable_phy_ref_clk(phy); writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); /* Loading @@ -2034,15 +2245,50 @@ out: return ret; } static bool msm_ufs_is_phy_config_restore_required(struct ufs_hba *hba) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; return (phy->quirks & MSM_UFS_PHY_QUIRK_CFG_RESTORE) && ufshcd_is_link_hibern8(hba) && hba->is_sys_suspended; } static int msm_ufs_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; int err; if (!phy) return 0; return msm_ufs_phy_power_on(phy); if (msm_ufs_is_phy_config_restore_required(hba)) { msm_ufs_assert_reset(hba); /* provide 1ms delay to let the reset pulse propagate */ usleep_range(1000, 1100); } err = msm_ufs_phy_power_on(phy); if (err) { dev_err(hba->dev, "%s: failed enabling regs, err = %d\n", __func__, err); goto out; } if (msm_ufs_is_phy_config_restore_required(hba)) { err = msm_ufs_power_up_sequence(hba, UFS_PHY_INIT_CFG_RESTORE); if (err) { dev_err(hba->dev, "%s: phy power up sequence failed err = %d\n", __func__, err); goto out; } hba->is_sys_suspended = false; } out: return err; } struct ufs_msm_dev_params { Loading Loading @@ -2302,6 +2548,8 @@ out: */ static void msm_ufs_advertise_quirks(struct ufs_hba *hba) { struct msm_ufs_host *host = hba->priv; struct msm_ufs_phy *phy = host->phy; u8 major; u16 minor, step; Loading @@ -2311,7 +2559,7 @@ static void msm_ufs_advertise_quirks(struct ufs_hba *hba) * Interrupt aggregation and HIBERN8 on UFS HW controller revision 1.1.0 * is broken. */ if ((major == 0x1) && (minor == 0x001) && (step == 0x0000)) if ((major == 0x1) && (minor == 0x001) && (step == 0x0000)) { hba->quirks |= (UFSHCD_QUIRK_BROKEN_INTR_AGGR | UFSHCD_QUIRK_BROKEN_HIBERN8 | UFSHCD_QUIRK_BROKEN_VER_REG_1_1 Loading @@ -2320,13 +2568,16 @@ static void msm_ufs_advertise_quirks(struct ufs_hba *hba) | UFSHCD_QUIRK_BROKEN_2_TX_LANES | UFSHCD_QUIRK_BROKEN_SUSPEND | UFSHCD_BROKEN_LCC); else if ((major == 0x1) && (minor == 0x001) && (step == 0x0001)) } else if ((major == 0x1) && (minor == 0x001) && (step == 0x0001)) { hba->quirks |= (UFSHCD_QUIRK_BROKEN_HIBERN8 | UFSHCD_QUIRK_DELAY_BEFORE_DME_CMDS | UFSHCD_QUIRK_BROKEN_INTR_AGGR | UFSHCD_QUIRK_BROKEN_SUSPEND | UFSHCD_BROKEN_GEAR_CHANGE_INTO_HS | UFSHCD_BROKEN_LCC); phy->quirks = MSM_UFS_PHY_QUIRK_CFG_RESTORE; } } static int msm_ufs_get_bus_vote(struct msm_ufs_host *host, Loading
drivers/scsi/ufs/ufs-msm.h +57 −1 Original line number Diff line number Diff line /* Copyright (c) 2013, The Linux Foundation. All rights reserved. /* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading Loading @@ -54,6 +54,16 @@ struct msm_ufs_phy_calibration { u32 cfg_value; }; struct msm_ufs_stored_attributes { u32 att; u32 value; }; enum msm_ufs_phy_init_type { UFS_PHY_INIT_FULL, UFS_PHY_INIT_CFG_RESTORE, }; struct msm_ufs_phy_vreg { const char *name; struct regulator *reg; Loading @@ -76,6 +86,39 @@ struct msm_ufs_phy { bool is_ref_clk_enabled; struct msm_ufs_phy_vreg vdda_pll; struct msm_ufs_phy_vreg vdda_phy; unsigned int quirks; /* * As part of UFS power management, UFS link would be put in hibernate * and UFS device would be put in SLEEP mode as part of runtime/system * suspend callback. But when system goes into suspend with VDD * minimization, UFS PHY states are being reset which means UFS link * hibernate exit command on system resume would fail. * If this quirk is enabled then above issue is workaround by saving * the UFS PHY state information before system goes into suspend and * restoring the saved state information during system resume but * before executing the hibern8 exit command. * Note that this quirk will help restoring the PHY state if even when * link in not kept in hibern8 during suspend. * * Here is the list of steps to save/restore the configuration: * Before entering into system suspend: * 1. Read Critical PCS SWI Registers + less critical PHY CSR * 2. Read RMMI Attributes * Enter into system suspend * After exiting from system suspend: * 1. Set UFS_PHY_SOFT_RESET bit in UFS_CFG1 register of the UFS * Controller * 2. Write 0x01 to the UFS_PHY_POWER_DOWN_CONTROL register in the * UFS PHY * 3. Write back the values of the PHY SWI registers * 4. Clear UFS_PHY_SOFT_RESET bit in UFS_CFG1 register of the UFS * Controller * 5. Write 0x01 to the UFS_PHY_PHY_START in the UFS PHY. This will * start the PLL calibration and bring-up of the PHY. * 6. Write back the values to the PHY RMMI Attributes * 7. Wait for UFS_PHY_PCS_READY_STATUS[0] to be '1' */ #define MSM_UFS_PHY_QUIRK_CFG_RESTORE (1 << 0) }; struct msm_ufs_bus_vote { Loading Loading @@ -248,6 +291,19 @@ static int msm_ufs_update_bus_bw_vote(struct msm_ufs_host *host); #define UFS_PHY_DEBUG_BUS_1_STATUS PHY_OFF(0x154) #define UFS_PHY_DEBUG_BUS_2_STATUS PHY_OFF(0x158) #define UFS_PHY_DEBUG_BUS_3_STATUS PHY_OFF(0x15C) #define UFS_PHY_RMMI_ATTR_CTRL PHY_OFF(0x16C) #define UFS_PHY_RMMI_RX_CFGUPDT_L1 (1 << 7) #define UFS_PHY_RMMI_TX_CFGUPDT_L1 (1 << 6) #define UFS_PHY_RMMI_CFGWR_L1 (1 << 5) #define UFS_PHY_RMMI_CFGRD_L1 (1 << 4) #define UFS_PHY_RMMI_RX_CFGUPDT_L0 (1 << 3) #define UFS_PHY_RMMI_TX_CFGUPDT_L0 (1 << 2) #define UFS_PHY_RMMI_CFGWR_L0 (1 << 1) #define UFS_PHY_RMMI_CFGRD_L0 (1 << 0) #define UFS_PHY_RMMI_ATTRID PHY_OFF(0x170) #define UFS_PHY_RMMI_ATTRWRVAL PHY_OFF(0x174) #define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS PHY_OFF(0x178) #define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS PHY_OFF(0x17C) /* TX LANE n (0, 1) registers */ #define QSERDES_TX_BIST_MODE_LANENO(n) TX_OFF(n, 0x00) Loading
drivers/scsi/ufs/ufshcd.c +2 −0 Original line number Diff line number Diff line Loading @@ -5608,6 +5608,8 @@ int ufshcd_system_suspend(struct ufs_hba *hba) out: trace_ufshcd_system_suspend(dev_name(hba->dev), ret, ktime_to_us(ktime_sub(ktime_get(), start))); if (!ret) hba->is_sys_suspended = true; return ret; } EXPORT_SYMBOL(ufshcd_system_suspend); Loading
drivers/scsi/ufs/ufshcd.h +1 −0 Original line number Diff line number Diff line Loading @@ -550,6 +550,7 @@ struct ufs_hba { struct ufs_stats ufs_stats; struct debugfs_files debugfs_files; #endif bool is_sys_suspended; }; /* Returns true if clocks can be gated. Otherwise false */ Loading
drivers/scsi/ufs/unipro.h +34 −1 Original line number Diff line number Diff line Loading @@ -15,8 +15,41 @@ /* * M-TX Configuration Attributes */ #define TX_LCC_ENABLE 0x2C #define TX_MODE 0x0021 #define TX_HSRATE_SERIES 0x0022 #define TX_HSGEAR 0x0023 #define TX_PWMGEAR 0x0024 #define TX_AMPLITUDE 0x0025 #define TX_HS_SLEWRATE 0x0026 #define TX_SYNC_SOURCE 0x0027 #define TX_HS_SYNC_LENGTH 0x0028 #define TX_HS_PREPARE_LENGTH 0x0029 #define TX_LS_PREPARE_LENGTH 0x002A #define TX_HIBERN8_CONTROL 0x002B #define TX_LCC_ENABLE 0x002C #define TX_PWM_BURST_CLOSURE_EXTENSION 0x002D #define TX_BYPASS_8B10B_ENABLE 0x002E #define TX_DRIVER_POLARITY 0x002F #define TX_HS_UNTERMINATED_LINE_DRIVE_ENABLE 0x0030 #define TX_LS_TERMINATED_LINE_DRIVE_ENABLE 0x0031 #define TX_LCC_SEQUENCER 0x0032 #define TX_MIN_ACTIVATETIME 0x0033 #define TX_PWM_G6_G7_SYNC_LENGTH 0x0034 /* * M-RX Configuration Attributes */ #define RX_MODE 0x00A1 #define RX_HSRATE_SERIES 0x00A2 #define RX_HSGEAR 0x00A3 #define RX_PWMGEAR 0x00A4 #define RX_LS_TERMINATED_ENABLE 0x00A5 #define RX_HS_UNTERMINATED_ENABLE 0x00A6 #define RX_ENTER_HIBERN8 0x00A7 #define RX_BYPASS_8B10B_ENABLE 0x00A8 #define RX_TERMINATION_FORCE_ENABLE 0x0089 #define is_mphy_tx_attr(attr) (attr < RX_MODE) /* * PHY Adpater attributes */ Loading