设置的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));
}