113e920d9SHaijun Liu // SPDX-License-Identifier: GPL-2.0-only
213e920d9SHaijun Liu /*
313e920d9SHaijun Liu  * Copyright (c) 2021, MediaTek Inc.
413e920d9SHaijun Liu  * Copyright (c) 2021-2022, Intel Corporation.
513e920d9SHaijun Liu  *
613e920d9SHaijun Liu  * Authors:
713e920d9SHaijun Liu  *  Haijun Liu <haijun.liu@mediatek.com>
813e920d9SHaijun Liu  *  Eliot Lee <eliot.lee@intel.com>
913e920d9SHaijun Liu  *  Moises Veleta <moises.veleta@intel.com>
1013e920d9SHaijun Liu  *  Ricardo Martinez <ricardo.martinez@linux.intel.com>
1113e920d9SHaijun Liu  *
1213e920d9SHaijun Liu  * Contributors:
1313e920d9SHaijun Liu  *  Amir Hanania <amir.hanania@intel.com>
1413e920d9SHaijun Liu  *  Sreehari Kancharla <sreehari.kancharla@intel.com>
1513e920d9SHaijun Liu  */
1613e920d9SHaijun Liu 
1713e920d9SHaijun Liu #include <linux/bits.h>
1813e920d9SHaijun Liu #include <linux/bitfield.h>
1913e920d9SHaijun Liu #include <linux/completion.h>
2013e920d9SHaijun Liu #include <linux/device.h>
2113e920d9SHaijun Liu #include <linux/delay.h>
2213e920d9SHaijun Liu #include <linux/err.h>
2313e920d9SHaijun Liu #include <linux/gfp.h>
2413e920d9SHaijun Liu #include <linux/iopoll.h>
2513e920d9SHaijun Liu #include <linux/jiffies.h>
2613e920d9SHaijun Liu #include <linux/kernel.h>
2713e920d9SHaijun Liu #include <linux/kthread.h>
2813e920d9SHaijun Liu #include <linux/list.h>
2913e920d9SHaijun Liu #include <linux/slab.h>
3013e920d9SHaijun Liu #include <linux/spinlock.h>
3113e920d9SHaijun Liu #include <linux/string.h>
3213e920d9SHaijun Liu #include <linux/types.h>
3313e920d9SHaijun Liu #include <linux/wait.h>
3413e920d9SHaijun Liu 
3513e920d9SHaijun Liu #include "t7xx_hif_cldma.h"
3613e920d9SHaijun Liu #include "t7xx_mhccif.h"
3713e920d9SHaijun Liu #include "t7xx_modem_ops.h"
3813e920d9SHaijun Liu #include "t7xx_pci.h"
3913e920d9SHaijun Liu #include "t7xx_pcie_mac.h"
4048cc2f5eSHaijun Liu #include "t7xx_port_proxy.h"
4113e920d9SHaijun Liu #include "t7xx_reg.h"
4213e920d9SHaijun Liu #include "t7xx_state_monitor.h"
4313e920d9SHaijun Liu 
4413e920d9SHaijun Liu #define FSM_DRM_DISABLE_DELAY_MS		200
4513e920d9SHaijun Liu #define FSM_EVENT_POLL_INTERVAL_MS		20
4613e920d9SHaijun Liu #define FSM_MD_EX_REC_OK_TIMEOUT_MS		10000
4713e920d9SHaijun Liu #define FSM_MD_EX_PASS_TIMEOUT_MS		45000
4813e920d9SHaijun Liu #define FSM_CMD_TIMEOUT_MS			2000
4913e920d9SHaijun Liu 
t7xx_fsm_notifier_register(struct t7xx_modem * md,struct t7xx_fsm_notifier * notifier)5013e920d9SHaijun Liu void t7xx_fsm_notifier_register(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier)
5113e920d9SHaijun Liu {
5213e920d9SHaijun Liu 	struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
5313e920d9SHaijun Liu 	unsigned long flags;
5413e920d9SHaijun Liu 
5513e920d9SHaijun Liu 	spin_lock_irqsave(&ctl->notifier_lock, flags);
5613e920d9SHaijun Liu 	list_add_tail(&notifier->entry, &ctl->notifier_list);
5713e920d9SHaijun Liu 	spin_unlock_irqrestore(&ctl->notifier_lock, flags);
5813e920d9SHaijun Liu }
5913e920d9SHaijun Liu 
t7xx_fsm_notifier_unregister(struct t7xx_modem * md,struct t7xx_fsm_notifier * notifier)6013e920d9SHaijun Liu void t7xx_fsm_notifier_unregister(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier)
6113e920d9SHaijun Liu {
6213e920d9SHaijun Liu 	struct t7xx_fsm_notifier *notifier_cur, *notifier_next;
6313e920d9SHaijun Liu 	struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
6413e920d9SHaijun Liu 	unsigned long flags;
6513e920d9SHaijun Liu 
6613e920d9SHaijun Liu 	spin_lock_irqsave(&ctl->notifier_lock, flags);
6713e920d9SHaijun Liu 	list_for_each_entry_safe(notifier_cur, notifier_next, &ctl->notifier_list, entry) {
6813e920d9SHaijun Liu 		if (notifier_cur == notifier)
6913e920d9SHaijun Liu 			list_del(&notifier->entry);
7013e920d9SHaijun Liu 	}
7113e920d9SHaijun Liu 	spin_unlock_irqrestore(&ctl->notifier_lock, flags);
7213e920d9SHaijun Liu }
7313e920d9SHaijun Liu 
fsm_state_notify(struct t7xx_modem * md,enum md_state state)7413e920d9SHaijun Liu static void fsm_state_notify(struct t7xx_modem *md, enum md_state state)
7513e920d9SHaijun Liu {
7613e920d9SHaijun Liu 	struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
7713e920d9SHaijun Liu 	struct t7xx_fsm_notifier *notifier;
7813e920d9SHaijun Liu 	unsigned long flags;
7913e920d9SHaijun Liu 
8013e920d9SHaijun Liu 	spin_lock_irqsave(&ctl->notifier_lock, flags);
8113e920d9SHaijun Liu 	list_for_each_entry(notifier, &ctl->notifier_list, entry) {
8213e920d9SHaijun Liu 		spin_unlock_irqrestore(&ctl->notifier_lock, flags);
8313e920d9SHaijun Liu 		if (notifier->notifier_fn)
8413e920d9SHaijun Liu 			notifier->notifier_fn(state, notifier->data);
8513e920d9SHaijun Liu 
8613e920d9SHaijun Liu 		spin_lock_irqsave(&ctl->notifier_lock, flags);
8713e920d9SHaijun Liu 	}
8813e920d9SHaijun Liu 	spin_unlock_irqrestore(&ctl->notifier_lock, flags);
8913e920d9SHaijun Liu }
9013e920d9SHaijun Liu 
t7xx_fsm_broadcast_state(struct t7xx_fsm_ctl * ctl,enum md_state state)9113e920d9SHaijun Liu void t7xx_fsm_broadcast_state(struct t7xx_fsm_ctl *ctl, enum md_state state)
9213e920d9SHaijun Liu {
9313e920d9SHaijun Liu 	ctl->md_state = state;
9448cc2f5eSHaijun Liu 
9548cc2f5eSHaijun Liu 	/* Update to port first, otherwise sending message on HS2 may fail */
9648cc2f5eSHaijun Liu 	t7xx_port_proxy_md_status_notify(ctl->md->port_prox, state);
9713e920d9SHaijun Liu 	fsm_state_notify(ctl->md, state);
9813e920d9SHaijun Liu }
9913e920d9SHaijun Liu 
fsm_finish_command(struct t7xx_fsm_ctl * ctl,struct t7xx_fsm_command * cmd,int result)10013e920d9SHaijun Liu static void fsm_finish_command(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd, int result)
10113e920d9SHaijun Liu {
10213e920d9SHaijun Liu 	if (cmd->flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) {
10313e920d9SHaijun Liu 		*cmd->ret = result;
10413e920d9SHaijun Liu 		complete_all(cmd->done);
10513e920d9SHaijun Liu 	}
10613e920d9SHaijun Liu 
10713e920d9SHaijun Liu 	kfree(cmd);
10813e920d9SHaijun Liu }
10913e920d9SHaijun Liu 
fsm_del_kf_event(struct t7xx_fsm_event * event)11013e920d9SHaijun Liu static void fsm_del_kf_event(struct t7xx_fsm_event *event)
11113e920d9SHaijun Liu {
11213e920d9SHaijun Liu 	list_del(&event->entry);
11313e920d9SHaijun Liu 	kfree(event);
11413e920d9SHaijun Liu }
11513e920d9SHaijun Liu 
fsm_flush_event_cmd_qs(struct t7xx_fsm_ctl * ctl)11613e920d9SHaijun Liu static void fsm_flush_event_cmd_qs(struct t7xx_fsm_ctl *ctl)
11713e920d9SHaijun Liu {
11813e920d9SHaijun Liu 	struct device *dev = &ctl->md->t7xx_dev->pdev->dev;
11913e920d9SHaijun Liu 	struct t7xx_fsm_event *event, *evt_next;
12013e920d9SHaijun Liu 	struct t7xx_fsm_command *cmd, *cmd_next;
12113e920d9SHaijun Liu 	unsigned long flags;
12213e920d9SHaijun Liu 
12313e920d9SHaijun Liu 	spin_lock_irqsave(&ctl->command_lock, flags);
12413e920d9SHaijun Liu 	list_for_each_entry_safe(cmd, cmd_next, &ctl->command_queue, entry) {
12513e920d9SHaijun Liu 		dev_warn(dev, "Unhandled command %d\n", cmd->cmd_id);
12613e920d9SHaijun Liu 		list_del(&cmd->entry);
12713e920d9SHaijun Liu 		fsm_finish_command(ctl, cmd, -EINVAL);
12813e920d9SHaijun Liu 	}
12913e920d9SHaijun Liu 	spin_unlock_irqrestore(&ctl->command_lock, flags);
13013e920d9SHaijun Liu 
13113e920d9SHaijun Liu 	spin_lock_irqsave(&ctl->event_lock, flags);
13213e920d9SHaijun Liu 	list_for_each_entry_safe(event, evt_next, &ctl->event_queue, entry) {
13313e920d9SHaijun Liu 		dev_warn(dev, "Unhandled event %d\n", event->event_id);
13413e920d9SHaijun Liu 		fsm_del_kf_event(event);
13513e920d9SHaijun Liu 	}
13613e920d9SHaijun Liu 	spin_unlock_irqrestore(&ctl->event_lock, flags);
13713e920d9SHaijun Liu }
13813e920d9SHaijun Liu 
fsm_wait_for_event(struct t7xx_fsm_ctl * ctl,enum t7xx_fsm_event_state event_expected,enum t7xx_fsm_event_state event_ignore,int retries)13913e920d9SHaijun Liu static void fsm_wait_for_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_expected,
14013e920d9SHaijun Liu 			       enum t7xx_fsm_event_state event_ignore, int retries)
14113e920d9SHaijun Liu {
14213e920d9SHaijun Liu 	struct t7xx_fsm_event *event;
14313e920d9SHaijun Liu 	bool event_received = false;
14413e920d9SHaijun Liu 	unsigned long flags;
14513e920d9SHaijun Liu 	int cnt = 0;
14613e920d9SHaijun Liu 
14713e920d9SHaijun Liu 	while (cnt++ < retries && !event_received) {
14813e920d9SHaijun Liu 		bool sleep_required = true;
14913e920d9SHaijun Liu 
15013e920d9SHaijun Liu 		if (kthread_should_stop())
15113e920d9SHaijun Liu 			return;
15213e920d9SHaijun Liu 
15313e920d9SHaijun Liu 		spin_lock_irqsave(&ctl->event_lock, flags);
15413e920d9SHaijun Liu 		event = list_first_entry_or_null(&ctl->event_queue, struct t7xx_fsm_event, entry);
15513e920d9SHaijun Liu 		if (event) {
15613e920d9SHaijun Liu 			event_received = event->event_id == event_expected;
15713e920d9SHaijun Liu 			if (event_received || event->event_id == event_ignore) {
15813e920d9SHaijun Liu 				fsm_del_kf_event(event);
15913e920d9SHaijun Liu 				sleep_required = false;
16013e920d9SHaijun Liu 			}
16113e920d9SHaijun Liu 		}
16213e920d9SHaijun Liu 		spin_unlock_irqrestore(&ctl->event_lock, flags);
16313e920d9SHaijun Liu 
16413e920d9SHaijun Liu 		if (sleep_required)
16513e920d9SHaijun Liu 			msleep(FSM_EVENT_POLL_INTERVAL_MS);
16613e920d9SHaijun Liu 	}
16713e920d9SHaijun Liu }
16813e920d9SHaijun Liu 
fsm_routine_exception(struct t7xx_fsm_ctl * ctl,struct t7xx_fsm_command * cmd,enum t7xx_ex_reason reason)16913e920d9SHaijun Liu static void fsm_routine_exception(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd,
17013e920d9SHaijun Liu 				  enum t7xx_ex_reason reason)
17113e920d9SHaijun Liu {
17213e920d9SHaijun Liu 	struct device *dev = &ctl->md->t7xx_dev->pdev->dev;
17313e920d9SHaijun Liu 
17413e920d9SHaijun Liu 	if (ctl->curr_state != FSM_STATE_READY && ctl->curr_state != FSM_STATE_STARTING) {
17513e920d9SHaijun Liu 		if (cmd)
17613e920d9SHaijun Liu 			fsm_finish_command(ctl, cmd, -EINVAL);
17713e920d9SHaijun Liu 
17813e920d9SHaijun Liu 		return;
17913e920d9SHaijun Liu 	}
18013e920d9SHaijun Liu 
18113e920d9SHaijun Liu 	ctl->curr_state = FSM_STATE_EXCEPTION;
18213e920d9SHaijun Liu 
18313e920d9SHaijun Liu 	switch (reason) {
18413e920d9SHaijun Liu 	case EXCEPTION_HS_TIMEOUT:
18513e920d9SHaijun Liu 		dev_err(dev, "Boot Handshake failure\n");
18613e920d9SHaijun Liu 		break;
18713e920d9SHaijun Liu 
18813e920d9SHaijun Liu 	case EXCEPTION_EVENT:
18913e920d9SHaijun Liu 		dev_err(dev, "Exception event\n");
19013e920d9SHaijun Liu 		t7xx_fsm_broadcast_state(ctl, MD_STATE_EXCEPTION);
19146e8f49eSHaijun Liu 		t7xx_pci_pm_exp_detected(ctl->md->t7xx_dev);
19213e920d9SHaijun Liu 		t7xx_md_exception_handshake(ctl->md);
19313e920d9SHaijun Liu 
19413e920d9SHaijun Liu 		fsm_wait_for_event(ctl, FSM_EVENT_MD_EX_REC_OK, FSM_EVENT_MD_EX,
19513e920d9SHaijun Liu 				   FSM_MD_EX_REC_OK_TIMEOUT_MS / FSM_EVENT_POLL_INTERVAL_MS);
19613e920d9SHaijun Liu 		fsm_wait_for_event(ctl, FSM_EVENT_MD_EX_PASS, FSM_EVENT_INVALID,
19713e920d9SHaijun Liu 				   FSM_MD_EX_PASS_TIMEOUT_MS / FSM_EVENT_POLL_INTERVAL_MS);
19813e920d9SHaijun Liu 		break;
19913e920d9SHaijun Liu 
20013e920d9SHaijun Liu 	default:
20113e920d9SHaijun Liu 		dev_err(dev, "Exception %d\n", reason);
20213e920d9SHaijun Liu 		break;
20313e920d9SHaijun Liu 	}
20413e920d9SHaijun Liu 
20513e920d9SHaijun Liu 	if (cmd)
20613e920d9SHaijun Liu 		fsm_finish_command(ctl, cmd, 0);
20713e920d9SHaijun Liu }
20813e920d9SHaijun Liu 
fsm_stopped_handler(struct t7xx_fsm_ctl * ctl)20913e920d9SHaijun Liu static int fsm_stopped_handler(struct t7xx_fsm_ctl *ctl)
21013e920d9SHaijun Liu {
21113e920d9SHaijun Liu 	ctl->curr_state = FSM_STATE_STOPPED;
21213e920d9SHaijun Liu 
21313e920d9SHaijun Liu 	t7xx_fsm_broadcast_state(ctl, MD_STATE_STOPPED);
21413e920d9SHaijun Liu 	return t7xx_md_reset(ctl->md->t7xx_dev);
21513e920d9SHaijun Liu }
21613e920d9SHaijun Liu 
fsm_routine_stopped(struct t7xx_fsm_ctl * ctl,struct t7xx_fsm_command * cmd)21713e920d9SHaijun Liu static void fsm_routine_stopped(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd)
21813e920d9SHaijun Liu {
21913e920d9SHaijun Liu 	if (ctl->curr_state == FSM_STATE_STOPPED) {
22013e920d9SHaijun Liu 		fsm_finish_command(ctl, cmd, -EINVAL);
22113e920d9SHaijun Liu 		return;
22213e920d9SHaijun Liu 	}
22313e920d9SHaijun Liu 
22413e920d9SHaijun Liu 	fsm_finish_command(ctl, cmd, fsm_stopped_handler(ctl));
22513e920d9SHaijun Liu }
22613e920d9SHaijun Liu 
fsm_routine_stopping(struct t7xx_fsm_ctl * ctl,struct t7xx_fsm_command * cmd)22713e920d9SHaijun Liu static void fsm_routine_stopping(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd)
22813e920d9SHaijun Liu {
22913e920d9SHaijun Liu 	struct t7xx_pci_dev *t7xx_dev;
23013e920d9SHaijun Liu 	struct cldma_ctrl *md_ctrl;
23113e920d9SHaijun Liu 	int err;
23213e920d9SHaijun Liu 
23313e920d9SHaijun Liu 	if (ctl->curr_state == FSM_STATE_STOPPED || ctl->curr_state == FSM_STATE_STOPPING) {
23413e920d9SHaijun Liu 		fsm_finish_command(ctl, cmd, -EINVAL);
23513e920d9SHaijun Liu 		return;
23613e920d9SHaijun Liu 	}
23713e920d9SHaijun Liu 
23813e920d9SHaijun Liu 	md_ctrl = ctl->md->md_ctrl[CLDMA_ID_MD];
23913e920d9SHaijun Liu 	t7xx_dev = ctl->md->t7xx_dev;
24013e920d9SHaijun Liu 
24113e920d9SHaijun Liu 	ctl->curr_state = FSM_STATE_STOPPING;
24213e920d9SHaijun Liu 	t7xx_fsm_broadcast_state(ctl, MD_STATE_WAITING_TO_STOP);
24313e920d9SHaijun Liu 	t7xx_cldma_stop(md_ctrl);
24413e920d9SHaijun Liu 
24513e920d9SHaijun Liu 	if (!ctl->md->rgu_irq_asserted) {
24613e920d9SHaijun Liu 		t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_DRM_DISABLE_AP);
24713e920d9SHaijun Liu 		/* Wait for the DRM disable to take effect */
24813e920d9SHaijun Liu 		msleep(FSM_DRM_DISABLE_DELAY_MS);
24913e920d9SHaijun Liu 
25013e920d9SHaijun Liu 		err = t7xx_acpi_fldr_func(t7xx_dev);
25113e920d9SHaijun Liu 		if (err)
25213e920d9SHaijun Liu 			t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_DEVICE_RESET);
25313e920d9SHaijun Liu 	}
25413e920d9SHaijun Liu 
25513e920d9SHaijun Liu 	fsm_finish_command(ctl, cmd, fsm_stopped_handler(ctl));
25613e920d9SHaijun Liu }
25713e920d9SHaijun Liu 
t7xx_fsm_broadcast_ready_state(struct t7xx_fsm_ctl * ctl)25813e920d9SHaijun Liu static void t7xx_fsm_broadcast_ready_state(struct t7xx_fsm_ctl *ctl)
25913e920d9SHaijun Liu {
26013e920d9SHaijun Liu 	if (ctl->md_state != MD_STATE_WAITING_FOR_HS2)
26113e920d9SHaijun Liu 		return;
26213e920d9SHaijun Liu 
26313e920d9SHaijun Liu 	ctl->md_state = MD_STATE_READY;
26413e920d9SHaijun Liu 
26513e920d9SHaijun Liu 	fsm_state_notify(ctl->md, MD_STATE_READY);
26648cc2f5eSHaijun Liu 	t7xx_port_proxy_md_status_notify(ctl->md->port_prox, MD_STATE_READY);
26713e920d9SHaijun Liu }
26813e920d9SHaijun Liu 
fsm_routine_ready(struct t7xx_fsm_ctl * ctl)26913e920d9SHaijun Liu static void fsm_routine_ready(struct t7xx_fsm_ctl *ctl)
27013e920d9SHaijun Liu {
27113e920d9SHaijun Liu 	struct t7xx_modem *md = ctl->md;
27213e920d9SHaijun Liu 
27313e920d9SHaijun Liu 	ctl->curr_state = FSM_STATE_READY;
27413e920d9SHaijun Liu 	t7xx_fsm_broadcast_ready_state(ctl);
27513e920d9SHaijun Liu 	t7xx_md_event_notify(md, FSM_READY);
27613e920d9SHaijun Liu }
27713e920d9SHaijun Liu 
fsm_routine_starting(struct t7xx_fsm_ctl * ctl)27813e920d9SHaijun Liu static int fsm_routine_starting(struct t7xx_fsm_ctl *ctl)
27913e920d9SHaijun Liu {
28013e920d9SHaijun Liu 	struct t7xx_modem *md = ctl->md;
28113e920d9SHaijun Liu 	struct device *dev;
28213e920d9SHaijun Liu 
28313e920d9SHaijun Liu 	ctl->curr_state = FSM_STATE_STARTING;
28413e920d9SHaijun Liu 
28513e920d9SHaijun Liu 	t7xx_fsm_broadcast_state(ctl, MD_STATE_WAITING_FOR_HS1);
28613e920d9SHaijun Liu 	t7xx_md_event_notify(md, FSM_START);
28713e920d9SHaijun Liu 
288*ba2274dcSJose Ignacio Tornos Martinez 	wait_event_interruptible_timeout(ctl->async_hk_wq,
289*ba2274dcSJose Ignacio Tornos Martinez 					 (md->core_md.ready && md->core_ap.ready) ||
290*ba2274dcSJose Ignacio Tornos Martinez 					  ctl->exp_flg, HZ * 60);
29113e920d9SHaijun Liu 	dev = &md->t7xx_dev->pdev->dev;
29213e920d9SHaijun Liu 
29313e920d9SHaijun Liu 	if (ctl->exp_flg)
29413e920d9SHaijun Liu 		dev_err(dev, "MD exception is captured during handshake\n");
29513e920d9SHaijun Liu 
29613e920d9SHaijun Liu 	if (!md->core_md.ready) {
29713e920d9SHaijun Liu 		dev_err(dev, "MD handshake timeout\n");
298da45d256SHaijun Liu 		if (md->core_md.handshake_ongoing)
299da45d256SHaijun Liu 			t7xx_fsm_append_event(ctl, FSM_EVENT_MD_HS2_EXIT, NULL, 0);
300da45d256SHaijun Liu 
30113e920d9SHaijun Liu 		fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT);
30213e920d9SHaijun Liu 		return -ETIMEDOUT;
303*ba2274dcSJose Ignacio Tornos Martinez 	} else if (!md->core_ap.ready) {
304*ba2274dcSJose Ignacio Tornos Martinez 		dev_err(dev, "AP handshake timeout\n");
305*ba2274dcSJose Ignacio Tornos Martinez 		if (md->core_ap.handshake_ongoing)
306*ba2274dcSJose Ignacio Tornos Martinez 			t7xx_fsm_append_event(ctl, FSM_EVENT_AP_HS2_EXIT, NULL, 0);
307*ba2274dcSJose Ignacio Tornos Martinez 
308*ba2274dcSJose Ignacio Tornos Martinez 		fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT);
309*ba2274dcSJose Ignacio Tornos Martinez 		return -ETIMEDOUT;
31013e920d9SHaijun Liu 	}
31113e920d9SHaijun Liu 
31246e8f49eSHaijun Liu 	t7xx_pci_pm_init_late(md->t7xx_dev);
31313e920d9SHaijun Liu 	fsm_routine_ready(ctl);
31413e920d9SHaijun Liu 	return 0;
31513e920d9SHaijun Liu }
31613e920d9SHaijun Liu 
fsm_routine_start(struct t7xx_fsm_ctl * ctl,struct t7xx_fsm_command * cmd)31713e920d9SHaijun Liu static void fsm_routine_start(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd)
31813e920d9SHaijun Liu {
31913e920d9SHaijun Liu 	struct t7xx_modem *md = ctl->md;
32013e920d9SHaijun Liu 	u32 dev_status;
32113e920d9SHaijun Liu 	int ret;
32213e920d9SHaijun Liu 
32313e920d9SHaijun Liu 	if (!md)
32413e920d9SHaijun Liu 		return;
32513e920d9SHaijun Liu 
32613e920d9SHaijun Liu 	if (ctl->curr_state != FSM_STATE_INIT && ctl->curr_state != FSM_STATE_PRE_START &&
32713e920d9SHaijun Liu 	    ctl->curr_state != FSM_STATE_STOPPED) {
32813e920d9SHaijun Liu 		fsm_finish_command(ctl, cmd, -EINVAL);
32913e920d9SHaijun Liu 		return;
33013e920d9SHaijun Liu 	}
33113e920d9SHaijun Liu 
33213e920d9SHaijun Liu 	ctl->curr_state = FSM_STATE_PRE_START;
33313e920d9SHaijun Liu 	t7xx_md_event_notify(md, FSM_PRE_START);
33413e920d9SHaijun Liu 
33513e920d9SHaijun Liu 	ret = read_poll_timeout(ioread32, dev_status,
33613e920d9SHaijun Liu 				(dev_status & MISC_STAGE_MASK) == LINUX_STAGE, 20000, 2000000,
33713e920d9SHaijun Liu 				false, IREG_BASE(md->t7xx_dev) + T7XX_PCIE_MISC_DEV_STATUS);
33813e920d9SHaijun Liu 	if (ret) {
33913e920d9SHaijun Liu 		struct device *dev = &md->t7xx_dev->pdev->dev;
34013e920d9SHaijun Liu 
34113e920d9SHaijun Liu 		fsm_finish_command(ctl, cmd, -ETIMEDOUT);
34213e920d9SHaijun Liu 		dev_err(dev, "Invalid device status 0x%lx\n", dev_status & MISC_STAGE_MASK);
34313e920d9SHaijun Liu 		return;
34413e920d9SHaijun Liu 	}
34513e920d9SHaijun Liu 
346*ba2274dcSJose Ignacio Tornos Martinez 	t7xx_cldma_hif_hw_init(md->md_ctrl[CLDMA_ID_AP]);
34713e920d9SHaijun Liu 	t7xx_cldma_hif_hw_init(md->md_ctrl[CLDMA_ID_MD]);
34813e920d9SHaijun Liu 	fsm_finish_command(ctl, cmd, fsm_routine_starting(ctl));
34913e920d9SHaijun Liu }
35013e920d9SHaijun Liu 
fsm_main_thread(void * data)35113e920d9SHaijun Liu static int fsm_main_thread(void *data)
35213e920d9SHaijun Liu {
35313e920d9SHaijun Liu 	struct t7xx_fsm_ctl *ctl = data;
35413e920d9SHaijun Liu 	struct t7xx_fsm_command *cmd;
35513e920d9SHaijun Liu 	unsigned long flags;
35613e920d9SHaijun Liu 
35713e920d9SHaijun Liu 	while (!kthread_should_stop()) {
35813e920d9SHaijun Liu 		if (wait_event_interruptible(ctl->command_wq, !list_empty(&ctl->command_queue) ||
35913e920d9SHaijun Liu 					     kthread_should_stop()))
36013e920d9SHaijun Liu 			continue;
36113e920d9SHaijun Liu 
36213e920d9SHaijun Liu 		if (kthread_should_stop())
36313e920d9SHaijun Liu 			break;
36413e920d9SHaijun Liu 
36513e920d9SHaijun Liu 		spin_lock_irqsave(&ctl->command_lock, flags);
36613e920d9SHaijun Liu 		cmd = list_first_entry(&ctl->command_queue, struct t7xx_fsm_command, entry);
36713e920d9SHaijun Liu 		list_del(&cmd->entry);
36813e920d9SHaijun Liu 		spin_unlock_irqrestore(&ctl->command_lock, flags);
36913e920d9SHaijun Liu 
37013e920d9SHaijun Liu 		switch (cmd->cmd_id) {
37113e920d9SHaijun Liu 		case FSM_CMD_START:
37213e920d9SHaijun Liu 			fsm_routine_start(ctl, cmd);
37313e920d9SHaijun Liu 			break;
37413e920d9SHaijun Liu 
37513e920d9SHaijun Liu 		case FSM_CMD_EXCEPTION:
37613e920d9SHaijun Liu 			fsm_routine_exception(ctl, cmd, FIELD_GET(FSM_CMD_EX_REASON, cmd->flag));
37713e920d9SHaijun Liu 			break;
37813e920d9SHaijun Liu 
37913e920d9SHaijun Liu 		case FSM_CMD_PRE_STOP:
38013e920d9SHaijun Liu 			fsm_routine_stopping(ctl, cmd);
38113e920d9SHaijun Liu 			break;
38213e920d9SHaijun Liu 
38313e920d9SHaijun Liu 		case FSM_CMD_STOP:
38413e920d9SHaijun Liu 			fsm_routine_stopped(ctl, cmd);
38513e920d9SHaijun Liu 			break;
38613e920d9SHaijun Liu 
38713e920d9SHaijun Liu 		default:
38813e920d9SHaijun Liu 			fsm_finish_command(ctl, cmd, -EINVAL);
38913e920d9SHaijun Liu 			fsm_flush_event_cmd_qs(ctl);
39013e920d9SHaijun Liu 			break;
39113e920d9SHaijun Liu 		}
39213e920d9SHaijun Liu 	}
39313e920d9SHaijun Liu 
39413e920d9SHaijun Liu 	return 0;
39513e920d9SHaijun Liu }
39613e920d9SHaijun Liu 
t7xx_fsm_append_cmd(struct t7xx_fsm_ctl * ctl,enum t7xx_fsm_cmd_state cmd_id,unsigned int flag)39713e920d9SHaijun Liu int t7xx_fsm_append_cmd(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_cmd_state cmd_id, unsigned int flag)
39813e920d9SHaijun Liu {
39913e920d9SHaijun Liu 	DECLARE_COMPLETION_ONSTACK(done);
40013e920d9SHaijun Liu 	struct t7xx_fsm_command *cmd;
40113e920d9SHaijun Liu 	unsigned long flags;
40213e920d9SHaijun Liu 	int ret;
40313e920d9SHaijun Liu 
40413e920d9SHaijun Liu 	cmd = kzalloc(sizeof(*cmd), flag & FSM_CMD_FLAG_IN_INTERRUPT ? GFP_ATOMIC : GFP_KERNEL);
40513e920d9SHaijun Liu 	if (!cmd)
40613e920d9SHaijun Liu 		return -ENOMEM;
40713e920d9SHaijun Liu 
40813e920d9SHaijun Liu 	INIT_LIST_HEAD(&cmd->entry);
40913e920d9SHaijun Liu 	cmd->cmd_id = cmd_id;
41013e920d9SHaijun Liu 	cmd->flag = flag;
41113e920d9SHaijun Liu 	if (flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) {
41213e920d9SHaijun Liu 		cmd->done = &done;
41313e920d9SHaijun Liu 		cmd->ret = &ret;
41413e920d9SHaijun Liu 	}
41513e920d9SHaijun Liu 
41613e920d9SHaijun Liu 	spin_lock_irqsave(&ctl->command_lock, flags);
41713e920d9SHaijun Liu 	list_add_tail(&cmd->entry, &ctl->command_queue);
41813e920d9SHaijun Liu 	spin_unlock_irqrestore(&ctl->command_lock, flags);
41913e920d9SHaijun Liu 
42013e920d9SHaijun Liu 	wake_up(&ctl->command_wq);
42113e920d9SHaijun Liu 
42213e920d9SHaijun Liu 	if (flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) {
42313e920d9SHaijun Liu 		unsigned long wait_ret;
42413e920d9SHaijun Liu 
42513e920d9SHaijun Liu 		wait_ret = wait_for_completion_timeout(&done,
42613e920d9SHaijun Liu 						       msecs_to_jiffies(FSM_CMD_TIMEOUT_MS));
42713e920d9SHaijun Liu 		if (!wait_ret)
42813e920d9SHaijun Liu 			return -ETIMEDOUT;
42913e920d9SHaijun Liu 
43013e920d9SHaijun Liu 		return ret;
43113e920d9SHaijun Liu 	}
43213e920d9SHaijun Liu 
43313e920d9SHaijun Liu 	return 0;
43413e920d9SHaijun Liu }
43513e920d9SHaijun Liu 
t7xx_fsm_append_event(struct t7xx_fsm_ctl * ctl,enum t7xx_fsm_event_state event_id,unsigned char * data,unsigned int length)43613e920d9SHaijun Liu int t7xx_fsm_append_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_id,
43713e920d9SHaijun Liu 			  unsigned char *data, unsigned int length)
43813e920d9SHaijun Liu {
43913e920d9SHaijun Liu 	struct device *dev = &ctl->md->t7xx_dev->pdev->dev;
44013e920d9SHaijun Liu 	struct t7xx_fsm_event *event;
44113e920d9SHaijun Liu 	unsigned long flags;
44213e920d9SHaijun Liu 
44313e920d9SHaijun Liu 	if (event_id <= FSM_EVENT_INVALID || event_id >= FSM_EVENT_MAX) {
44413e920d9SHaijun Liu 		dev_err(dev, "Invalid event %d\n", event_id);
44513e920d9SHaijun Liu 		return -EINVAL;
44613e920d9SHaijun Liu 	}
44713e920d9SHaijun Liu 
44813e920d9SHaijun Liu 	event = kmalloc(sizeof(*event) + length, in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
44913e920d9SHaijun Liu 	if (!event)
45013e920d9SHaijun Liu 		return -ENOMEM;
45113e920d9SHaijun Liu 
45213e920d9SHaijun Liu 	INIT_LIST_HEAD(&event->entry);
45313e920d9SHaijun Liu 	event->event_id = event_id;
45413e920d9SHaijun Liu 	event->length = length;
45513e920d9SHaijun Liu 
45613e920d9SHaijun Liu 	if (data && length)
45713e920d9SHaijun Liu 		memcpy(event->data, data, length);
45813e920d9SHaijun Liu 
45913e920d9SHaijun Liu 	spin_lock_irqsave(&ctl->event_lock, flags);
46013e920d9SHaijun Liu 	list_add_tail(&event->entry, &ctl->event_queue);
46113e920d9SHaijun Liu 	spin_unlock_irqrestore(&ctl->event_lock, flags);
46213e920d9SHaijun Liu 
46313e920d9SHaijun Liu 	wake_up_all(&ctl->event_wq);
46413e920d9SHaijun Liu 	return 0;
46513e920d9SHaijun Liu }
46613e920d9SHaijun Liu 
t7xx_fsm_clr_event(struct t7xx_fsm_ctl * ctl,enum t7xx_fsm_event_state event_id)46713e920d9SHaijun Liu void t7xx_fsm_clr_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_id)
46813e920d9SHaijun Liu {
46913e920d9SHaijun Liu 	struct t7xx_fsm_event *event, *evt_next;
47013e920d9SHaijun Liu 	unsigned long flags;
47113e920d9SHaijun Liu 
47213e920d9SHaijun Liu 	spin_lock_irqsave(&ctl->event_lock, flags);
47313e920d9SHaijun Liu 	list_for_each_entry_safe(event, evt_next, &ctl->event_queue, entry) {
47413e920d9SHaijun Liu 		if (event->event_id == event_id)
47513e920d9SHaijun Liu 			fsm_del_kf_event(event);
47613e920d9SHaijun Liu 	}
47713e920d9SHaijun Liu 	spin_unlock_irqrestore(&ctl->event_lock, flags);
47813e920d9SHaijun Liu }
47913e920d9SHaijun Liu 
t7xx_fsm_get_md_state(struct t7xx_fsm_ctl * ctl)48013e920d9SHaijun Liu enum md_state t7xx_fsm_get_md_state(struct t7xx_fsm_ctl *ctl)
48113e920d9SHaijun Liu {
48213e920d9SHaijun Liu 	if (ctl)
48313e920d9SHaijun Liu 		return ctl->md_state;
48413e920d9SHaijun Liu 
48513e920d9SHaijun Liu 	return MD_STATE_INVALID;
48613e920d9SHaijun Liu }
48713e920d9SHaijun Liu 
t7xx_fsm_get_ctl_state(struct t7xx_fsm_ctl * ctl)48813e920d9SHaijun Liu unsigned int t7xx_fsm_get_ctl_state(struct t7xx_fsm_ctl *ctl)
48913e920d9SHaijun Liu {
49013e920d9SHaijun Liu 	if (ctl)
49113e920d9SHaijun Liu 		return ctl->curr_state;
49213e920d9SHaijun Liu 
49313e920d9SHaijun Liu 	return FSM_STATE_STOPPED;
49413e920d9SHaijun Liu }
49513e920d9SHaijun Liu 
t7xx_fsm_recv_md_intr(struct t7xx_fsm_ctl * ctl,enum t7xx_md_irq_type type)49613e920d9SHaijun Liu int t7xx_fsm_recv_md_intr(struct t7xx_fsm_ctl *ctl, enum t7xx_md_irq_type type)
49713e920d9SHaijun Liu {
49813e920d9SHaijun Liu 	unsigned int cmd_flags = FSM_CMD_FLAG_IN_INTERRUPT;
49913e920d9SHaijun Liu 
50013e920d9SHaijun Liu 	if (type == MD_IRQ_PORT_ENUM) {
50113e920d9SHaijun Liu 		return t7xx_fsm_append_cmd(ctl, FSM_CMD_START, cmd_flags);
50213e920d9SHaijun Liu 	} else if (type == MD_IRQ_CCIF_EX) {
50313e920d9SHaijun Liu 		ctl->exp_flg = true;
50413e920d9SHaijun Liu 		wake_up(&ctl->async_hk_wq);
50513e920d9SHaijun Liu 		cmd_flags |= FIELD_PREP(FSM_CMD_EX_REASON, EXCEPTION_EVENT);
50613e920d9SHaijun Liu 		return t7xx_fsm_append_cmd(ctl, FSM_CMD_EXCEPTION, cmd_flags);
50713e920d9SHaijun Liu 	}
50813e920d9SHaijun Liu 
50913e920d9SHaijun Liu 	return -EINVAL;
51013e920d9SHaijun Liu }
51113e920d9SHaijun Liu 
t7xx_fsm_reset(struct t7xx_modem * md)51213e920d9SHaijun Liu void t7xx_fsm_reset(struct t7xx_modem *md)
51313e920d9SHaijun Liu {
51413e920d9SHaijun Liu 	struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
51513e920d9SHaijun Liu 
51613e920d9SHaijun Liu 	fsm_flush_event_cmd_qs(ctl);
51713e920d9SHaijun Liu 	ctl->curr_state = FSM_STATE_STOPPED;
51813e920d9SHaijun Liu 	ctl->exp_flg = false;
51913e920d9SHaijun Liu }
52013e920d9SHaijun Liu 
t7xx_fsm_init(struct t7xx_modem * md)52113e920d9SHaijun Liu int t7xx_fsm_init(struct t7xx_modem *md)
52213e920d9SHaijun Liu {
52313e920d9SHaijun Liu 	struct device *dev = &md->t7xx_dev->pdev->dev;
52413e920d9SHaijun Liu 	struct t7xx_fsm_ctl *ctl;
52513e920d9SHaijun Liu 
52613e920d9SHaijun Liu 	ctl = devm_kzalloc(dev, sizeof(*ctl), GFP_KERNEL);
52713e920d9SHaijun Liu 	if (!ctl)
52813e920d9SHaijun Liu 		return -ENOMEM;
52913e920d9SHaijun Liu 
53013e920d9SHaijun Liu 	md->fsm_ctl = ctl;
53113e920d9SHaijun Liu 	ctl->md = md;
53213e920d9SHaijun Liu 	ctl->curr_state = FSM_STATE_INIT;
53313e920d9SHaijun Liu 	INIT_LIST_HEAD(&ctl->command_queue);
53413e920d9SHaijun Liu 	INIT_LIST_HEAD(&ctl->event_queue);
53513e920d9SHaijun Liu 	init_waitqueue_head(&ctl->async_hk_wq);
53613e920d9SHaijun Liu 	init_waitqueue_head(&ctl->event_wq);
53713e920d9SHaijun Liu 	INIT_LIST_HEAD(&ctl->notifier_list);
53813e920d9SHaijun Liu 	init_waitqueue_head(&ctl->command_wq);
53913e920d9SHaijun Liu 	spin_lock_init(&ctl->event_lock);
54013e920d9SHaijun Liu 	spin_lock_init(&ctl->command_lock);
54113e920d9SHaijun Liu 	ctl->exp_flg = false;
54213e920d9SHaijun Liu 	spin_lock_init(&ctl->notifier_lock);
54313e920d9SHaijun Liu 
54413e920d9SHaijun Liu 	ctl->fsm_thread = kthread_run(fsm_main_thread, ctl, "t7xx_fsm");
54513e920d9SHaijun Liu 	return PTR_ERR_OR_ZERO(ctl->fsm_thread);
54613e920d9SHaijun Liu }
54713e920d9SHaijun Liu 
t7xx_fsm_uninit(struct t7xx_modem * md)54813e920d9SHaijun Liu void t7xx_fsm_uninit(struct t7xx_modem *md)
54913e920d9SHaijun Liu {
55013e920d9SHaijun Liu 	struct t7xx_fsm_ctl *ctl = md->fsm_ctl;
55113e920d9SHaijun Liu 
55213e920d9SHaijun Liu 	if (!ctl)
55313e920d9SHaijun Liu 		return;
55413e920d9SHaijun Liu 
55513e920d9SHaijun Liu 	if (ctl->fsm_thread)
55613e920d9SHaijun Liu 		kthread_stop(ctl->fsm_thread);
55713e920d9SHaijun Liu 
55813e920d9SHaijun Liu 	fsm_flush_event_cmd_qs(ctl);
55913e920d9SHaijun Liu }
560