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不能由軟件控制)。

高通平臺的狀態轉換圖:

這裏寫圖片描述

三星平臺的狀態轉換圖:

這裏寫圖片描述

狀態轉換的說明:

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));
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章