| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2021, MediaTek Inc. |
| * Copyright (c) 2021-2022, Intel Corporation. |
| * |
| * Authors: |
| * Haijun Liu <haijun.liu@mediatek.com> |
| * Eliot Lee <eliot.lee@intel.com> |
| * Moises Veleta <moises.veleta@intel.com> |
| * Ricardo Martinez <ricardo.martinez@linux.intel.com> |
| * |
| * Contributors: |
| * Amir Hanania <amir.hanania@intel.com> |
| * Sreehari Kancharla <sreehari.kancharla@intel.com> |
| */ |
| |
| #include <linux/bits.h> |
| #include <linux/bitfield.h> |
| #include <linux/completion.h> |
| #include <linux/device.h> |
| #include <linux/delay.h> |
| #include <linux/err.h> |
| #include <linux/gfp.h> |
| #include <linux/iopoll.h> |
| #include <linux/jiffies.h> |
| #include <linux/kernel.h> |
| #include <linux/kthread.h> |
| #include <linux/list.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/string.h> |
| #include <linux/types.h> |
| #include <linux/wait.h> |
| |
| #include "t7xx_hif_cldma.h" |
| #include "t7xx_mhccif.h" |
| #include "t7xx_modem_ops.h" |
| #include "t7xx_pci.h" |
| #include "t7xx_pcie_mac.h" |
| #include "t7xx_port_proxy.h" |
| #include "t7xx_reg.h" |
| #include "t7xx_state_monitor.h" |
| |
| #define FSM_DRM_DISABLE_DELAY_MS 200 |
| #define FSM_EVENT_POLL_INTERVAL_MS 20 |
| #define FSM_MD_EX_REC_OK_TIMEOUT_MS 10000 |
| #define FSM_MD_EX_PASS_TIMEOUT_MS 45000 |
| #define FSM_CMD_TIMEOUT_MS 2000 |
| |
| #define wait_for_expected_dev_stage(status) \ |
| read_poll_timeout(ioread32, status, \ |
| ((status & MISC_STAGE_MASK) == T7XX_DEV_STAGE_LINUX) || \ |
| ((status & MISC_STAGE_MASK) == T7XX_DEV_STAGE_LK), 100000, \ |
| 20000000, false, IREG_BASE(md->t7xx_dev) + \ |
| T7XX_PCIE_MISC_DEV_STATUS) |
| |
| void t7xx_fsm_notifier_register(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier) |
| { |
| struct t7xx_fsm_ctl *ctl = md->fsm_ctl; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&ctl->notifier_lock, flags); |
| list_add_tail(¬ifier->entry, &ctl->notifier_list); |
| spin_unlock_irqrestore(&ctl->notifier_lock, flags); |
| } |
| |
| void t7xx_fsm_notifier_unregister(struct t7xx_modem *md, struct t7xx_fsm_notifier *notifier) |
| { |
| struct t7xx_fsm_notifier *notifier_cur, *notifier_next; |
| struct t7xx_fsm_ctl *ctl = md->fsm_ctl; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&ctl->notifier_lock, flags); |
| list_for_each_entry_safe(notifier_cur, notifier_next, &ctl->notifier_list, entry) { |
| if (notifier_cur == notifier) |
| list_del(¬ifier->entry); |
| } |
| spin_unlock_irqrestore(&ctl->notifier_lock, flags); |
| } |
| |
| static void fsm_state_notify(struct t7xx_modem *md, enum md_state state) |
| { |
| struct t7xx_fsm_ctl *ctl = md->fsm_ctl; |
| struct t7xx_fsm_notifier *notifier; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&ctl->notifier_lock, flags); |
| list_for_each_entry(notifier, &ctl->notifier_list, entry) { |
| spin_unlock_irqrestore(&ctl->notifier_lock, flags); |
| if (notifier->notifier_fn) |
| notifier->notifier_fn(state, notifier->data); |
| |
| spin_lock_irqsave(&ctl->notifier_lock, flags); |
| } |
| spin_unlock_irqrestore(&ctl->notifier_lock, flags); |
| } |
| |
| void t7xx_fsm_broadcast_state(struct t7xx_fsm_ctl *ctl, enum md_state state) |
| { |
| ctl->md_state = state; |
| |
| /* Update to port first, otherwise sending message on HS2 may fail */ |
| t7xx_port_proxy_md_status_notify(ctl->md->port_prox, state); |
| fsm_state_notify(ctl->md, state); |
| } |
| |
| static void fsm_finish_command(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd, int result) |
| { |
| if (cmd->flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) { |
| *cmd->ret = result; |
| complete_all(cmd->done); |
| } |
| |
| kfree(cmd); |
| } |
| |
| static void fsm_del_kf_event(struct t7xx_fsm_event *event) |
| { |
| list_del(&event->entry); |
| kfree(event); |
| } |
| |
| static void fsm_flush_event_cmd_qs(struct t7xx_fsm_ctl *ctl) |
| { |
| struct device *dev = &ctl->md->t7xx_dev->pdev->dev; |
| struct t7xx_fsm_event *event, *evt_next; |
| struct t7xx_fsm_command *cmd, *cmd_next; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&ctl->command_lock, flags); |
| list_for_each_entry_safe(cmd, cmd_next, &ctl->command_queue, entry) { |
| dev_warn(dev, "Unhandled command %d\n", cmd->cmd_id); |
| list_del(&cmd->entry); |
| fsm_finish_command(ctl, cmd, -EINVAL); |
| } |
| spin_unlock_irqrestore(&ctl->command_lock, flags); |
| |
| spin_lock_irqsave(&ctl->event_lock, flags); |
| list_for_each_entry_safe(event, evt_next, &ctl->event_queue, entry) { |
| dev_warn(dev, "Unhandled event %d\n", event->event_id); |
| fsm_del_kf_event(event); |
| } |
| spin_unlock_irqrestore(&ctl->event_lock, flags); |
| } |
| |
| static void 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) |
| { |
| struct t7xx_fsm_event *event; |
| bool event_received = false; |
| unsigned long flags; |
| int cnt = 0; |
| |
| while (cnt++ < retries && !event_received) { |
| bool sleep_required = true; |
| |
| if (kthread_should_stop()) |
| return; |
| |
| spin_lock_irqsave(&ctl->event_lock, flags); |
| event = list_first_entry_or_null(&ctl->event_queue, struct t7xx_fsm_event, entry); |
| if (event) { |
| event_received = event->event_id == event_expected; |
| if (event_received || event->event_id == event_ignore) { |
| fsm_del_kf_event(event); |
| sleep_required = false; |
| } |
| } |
| spin_unlock_irqrestore(&ctl->event_lock, flags); |
| |
| if (sleep_required) |
| msleep(FSM_EVENT_POLL_INTERVAL_MS); |
| } |
| } |
| |
| static void fsm_routine_exception(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd, |
| enum t7xx_ex_reason reason) |
| { |
| struct device *dev = &ctl->md->t7xx_dev->pdev->dev; |
| |
| if (ctl->curr_state != FSM_STATE_READY && ctl->curr_state != FSM_STATE_STARTING) { |
| if (cmd) |
| fsm_finish_command(ctl, cmd, -EINVAL); |
| |
| return; |
| } |
| |
| ctl->curr_state = FSM_STATE_EXCEPTION; |
| |
| switch (reason) { |
| case EXCEPTION_HS_TIMEOUT: |
| dev_err(dev, "Boot Handshake failure\n"); |
| break; |
| |
| case EXCEPTION_EVENT: |
| dev_err(dev, "Exception event\n"); |
| t7xx_fsm_broadcast_state(ctl, MD_STATE_EXCEPTION); |
| t7xx_pci_pm_exp_detected(ctl->md->t7xx_dev); |
| t7xx_md_exception_handshake(ctl->md); |
| |
| fsm_wait_for_event(ctl, FSM_EVENT_MD_EX_REC_OK, FSM_EVENT_MD_EX, |
| FSM_MD_EX_REC_OK_TIMEOUT_MS / FSM_EVENT_POLL_INTERVAL_MS); |
| fsm_wait_for_event(ctl, FSM_EVENT_MD_EX_PASS, FSM_EVENT_INVALID, |
| FSM_MD_EX_PASS_TIMEOUT_MS / FSM_EVENT_POLL_INTERVAL_MS); |
| break; |
| |
| default: |
| dev_err(dev, "Exception %d\n", reason); |
| break; |
| } |
| |
| if (cmd) |
| fsm_finish_command(ctl, cmd, 0); |
| } |
| |
| static void t7xx_lk_stage_event_handling(struct t7xx_fsm_ctl *ctl, unsigned int status) |
| { |
| struct t7xx_modem *md = ctl->md; |
| struct cldma_ctrl *md_ctrl; |
| enum lk_event_id lk_event; |
| struct device *dev; |
| struct t7xx_port *port; |
| |
| dev = &md->t7xx_dev->pdev->dev; |
| lk_event = FIELD_GET(MISC_LK_EVENT_MASK, status); |
| switch (lk_event) { |
| case LK_EVENT_NORMAL: |
| case LK_EVENT_RESET: |
| break; |
| |
| case LK_EVENT_CREATE_PD_PORT: |
| case LK_EVENT_CREATE_POST_DL_PORT: |
| md_ctrl = md->md_ctrl[CLDMA_ID_AP]; |
| t7xx_cldma_hif_hw_init(md_ctrl); |
| t7xx_cldma_stop(md_ctrl); |
| t7xx_cldma_switch_cfg(md_ctrl, CLDMA_DEDICATED_Q_CFG); |
| |
| port = &ctl->md->port_prox->ports[0]; |
| port->port_conf->ops->enable_chl(port); |
| |
| t7xx_cldma_start(md_ctrl); |
| |
| if (lk_event == LK_EVENT_CREATE_POST_DL_PORT) |
| t7xx_mode_update(md->t7xx_dev, T7XX_FASTBOOT_DOWNLOAD); |
| else |
| t7xx_mode_update(md->t7xx_dev, T7XX_FASTBOOT_DUMP); |
| break; |
| |
| default: |
| dev_err(dev, "Invalid LK event %d\n", lk_event); |
| break; |
| } |
| } |
| |
| static int fsm_stopped_handler(struct t7xx_fsm_ctl *ctl) |
| { |
| enum t7xx_mode mode; |
| |
| ctl->curr_state = FSM_STATE_STOPPED; |
| |
| mode = READ_ONCE(ctl->md->t7xx_dev->mode); |
| if (mode == T7XX_FASTBOOT_DOWNLOAD || mode == T7XX_FASTBOOT_DUMP) |
| return 0; |
| |
| t7xx_fsm_broadcast_state(ctl, MD_STATE_STOPPED); |
| return t7xx_md_reset(ctl->md->t7xx_dev); |
| } |
| |
| static void fsm_routine_stopped(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd) |
| { |
| if (ctl->curr_state == FSM_STATE_STOPPED) { |
| fsm_finish_command(ctl, cmd, -EINVAL); |
| return; |
| } |
| |
| fsm_finish_command(ctl, cmd, fsm_stopped_handler(ctl)); |
| } |
| |
| static void fsm_routine_stopping(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd) |
| { |
| struct cldma_ctrl *md_ctrl = ctl->md->md_ctrl[CLDMA_ID_MD]; |
| struct t7xx_pci_dev *t7xx_dev = ctl->md->t7xx_dev; |
| |
| if (ctl->curr_state == FSM_STATE_STOPPED || ctl->curr_state == FSM_STATE_STOPPING) { |
| fsm_finish_command(ctl, cmd, -EINVAL); |
| return; |
| } |
| |
| ctl->curr_state = FSM_STATE_STOPPING; |
| t7xx_fsm_broadcast_state(ctl, MD_STATE_WAITING_TO_STOP); |
| t7xx_cldma_stop(md_ctrl); |
| |
| t7xx_mhccif_h2d_swint_trigger(t7xx_dev, H2D_CH_DRM_DISABLE_AP); |
| /* Wait for the DRM disable to take effect */ |
| msleep(FSM_DRM_DISABLE_DELAY_MS); |
| |
| fsm_finish_command(ctl, cmd, fsm_stopped_handler(ctl)); |
| } |
| |
| static void t7xx_fsm_broadcast_ready_state(struct t7xx_fsm_ctl *ctl) |
| { |
| if (ctl->md_state != MD_STATE_WAITING_FOR_HS2) |
| return; |
| |
| ctl->md_state = MD_STATE_READY; |
| |
| fsm_state_notify(ctl->md, MD_STATE_READY); |
| t7xx_port_proxy_md_status_notify(ctl->md->port_prox, MD_STATE_READY); |
| } |
| |
| static void fsm_routine_ready(struct t7xx_fsm_ctl *ctl) |
| { |
| struct t7xx_modem *md = ctl->md; |
| |
| ctl->curr_state = FSM_STATE_READY; |
| t7xx_fsm_broadcast_ready_state(ctl); |
| t7xx_mode_update(md->t7xx_dev, T7XX_READY); |
| t7xx_md_event_notify(md, FSM_READY); |
| } |
| |
| static int fsm_routine_starting(struct t7xx_fsm_ctl *ctl) |
| { |
| struct t7xx_modem *md = ctl->md; |
| struct device *dev; |
| |
| ctl->curr_state = FSM_STATE_STARTING; |
| |
| t7xx_fsm_broadcast_state(ctl, MD_STATE_WAITING_FOR_HS1); |
| t7xx_md_event_notify(md, FSM_START); |
| |
| wait_event_interruptible_timeout(ctl->async_hk_wq, |
| (md->core_md.ready && md->core_ap.ready) || |
| ctl->exp_flg, HZ * 60); |
| dev = &md->t7xx_dev->pdev->dev; |
| |
| if (ctl->exp_flg) |
| dev_err(dev, "MD exception is captured during handshake\n"); |
| |
| if (!md->core_md.ready) { |
| dev_err(dev, "MD handshake timeout\n"); |
| if (md->core_md.handshake_ongoing) |
| t7xx_fsm_append_event(ctl, FSM_EVENT_MD_HS2_EXIT, NULL, 0); |
| |
| fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT); |
| return -ETIMEDOUT; |
| } else if (!md->core_ap.ready) { |
| dev_err(dev, "AP handshake timeout\n"); |
| if (md->core_ap.handshake_ongoing) |
| t7xx_fsm_append_event(ctl, FSM_EVENT_AP_HS2_EXIT, NULL, 0); |
| |
| fsm_routine_exception(ctl, NULL, EXCEPTION_HS_TIMEOUT); |
| return -ETIMEDOUT; |
| } |
| |
| t7xx_pci_pm_init_late(md->t7xx_dev); |
| fsm_routine_ready(ctl); |
| return 0; |
| } |
| |
| static void fsm_routine_start(struct t7xx_fsm_ctl *ctl, struct t7xx_fsm_command *cmd) |
| { |
| struct t7xx_modem *md = ctl->md; |
| struct device *dev; |
| u32 status; |
| int ret; |
| |
| if (!md) |
| return; |
| |
| if (ctl->curr_state != FSM_STATE_INIT && ctl->curr_state != FSM_STATE_PRE_START && |
| ctl->curr_state != FSM_STATE_STOPPED) { |
| fsm_finish_command(ctl, cmd, -EINVAL); |
| return; |
| } |
| |
| dev = &md->t7xx_dev->pdev->dev; |
| ctl->curr_state = FSM_STATE_PRE_START; |
| t7xx_md_event_notify(md, FSM_PRE_START); |
| |
| ret = wait_for_expected_dev_stage(status); |
| |
| if (ret) { |
| dev_err(dev, "read poll timeout %d\n", ret); |
| goto finish_command; |
| } |
| |
| if (status != ctl->status || cmd->flag != 0) { |
| u32 stage = FIELD_GET(MISC_STAGE_MASK, status); |
| |
| switch (stage) { |
| case T7XX_DEV_STAGE_INIT: |
| case T7XX_DEV_STAGE_BROM_PRE: |
| case T7XX_DEV_STAGE_BROM_POST: |
| dev_dbg(dev, "BROM_STAGE Entered\n"); |
| ret = t7xx_fsm_append_cmd(ctl, FSM_CMD_START, 0); |
| break; |
| |
| case T7XX_DEV_STAGE_LK: |
| dev_dbg(dev, "LK_STAGE Entered\n"); |
| t7xx_port_proxy_set_cfg(md, PORT_CFG_ID_EARLY); |
| t7xx_lk_stage_event_handling(ctl, status); |
| |
| break; |
| |
| case T7XX_DEV_STAGE_LINUX: |
| dev_dbg(dev, "LINUX_STAGE Entered\n"); |
| t7xx_mhccif_mask_clr(md->t7xx_dev, D2H_INT_PORT_ENUM | |
| D2H_INT_ASYNC_MD_HK | D2H_INT_ASYNC_AP_HK); |
| if (cmd->flag == 0) |
| break; |
| t7xx_cldma_hif_hw_init(md->md_ctrl[CLDMA_ID_AP]); |
| t7xx_cldma_hif_hw_init(md->md_ctrl[CLDMA_ID_MD]); |
| t7xx_port_proxy_set_cfg(md, PORT_CFG_ID_NORMAL); |
| ret = fsm_routine_starting(ctl); |
| break; |
| |
| default: |
| break; |
| } |
| ctl->status = status; |
| } |
| |
| finish_command: |
| if (ret) |
| t7xx_mode_update(md->t7xx_dev, T7XX_UNKNOWN); |
| |
| fsm_finish_command(ctl, cmd, ret); |
| } |
| |
| static int fsm_main_thread(void *data) |
| { |
| struct t7xx_fsm_ctl *ctl = data; |
| struct t7xx_fsm_command *cmd; |
| unsigned long flags; |
| |
| while (!kthread_should_stop()) { |
| if (wait_event_interruptible(ctl->command_wq, !list_empty(&ctl->command_queue) || |
| kthread_should_stop())) |
| continue; |
| |
| if (kthread_should_stop()) |
| break; |
| |
| spin_lock_irqsave(&ctl->command_lock, flags); |
| cmd = list_first_entry(&ctl->command_queue, struct t7xx_fsm_command, entry); |
| list_del(&cmd->entry); |
| spin_unlock_irqrestore(&ctl->command_lock, flags); |
| |
| switch (cmd->cmd_id) { |
| case FSM_CMD_START: |
| fsm_routine_start(ctl, cmd); |
| break; |
| |
| case FSM_CMD_EXCEPTION: |
| fsm_routine_exception(ctl, cmd, FIELD_GET(FSM_CMD_EX_REASON, cmd->flag)); |
| break; |
| |
| case FSM_CMD_PRE_STOP: |
| fsm_routine_stopping(ctl, cmd); |
| break; |
| |
| case FSM_CMD_STOP: |
| fsm_routine_stopped(ctl, cmd); |
| break; |
| |
| default: |
| fsm_finish_command(ctl, cmd, -EINVAL); |
| fsm_flush_event_cmd_qs(ctl); |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int t7xx_fsm_append_cmd(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_cmd_state cmd_id, unsigned int flag) |
| { |
| DECLARE_COMPLETION_ONSTACK(done); |
| struct t7xx_fsm_command *cmd; |
| unsigned long flags; |
| int ret; |
| |
| cmd = kzalloc(sizeof(*cmd), flag & FSM_CMD_FLAG_IN_INTERRUPT ? GFP_ATOMIC : GFP_KERNEL); |
| if (!cmd) |
| return -ENOMEM; |
| |
| INIT_LIST_HEAD(&cmd->entry); |
| cmd->cmd_id = cmd_id; |
| cmd->flag = flag; |
| if (flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) { |
| cmd->done = &done; |
| cmd->ret = &ret; |
| } |
| |
| spin_lock_irqsave(&ctl->command_lock, flags); |
| list_add_tail(&cmd->entry, &ctl->command_queue); |
| spin_unlock_irqrestore(&ctl->command_lock, flags); |
| |
| wake_up(&ctl->command_wq); |
| |
| if (flag & FSM_CMD_FLAG_WAIT_FOR_COMPLETION) { |
| unsigned long wait_ret; |
| |
| wait_ret = wait_for_completion_timeout(&done, |
| msecs_to_jiffies(FSM_CMD_TIMEOUT_MS)); |
| if (!wait_ret) |
| return -ETIMEDOUT; |
| |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int t7xx_fsm_append_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_id, |
| unsigned char *data, unsigned int length) |
| { |
| struct device *dev = &ctl->md->t7xx_dev->pdev->dev; |
| struct t7xx_fsm_event *event; |
| unsigned long flags; |
| |
| if (event_id <= FSM_EVENT_INVALID || event_id >= FSM_EVENT_MAX) { |
| dev_err(dev, "Invalid event %d\n", event_id); |
| return -EINVAL; |
| } |
| |
| event = kmalloc(struct_size(event, data, length), |
| in_interrupt() ? GFP_ATOMIC : GFP_KERNEL); |
| if (!event) |
| return -ENOMEM; |
| |
| INIT_LIST_HEAD(&event->entry); |
| event->event_id = event_id; |
| event->length = length; |
| |
| if (data && length) |
| memcpy(event->data, data, length); |
| |
| spin_lock_irqsave(&ctl->event_lock, flags); |
| list_add_tail(&event->entry, &ctl->event_queue); |
| spin_unlock_irqrestore(&ctl->event_lock, flags); |
| |
| wake_up_all(&ctl->event_wq); |
| return 0; |
| } |
| |
| void t7xx_fsm_clr_event(struct t7xx_fsm_ctl *ctl, enum t7xx_fsm_event_state event_id) |
| { |
| struct t7xx_fsm_event *event, *evt_next; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&ctl->event_lock, flags); |
| list_for_each_entry_safe(event, evt_next, &ctl->event_queue, entry) { |
| if (event->event_id == event_id) |
| fsm_del_kf_event(event); |
| } |
| spin_unlock_irqrestore(&ctl->event_lock, flags); |
| } |
| |
| enum md_state t7xx_fsm_get_md_state(struct t7xx_fsm_ctl *ctl) |
| { |
| if (ctl) |
| return ctl->md_state; |
| |
| return MD_STATE_INVALID; |
| } |
| |
| unsigned int t7xx_fsm_get_ctl_state(struct t7xx_fsm_ctl *ctl) |
| { |
| if (ctl) |
| return ctl->curr_state; |
| |
| return FSM_STATE_STOPPED; |
| } |
| |
| int t7xx_fsm_recv_md_intr(struct t7xx_fsm_ctl *ctl, enum t7xx_md_irq_type type) |
| { |
| unsigned int cmd_flags = FSM_CMD_FLAG_IN_INTERRUPT; |
| |
| if (type == MD_IRQ_PORT_ENUM) { |
| return t7xx_fsm_append_cmd(ctl, FSM_CMD_START, cmd_flags); |
| } else if (type == MD_IRQ_CCIF_EX) { |
| ctl->exp_flg = true; |
| wake_up(&ctl->async_hk_wq); |
| cmd_flags |= FIELD_PREP(FSM_CMD_EX_REASON, EXCEPTION_EVENT); |
| return t7xx_fsm_append_cmd(ctl, FSM_CMD_EXCEPTION, cmd_flags); |
| } |
| |
| return -EINVAL; |
| } |
| |
| void t7xx_fsm_reset(struct t7xx_modem *md) |
| { |
| struct t7xx_fsm_ctl *ctl = md->fsm_ctl; |
| |
| fsm_flush_event_cmd_qs(ctl); |
| ctl->curr_state = FSM_STATE_STOPPED; |
| ctl->exp_flg = false; |
| ctl->status = T7XX_DEV_STAGE_INIT; |
| } |
| |
| int t7xx_fsm_init(struct t7xx_modem *md) |
| { |
| struct device *dev = &md->t7xx_dev->pdev->dev; |
| struct t7xx_fsm_ctl *ctl; |
| |
| ctl = devm_kzalloc(dev, sizeof(*ctl), GFP_KERNEL); |
| if (!ctl) |
| return -ENOMEM; |
| |
| md->fsm_ctl = ctl; |
| ctl->md = md; |
| ctl->curr_state = FSM_STATE_INIT; |
| INIT_LIST_HEAD(&ctl->command_queue); |
| INIT_LIST_HEAD(&ctl->event_queue); |
| init_waitqueue_head(&ctl->async_hk_wq); |
| init_waitqueue_head(&ctl->event_wq); |
| INIT_LIST_HEAD(&ctl->notifier_list); |
| init_waitqueue_head(&ctl->command_wq); |
| spin_lock_init(&ctl->event_lock); |
| spin_lock_init(&ctl->command_lock); |
| ctl->exp_flg = false; |
| spin_lock_init(&ctl->notifier_lock); |
| |
| ctl->fsm_thread = kthread_run(fsm_main_thread, ctl, "t7xx_fsm"); |
| return PTR_ERR_OR_ZERO(ctl->fsm_thread); |
| } |
| |
| void t7xx_fsm_uninit(struct t7xx_modem *md) |
| { |
| struct t7xx_fsm_ctl *ctl = md->fsm_ctl; |
| |
| if (!ctl) |
| return; |
| |
| if (ctl->fsm_thread) |
| kthread_stop(ctl->fsm_thread); |
| |
| fsm_flush_event_cmd_qs(ctl); |
| } |