static void start_link_pm(struct modem_link_pm *pm, enum pm_event event) { int cp2ap_wakeup; int cp2ap_status; enum pm_state state; unsigned long flags; spin_lock_irqsave(&pm->lock, flags); if (pm->active) { mif_err("%s: PM is already ACTIVE\n", pm->link_name); goto exit; } lock_pm_wake(pm); if (event == PM_EVENT_LOCK_ON) { state = PM_STATE_LOCKED_ON; assert_ap2cp_wakeup(pm); assert_ap2cp_status(pm); } else if (event == PM_EVENT_CP_BOOTING) { state = PM_STATE_CP_BOOTING; assert_ap2cp_wakeup(pm); assert_ap2cp_status(pm); } else { state = PM_STATE_UNMOUNTED; release_ap2cp_wakeup(pm); release_ap2cp_status(pm); } /* Enable every CP-to-AP IRQ and set it as a wake-up source */ cp2ap_wakeup = gpio_get_value(pm->gpio_cp2ap_wakeup); change_irq_level(pm->cp2ap_wakeup_irq.num, cp2ap_wakeup); mif_enable_irq(&pm->cp2ap_wakeup_irq); cp2ap_status = gpio_get_value(pm->gpio_cp2ap_status); change_irq_level(pm->cp2ap_status_irq.num, cp2ap_status); mif_enable_irq(&pm->cp2ap_status_irq); set_pm_fsm(pm, PM_STATE_UNMOUNTED, state, event); pm->hold_requested = false; pm->active = true; #ifdef DEBUG_MODEM_IF print_pm_fsm(pm); #endif exit: spin_unlock_irqrestore(&pm->lock, flags); }
static void run_pm_fsm(struct modem_link_pm *pm, enum pm_event event) { struct link_device *ld = pm_to_link_device(pm); struct modem_ctl *mc = ld->mc; struct pm_fsm *fsm = &pm->fsm; enum pm_state c_state; enum pm_state n_state; unsigned long flags; spin_lock_irqsave(&pm->lock, flags); c_state = fsm->state; n_state = fsm->state; if (!pm->active) { release_ap2cp_wakeup(pm); if (event == PM_EVENT_LINK_ERROR) n_state = PM_STATE_CP_FAIL; goto exit; } if (fsm_locked(c_state)) goto exit; if (event == PM_EVENT_CP_HOLD_REQUEST) { if (!cp_online(mc)) goto exit; pm->hold_requested = true; if (!hold_possible(c_state)) goto exit; } #if 0 print_pm_event(pm, event); #endif switch (c_state) { case PM_STATE_UNMOUNTED: if (event == PM_EVENT_LINK_SUSPENDED) { n_state = PM_STATE_SUSPENDED; } else if (event == PM_EVENT_CP2AP_WAKEUP_HIGH || event == PM_EVENT_CP_HOLD_REQUEST) { if (link_suspended(pm)) { n_state = PM_STATE_SUSPENDED; prepare_mount(pm); } else { n_state = next_state_from_resume(pm); if (n_state == PM_STATE_RESETTING) { prepare_mount(pm); unmounted_to_resetting(pm); } else if (n_state == PM_STATE_HOLDING) { prepare_mount(pm); unmounted_to_holding(pm); } } } break; case PM_STATE_SUSPENDED: if (event == PM_EVENT_LINK_RESUMED) { n_state = next_state_from_resume(pm); if (n_state == PM_STATE_RESETTING) { prepare_mount(pm); unmounted_to_resetting(pm); } else if (n_state == PM_STATE_HOLDING) { prepare_mount(pm); unmounted_to_holding(pm); } } else if (event == PM_EVENT_CP2AP_WAKEUP_HIGH || event == PM_EVENT_CP_HOLD_REQUEST) { n_state = PM_STATE_SUSPENDED; prepare_mount(pm); } break; case PM_STATE_HOLDING: if (event == PM_EVENT_CP2AP_WAKEUP_HIGH) { n_state = PM_STATE_RESETTING; holding_to_resetting(pm); } else if (event == PM_EVENT_WDOG_TIMEOUT) { /* It is not guaranteed for FSM to succeed in getting GPIO interrupt events or for stop_pm_wdog() to succeed in deleting the WDOG timer always. So, gpio_cp2ap_wakeup and gpio_cp2ap_status must always be checked before state transition. */ if (gpio_get_value(pm->gpio_cp2ap_wakeup)) { n_state = PM_STATE_RESETTING; holding_to_resetting(pm); } else { n_state = PM_STATE_WDOG_TIMEOUT; } } break; case PM_STATE_RESETTING: if (event == PM_EVENT_LINK_RESET) { n_state = PM_STATE_MOUNTING; stop_pm_wdog(pm, c_state, event); assert_ap2cp_status(pm); start_pm_wdog(pm, n_state, PM_STATE_ACTIVE, PM_EVENT_LINK_MOUNTED, LINKPM_WATCHDOG_TIMEOUT); } else if (event == PM_EVENT_WDOG_TIMEOUT) { n_state = PM_STATE_AP_FAIL; } else if (event == PM_EVENT_CP2AP_WAKEUP_LOW) { n_state = PM_STATE_CP_FAIL; } break; case PM_STATE_MOUNTING: if (event == PM_EVENT_LINK_MOUNTED || event == PM_EVENT_CP2AP_STATUS_HIGH) { n_state = PM_STATE_ACTIVE; stop_pm_wdog(pm, c_state, event); } else if (event == PM_EVENT_WDOG_TIMEOUT) { n_state = PM_STATE_WDOG_TIMEOUT; } else if (event == PM_EVENT_CP2AP_WAKEUP_LOW) { #if 0 n_state = PM_STATE_CP_FAIL; #else n_state = PM_STATE_AP_FAIL; #endif } break; case PM_STATE_ACTIVE: if (event == PM_EVENT_CP2AP_WAKEUP_LOW) { n_state = PM_STATE_AP_FREE; schedule_cp_free(pm); } else if (event == PM_EVENT_CP2AP_STATUS_LOW) { n_state = PM_STATE_CP_FAIL; } break; case PM_STATE_AP_FREE: if (event == PM_EVENT_CP2AP_WAKEUP_HIGH) { n_state = PM_STATE_ACTIVE; cancel_cp_free(pm); assert_ap2cp_wakeup(pm); } else if (event == PM_EVENT_CP_HOLD_REQUEST) { n_state = PM_STATE_AP_FREE; cancel_cp_free(pm); assert_ap2cp_wakeup(pm); schedule_cp_free(pm); } else if (event == PM_EVENT_CP_HOLD_TIMEOUT) { /* It is not guaranteed for cancel_cp_free() to succeed in canceling the cp_free_dwork always. So, cp2ap_wakeup must always be checked before state transition. */ if (!gpio_get_value(pm->gpio_cp2ap_wakeup)) { n_state = PM_STATE_CP_FREE; pm->hold_requested = false; release_ap2cp_wakeup(pm); } else { n_state = PM_STATE_ACTIVE; cancel_cp_free(pm); assert_ap2cp_wakeup(pm); } } else if (event == PM_EVENT_CP2AP_STATUS_LOW) { n_state = PM_STATE_CP_FAIL; } break; case PM_STATE_CP_FREE: if (event == PM_EVENT_CP2AP_STATUS_LOW) { n_state = PM_STATE_UNMOUNTING; start_pm_wdog(pm, n_state, PM_STATE_UNMOUNTED, PM_EVENT_LINK_UNMOUNTED, LINKPM_WATCHDOG_TIMEOUT); } else if (event == PM_EVENT_CP2AP_WAKEUP_HIGH) { n_state = PM_STATE_ACTIVE; assert_ap2cp_wakeup(pm); } else if (event == PM_EVENT_CP_HOLD_REQUEST) { n_state = PM_STATE_ACTIVATING; assert_ap2cp_wakeup(pm); start_pm_wdog(pm, n_state, PM_STATE_ACTIVE, PM_EVENT_CP2AP_WAKEUP_HIGH, LINKPM_WATCHDOG_TIMEOUT); } break; case PM_STATE_ACTIVATING: if (event == PM_EVENT_CP2AP_WAKEUP_HIGH) { n_state = PM_STATE_ACTIVE; stop_pm_wdog(pm, c_state, event); assert_ap2cp_wakeup(pm); } else if (event == PM_EVENT_CP2AP_STATUS_LOW) { n_state = PM_STATE_UNMOUNTING; stop_pm_wdog(pm, c_state, event); release_ap2cp_wakeup(pm); } else if (event == PM_EVENT_WDOG_TIMEOUT) { /* It is not guaranteed for FSM to succeed in getting GPIO interrupt events or for stop_pm_wdog() to succeed in deleting the WDOG timer always. So, gpio_cp2ap_wakeup and gpio_cp2ap_status must always be checked before state transition. */ if (gpio_get_value(pm->gpio_cp2ap_wakeup)) n_state = PM_STATE_ACTIVE; else if (!gpio_get_value(pm->gpio_cp2ap_status)) n_state = PM_STATE_UNMOUNTING; else n_state = PM_STATE_WDOG_TIMEOUT; } break; case PM_STATE_UNMOUNTING: if (event == PM_EVENT_LINK_UNMOUNTED) { if (pm->hold_requested) { if (cp_online(mc)) n_state = PM_STATE_HOLDING; else n_state = PM_STATE_UNMOUNTED; pm->hold_requested = false; } else { n_state = PM_STATE_UNMOUNTED; } stop_pm_wdog(pm, c_state, event); release_ap2cp_status(pm); if (n_state == PM_STATE_HOLDING) { prepare_mount(pm); unmounted_to_holding(pm); } } else if (event == PM_EVENT_WDOG_TIMEOUT) { n_state = PM_STATE_WDOG_TIMEOUT; } break; case PM_STATE_CP_BOOTING: if (event == PM_EVENT_CP2AP_WAKEUP_HIGH) { n_state = PM_STATE_ACTIVE; assert_ap2cp_wakeup(pm); } else if (event == PM_EVENT_LINK_ERROR) { n_state = PM_STATE_CP_FAIL; } break; default: break; } set_pm_fsm(pm, c_state, n_state, event); print_pm_fsm(pm); decide_pm_wake(pm, c_state, n_state); exit: spin_unlock_irqrestore(&pm->lock, flags); check_pm_fail(pm, c_state, n_state); }