MDM9x25 Flashless boot(Power Manager)

设置的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不能由软件控制)。

高通平台的状态转换图:

MDM9x25 Flashless boot(Power Manager)_第1张图片

三星平台的状态转换图:

MDM9x25 Flashless boot(Power Manager)_第2张图片

状态转换的说明:

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));
    }

你可能感兴趣的:(MDM9x25 Flashless boot(Power Manager))