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);
}
Esempio n. 2
0
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);
}