一. UFS Auto Hibernate简介
UFS Auto Hibernate是UFS Host的功能,在UFS子系统空闲的时候,由UFS HCI硬件控制链路层Link自动进入Hibernate。
UFS Auto Hibernate是现代智能手机高效管理电源、提升电池续航的一个标准且重要的组成部分, 整个过程完全由硬件自动化控制,用户无需任何操作。
二. UFS Auto Hibernate 概述
1 . UFS Auto Hibernate框图

2 . UFS Auto Hibernate流程
UFS采用UniPro及INCITS T10草案标准的组件作为其电源管理框架。为提升能效表现,UFS主机控制器可支持名为自动休眠的机制。该机制允许主机控制器自主将UniPro链路置于休眠状态。
主机寄存器CAP.AUTOH8为软件提供检测此功能支持的途径,而AHIT.AH8ITV寄存器则赋予软件直接控制该功能的权限。
Offset 00h: CAP -- Controller Capabilities

Controller Capabilities Register Bit[23] 表示UFS Host是否支持Auto-Hibernation功能
Offset 18h: AHIT -- Auto-Hibernate Idle Timer

Auto-Hibern8 Idle Time Value(AH8ITV): 表示UFS系统空闲的时候,AH8ITV值会递减,当递减到0的时候,UFS Host Controller会将Unipro Link放到Hibernate State.
UFS系统空闲, 自动将Unipro Link 进入Hibernate State的条件:
|-------------------------------|----------------------------------------------|
| 条件 | 描述 |
| UTRLDBR= 0 | UFS Host Controller 检测到scsi/query cmd为0 |
| UTMRLDBR= 0 | UFS Host Controller 检测到task management cmd为0 |
| No UIC command is outstanding | UFS Host Controller检测到uic cmd(dme cmd)为0 |
UFS系统非空闲,自动将Unipro Link 退出Hibernate State的条件:
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------|
| 条件 | 描述 |
| UTRLDBR != 0 | UFS Host Controller 检测到scsi/query cmd, Unipro Link退出Hibernate State |
| UTMRLDBR != '0' | UFS Host Controller 检测到task management cmd, Unipro Link退出Hibernate State |
| UIC CMD ! = 0 | UFS Host Controller检测到uic cmd(dme cmd), Unipro Link退出Hibernate State(dme_get local侧不会退出hibernate, dme get peer端和其他的uic cmd都会退出hibernate) |
| AH8ITV = 0 | AH8ITV为0,会关闭Auto Hibernate功能,Unipro Link退出Hibernate State |
三. UFS Auto Hibernate 详解
1. Auto Hibernate 和 DME H8的相同点
Auto Hibernate进入Hibernate 和DME Hibernate进入Hibernate 对于UFS Device来说都是一样的效果,
Hibernate Enter流程都是:
(1). UFS Host侧发送PA_LM_HIBERNATE_ENTER.req请求给UFS Host Local PA
(2). UFS Host Local PA收到请求后,回复PA_LM_HIBERNATE_ENTER.cnf_L
(3). UFS Host Local PA发送PACP_PWR_req原语给UFS Device Remote PA
(4). UFS Device Remote PA收到原语后, 回复PACP_PWR_cnf
(5). UFS Host Local TX结束Burst状态,Link进入Hibenate状态
(6). UFS Host Remote TX结束Burst状态,Link进入Hibernate状态

Hibernate退出流程都是:
(1) UFS Host侧发送PA_LM_HIBERNATE_EXIT.req 给UFS Host Local PA
(2) UFS Host Local PA收到请求后,回复PA_LM_HIBERNATE_EXIT.cnf_L
(3) UFS Host Local PA唤醒 UFS Device M-RX的Link退出Hibernate
(4) UFS Device Remote PA唤醒 UFS Host M-RX的Link退出Hibernate

2. Auto Hibernate 和 DME Hibernate的不同点
(1) Auto Hibernate是硬件控制自动进出Hibernate状态,DME Hibernate是由用户控制进入Hibernate状态。
(2) 当Auto Hibernate混发的时候,开启Auto Hibernate 进入Hibernate State, 再发DME Hibernate Enter, 会先唤醒Auto Hibernate退出Hibernate State, 再由DME Hibernate Enter进入Hibernate
(3)当Auto Hibernate混发的时候, DME Hibernate 进入Hibernate State, 然后开启Autro Hibernate之后,没有唤醒DME Hibernate进入的Hibernate State, 而是保持DME Hibernate进入的Hibernate State。
3. Auto Hibernate进出Hibernate的MPHY State和Power State
Auto Hibernate进入Hibernate和退出Hibernate的MPHY State:

Auto Hibernate进入Hibernate和退出Hibernate的M-TX/M-RX Power变化: 可以通过万用表或者示波器观察Auto H8 进出Hibernate的电压变化
'
UFS MPHY M-PHY Lane Example:M-TX / M-RX

4. 手机系统上Auto Hibernate的用法
(1) Auto hibernate: UFS子系统idle 1ms之后进入Hibernate, 由硬件控制进入Hibernate
(2) clkgate hibernate: UFS子系统idle 10ms之后进入Hibeante, 由驱动发送DME H8 Enter进入Hibernate
(3) suspend hibernate: UFS子系统idle 3s之后进入Hibernate, 由驱动发送DME H8 Enter进入Hibernate
5. 手机系统上Auto Hibernate的异常和恢复
(1) 当系统发命令出错之后,重新初始化HCI,会将Auto H8关闭,重新开启Auto H8可以恢复
(2) 当系统发送hardware reset/dme reset/endpoint reset, 然后直接发送命令,会导致Auto H8异常,可以通过重新link startup可以恢复Auto H8功能
(3) 当系统发命令出现HCI异常,HCI认为一直有命令,Auto H8会一直进不去Hibernate, 可以通过重新初始化HCI + link startup + 开启Auto H8恢复功能
6. Auto Hibernate在PA上的交互
Auto Hibernate 进出Hibeanate和DME H8 进出Hibernate在PA上表现是一样的流程
(1) Hibernate Enter在PA上的交互:
|------------------|--------------------|
| Host侧PA | Device侧PA |
| PACP_PWR_req | |
| | End of Burst |
| | End Of Burst |
| | Sleep |
| | Prepare |
| | Burst |
| | Start of Burst |
| | Start of Burst |
| | PACP_PWR_cnf |
| End of Burst | |
| End of Burst | |
| Sleep | |
| Hibern8 | |
| | End of Burst |
| | End of Burst |
| | Sleep |
| | Hibern8w |
(2) Hibernate Exit 在PA上的交互:
|--------------------|--------------------|
| Host侧PA | Device侧PA |
| Sleep | |
| | Sleep |
| Prepare | |
| Burst | |
| Start of Burst | |
| Start of Burst | |
| AFC TC1 | |
| AFC TC0 | |
| | Prepare |
| | Burst |
| | Start of Burst |
| | Start of Burst |
| | AFC TC1 |
| | AFC TC0 |
| | AFC TC1 |
| | AFC TC0 |
| AFC TC1 | |
| AFC TC0 | |
7. Auto Hibernate过程的Unipro状态变化

|--------------|----------------------------------------------------------------|
| Unipro State | Auto Hibernate |
| Off | UFS Device Vcc和Unipro Vccq没有上电 |
| Disabled | UFS Device Vcc和Unipro Vccq上电了, 没有执行DME Enable |
| LinkDown | UFS Device Vcc和Unipro Vccq上电了,但是没有执行DME LinkStartup |
| Linkup | DME LinkStartup执行成功,UFS Host和UFS Device建链成功 |
| LinkCfg | Auto Hibernate Enable,会进入LinkCfg状态, Link配置完成后回到Linkup状态 |
| Hibernate | Auto Hibernate Enable, 并且idle time vaule递减到零的时候,会进入Hibernate状态 |
四. UFS Auto Hibernate 代码
1. QCOM Auto Hibernate 方案
高通的方案是Auto Hibernate和DME H8可以混合发送的,DME H8工作的时候,同时Auto H8也是可以开启的。
(1) Auto Hibernate的开启
在UFS初始化的阶段,如果HCI支持auto-hibernate功能,并且设置了ahit值,就会开启auto hibernate功能。
int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) {
/* Set the default auto-hiberate idle timer value to 150 ms */
if (ufshcd_is_auto_hibern8_supported(hba) && !hba->ahit) {
hba->ahit = FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, 150) |
FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, 3);
}
/* Enable Auto-Hibernate if configured */
ufshcd_auto_hibern8_enable(hba);
}
void ufshcd_auto_hibern8_enable(struct ufs_hba *hba)
{
if (!ufshcd_is_auto_hibern8_supported(hba))
return;
ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
}
(2) clkgate hibernate: ufs子系统idle 150ms通过发送DME Hibernate Enter进入clkgate hibernate, 过程中没有操作auto-hibernate
static void ufshcd_gate_work(struct work_struct *work)
{
struct ufs_hba *hba = container_of(work, struct ufs_hba,
clk_gating.gate_work.work);
unsigned long flags;
int ret;
spin_lock_irqsave(hba->host->host_lock, flags);
/*
* In case you are here to cancel this work the gating state
* would be marked as REQ_CLKS_ON. In this case save time by
* skipping the gating work and exit after changing the clock
* state to CLKS_ON.
*/
if (hba->clk_gating.is_suspended ||
(hba->clk_gating.state != REQ_CLKS_OFF)) {
hba->clk_gating.state = CLKS_ON;
trace_ufshcd_clk_gating(dev_name(hba->dev),
hba->clk_gating.state);
goto rel_lock;
}
if (hba->clk_gating.active_reqs
|| hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL
|| hba->outstanding_reqs || hba->outstanding_tasks
|| hba->active_uic_cmd || hba->uic_async_done)
goto rel_lock;
spin_unlock_irqrestore(hba->host->host_lock, flags);
/* put the link into hibern8 mode before turning off clocks */
if (ufshcd_can_hibern8_during_gating(hba)) {
ret = ufshcd_uic_hibern8_enter(hba);
if (ret) {
hba->clk_gating.state = CLKS_ON;
dev_err(hba->dev, "%s: hibern8 enter failed %d\n",
__func__, ret);
trace_ufshcd_clk_gating(dev_name(hba->dev),
hba->clk_gating.state);
goto out;
}
ufshcd_set_link_hibern8(hba);
}
ufshcd_disable_irq(hba);
ufshcd_setup_clocks(hba, false);
/* Put the host controller in low power mode if possible */
ufshcd_hba_vreg_set_lpm(hba);
/*
* In case you are here to cancel this work the gating state
* would be marked as REQ_CLKS_ON. In this case keep the state
* as REQ_CLKS_ON which would anyway imply that clocks are off
* and a request to turn them on is pending. By doing this way,
* we keep the state machine in tact and this would ultimately
* prevent from doing cancel work multiple times when there are
* new requests arriving before the current cancel work is done.
*/
spin_lock_irqsave(hba->host->host_lock, flags);
if (hba->clk_gating.state == REQ_CLKS_OFF) {
hba->clk_gating.state = CLKS_OFF;
trace_ufshcd_clk_gating(dev_name(hba->dev),
hba->clk_gating.state);
}
rel_lock:
spin_unlock_irqrestore(hba->host->host_lock, flags);
out:
return;
}
static void ufshcd_init_clk_gating(struct ufs_hba *hba)
{
char wq_name[sizeof("ufs_clk_gating_00")];
if (!ufshcd_is_clkgating_allowed(hba))
return;
hba->clk_gating.state = CLKS_ON;
hba->clk_gating.delay_ms = 150;
INIT_DELAYED_WORK(&hba->clk_gating.gate_work, ufshcd_gate_work);
INIT_WORK(&hba->clk_gating.ungate_work, ufshcd_ungate_work);
snprintf(wq_name, ARRAY_SIZE(wq_name), "ufs_clk_gating_%d",
hba->host->host_no);
hba->clk_gating.clk_gating_workq = alloc_ordered_workqueue(wq_name,
WQ_MEM_RECLAIM | WQ_HIGHPRI);
ufshcd_init_clk_gating_sysfs(hba);
hba->clk_gating.is_enabled = true;
hba->clk_gating.is_initialized = true;
}
(3) suspend hibernate:ufs子系统idle 3s通过发送DME Hibernate Enter进入suspend hibernate, 过程中没有操作auto-hibernate
static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
int ret = 0;
bool check_for_bkops;
enum ufs_pm_level pm_lvl;
enum ufs_dev_pwr_mode req_dev_pwr_mode;
enum uic_link_state req_link_state;
hba->pm_op_in_progress = true;
if (pm_op != UFS_SHUTDOWN_PM) {
pm_lvl = pm_op == UFS_RUNTIME_PM ?
hba->rpm_lvl : hba->spm_lvl;
req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl);
req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl);
} else {
req_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE;
req_link_state = UIC_LINK_OFF_STATE;
}
/*
* If we can't transition into any of the low power modes
* just gate the clocks.
*/
ufshcd_hold(hba);
hba->clk_gating.is_suspended = true;
if (ufshcd_is_clkscaling_supported(hba))
ufshcd_clk_scaling_suspend(hba, true);
if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE &&
req_link_state == UIC_LINK_ACTIVE_STATE) {
goto vops_suspend;
}
if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) &&
(req_link_state == hba->uic_link_state))
goto enable_scaling;
/* UFS device & link must be active before we enter in this function */
if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) {
ret = -EINVAL;
goto enable_scaling;
}
if (pm_op == UFS_RUNTIME_PM) {
if (ufshcd_can_autobkops_during_suspend(hba)) {
/*
* The device is idle with no requests in the queue,
* allow background operations if bkops status shows
* that performance might be impacted.
*/
ret = ufshcd_urgent_bkops(hba);
if (ret) {
/*
* If return err in suspend flow, IO will hang.
* Trigger error handler and break suspend for
* error recovery.
*/
ufshcd_force_error_recovery(hba);
ret = -EBUSY;
goto enable_scaling;
}
} else {
/* make sure that auto bkops is disabled */
ufshcd_disable_auto_bkops(hba);
}
/*
* If device needs to do BKOP or WB buffer flush during
* Hibern8, keep device power mode as "active power mode"
* and VCC supply.
*/
hba->dev_info.b_rpm_dev_flush_capable =
hba->auto_bkops_enabled ||
(((req_link_state == UIC_LINK_HIBERN8_STATE) ||
((req_link_state == UIC_LINK_ACTIVE_STATE) &&
ufshcd_is_auto_hibern8_enabled(hba))) &&
ufshcd_wb_need_flush(hba));
}
flush_work(&hba->eeh_work);
ret = ufshcd_vops_suspend(hba, pm_op, PRE_CHANGE);
if (ret)
goto enable_scaling;
if (req_dev_pwr_mode != hba->curr_dev_pwr_mode) {
if (pm_op != UFS_RUNTIME_PM)
/* ensure that bkops is disabled */
ufshcd_disable_auto_bkops(hba);
if (!hba->dev_info.b_rpm_dev_flush_capable) {
ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
if (ret && pm_op != UFS_SHUTDOWN_PM) {
/*
* If return err in suspend flow, IO will hang.
* Trigger error handler and break suspend for
* error recovery.
*/
ufshcd_force_error_recovery(hba);
ret = -EBUSY;
}
if (ret)
goto enable_scaling;
}
}
/*
* In the case of DeepSleep, the device is expected to remain powered
* with the link off, so do not check for bkops.
*/
check_for_bkops = !ufshcd_is_ufs_dev_deepsleep(hba);
ret = ufshcd_link_state_transition(hba, req_link_state, check_for_bkops);
if (ret && pm_op != UFS_SHUTDOWN_PM) {
/*
* If return err in suspend flow, IO will hang.
* Trigger error handler and break suspend for
* error recovery.
*/
ufshcd_force_error_recovery(hba);
ret = -EBUSY;
}
if (ret)
goto set_dev_active;
vops_suspend:
/*
* Call vendor specific suspend callback. As these callbacks may access
* vendor specific host controller register space call them before the
* host clocks are ON.
*/
ret = ufshcd_vops_suspend(hba, pm_op, POST_CHANGE);
if (ret)
goto set_link_active;
goto out;
set_link_active:
/*
* Device hardware reset is required to exit DeepSleep. Also, for
* DeepSleep, the link is off so host reset and restore will be done
* further below.
*/
if (ufshcd_is_ufs_dev_deepsleep(hba)) {
ufshcd_device_reset(hba);
WARN_ON(!ufshcd_is_link_off(hba));
}
if (ufshcd_is_link_hibern8(hba) && !ufshcd_uic_hibern8_exit(hba))
ufshcd_set_link_active(hba);
else if (ufshcd_is_link_off(hba))
ufshcd_host_reset_and_restore(hba);
set_dev_active:
/* Can also get here needing to exit DeepSleep */
if (ufshcd_is_ufs_dev_deepsleep(hba)) {
ufshcd_device_reset(hba);
ufshcd_host_reset_and_restore(hba);
}
if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE))
ufshcd_disable_auto_bkops(hba);
enable_scaling:
if (ufshcd_is_clkscaling_supported(hba))
ufshcd_clk_scaling_suspend(hba, false);
hba->dev_info.b_rpm_dev_flush_capable = false;
out:
if (hba->dev_info.b_rpm_dev_flush_capable) {
schedule_delayed_work(&hba->rpm_dev_flush_recheck_work,
msecs_to_jiffies(RPM_DEV_FLUSH_RECHECK_WORK_DELAY_MS));
}
if (ret) {
ufshcd_update_evt_hist(hba, UFS_EVT_WL_SUSP_ERR, (u32)ret);
hba->clk_gating.is_suspended = false;
ufshcd_release(hba);
}
hba->pm_op_in_progress = false;
return ret;
}
static int ufshcd_link_state_transition(struct ufs_hba *hba,
enum uic_link_state req_link_state,
bool check_for_bkops)
{
int ret = 0;
if (req_link_state == hba->uic_link_state)
return 0;
if (req_link_state == UIC_LINK_HIBERN8_STATE) {
ret = ufshcd_uic_hibern8_enter(hba);
if (!ret) {
ufshcd_set_link_hibern8(hba);
} else {
dev_err(hba->dev, "%s: hibern8 enter failed %d\n",
__func__, ret);
goto out;
}
}
/*
* If autobkops is enabled, link can't be turned off because
* turning off the link would also turn off the device, except in the
* case of DeepSleep where the device is expected to remain powered.
*/
else if ((req_link_state == UIC_LINK_OFF_STATE) &&
(!check_for_bkops || !hba->auto_bkops_enabled)) {
/*
* Let's make sure that link is in low power mode, we are doing
* this currently by putting the link in Hibern8. Otherway to
* put the link in low power mode is to send the DME end point
* to device and then send the DME reset command to local
* unipro. But putting the link in hibern8 is much faster.
*
* Note also that putting the link in Hibern8 is a requirement
* for entering DeepSleep.
*/
ret = ufshcd_uic_hibern8_enter(hba);
if (ret) {
dev_err(hba->dev, "%s: hibern8 enter failed %d\n",
__func__, ret);
goto out;
}
/*
* Change controller state to "reset state" which
* should also put the link in off/reset state
*/
ufshcd_hba_stop(hba);
/*
* TODO: Check if we need any delay to make sure that
* controller is reset
*/
ufshcd_set_link_off(hba);
}
out:
return ret;
}
(4)通过查询/sys目录下的auto_hibern8节点,可以知道auto hibernate的状态信息
static ssize_t auto_hibern8_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
u32 ahit;
int ret;
struct ufs_hba *hba = dev_get_drvdata(dev);
if (!ufshcd_is_auto_hibern8_supported(hba))
return -EOPNOTSUPP;
down(&hba->host_sem);
if (!ufshcd_is_user_access_allowed(hba)) {
ret = -EBUSY;
goto out;
}
pm_runtime_get_sync(hba->dev);
ufshcd_hold(hba);
ahit = ufshcd_readl(hba, REG_AUTO_HIBERNATE_IDLE_TIMER);
ufshcd_release(hba);
pm_runtime_put_sync(hba->dev);
ret = sysfs_emit(buf, "%d\n", ufshcd_ahit_to_us(ahit));
out:
up(&hba->host_sem);
return ret;
}
static ssize_t auto_hibern8_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
unsigned int timer;
int ret = 0;
if (!ufshcd_is_auto_hibern8_supported(hba))
return -EOPNOTSUPP;
if (kstrtouint(buf, 0, &timer))
return -EINVAL;
if (timer > UFSHCI_AHIBERN8_MAX)
return -EINVAL;
down(&hba->host_sem);
if (!ufshcd_is_user_access_allowed(hba)) {
ret = -EBUSY;
goto out;
}
ufshcd_auto_hibern8_update(hba, ufshcd_us_to_ahit(timer));
out:
up(&hba->host_sem);
return ret ? ret : count;
}
2. MTK Auto Hibernate 方案
MTK的方案是Auto Hibernate和DME H8是互斥的,DME H8工作的时候,关闭Auto Hibernate, DME H8不工作的时候,开启Auto Hibernate
(1) Auto Hibernate的开启:和Qcom方案是一样的
在UFS初始化的阶段,如果HCI支持auto-hibernate功能,并且设置了ahit值,就会开启auto hibernate功能。
(2) clkgate hibernate: ufs子系统idle 150ms通过发送DME Hibernate Enter进入clkgate hibernate, MTK的方案Auto Hibernate和clkgate hibernate是互斥的,
如果在DTS设置了mediatek,ufs-disable-ah8",UFS Driver就会关闭Auto Hibernate功能,开启clkgate hibernate功能。
如果在DTS没有设置mediatek,ufs-disable-ah8", UFS Driver就会开启Auto Hibernate功能,关闭clkgate hibernate功能
/**
* ufs_mtk_init - find other essential mmio bases
* @hba: host controller instance
*
* Binds PHY with controller and powers up PHY enabling clocks
* and regulators.
*
* Return: -EPROBE_DEFER if binding fails, returns negative error
* on phy power up failure and returns zero on success.
*/
static int ufs_mtk_init(struct ufs_hba *hba )
{
if (host->caps & UFS_MTK_CAP_DISABLE_AH8)
hba->caps |= UFSHCD_CAP_HIBERN8_WITH_CLK_GATING;
}
static void ufs_mtk_init_host_caps(struct ufs_hba *hba)
{
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
struct device_node *np = hba->dev->of_node;
if (of_property_read_bool(np, "mediatek,ufs-boost-crypt"))
ufs_mtk_init_boost_crypt(hba);
if (of_property_read_bool(np, "mediatek,ufs-support-va09"))
ufs_mtk_init_va09_pwr_ctrl(hba);
if (of_property_read_bool(np, "mediatek,ufs-disable-ah8"))
host->caps |= UFS_MTK_CAP_DISABLE_AH8;
if (of_property_read_bool(np, "mediatek,ufs-broken-vcc"))
host->caps |= UFS_MTK_CAP_BROKEN_VCC;
if (of_property_read_bool(np, "mediatek,ufs-pmc-via-fastauto"))
host->caps |= UFS_MTK_CAP_PMC_VIA_FASTAUTO;
dev_info(hba->dev, "caps: 0x%x", host->caps);
}
static int ufs_mtk_hce_enable_notify(struct ufs_hba *hba,
enum ufs_notify_change_status status)
{
struct ufs_mtk_host *host = ufshcd_get_variant(hba);
if (status == PRE_CHANGE) {
if (host->unipro_lpm) {
hba->vps->hba_enable_delay_us = 0;
} else {
hba->vps->hba_enable_delay_us = 600;
ufs_mtk_host_reset(hba);
}
if (hba->caps & UFSHCD_CAP_CRYPTO)
ufs_mtk_crypto_enable(hba);
if (host->caps & UFS_MTK_CAP_DISABLE_AH8) {
ufshcd_writel(hba, 0,
REG_AUTO_HIBERNATE_IDLE_TIMER);
hba->capabilities &= ~MASK_AUTO_HIBERN8_SUPPORT;
hba->ahit = 0;
}
/*
* Turn on CLK_CG early to bypass abnormal ERR_CHK signal
* to prevent host hang issue
*/
ufshcd_writel(hba,
ufshcd_readl(hba, REG_UFS_XOUFS_CTRL) | 0x80,
REG_UFS_XOUFS_CTRL);
}
return 0;
}
(3) suspend hibernate:ufs子系统idle 3s通过发送DME Hibernate Enter进入suspend hibernate, 在DME Hibernate Enter发送之前会关闭Auto Hibernate
static int __ufshcd_wl_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
ret = ufshcd_vops_suspend(hba, pm_op, PRE_CHANGE);
if (ret)
goto enable_scaling;
if (req_dev_pwr_mode != hba->curr_dev_pwr_mode) {
if (pm_op != UFS_RUNTIME_PM)
/* ensure that bkops is disabled */
ufshcd_disable_auto_bkops(hba);
if (!hba->dev_info.b_rpm_dev_flush_capable) {
ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
if (ret && pm_op != UFS_SHUTDOWN_PM) {
/*
* If return err in suspend flow, IO will hang.
* Trigger error handler and break suspend for
* error recovery.
*/
ufshcd_force_error_recovery(hba);
ret = -EBUSY;
}
if (ret)
goto enable_scaling;
}
}
/*
* In the case of DeepSleep, the device is expected to remain powered
* with the link off, so do not check for bkops.
*/
check_for_bkops = !ufshcd_is_ufs_dev_deepsleep(hba);
ret = ufshcd_link_state_transition(hba, req_link_state, check_for_bkops);
if (ret && pm_op != UFS_SHUTDOWN_PM) {
/*
* If return err in suspend flow, IO will hang.
* Trigger error handler and break suspend for
* error recovery.
*/
ufshcd_force_error_recovery(hba);
ret = -EBUSY;
}
if (ret)
goto set_dev_active;
vops_suspend:
/*
* Call vendor specific suspend callback. As these callbacks may access
* vendor specific host controller register space call them before the
* host clocks are ON.
*/
ret = ufshcd_vops_suspend(hba, pm_op, POST_CHANGE);
if (ret)
goto set_link_active;
goto out;
}
static int ufs_mtk_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op,
enum ufs_notify_change_status status)
{
int err;
struct arm_smccc_res res;
if (status == PRE_CHANGE) {
if (ufshcd_is_auto_hibern8_supported(hba))
ufs_mtk_auto_hibern8_disable(hba);
return 0;
}
if (ufshcd_is_link_hibern8(hba)) {
err = ufs_mtk_link_set_lpm(hba);
if (err)
goto fail;
}
if (!ufshcd_is_link_active(hba)) {
/*
* Make sure no error will be returned to prevent
* ufshcd_suspend() re-enabling regulators while vreg is still
* in low-power mode.
*/
err = ufs_mtk_mphy_power_on(hba, false);
if (err)
goto fail;
}
if (ufshcd_is_link_off(hba))
ufs_mtk_device_reset_ctrl(0, res);
ufs_mtk_host_pwr_ctrl(HOST_PWR_HCI, false, res);
return 0;
fail:
/*
* Set link as off state enforcedly to trigger
* ufshcd_host_reset_and_restore() in ufshcd_suspend()
* for completed host reset.
*/
ufshcd_set_link_off(hba);
return -EAGAIN;
}
(4)通过查询/sys目录下的auto_hibern8节点,可以知道auto hibernate的状态信息, 这块和Qcom方案是一样的
3. Auto Hibernate 异常状态监测
Auto Hibernate进出Hibernate全程都是硬件驱动的,要是进出Hibernate出错的话我们怎么知道?
这个HCI已经帮我们做好了状态监测方案,当Auto Hibernate进出Hibernate出错的时候,HCI会将Offset 20h: IS -- Interrupt Status 的Bit[5]或者Bit[6]设置为1

UFS Driver也会适配HCI的状态监测,如果产生了UIC_HIBERN8(包括H8 Enter和H8 Exit)的中断,并且没有发送DME H8 Enter和DME H8 Exit的命令,就说明是Auto Hibernate进出Hibernate出错了。
static bool ufshcd_is_auto_hibern8_error(struct ufs_hba *hba,
u32 intr_mask)
{
if (!ufshcd_is_auto_hibern8_supported(hba) ||
!ufshcd_is_auto_hibern8_enabled(hba))
return false;
if (!(intr_mask & UFSHCD_UIC_HIBERN8_MASK))
return false;
if (hba->active_uic_cmd &&
(hba->active_uic_cmd->command == UIC_CMD_DME_HIBER_ENTER ||
hba->active_uic_cmd->command == UIC_CMD_DME_HIBER_EXIT))
return false;
return true;
}
/**
* ufshcd_uic_cmd_compl - handle completion of uic command
* @hba: per adapter instance
* @intr_status: interrupt status generated by the controller
*
* Return:
* IRQ_HANDLED - If interrupt is valid
* IRQ_NONE - If invalid interrupt
*/
static irqreturn_t ufshcd_uic_cmd_compl(struct ufs_hba *hba, u32 intr_status)
{
irqreturn_t retval = IRQ_NONE;
struct uic_command *cmd;
spin_lock(hba->host->host_lock);
cmd = hba->active_uic_cmd;
if (ufshcd_is_auto_hibern8_error(hba, intr_status))
hba->errors |= (UFSHCD_UIC_HIBERN8_MASK & intr_status);
if (intr_status & UIC_COMMAND_COMPL && cmd) {
cmd->argument2 |= ufshcd_get_uic_cmd_result(hba);
cmd->argument3 = ufshcd_get_dme_attr_val(hba);
if (!hba->uic_async_done)
cmd->cmd_active = 0;
complete(&cmd->done);
retval = IRQ_HANDLED;
}
if (intr_status & UFSHCD_UIC_PWR_MASK && hba->uic_async_done) {
cmd->cmd_active = 0;
complete(hba->uic_async_done);
retval = IRQ_HANDLED;
}
if (retval == IRQ_HANDLED)
ufshcd_add_uic_command_trace(hba, cmd, UFS_CMD_COMP);
spin_unlock(hba->host->host_lock);
return retval;
}
监控到Auto H8进出Hibernate出错的时候,可以查询**Offset 30h: HCS -- Host Controller Status寄存器 Bit[10:8]**查询UPMCRS, 看下Auto H8出错在哪个阶段和出错的原因

五. 参考资料
-
UFS MIPI Unipro Version1.8
-
UFS MIPI MPHY Version4-1a
-
UFS3.1协议
-
UFS HCI协议
-
kernel.org source code