設置的device tree和相關的platform初始化函數,主要設置HOST_WAKE, DEVICE_RDY, HOST_RDY 三個GPIO和設置
HOST_WAKE和DEVICE_RDY的中斷函數。並設置PM和HSIC的power manager相關的notifier call函數。
mdmpm_pdata {
compatible = "qcom,mdm-hsic-pm";
qcom,ap2mdm-hostrdy-gpio = <&gpf5 0 0x1>;
qcom,mdm2ap-devicerdy-gpio = <&gpa1 2 0x0>;
qcom,mdm2ap-hostwake-gpio = <&gpa3 1 0xf>;
reg = <0x15510000 0x100>, /* EHCI */
<0x15530000 0x100>, /* PHY */
<0x105C0704 0xC>, /* PMU */
<0x156E0204 0xC>; /* USB phy clk */
};
static int mdm_hsic_pm_probe(struct platform_device *pdev)
{
int ret;
struct mdm_hsic_pm_data *pm_data = pdev->dev.platform_data;;
pr_info("%s for %s\n", __func__, pdev->name);
if (pdev->dev.of_node) {
pm_data = mdm_pm_parse_dt_pdata(&pdev->dev);
if (IS_ERR(pm_data)) {
pr_err("MDM DT parse error!\n");
goto err_gpio_init_fail;
}
#ifdef EHCI_REG_DUMP
ehci_port_reg_init(pdev);
#endif
} else {
if (!pm_data) {
pr_err("MDM Non-DT, incorrect pdata!\n");
return -EINVAL;
}
}
memcpy(pm_data->name, pdev->name, strlen(pdev->name));
/* request irq for host wake interrupt */
ret = request_irq(pm_data->irq, mdm_hsic_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_DISABLED,
"mdm_hsic_pm", (void *)pm_data);
if (ret < 0) {
pr_err("%s: fail to request mdm_hsic_pm irq(%d)\n", __func__, ret);
goto err_request_irq;
}
ret = enable_irq_wake(pm_data->irq);
if (ret < 0) {
pr_err("%s: fail to set wake irq(%d)\n", __func__, ret);
goto err_set_wake_irq;
}
ret = request_irq(pm_data->dev_rdy_irq, mdm_device_ready_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_DISABLED,
"mdm_device_ready", (void *)pm_data);
if (ret < 0) {
pr_err("%s: fail to request mdm_device_ready irq(%d)\n", __func__, ret);
goto err_request_irq;
}
pm_data->wq = create_singlethread_workqueue("hsicrpmd");
if (!pm_data->wq) {
pr_err("%s: fail to create wq\n", __func__);
goto err_create_wq;
}
if (sysfs_create_group(&pdev->dev.kobj, &mdm_hsic_attrgroup) < 0) {
pr_err("%s: fail to create sysfs\n", __func__);
goto err_create_sys_file;
}
pm_data->mdm_pdata =
(struct mdm_hsic_pm_platform_data *)pdev->dev.platform_data;
INIT_DELAYED_WORK(&pm_data->auto_rpm_start_work, mdm_hsic_rpm_start);
INIT_DELAYED_WORK(&pm_data->auto_rpm_restart_work,
mdm_hsic_rpm_restart);
INIT_DELAYED_WORK(&pm_data->request_resume_work, mdm_hsic_rpm_check);
INIT_DELAYED_WORK(&pm_data->fast_dormancy_work, fast_dormancy_func);
/* register notifier call */
pm_data->pm_notifier.notifier_call = mdm_hsic_pm_notify_event;
register_pm_notifier(&pm_data->pm_notifier);
blocking_notifier_chain_register(&mdm_reset_notifier_list,
&mdm_reset_main_block);
#ifdef CONFIG_CPU_FREQ_TETHERING
pm_data->netdev_notifier.notifier_call = link_pm_netdev_event;
register_netdevice_notifier(&pm_data->netdev_notifier);
pm_data->usb_composite_notifier.notifier_call =
usb_composite_notifier_event;
register_usb_composite_notifier(&pm_data->usb_composite_notifier);
#endif
#if defined(CONFIG_OF)
pm_data->phy_nfb.notifier_call = mdm_hsic_pm_phy_notify;
phy_register_notifier(&pm_data->phy_nfb);
#endif
wake_lock_init(&pm_data->l2_wake, WAKE_LOCK_SUSPEND, pm_data->name);
wake_lock_init(&pm_data->boot_wake, WAKE_LOCK_SUSPEND, "mdm_boot");
wake_lock_init(&pm_data->fd_wake, WAKE_LOCK_SUSPEND, "fast_dormancy");
pm_data->fd_wake_time = DEFAULT_RAW_WAKE_TIME;
pm_data->qmicm_mode = false;
print_pm_dev_info(pm_data);
list_add(&pm_data->list, &hsic_pm_dev_list);
platform_set_drvdata(pdev, pm_data);
pr_info("%s for %s has done\n", __func__, pdev->name);
debug_ehci_reg_dump();
return 0;
err_create_sys_file:
destroy_workqueue(pm_data->wq);
err_create_wq:
disable_irq_wake(pm_data->irq);
err_set_wake_irq:
free_irq(pm_data->irq, (void *)pm_data);
err_request_irq:
err_gpio_init_fail:
mdm_hsic_pm_gpio_free(pm_data);
kfree(pm_data);
return -ENXIO;
}
HS-USB/HSIC PM 狀態有四種,分別表示成L0,L1,L2,L3。
但無論高通還是三星平臺都沒有實現L1狀態,所以都只在L0,L1,L3狀態下循環。
(1) L0 (Data Active)
HSIC 發送和接收數據的狀態
(2) L1(LPM)
在很短時間裏(T1),HSIC如果沒有數據發送或接收的時候進入省電模式的狀態(stand alone operation)。
這個狀態由於相對省電效果不是很明顯而且實現起來比較麻煩,所以無論三星或者高通平臺目前都沒有實現
(3) L2(SUSPEND)
100~300ms 時間裏(T2) 如果沒有數據發送接收,則進入L2狀態(PHY SUSPEND mode)進行power save
(4) L3(SLEEP)
T3時間沒有數據發送接收則進入Sleep狀態。
這個狀態關閉EHCI以節省電流消耗,但缺點是需要re-enumeration,所以wakeup時間較長。
HSIC bus被 floating。關於HSIC的狀態,以後再說。
Samsung AP和Qualcomm APQ的HSIC PM差異:
Qualcomm APQ進入L2狀態的時候會把HSIC PHY off掉然後拉低STROBE / DATA line,所以在XO Sleep的時候也可以通過MPM Interrupt來喚醒系統,所以不必實現L3狀態也可以得到滿意的睡眠電流。
但Samsung AP在進入L2狀態的時候,沒有實現通過STROBE Line進行喚醒的功能,所以需要增加一個host_wakeup gpio來實現了L2狀態下的AP喚醒。而且HSIC PHY/LINK Controller的電源和其他的h/w block相連,所以實現了L3狀態,在XO sleep的時候需要把HSIC PHY電源關掉(DATA/STROBE line不能由軟件控制)。
高通平臺的狀態轉換圖:
三星平臺的狀態轉換圖:
狀態轉換的說明:
1) L0 -> L2 : suspend signaling by AP
2) L2 -> L0 :
AP init : resume signaling
CP init : (AP cannot recognize remote wakeup) CP request resume by Trigger
HOST_WAKE GPIO
3) L2 -> L3 : AP notify entering L3 with HOST_RDY GPIO Low, after CP response via DEVICE_RDY
GPIO Low to AP, After receiving DEVICE_RADY GPIO Low, AP triggers L3 and every
hsic related HOST power goes off.
4) L3 -> L0 :
AP init : AP wakes up CP via HOST_RDY GPIO high, CP acks by DEVICE_RDY GPIO high,
HOST do reset_resume for fast detect for last attached device.
CP init : CP wakes up AP through HOST_WAKE GPIO, followed seq same as AP init.
下面來根據代碼看一下狀態轉換的實現
在mdm_hsic_pm.c文件的probe函數裏,註冊了兩個notifier call函數。這兩個函數會分別查看系統狀態和hsic狀態。
pm_data->pm_notifier.notifier_call = mdm_hsic_pm_notify_event;
register_pm_notifier(&pm_data->pm_notifier);
pm_data->phy_nfb.notifier_call = mdm_hsic_pm_phy_notify;
phy_register_notifier(&pm_data->phy_nfb);
//這個函數就是對/drivers/usb/host/ehci-5p.c文件中hsic的電源狀態做處理的
//前面說的T1,T2,T3時間怎麼設置的,在什麼時間點發狀態,需要看hsic驅動,後面再說
//ehci-5p.c在L2狀態下只是wake_unlock了pm_data->l2_wake,沒有發送notifier event,只是做hsic相關
//的處理。(s5p_ehci_runtime_suspend()->request_active_lock_release()->wake_unlock())
//所以以下函數裏處理的都是L0<->L3之前的轉換
static int mdm_hsic_pm_phy_notify(struct notifier_block *nfb,
unsigned long event, void *arg)
{
struct mdm_hsic_pm_data *pm_data =
container_of(nfb, struct mdm_hsic_pm_data, phy_nfb);
int ret = 0;
/* in shutdown(including modem fatal) do not need to wait dev ready */
if (pm_data->shutdown)
return 0;
switch (event) {
case STATE_HSIC_RESUME://L3狀態狀態轉到L0狀態,和前面說的一樣,拉低host_ready端口,等待
//device_ready也被拉低
set_host_stat(pm_data, POWER_ON);
ret = wait_dev_pwr_stat(pm_data, POWER_ON);
break;
//STATE_HSIC_SUSPEND在s5p_ehci_suspend()裏發送這個函數是suspend,
//所以應該是已經要power off的時候才調用的。
//static const struct dev_pm_ops s5p_ehci_pm_ops = {
// .suspend = s5p_ehci_suspend,
// .resume = s5p_ehci_resume,
// .runtime_suspend = s5p_ehci_runtime_suspend,
// .runtime_resume = s5p_ehci_runtime_resume,
//};
case STATE_HSIC_SUSPEND://L2轉到L3狀態!!
case STATE_HSIC_LPA_ENTER:
set_host_stat(pm_data, POWER_OFF);
ret = wait_dev_pwr_stat(pm_data, POWER_OFF);
if (ret) {
set_host_stat(pm_data, POWER_ON);
/* pm_runtime_resume(pm_data->udev->dev.parent->parent); */
return ret;
}
break;
case STATE_HSIC_LPA_WAKE:
lpa_handling = true;
pr_info("%s: set lpa handling to true\n", __func__);
request_active_lock_set("15510000.mdmpm_pdata");
pr_info("set hsic lpa wake\n");
break;
case STATE_HSIC_LPA_PHY_INIT:
pr_info("set hsic lpa phy init\n");
break;
case STATE_HSIC_LPA_CHECK:
#if 0
if (lpcharge)
return 0;
else
#endif
if (!get_pm_data_by_dev_name("15510000.mdmpm_pdata"))
return 1;
else
return 0;
case STATE_HSIC_LPA_ENABLE:
#if 0
if (lpcharge)
return 0;
else
#endif
if (pm_data)
return pm_data->shutdown;
else
return 1;
default:
pr_info("unknown lpa state\n");
break;
}
return NOTIFY_DONE;
}
看完上面這個函數,就可以知道L0 <-> L2之間的狀態轉換,只是HISC驅動做的事情,不需要做上面說的三個gpio管腳的控制。只是在s5p_ehci_runtime_suspend()和s5p_ehci_runtime_resume()函數中添加wake_lock和wake_unlock操作就可以。
上面也說了L3->L0的狀態轉換,那CP喚醒AP的時候的操作是怎麼樣的?
host_wake gpio已經設定成了enable_irq_wake()。
static irqreturn_t mdm_hsic_irq_handler(int irq, void *data)
{
int irq_level;
struct mdm_hsic_pm_data *pm_data = data;
if (!pm_data || !pm_data->intf_cnt || !pm_data->udev)
return IRQ_HANDLED;
if (pm_data->shutdown)
return IRQ_HANDLED;
/**
* host wake up handler, takes both edge
* in rising, isr triggers L2 -> L0 resume
*/
//註釋應該是寫錯了,應該是從L3 -> L0
irq_level = gpio_get_value(pm_data->gpio_host_wake);
pr_info("%s: detect %s edge\n", __func__,
irq_level ? "Rising" : "Falling");
if (irq_level != HSIC_RESUME_TRIGGER_LEVEL)
return IRQ_HANDLED;
//pm_data->block_request在mdm_hsic_pm_notify_event()裏
//PM_SUSPEND_PREPARE的時候設置成了true。PM_POST_SUSPEND的時候設置成了false。
//所以只有在PM_SUSPEND時候才進入下面的if語句。由於已經使能了host_wake管腳的
//wakeup功能(enable_irq_wake)。所以系統應該開始啓動。跑到這裏如果發現block_request還是1
//表示還沒有啓動完畢,就只需要在這裏設置wake_lock即可,不需要做其他操作。
//如果啓動完畢,在mdm_hsic_pm_notify_event()函數的PM_POST_SUSPEND裏block_request爲0
//也就不會進入這個以下的if語句。
if (pm_data->block_request) {
pr_info("%s: request blocked by kernel suspending\n", __func__);
pm_data->state_busy = true;
/* for blocked request, set wakelock to return at dpm suspend */
wake_lock(&pm_data->l2_wake);
return IRQ_HANDLED;
}
#if 0
if (pm_request_resume(&pm_data->udev->dev) < 0)
pr_err("%s: unable to add pm work for rpm\n", __func__);
/* check runtime pm runs in Active state, after 100ms */
queue_delayed_work(pm_data->wq, &pm_data->request_resume_work,
msecs_to_jiffies(200));
#else
queue_delayed_work(pm_data->wq, &pm_data->request_resume_work, 0);
#endif
return IRQ_HANDLED;
}
在系統醒來的時候狀態下,也就是在L0狀態下如果發生host_wake中斷,就會調用request_resume_work對應的workqueue函數mdm_hsic_rpm_check().
這個函數用了兩個函數pm_runtime_resume和pm_runtime_suspended。
int pm_runtime_resume(struct device *dev);
對設備執行子系統級的恢復回調;返回0表示成功;如果設備的運行時PM狀態已經是“活躍的(active)”就返回1;或失敗時錯誤代碼,其中-EAGAIN意味着在未來試圖恢復設備可能是安全的;但應附加對‘power.runtime_error’進行檢查
int pm_runtime_suspend(struct device *dev);
對設備執行子系統級的掛起回調;返回0表示成功;如果設備的運行時PM狀態已經是“掛起”則返回1;或失敗時返回錯誤代碼,其中,-EAGAIN或-EBUSY意味着企圖在未來再次掛起設備是安全的。
static void mdm_hsic_rpm_check(struct work_struct *work)
{
struct mdm_hsic_pm_data *pm_data =
container_of(work, struct mdm_hsic_pm_data,
request_resume_work.work);
struct device *dev;
if (pm_data->shutdown)
return;
pr_info("%s\n", __func__);
if (!pm_data->udev)
return;
if (lpa_handling) {
pr_info("ignore resume req, lpa handling\n");
return;
}
dev = &pm_data->udev->dev;
if (pm_runtime_resume(dev) < 0)
//看上面說明,小於0表示設備resume失敗,需要delay 20ms重新調用
queue_delayed_work(pm_data->wq, &pm_data->request_resume_work,
msecs_to_jiffies(20));
if (pm_runtime_suspended(dev))
//pm_runtime_suspended返回1表示設備已經掛起,則和上面一樣,
//delay 20ms在調用resume函數
queue_delayed_work(pm_data->wq, &pm_data->request_resume_work,
msecs_to_jiffies(20));
}