static bool check_link_status(struct mem_link_device *mld) { if (mipi_lli_get_link_status() != LLI_MOUNTED) return false; if (gpio_get_value(mld->gpio_cp_status) == 0) return false; return true; }
/** @brief interrupt handler for a wakeup interrupt 1) Reads the interrupt value\n 2) Performs interrupt handling\n @param irq the IRQ number @param data the pointer to a data */ static irqreturn_t ap_wakeup_interrupt(int irq, void *data) { struct mem_link_device *mld = (struct mem_link_device *)data; int ap_wakeup = gpio_get_value(mld->gpio_ap_wakeup); int cp_wakeup = gpio_get_value(mld->gpio_cp_wakeup); int cpu = raw_smp_processor_id(); change_irq_type(irq, ap_wakeup); if (work_pending(&mld->cp_sleep_dwork.work)) cancel_delayed_work(&mld->cp_sleep_dwork); if (ap_wakeup) { mld->last_cp2ap_intr = cpu_clock(cpu); if (!cp_wakeup) gpio_set_value(mld->gpio_cp_wakeup, 1); if (!wake_lock_active(&mld->ap_wlock)) wake_lock(&mld->ap_wlock); if (mipi_lli_get_link_status() == LLI_UNMOUNTED) mipi_lli_set_link_status(LLI_WAITFORMOUNT); if (!mipi_lli_suspended()) gpio_set_value(mld->gpio_ap_status, 1); } else { if (wake_lock_active(&mld->ap_wlock)) wake_unlock(&mld->ap_wlock); if (mipi_lli_get_link_status() & LLI_WAITFORMOUNT) mipi_lli_set_link_status(LLI_UNMOUNTED); queue_delayed_work(system_nrt_wq, &mld->cp_sleep_dwork, msecs_to_jiffies(sleep_timeout)); } print_pm_status(mld); return IRQ_HANDLED; }
static bool check_link_status(struct mem_link_device *mld) { struct link_device *ld = &mld->link_dev; struct modem_ctl *mc = ld->mc; struct modem_link_pm *pm = &ld->pm; if (gpio_get_value(mld->gpio_cp_status) == 0) return false; if (mipi_lli_get_link_status() != LLI_MOUNTED) return false; if (cp_online(mc)) return pm->link_active ? pm->link_active(pm) : true; return true; }
static bool lli_link_unmounted(struct link_device *ld) { return (mipi_lli_get_link_status() == LLI_UNMOUNTED); }
static void run_pm_fsm(struct modem_link_pm *pm, enum pm_event event) { struct link_device *ld = container_of(pm, struct 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; } #ifdef DEBUG_MODEM_IF 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); mif_info("%s: state: ap2cp_status_pin_done\n", __func__); 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); #if 0 if (mipi_lli_get_link_status() == LLI_MOUNTED) { n_state = PM_STATE_AP_FREE; schedule_cp_free(pm); } #endif } else if (event == PM_EVENT_CP2AP_STATUS_LOW) { #ifdef REPORT_CRASHDMP n_state = PM_STATE_CP_FAIL; #else n_state = PM_STATE_AP_FREE; schedule_cp_free(pm); #endif } 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); #ifdef DEBUG_MODEM_IF print_pm_fsm(pm); #endif decide_pm_wake(pm, c_state, n_state); exit: spin_unlock_irqrestore(&pm->lock, flags); check_pm_fail(pm, c_state, n_state); }