| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2020-21 Intel Corporation. |
| */ |
| |
| #include <linux/delay.h> |
| |
| #include "iosm_ipc_chnl_cfg.h" |
| #include "iosm_ipc_devlink.h" |
| #include "iosm_ipc_flash.h" |
| #include "iosm_ipc_imem.h" |
| #include "iosm_ipc_port.h" |
| |
| /* Check the wwan ips if it is valid with Channel as input. */ |
| static int ipc_imem_check_wwan_ips(struct ipc_mem_channel *chnl) |
| { |
| if (chnl) |
| return chnl->ctype == IPC_CTYPE_WWAN && |
| chnl->if_id == IPC_MEM_MUX_IP_CH_IF_ID; |
| return false; |
| } |
| |
| static int ipc_imem_msg_send_device_sleep(struct iosm_imem *ipc_imem, u32 state) |
| { |
| union ipc_msg_prep_args prep_args = { |
| .sleep.target = 1, |
| .sleep.state = state, |
| }; |
| |
| ipc_imem->device_sleep = state; |
| |
| return ipc_protocol_tq_msg_send(ipc_imem->ipc_protocol, |
| IPC_MSG_PREP_SLEEP, &prep_args, NULL); |
| } |
| |
| static bool ipc_imem_dl_skb_alloc(struct iosm_imem *ipc_imem, |
| struct ipc_pipe *pipe) |
| { |
| /* limit max. nr of entries */ |
| if (pipe->nr_of_queued_entries >= pipe->max_nr_of_queued_entries) |
| return false; |
| |
| return ipc_protocol_dl_td_prepare(ipc_imem->ipc_protocol, pipe); |
| } |
| |
| /* This timer handler will retry DL buff allocation if a pipe has no free buf |
| * and gives doorbell if TD is available |
| */ |
| static int ipc_imem_tq_td_alloc_timer(struct iosm_imem *ipc_imem, int arg, |
| void *msg, size_t size) |
| { |
| bool new_buffers_available = false; |
| bool retry_allocation = false; |
| int i; |
| |
| for (i = 0; i < IPC_MEM_MAX_CHANNELS; i++) { |
| struct ipc_pipe *pipe = &ipc_imem->channels[i].dl_pipe; |
| |
| if (!pipe->is_open || pipe->nr_of_queued_entries > 0) |
| continue; |
| |
| while (ipc_imem_dl_skb_alloc(ipc_imem, pipe)) |
| new_buffers_available = true; |
| |
| if (pipe->nr_of_queued_entries == 0) |
| retry_allocation = true; |
| } |
| |
| if (new_buffers_available) |
| ipc_protocol_doorbell_trigger(ipc_imem->ipc_protocol, |
| IPC_HP_DL_PROCESS); |
| |
| if (retry_allocation) { |
| ipc_imem->hrtimer_period = |
| ktime_set(0, IPC_TD_ALLOC_TIMER_PERIOD_MS * 1000 * 1000ULL); |
| if (!hrtimer_active(&ipc_imem->td_alloc_timer)) |
| hrtimer_start(&ipc_imem->td_alloc_timer, |
| ipc_imem->hrtimer_period, |
| HRTIMER_MODE_REL); |
| } |
| return 0; |
| } |
| |
| static enum hrtimer_restart ipc_imem_td_alloc_timer_cb(struct hrtimer *hr_timer) |
| { |
| struct iosm_imem *ipc_imem = |
| container_of(hr_timer, struct iosm_imem, td_alloc_timer); |
| /* Post an async tasklet event to trigger HP update Doorbell */ |
| ipc_task_queue_send_task(ipc_imem, ipc_imem_tq_td_alloc_timer, 0, NULL, |
| 0, false); |
| return HRTIMER_NORESTART; |
| } |
| |
| /* Fast update timer tasklet handler to trigger HP update */ |
| static int ipc_imem_tq_fast_update_timer_cb(struct iosm_imem *ipc_imem, int arg, |
| void *msg, size_t size) |
| { |
| ipc_protocol_doorbell_trigger(ipc_imem->ipc_protocol, |
| IPC_HP_FAST_TD_UPD_TMR); |
| |
| return 0; |
| } |
| |
| static enum hrtimer_restart |
| ipc_imem_fast_update_timer_cb(struct hrtimer *hr_timer) |
| { |
| struct iosm_imem *ipc_imem = |
| container_of(hr_timer, struct iosm_imem, fast_update_timer); |
| /* Post an async tasklet event to trigger HP update Doorbell */ |
| ipc_task_queue_send_task(ipc_imem, ipc_imem_tq_fast_update_timer_cb, 0, |
| NULL, 0, false); |
| return HRTIMER_NORESTART; |
| } |
| |
| static int ipc_imem_setup_cp_mux_cap_init(struct iosm_imem *ipc_imem, |
| struct ipc_mux_config *cfg) |
| { |
| ipc_mmio_update_cp_capability(ipc_imem->mmio); |
| |
| if (!ipc_imem->mmio->has_mux_lite) { |
| dev_err(ipc_imem->dev, "Failed to get Mux capability."); |
| return -EINVAL; |
| } |
| |
| cfg->protocol = MUX_LITE; |
| |
| cfg->ul_flow = (ipc_imem->mmio->has_ul_flow_credit == 1) ? |
| MUX_UL_ON_CREDITS : |
| MUX_UL; |
| |
| /* The instance ID is same as channel ID because this is been reused |
| * for channel alloc function. |
| */ |
| cfg->instance_id = IPC_MEM_MUX_IP_CH_IF_ID; |
| cfg->nr_sessions = IPC_MEM_MUX_IP_SESSION_ENTRIES; |
| |
| return 0; |
| } |
| |
| void ipc_imem_msg_send_feature_set(struct iosm_imem *ipc_imem, |
| unsigned int reset_enable, bool atomic_ctx) |
| { |
| union ipc_msg_prep_args prep_args = { .feature_set.reset_enable = |
| reset_enable }; |
| |
| if (atomic_ctx) |
| ipc_protocol_tq_msg_send(ipc_imem->ipc_protocol, |
| IPC_MSG_PREP_FEATURE_SET, &prep_args, |
| NULL); |
| else |
| ipc_protocol_msg_send(ipc_imem->ipc_protocol, |
| IPC_MSG_PREP_FEATURE_SET, &prep_args); |
| } |
| |
| void ipc_imem_td_update_timer_start(struct iosm_imem *ipc_imem) |
| { |
| /* Use the TD update timer only in the runtime phase */ |
| if (!ipc_imem->enter_runtime || ipc_imem->td_update_timer_suspended) { |
| /* trigger the doorbell irq on CP directly. */ |
| ipc_protocol_doorbell_trigger(ipc_imem->ipc_protocol, |
| IPC_HP_TD_UPD_TMR_START); |
| return; |
| } |
| |
| if (!hrtimer_active(&ipc_imem->tdupdate_timer)) { |
| ipc_imem->hrtimer_period = |
| ktime_set(0, TD_UPDATE_DEFAULT_TIMEOUT_USEC * 1000ULL); |
| if (!hrtimer_active(&ipc_imem->tdupdate_timer)) |
| hrtimer_start(&ipc_imem->tdupdate_timer, |
| ipc_imem->hrtimer_period, |
| HRTIMER_MODE_REL); |
| } |
| } |
| |
| void ipc_imem_hrtimer_stop(struct hrtimer *hr_timer) |
| { |
| if (hrtimer_active(hr_timer)) |
| hrtimer_cancel(hr_timer); |
| } |
| |
| bool ipc_imem_ul_write_td(struct iosm_imem *ipc_imem) |
| { |
| struct ipc_mem_channel *channel; |
| struct sk_buff_head *ul_list; |
| bool hpda_pending = false; |
| bool forced_hpdu = false; |
| struct ipc_pipe *pipe; |
| int i; |
| |
| /* Analyze the uplink pipe of all active channels. */ |
| for (i = 0; i < ipc_imem->nr_of_channels; i++) { |
| channel = &ipc_imem->channels[i]; |
| |
| if (channel->state != IMEM_CHANNEL_ACTIVE) |
| continue; |
| |
| pipe = &channel->ul_pipe; |
| |
| /* Get the reference to the skbuf accumulator list. */ |
| ul_list = &channel->ul_list; |
| |
| /* Fill the transfer descriptor with the uplink buffer info. */ |
| hpda_pending |= ipc_protocol_ul_td_send(ipc_imem->ipc_protocol, |
| pipe, ul_list); |
| |
| /* forced HP update needed for non data channels */ |
| if (hpda_pending && !ipc_imem_check_wwan_ips(channel)) |
| forced_hpdu = true; |
| } |
| |
| if (forced_hpdu) { |
| hpda_pending = false; |
| ipc_protocol_doorbell_trigger(ipc_imem->ipc_protocol, |
| IPC_HP_UL_WRITE_TD); |
| } |
| |
| return hpda_pending; |
| } |
| |
| void ipc_imem_ipc_init_check(struct iosm_imem *ipc_imem) |
| { |
| int timeout = IPC_MODEM_BOOT_TIMEOUT; |
| |
| ipc_imem->ipc_requested_state = IPC_MEM_DEVICE_IPC_INIT; |
| |
| /* Trigger the CP interrupt to enter the init state. */ |
| ipc_doorbell_fire(ipc_imem->pcie, IPC_DOORBELL_IRQ_IPC, |
| IPC_MEM_DEVICE_IPC_INIT); |
| /* Wait for the CP update. */ |
| do { |
| if (ipc_mmio_get_ipc_state(ipc_imem->mmio) == |
| ipc_imem->ipc_requested_state) { |
| /* Prepare the MMIO space */ |
| ipc_mmio_config(ipc_imem->mmio); |
| |
| /* Trigger the CP irq to enter the running state. */ |
| ipc_imem->ipc_requested_state = |
| IPC_MEM_DEVICE_IPC_RUNNING; |
| ipc_doorbell_fire(ipc_imem->pcie, IPC_DOORBELL_IRQ_IPC, |
| IPC_MEM_DEVICE_IPC_RUNNING); |
| |
| return; |
| } |
| msleep(20); |
| } while (--timeout); |
| |
| /* timeout */ |
| dev_err(ipc_imem->dev, "%s: ipc_status(%d) ne. IPC_MEM_DEVICE_IPC_INIT", |
| ipc_imem_phase_get_string(ipc_imem->phase), |
| ipc_mmio_get_ipc_state(ipc_imem->mmio)); |
| |
| ipc_uevent_send(ipc_imem->dev, UEVENT_MDM_TIMEOUT); |
| } |
| |
| /* Analyze the packet type and distribute it. */ |
| static void ipc_imem_dl_skb_process(struct iosm_imem *ipc_imem, |
| struct ipc_pipe *pipe, struct sk_buff *skb) |
| { |
| u16 port_id; |
| |
| if (!skb) |
| return; |
| |
| /* An AT/control or IP packet is expected. */ |
| switch (pipe->channel->ctype) { |
| case IPC_CTYPE_CTRL: |
| port_id = pipe->channel->channel_id; |
| if (port_id == IPC_MEM_CTRL_CHL_ID_7) |
| ipc_imem_sys_devlink_notify_rx(ipc_imem->ipc_devlink, |
| skb); |
| else |
| wwan_port_rx(ipc_imem->ipc_port[port_id]->iosm_port, |
| skb); |
| break; |
| |
| case IPC_CTYPE_WWAN: |
| if (pipe->channel->if_id == IPC_MEM_MUX_IP_CH_IF_ID) |
| ipc_mux_dl_decode(ipc_imem->mux, skb); |
| break; |
| default: |
| dev_err(ipc_imem->dev, "Invalid channel type"); |
| break; |
| } |
| } |
| |
| /* Process the downlink data and pass them to the char or net layer. */ |
| static void ipc_imem_dl_pipe_process(struct iosm_imem *ipc_imem, |
| struct ipc_pipe *pipe) |
| { |
| s32 cnt = 0, processed_td_cnt = 0; |
| struct ipc_mem_channel *channel; |
| u32 head = 0, tail = 0; |
| bool processed = false; |
| struct sk_buff *skb; |
| |
| channel = pipe->channel; |
| |
| ipc_protocol_get_head_tail_index(ipc_imem->ipc_protocol, pipe, &head, |
| &tail); |
| if (pipe->old_tail != tail) { |
| if (pipe->old_tail < tail) |
| cnt = tail - pipe->old_tail; |
| else |
| cnt = pipe->nr_of_entries - pipe->old_tail + tail; |
| } |
| |
| processed_td_cnt = cnt; |
| |
| /* Seek for pipes with pending DL data. */ |
| while (cnt--) { |
| skb = ipc_protocol_dl_td_process(ipc_imem->ipc_protocol, pipe); |
| |
| /* Analyze the packet type and distribute it. */ |
| ipc_imem_dl_skb_process(ipc_imem, pipe, skb); |
| } |
| |
| /* try to allocate new empty DL SKbs from head..tail - 1*/ |
| while (ipc_imem_dl_skb_alloc(ipc_imem, pipe)) |
| processed = true; |
| |
| if (processed && !ipc_imem_check_wwan_ips(channel)) { |
| /* Force HP update for non IP channels */ |
| ipc_protocol_doorbell_trigger(ipc_imem->ipc_protocol, |
| IPC_HP_DL_PROCESS); |
| processed = false; |
| |
| /* If Fast Update timer is already running then stop */ |
| ipc_imem_hrtimer_stop(&ipc_imem->fast_update_timer); |
| } |
| |
| /* Any control channel process will get immediate HP update. |
| * Start Fast update timer only for IP channel if all the TDs were |
| * used in last process. |
| */ |
| if (processed && (processed_td_cnt == pipe->nr_of_entries - 1)) { |
| ipc_imem->hrtimer_period = |
| ktime_set(0, FORCE_UPDATE_DEFAULT_TIMEOUT_USEC * 1000ULL); |
| hrtimer_start(&ipc_imem->fast_update_timer, |
| ipc_imem->hrtimer_period, HRTIMER_MODE_REL); |
| } |
| |
| if (ipc_imem->app_notify_dl_pend) |
| complete(&ipc_imem->dl_pend_sem); |
| } |
| |
| /* process open uplink pipe */ |
| static void ipc_imem_ul_pipe_process(struct iosm_imem *ipc_imem, |
| struct ipc_pipe *pipe) |
| { |
| struct ipc_mem_channel *channel; |
| u32 tail = 0, head = 0; |
| struct sk_buff *skb; |
| s32 cnt = 0; |
| |
| channel = pipe->channel; |
| |
| /* Get the internal phase. */ |
| ipc_protocol_get_head_tail_index(ipc_imem->ipc_protocol, pipe, &head, |
| &tail); |
| |
| if (pipe->old_tail != tail) { |
| if (pipe->old_tail < tail) |
| cnt = tail - pipe->old_tail; |
| else |
| cnt = pipe->nr_of_entries - pipe->old_tail + tail; |
| } |
| |
| /* Free UL buffers. */ |
| while (cnt--) { |
| skb = ipc_protocol_ul_td_process(ipc_imem->ipc_protocol, pipe); |
| |
| if (!skb) |
| continue; |
| |
| /* If the user app was suspended in uplink direction - blocking |
| * write, resume it. |
| */ |
| if (IPC_CB(skb)->op_type == UL_USR_OP_BLOCKED) |
| complete(&channel->ul_sem); |
| |
| /* Free the skbuf element. */ |
| if (IPC_CB(skb)->op_type == UL_MUX_OP_ADB) { |
| if (channel->if_id == IPC_MEM_MUX_IP_CH_IF_ID) |
| ipc_mux_ul_encoded_process(ipc_imem->mux, skb); |
| else |
| dev_err(ipc_imem->dev, |
| "OP Type is UL_MUX, unknown if_id %d", |
| channel->if_id); |
| } else { |
| ipc_pcie_kfree_skb(ipc_imem->pcie, skb); |
| } |
| } |
| |
| /* Trace channel stats for IP UL pipe. */ |
| if (ipc_imem_check_wwan_ips(pipe->channel)) |
| ipc_mux_check_n_restart_tx(ipc_imem->mux); |
| |
| if (ipc_imem->app_notify_ul_pend) |
| complete(&ipc_imem->ul_pend_sem); |
| } |
| |
| /* Executes the irq. */ |
| static void ipc_imem_rom_irq_exec(struct iosm_imem *ipc_imem) |
| { |
| struct ipc_mem_channel *channel; |
| |
| channel = ipc_imem->ipc_devlink->devlink_sio.channel; |
| ipc_imem->rom_exit_code = ipc_mmio_get_rom_exit_code(ipc_imem->mmio); |
| complete(&channel->ul_sem); |
| } |
| |
| /* Execute the UL bundle timer actions, generating the doorbell irq. */ |
| static int ipc_imem_tq_td_update_timer_cb(struct iosm_imem *ipc_imem, int arg, |
| void *msg, size_t size) |
| { |
| ipc_protocol_doorbell_trigger(ipc_imem->ipc_protocol, |
| IPC_HP_TD_UPD_TMR); |
| return 0; |
| } |
| |
| /* Consider link power management in the runtime phase. */ |
| static void ipc_imem_slp_control_exec(struct iosm_imem *ipc_imem) |
| { |
| /* link will go down, Test pending UL packets.*/ |
| if (ipc_protocol_pm_dev_sleep_handle(ipc_imem->ipc_protocol) && |
| hrtimer_active(&ipc_imem->tdupdate_timer)) { |
| /* Generate the doorbell irq. */ |
| ipc_imem_tq_td_update_timer_cb(ipc_imem, 0, NULL, 0); |
| /* Stop the TD update timer. */ |
| ipc_imem_hrtimer_stop(&ipc_imem->tdupdate_timer); |
| /* Stop the fast update timer. */ |
| ipc_imem_hrtimer_stop(&ipc_imem->fast_update_timer); |
| } |
| } |
| |
| /* Execute startup timer and wait for delayed start (e.g. NAND) */ |
| static int ipc_imem_tq_startup_timer_cb(struct iosm_imem *ipc_imem, int arg, |
| void *msg, size_t size) |
| { |
| /* Update & check the current operation phase. */ |
| if (ipc_imem_phase_update(ipc_imem) != IPC_P_RUN) |
| return -EIO; |
| |
| if (ipc_mmio_get_ipc_state(ipc_imem->mmio) == |
| IPC_MEM_DEVICE_IPC_UNINIT) { |
| ipc_imem->ipc_requested_state = IPC_MEM_DEVICE_IPC_INIT; |
| |
| ipc_doorbell_fire(ipc_imem->pcie, IPC_DOORBELL_IRQ_IPC, |
| IPC_MEM_DEVICE_IPC_INIT); |
| |
| ipc_imem->hrtimer_period = ktime_set(0, 100 * 1000UL * 1000ULL); |
| /* reduce period to 100 ms to check for mmio init state */ |
| if (!hrtimer_active(&ipc_imem->startup_timer)) |
| hrtimer_start(&ipc_imem->startup_timer, |
| ipc_imem->hrtimer_period, |
| HRTIMER_MODE_REL); |
| } else if (ipc_mmio_get_ipc_state(ipc_imem->mmio) == |
| IPC_MEM_DEVICE_IPC_INIT) { |
| /* Startup complete - disable timer */ |
| ipc_imem_hrtimer_stop(&ipc_imem->startup_timer); |
| |
| /* Prepare the MMIO space */ |
| ipc_mmio_config(ipc_imem->mmio); |
| ipc_imem->ipc_requested_state = IPC_MEM_DEVICE_IPC_RUNNING; |
| ipc_doorbell_fire(ipc_imem->pcie, IPC_DOORBELL_IRQ_IPC, |
| IPC_MEM_DEVICE_IPC_RUNNING); |
| } |
| |
| return 0; |
| } |
| |
| static enum hrtimer_restart ipc_imem_startup_timer_cb(struct hrtimer *hr_timer) |
| { |
| enum hrtimer_restart result = HRTIMER_NORESTART; |
| struct iosm_imem *ipc_imem = |
| container_of(hr_timer, struct iosm_imem, startup_timer); |
| |
| if (ktime_to_ns(ipc_imem->hrtimer_period)) { |
| hrtimer_forward_now(&ipc_imem->startup_timer, |
| ipc_imem->hrtimer_period); |
| result = HRTIMER_RESTART; |
| } |
| |
| ipc_task_queue_send_task(ipc_imem, ipc_imem_tq_startup_timer_cb, 0, |
| NULL, 0, false); |
| return result; |
| } |
| |
| /* Get the CP execution stage */ |
| static enum ipc_mem_exec_stage |
| ipc_imem_get_exec_stage_buffered(struct iosm_imem *ipc_imem) |
| { |
| return (ipc_imem->phase == IPC_P_RUN && |
| ipc_imem->ipc_status == IPC_MEM_DEVICE_IPC_RUNNING) ? |
| ipc_protocol_get_ap_exec_stage(ipc_imem->ipc_protocol) : |
| ipc_mmio_get_exec_stage(ipc_imem->mmio); |
| } |
| |
| /* Callback to send the modem ready uevent */ |
| static int ipc_imem_send_mdm_rdy_cb(struct iosm_imem *ipc_imem, int arg, |
| void *msg, size_t size) |
| { |
| enum ipc_mem_exec_stage exec_stage = |
| ipc_imem_get_exec_stage_buffered(ipc_imem); |
| |
| if (exec_stage == IPC_MEM_EXEC_STAGE_RUN) |
| ipc_uevent_send(ipc_imem->dev, UEVENT_MDM_READY); |
| |
| return 0; |
| } |
| |
| /* This function is executed in a task context via an ipc_worker object, |
| * as the creation or removal of device can't be done from tasklet. |
| */ |
| static void ipc_imem_run_state_worker(struct work_struct *instance) |
| { |
| struct ipc_chnl_cfg chnl_cfg_port = { 0 }; |
| struct ipc_mux_config mux_cfg; |
| struct iosm_imem *ipc_imem; |
| u8 ctrl_chl_idx = 0; |
| |
| ipc_imem = container_of(instance, struct iosm_imem, run_state_worker); |
| |
| if (ipc_imem->phase != IPC_P_RUN) { |
| dev_err(ipc_imem->dev, |
| "Modem link down. Exit run state worker."); |
| return; |
| } |
| |
| if (!ipc_imem_setup_cp_mux_cap_init(ipc_imem, &mux_cfg)) |
| ipc_imem->mux = ipc_mux_init(&mux_cfg, ipc_imem); |
| |
| ipc_imem_wwan_channel_init(ipc_imem, mux_cfg.protocol); |
| if (ipc_imem->mux) |
| ipc_imem->mux->wwan = ipc_imem->wwan; |
| |
| while (ctrl_chl_idx < IPC_MEM_MAX_CHANNELS) { |
| if (!ipc_chnl_cfg_get(&chnl_cfg_port, ctrl_chl_idx)) { |
| ipc_imem->ipc_port[ctrl_chl_idx] = NULL; |
| if (chnl_cfg_port.wwan_port_type != WWAN_PORT_UNKNOWN) { |
| ipc_imem_channel_init(ipc_imem, IPC_CTYPE_CTRL, |
| chnl_cfg_port, |
| IRQ_MOD_OFF); |
| ipc_imem->ipc_port[ctrl_chl_idx] = |
| ipc_port_init(ipc_imem, chnl_cfg_port); |
| } |
| } |
| ctrl_chl_idx++; |
| } |
| |
| ipc_task_queue_send_task(ipc_imem, ipc_imem_send_mdm_rdy_cb, 0, NULL, 0, |
| false); |
| |
| /* Complete all memory stores before setting bit */ |
| smp_mb__before_atomic(); |
| |
| set_bit(FULLY_FUNCTIONAL, &ipc_imem->flag); |
| |
| /* Complete all memory stores after setting bit */ |
| smp_mb__after_atomic(); |
| } |
| |
| static void ipc_imem_handle_irq(struct iosm_imem *ipc_imem, int irq) |
| { |
| enum ipc_mem_device_ipc_state curr_ipc_status; |
| enum ipc_phase old_phase, phase; |
| bool retry_allocation = false; |
| bool ul_pending = false; |
| int i; |
| |
| if (irq != IMEM_IRQ_DONT_CARE) |
| ipc_imem->ev_irq_pending[irq] = false; |
| |
| /* Get the internal phase. */ |
| old_phase = ipc_imem->phase; |
| |
| if (old_phase == IPC_P_OFF_REQ) { |
| dev_dbg(ipc_imem->dev, |
| "[%s]: Ignoring MSI. Deinit sequence in progress!", |
| ipc_imem_phase_get_string(old_phase)); |
| return; |
| } |
| |
| /* Update the phase controlled by CP. */ |
| phase = ipc_imem_phase_update(ipc_imem); |
| |
| switch (phase) { |
| case IPC_P_RUN: |
| if (!ipc_imem->enter_runtime) { |
| /* Excute the transition from flash/boot to runtime. */ |
| ipc_imem->enter_runtime = 1; |
| |
| /* allow device to sleep, default value is |
| * IPC_HOST_SLEEP_ENTER_SLEEP |
| */ |
| ipc_imem_msg_send_device_sleep(ipc_imem, |
| ipc_imem->device_sleep); |
| |
| ipc_imem_msg_send_feature_set(ipc_imem, |
| IPC_MEM_INBAND_CRASH_SIG, |
| true); |
| } |
| |
| curr_ipc_status = |
| ipc_protocol_get_ipc_status(ipc_imem->ipc_protocol); |
| |
| /* check ipc_status change */ |
| if (ipc_imem->ipc_status != curr_ipc_status) { |
| ipc_imem->ipc_status = curr_ipc_status; |
| |
| if (ipc_imem->ipc_status == |
| IPC_MEM_DEVICE_IPC_RUNNING) { |
| schedule_work(&ipc_imem->run_state_worker); |
| } |
| } |
| |
| /* Consider power management in the runtime phase. */ |
| ipc_imem_slp_control_exec(ipc_imem); |
| break; /* Continue with skbuf processing. */ |
| |
| /* Unexpected phases. */ |
| case IPC_P_OFF: |
| case IPC_P_OFF_REQ: |
| dev_err(ipc_imem->dev, "confused phase %s", |
| ipc_imem_phase_get_string(phase)); |
| return; |
| |
| case IPC_P_PSI: |
| if (old_phase != IPC_P_ROM) |
| break; |
| |
| fallthrough; |
| /* On CP the PSI phase is already active. */ |
| |
| case IPC_P_ROM: |
| /* Before CP ROM driver starts the PSI image, it sets |
| * the exit_code field on the doorbell scratchpad and |
| * triggers the irq. |
| */ |
| ipc_imem_rom_irq_exec(ipc_imem); |
| return; |
| |
| default: |
| break; |
| } |
| |
| /* process message ring */ |
| ipc_protocol_msg_process(ipc_imem, irq); |
| |
| /* process all open pipes */ |
| for (i = 0; i < IPC_MEM_MAX_CHANNELS; i++) { |
| struct ipc_pipe *ul_pipe = &ipc_imem->channels[i].ul_pipe; |
| struct ipc_pipe *dl_pipe = &ipc_imem->channels[i].dl_pipe; |
| |
| if (dl_pipe->is_open && |
| (irq == IMEM_IRQ_DONT_CARE || irq == dl_pipe->irq)) { |
| ipc_imem_dl_pipe_process(ipc_imem, dl_pipe); |
| |
| if (dl_pipe->nr_of_queued_entries == 0) |
| retry_allocation = true; |
| } |
| |
| if (ul_pipe->is_open) |
| ipc_imem_ul_pipe_process(ipc_imem, ul_pipe); |
| } |
| |
| /* Try to generate new ADB or ADGH. */ |
| if (ipc_mux_ul_data_encode(ipc_imem->mux)) |
| ipc_imem_td_update_timer_start(ipc_imem); |
| |
| /* Continue the send procedure with accumulated SIO or NETIF packets. |
| * Reset the debounce flags. |
| */ |
| ul_pending |= ipc_imem_ul_write_td(ipc_imem); |
| |
| /* if UL data is pending restart TD update timer */ |
| if (ul_pending) { |
| ipc_imem->hrtimer_period = |
| ktime_set(0, TD_UPDATE_DEFAULT_TIMEOUT_USEC * 1000ULL); |
| if (!hrtimer_active(&ipc_imem->tdupdate_timer)) |
| hrtimer_start(&ipc_imem->tdupdate_timer, |
| ipc_imem->hrtimer_period, |
| HRTIMER_MODE_REL); |
| } |
| |
| /* If CP has executed the transition |
| * from IPC_INIT to IPC_RUNNING in the PSI |
| * phase, wake up the flash app to open the pipes. |
| */ |
| if ((phase == IPC_P_PSI || phase == IPC_P_EBL) && |
| ipc_imem->ipc_requested_state == IPC_MEM_DEVICE_IPC_RUNNING && |
| ipc_mmio_get_ipc_state(ipc_imem->mmio) == |
| IPC_MEM_DEVICE_IPC_RUNNING) { |
| complete(&ipc_imem->ipc_devlink->devlink_sio.channel->ul_sem); |
| } |
| |
| /* Reset the expected CP state. */ |
| ipc_imem->ipc_requested_state = IPC_MEM_DEVICE_IPC_DONT_CARE; |
| |
| if (retry_allocation) { |
| ipc_imem->hrtimer_period = |
| ktime_set(0, IPC_TD_ALLOC_TIMER_PERIOD_MS * 1000 * 1000ULL); |
| if (!hrtimer_active(&ipc_imem->td_alloc_timer)) |
| hrtimer_start(&ipc_imem->td_alloc_timer, |
| ipc_imem->hrtimer_period, |
| HRTIMER_MODE_REL); |
| } |
| } |
| |
| /* Callback by tasklet for handling interrupt events. */ |
| static int ipc_imem_tq_irq_cb(struct iosm_imem *ipc_imem, int arg, void *msg, |
| size_t size) |
| { |
| ipc_imem_handle_irq(ipc_imem, arg); |
| |
| return 0; |
| } |
| |
| void ipc_imem_ul_send(struct iosm_imem *ipc_imem) |
| { |
| /* start doorbell irq delay timer if UL is pending */ |
| if (ipc_imem_ul_write_td(ipc_imem)) |
| ipc_imem_td_update_timer_start(ipc_imem); |
| } |
| |
| /* Check the execution stage and update the AP phase */ |
| static enum ipc_phase ipc_imem_phase_update_check(struct iosm_imem *ipc_imem, |
| enum ipc_mem_exec_stage stage) |
| { |
| switch (stage) { |
| case IPC_MEM_EXEC_STAGE_BOOT: |
| if (ipc_imem->phase != IPC_P_ROM) { |
| /* Send this event only once */ |
| ipc_uevent_send(ipc_imem->dev, UEVENT_ROM_READY); |
| } |
| |
| ipc_imem->phase = IPC_P_ROM; |
| break; |
| |
| case IPC_MEM_EXEC_STAGE_PSI: |
| ipc_imem->phase = IPC_P_PSI; |
| break; |
| |
| case IPC_MEM_EXEC_STAGE_EBL: |
| ipc_imem->phase = IPC_P_EBL; |
| break; |
| |
| case IPC_MEM_EXEC_STAGE_RUN: |
| if (ipc_imem->phase != IPC_P_RUN && |
| ipc_imem->ipc_status == IPC_MEM_DEVICE_IPC_RUNNING) { |
| ipc_uevent_send(ipc_imem->dev, UEVENT_MDM_READY); |
| } |
| ipc_imem->phase = IPC_P_RUN; |
| break; |
| |
| case IPC_MEM_EXEC_STAGE_CRASH: |
| if (ipc_imem->phase != IPC_P_CRASH) |
| ipc_uevent_send(ipc_imem->dev, UEVENT_CRASH); |
| |
| ipc_imem->phase = IPC_P_CRASH; |
| break; |
| |
| case IPC_MEM_EXEC_STAGE_CD_READY: |
| if (ipc_imem->phase != IPC_P_CD_READY) |
| ipc_uevent_send(ipc_imem->dev, UEVENT_CD_READY); |
| ipc_imem->phase = IPC_P_CD_READY; |
| break; |
| |
| default: |
| /* unknown exec stage: |
| * assume that link is down and send info to listeners |
| */ |
| ipc_uevent_send(ipc_imem->dev, UEVENT_CD_READY_LINK_DOWN); |
| break; |
| } |
| |
| return ipc_imem->phase; |
| } |
| |
| /* Send msg to device to open pipe */ |
| static bool ipc_imem_pipe_open(struct iosm_imem *ipc_imem, |
| struct ipc_pipe *pipe) |
| { |
| union ipc_msg_prep_args prep_args = { |
| .pipe_open.pipe = pipe, |
| }; |
| |
| if (ipc_protocol_msg_send(ipc_imem->ipc_protocol, |
| IPC_MSG_PREP_PIPE_OPEN, &prep_args) == 0) |
| pipe->is_open = true; |
| |
| return pipe->is_open; |
| } |
| |
| /* Allocates the TDs for the given pipe along with firing HP update DB. */ |
| static int ipc_imem_tq_pipe_td_alloc(struct iosm_imem *ipc_imem, int arg, |
| void *msg, size_t size) |
| { |
| struct ipc_pipe *dl_pipe = msg; |
| bool processed = false; |
| int i; |
| |
| for (i = 0; i < dl_pipe->nr_of_entries - 1; i++) |
| processed |= ipc_imem_dl_skb_alloc(ipc_imem, dl_pipe); |
| |
| /* Trigger the doorbell irq to inform CP that new downlink buffers are |
| * available. |
| */ |
| if (processed) |
| ipc_protocol_doorbell_trigger(ipc_imem->ipc_protocol, arg); |
| |
| return 0; |
| } |
| |
| static enum hrtimer_restart |
| ipc_imem_td_update_timer_cb(struct hrtimer *hr_timer) |
| { |
| struct iosm_imem *ipc_imem = |
| container_of(hr_timer, struct iosm_imem, tdupdate_timer); |
| |
| ipc_task_queue_send_task(ipc_imem, ipc_imem_tq_td_update_timer_cb, 0, |
| NULL, 0, false); |
| return HRTIMER_NORESTART; |
| } |
| |
| /* Get the CP execution state and map it to the AP phase. */ |
| enum ipc_phase ipc_imem_phase_update(struct iosm_imem *ipc_imem) |
| { |
| enum ipc_mem_exec_stage exec_stage = |
| ipc_imem_get_exec_stage_buffered(ipc_imem); |
| /* If the CP stage is undef, return the internal precalculated phase. */ |
| return ipc_imem->phase == IPC_P_OFF_REQ ? |
| ipc_imem->phase : |
| ipc_imem_phase_update_check(ipc_imem, exec_stage); |
| } |
| |
| const char *ipc_imem_phase_get_string(enum ipc_phase phase) |
| { |
| switch (phase) { |
| case IPC_P_RUN: |
| return "A-RUN"; |
| |
| case IPC_P_OFF: |
| return "A-OFF"; |
| |
| case IPC_P_ROM: |
| return "A-ROM"; |
| |
| case IPC_P_PSI: |
| return "A-PSI"; |
| |
| case IPC_P_EBL: |
| return "A-EBL"; |
| |
| case IPC_P_CRASH: |
| return "A-CRASH"; |
| |
| case IPC_P_CD_READY: |
| return "A-CD_READY"; |
| |
| case IPC_P_OFF_REQ: |
| return "A-OFF_REQ"; |
| |
| default: |
| return "A-???"; |
| } |
| } |
| |
| void ipc_imem_pipe_close(struct iosm_imem *ipc_imem, struct ipc_pipe *pipe) |
| { |
| union ipc_msg_prep_args prep_args = { .pipe_close.pipe = pipe }; |
| |
| pipe->is_open = false; |
| ipc_protocol_msg_send(ipc_imem->ipc_protocol, IPC_MSG_PREP_PIPE_CLOSE, |
| &prep_args); |
| |
| ipc_imem_pipe_cleanup(ipc_imem, pipe); |
| } |
| |
| void ipc_imem_channel_close(struct iosm_imem *ipc_imem, int channel_id) |
| { |
| struct ipc_mem_channel *channel; |
| |
| if (channel_id < 0 || channel_id >= ipc_imem->nr_of_channels) { |
| dev_err(ipc_imem->dev, "invalid channel id %d", channel_id); |
| return; |
| } |
| |
| channel = &ipc_imem->channels[channel_id]; |
| |
| if (channel->state == IMEM_CHANNEL_FREE) { |
| dev_err(ipc_imem->dev, "ch[%d]: invalid channel state %d", |
| channel_id, channel->state); |
| return; |
| } |
| |
| /* Free only the channel id in the CP power off mode. */ |
| if (channel->state == IMEM_CHANNEL_RESERVED) |
| /* Release only the channel id. */ |
| goto channel_free; |
| |
| if (ipc_imem->phase == IPC_P_RUN) { |
| ipc_imem_pipe_close(ipc_imem, &channel->ul_pipe); |
| ipc_imem_pipe_close(ipc_imem, &channel->dl_pipe); |
| } |
| |
| ipc_imem_pipe_cleanup(ipc_imem, &channel->ul_pipe); |
| ipc_imem_pipe_cleanup(ipc_imem, &channel->dl_pipe); |
| |
| channel_free: |
| ipc_imem_channel_free(channel); |
| } |
| |
| struct ipc_mem_channel *ipc_imem_channel_open(struct iosm_imem *ipc_imem, |
| int channel_id, u32 db_id) |
| { |
| struct ipc_mem_channel *channel; |
| |
| if (channel_id < 0 || channel_id >= IPC_MEM_MAX_CHANNELS) { |
| dev_err(ipc_imem->dev, "invalid channel ID: %d", channel_id); |
| return NULL; |
| } |
| |
| channel = &ipc_imem->channels[channel_id]; |
| |
| channel->state = IMEM_CHANNEL_ACTIVE; |
| |
| if (!ipc_imem_pipe_open(ipc_imem, &channel->ul_pipe)) |
| goto ul_pipe_err; |
| |
| if (!ipc_imem_pipe_open(ipc_imem, &channel->dl_pipe)) |
| goto dl_pipe_err; |
| |
| /* Allocate the downlink buffers in tasklet context. */ |
| if (ipc_task_queue_send_task(ipc_imem, ipc_imem_tq_pipe_td_alloc, db_id, |
| &channel->dl_pipe, 0, false)) { |
| dev_err(ipc_imem->dev, "td allocation failed : %d", channel_id); |
| goto task_failed; |
| } |
| |
| /* Active channel. */ |
| return channel; |
| task_failed: |
| ipc_imem_pipe_close(ipc_imem, &channel->dl_pipe); |
| dl_pipe_err: |
| ipc_imem_pipe_close(ipc_imem, &channel->ul_pipe); |
| ul_pipe_err: |
| ipc_imem_channel_free(channel); |
| return NULL; |
| } |
| |
| void ipc_imem_pm_suspend(struct iosm_imem *ipc_imem) |
| { |
| ipc_protocol_suspend(ipc_imem->ipc_protocol); |
| } |
| |
| void ipc_imem_pm_s2idle_sleep(struct iosm_imem *ipc_imem, bool sleep) |
| { |
| ipc_protocol_s2idle_sleep(ipc_imem->ipc_protocol, sleep); |
| } |
| |
| void ipc_imem_pm_resume(struct iosm_imem *ipc_imem) |
| { |
| enum ipc_mem_exec_stage stage; |
| |
| if (ipc_protocol_resume(ipc_imem->ipc_protocol)) { |
| stage = ipc_mmio_get_exec_stage(ipc_imem->mmio); |
| ipc_imem_phase_update_check(ipc_imem, stage); |
| } |
| } |
| |
| void ipc_imem_channel_free(struct ipc_mem_channel *channel) |
| { |
| /* Reset dynamic channel elements. */ |
| channel->state = IMEM_CHANNEL_FREE; |
| } |
| |
| int ipc_imem_channel_alloc(struct iosm_imem *ipc_imem, int index, |
| enum ipc_ctype ctype) |
| { |
| struct ipc_mem_channel *channel; |
| int i; |
| |
| /* Find channel of given type/index */ |
| for (i = 0; i < ipc_imem->nr_of_channels; i++) { |
| channel = &ipc_imem->channels[i]; |
| if (channel->ctype == ctype && channel->index == index) |
| break; |
| } |
| |
| if (i >= ipc_imem->nr_of_channels) { |
| dev_dbg(ipc_imem->dev, |
| "no channel definition for index=%d ctype=%d", index, |
| ctype); |
| return -ECHRNG; |
| } |
| |
| if (ipc_imem->channels[i].state != IMEM_CHANNEL_FREE) { |
| dev_dbg(ipc_imem->dev, "channel is in use"); |
| return -EBUSY; |
| } |
| |
| if (channel->ctype == IPC_CTYPE_WWAN && |
| index == IPC_MEM_MUX_IP_CH_IF_ID) |
| channel->if_id = index; |
| |
| channel->channel_id = index; |
| channel->state = IMEM_CHANNEL_RESERVED; |
| |
| return i; |
| } |
| |
| void ipc_imem_channel_init(struct iosm_imem *ipc_imem, enum ipc_ctype ctype, |
| struct ipc_chnl_cfg chnl_cfg, u32 irq_moderation) |
| { |
| struct ipc_mem_channel *channel; |
| |
| if (chnl_cfg.ul_pipe >= IPC_MEM_MAX_PIPES || |
| chnl_cfg.dl_pipe >= IPC_MEM_MAX_PIPES) { |
| dev_err(ipc_imem->dev, "invalid pipe: ul_pipe=%d, dl_pipe=%d", |
| chnl_cfg.ul_pipe, chnl_cfg.dl_pipe); |
| return; |
| } |
| |
| if (ipc_imem->nr_of_channels >= IPC_MEM_MAX_CHANNELS) { |
| dev_err(ipc_imem->dev, "too many channels"); |
| return; |
| } |
| |
| channel = &ipc_imem->channels[ipc_imem->nr_of_channels]; |
| channel->channel_id = ipc_imem->nr_of_channels; |
| channel->ctype = ctype; |
| channel->index = chnl_cfg.id; |
| channel->net_err_count = 0; |
| channel->state = IMEM_CHANNEL_FREE; |
| ipc_imem->nr_of_channels++; |
| |
| ipc_imem_channel_update(ipc_imem, channel->channel_id, chnl_cfg, |
| IRQ_MOD_OFF); |
| |
| skb_queue_head_init(&channel->ul_list); |
| |
| init_completion(&channel->ul_sem); |
| } |
| |
| void ipc_imem_channel_update(struct iosm_imem *ipc_imem, int id, |
| struct ipc_chnl_cfg chnl_cfg, u32 irq_moderation) |
| { |
| struct ipc_mem_channel *channel; |
| |
| if (id < 0 || id >= ipc_imem->nr_of_channels) { |
| dev_err(ipc_imem->dev, "invalid channel id %d", id); |
| return; |
| } |
| |
| channel = &ipc_imem->channels[id]; |
| |
| if (channel->state != IMEM_CHANNEL_FREE && |
| channel->state != IMEM_CHANNEL_RESERVED) { |
| dev_err(ipc_imem->dev, "invalid channel state %d", |
| channel->state); |
| return; |
| } |
| |
| channel->ul_pipe.nr_of_entries = chnl_cfg.ul_nr_of_entries; |
| channel->ul_pipe.pipe_nr = chnl_cfg.ul_pipe; |
| channel->ul_pipe.is_open = false; |
| channel->ul_pipe.irq = IPC_UL_PIPE_IRQ_VECTOR; |
| channel->ul_pipe.channel = channel; |
| channel->ul_pipe.dir = IPC_MEM_DIR_UL; |
| channel->ul_pipe.accumulation_backoff = chnl_cfg.accumulation_backoff; |
| channel->ul_pipe.irq_moderation = irq_moderation; |
| channel->ul_pipe.buf_size = 0; |
| |
| channel->dl_pipe.nr_of_entries = chnl_cfg.dl_nr_of_entries; |
| channel->dl_pipe.pipe_nr = chnl_cfg.dl_pipe; |
| channel->dl_pipe.is_open = false; |
| channel->dl_pipe.irq = IPC_DL_PIPE_IRQ_VECTOR; |
| channel->dl_pipe.channel = channel; |
| channel->dl_pipe.dir = IPC_MEM_DIR_DL; |
| channel->dl_pipe.accumulation_backoff = chnl_cfg.accumulation_backoff; |
| channel->dl_pipe.irq_moderation = irq_moderation; |
| channel->dl_pipe.buf_size = chnl_cfg.dl_buf_size; |
| } |
| |
| static void ipc_imem_channel_reset(struct iosm_imem *ipc_imem) |
| { |
| int i; |
| |
| for (i = 0; i < ipc_imem->nr_of_channels; i++) { |
| struct ipc_mem_channel *channel; |
| |
| channel = &ipc_imem->channels[i]; |
| |
| ipc_imem_pipe_cleanup(ipc_imem, &channel->dl_pipe); |
| ipc_imem_pipe_cleanup(ipc_imem, &channel->ul_pipe); |
| |
| ipc_imem_channel_free(channel); |
| } |
| } |
| |
| void ipc_imem_pipe_cleanup(struct iosm_imem *ipc_imem, struct ipc_pipe *pipe) |
| { |
| struct sk_buff *skb; |
| |
| /* Force pipe to closed state also when not explicitly closed through |
| * ipc_imem_pipe_close() |
| */ |
| pipe->is_open = false; |
| |
| /* Empty the uplink skb accumulator. */ |
| while ((skb = skb_dequeue(&pipe->channel->ul_list))) |
| ipc_pcie_kfree_skb(ipc_imem->pcie, skb); |
| |
| ipc_protocol_pipe_cleanup(ipc_imem->ipc_protocol, pipe); |
| } |
| |
| /* Send IPC protocol uninit to the modem when Link is active. */ |
| static void ipc_imem_device_ipc_uninit(struct iosm_imem *ipc_imem) |
| { |
| int timeout = IPC_MODEM_UNINIT_TIMEOUT_MS; |
| enum ipc_mem_device_ipc_state ipc_state; |
| |
| /* When PCIe link is up set IPC_UNINIT |
| * of the modem otherwise ignore it when PCIe link down happens. |
| */ |
| if (ipc_pcie_check_data_link_active(ipc_imem->pcie)) { |
| /* set modem to UNINIT |
| * (in case we want to reload the AP driver without resetting |
| * the modem) |
| */ |
| ipc_doorbell_fire(ipc_imem->pcie, IPC_DOORBELL_IRQ_IPC, |
| IPC_MEM_DEVICE_IPC_UNINIT); |
| ipc_state = ipc_mmio_get_ipc_state(ipc_imem->mmio); |
| |
| /* Wait for maximum 30ms to allow the Modem to uninitialize the |
| * protocol. |
| */ |
| while ((ipc_state <= IPC_MEM_DEVICE_IPC_DONT_CARE) && |
| (ipc_state != IPC_MEM_DEVICE_IPC_UNINIT) && |
| (timeout > 0)) { |
| usleep_range(1000, 1250); |
| timeout--; |
| ipc_state = ipc_mmio_get_ipc_state(ipc_imem->mmio); |
| } |
| } |
| } |
| |
| void ipc_imem_cleanup(struct iosm_imem *ipc_imem) |
| { |
| ipc_imem->phase = IPC_P_OFF_REQ; |
| |
| /* forward MDM_NOT_READY to listeners */ |
| ipc_uevent_send(ipc_imem->dev, UEVENT_MDM_NOT_READY); |
| |
| hrtimer_cancel(&ipc_imem->td_alloc_timer); |
| hrtimer_cancel(&ipc_imem->tdupdate_timer); |
| hrtimer_cancel(&ipc_imem->fast_update_timer); |
| hrtimer_cancel(&ipc_imem->startup_timer); |
| |
| /* cancel the workqueue */ |
| cancel_work_sync(&ipc_imem->run_state_worker); |
| |
| if (test_and_clear_bit(FULLY_FUNCTIONAL, &ipc_imem->flag)) { |
| ipc_mux_deinit(ipc_imem->mux); |
| ipc_wwan_deinit(ipc_imem->wwan); |
| ipc_port_deinit(ipc_imem->ipc_port); |
| } |
| |
| if (ipc_imem->ipc_devlink) |
| ipc_devlink_deinit(ipc_imem->ipc_devlink); |
| |
| ipc_imem_device_ipc_uninit(ipc_imem); |
| ipc_imem_channel_reset(ipc_imem); |
| |
| ipc_protocol_deinit(ipc_imem->ipc_protocol); |
| ipc_task_deinit(ipc_imem->ipc_task); |
| |
| kfree(ipc_imem->ipc_task); |
| kfree(ipc_imem->mmio); |
| |
| ipc_imem->phase = IPC_P_OFF; |
| } |
| |
| /* After CP has unblocked the PCIe link, save the start address of the doorbell |
| * scratchpad and prepare the shared memory region. If the flashing to RAM |
| * procedure shall be executed, copy the chip information from the doorbell |
| * scratchtpad to the application buffer and wake up the flash app. |
| */ |
| static int ipc_imem_config(struct iosm_imem *ipc_imem) |
| { |
| enum ipc_phase phase; |
| |
| /* Initialize the semaphore for the blocking read UL/DL transfer. */ |
| init_completion(&ipc_imem->ul_pend_sem); |
| |
| init_completion(&ipc_imem->dl_pend_sem); |
| |
| /* clear internal flags */ |
| ipc_imem->ipc_status = IPC_MEM_DEVICE_IPC_UNINIT; |
| ipc_imem->enter_runtime = 0; |
| |
| phase = ipc_imem_phase_update(ipc_imem); |
| |
| /* Either CP shall be in the power off or power on phase. */ |
| switch (phase) { |
| case IPC_P_ROM: |
| ipc_imem->hrtimer_period = ktime_set(0, 1000 * 1000 * 1000ULL); |
| /* poll execution stage (for delayed start, e.g. NAND) */ |
| if (!hrtimer_active(&ipc_imem->startup_timer)) |
| hrtimer_start(&ipc_imem->startup_timer, |
| ipc_imem->hrtimer_period, |
| HRTIMER_MODE_REL); |
| return 0; |
| |
| case IPC_P_PSI: |
| case IPC_P_EBL: |
| case IPC_P_RUN: |
| /* The initial IPC state is IPC_MEM_DEVICE_IPC_UNINIT. */ |
| ipc_imem->ipc_requested_state = IPC_MEM_DEVICE_IPC_UNINIT; |
| |
| /* Verify the exepected initial state. */ |
| if (ipc_imem->ipc_requested_state == |
| ipc_mmio_get_ipc_state(ipc_imem->mmio)) { |
| ipc_imem_ipc_init_check(ipc_imem); |
| |
| return 0; |
| } |
| dev_err(ipc_imem->dev, |
| "ipc_status(%d) != IPC_MEM_DEVICE_IPC_UNINIT", |
| ipc_mmio_get_ipc_state(ipc_imem->mmio)); |
| break; |
| case IPC_P_CRASH: |
| case IPC_P_CD_READY: |
| dev_dbg(ipc_imem->dev, |
| "Modem is in phase %d, reset Modem to collect CD", |
| phase); |
| return 0; |
| default: |
| dev_err(ipc_imem->dev, "unexpected operation phase %d", phase); |
| break; |
| } |
| |
| complete(&ipc_imem->dl_pend_sem); |
| complete(&ipc_imem->ul_pend_sem); |
| ipc_imem->phase = IPC_P_OFF; |
| return -EIO; |
| } |
| |
| /* Pass the dev ptr to the shared memory driver and request the entry points */ |
| struct iosm_imem *ipc_imem_init(struct iosm_pcie *pcie, unsigned int device_id, |
| void __iomem *mmio, struct device *dev) |
| { |
| struct iosm_imem *ipc_imem = kzalloc(sizeof(*pcie->imem), GFP_KERNEL); |
| enum ipc_mem_exec_stage stage; |
| |
| if (!ipc_imem) |
| return NULL; |
| |
| /* Save the device address. */ |
| ipc_imem->pcie = pcie; |
| ipc_imem->dev = dev; |
| |
| ipc_imem->pci_device_id = device_id; |
| |
| ipc_imem->ev_cdev_write_pending = false; |
| ipc_imem->cp_version = 0; |
| ipc_imem->device_sleep = IPC_HOST_SLEEP_ENTER_SLEEP; |
| |
| /* Reset the max number of configured channels */ |
| ipc_imem->nr_of_channels = 0; |
| |
| /* allocate IPC MMIO */ |
| ipc_imem->mmio = ipc_mmio_init(mmio, ipc_imem->dev); |
| if (!ipc_imem->mmio) { |
| dev_err(ipc_imem->dev, "failed to initialize mmio region"); |
| goto mmio_init_fail; |
| } |
| |
| ipc_imem->ipc_task = kzalloc(sizeof(*ipc_imem->ipc_task), |
| GFP_KERNEL); |
| |
| /* Create tasklet for event handling*/ |
| if (!ipc_imem->ipc_task) |
| goto ipc_task_fail; |
| |
| if (ipc_task_init(ipc_imem->ipc_task)) |
| goto ipc_task_init_fail; |
| |
| ipc_imem->ipc_task->dev = ipc_imem->dev; |
| |
| INIT_WORK(&ipc_imem->run_state_worker, ipc_imem_run_state_worker); |
| |
| ipc_imem->ipc_protocol = ipc_protocol_init(ipc_imem); |
| |
| if (!ipc_imem->ipc_protocol) |
| goto protocol_init_fail; |
| |
| /* The phase is set to power off. */ |
| ipc_imem->phase = IPC_P_OFF; |
| |
| hrtimer_init(&ipc_imem->startup_timer, CLOCK_MONOTONIC, |
| HRTIMER_MODE_REL); |
| ipc_imem->startup_timer.function = ipc_imem_startup_timer_cb; |
| |
| hrtimer_init(&ipc_imem->tdupdate_timer, CLOCK_MONOTONIC, |
| HRTIMER_MODE_REL); |
| ipc_imem->tdupdate_timer.function = ipc_imem_td_update_timer_cb; |
| |
| hrtimer_init(&ipc_imem->fast_update_timer, CLOCK_MONOTONIC, |
| HRTIMER_MODE_REL); |
| ipc_imem->fast_update_timer.function = ipc_imem_fast_update_timer_cb; |
| |
| hrtimer_init(&ipc_imem->td_alloc_timer, CLOCK_MONOTONIC, |
| HRTIMER_MODE_REL); |
| ipc_imem->td_alloc_timer.function = ipc_imem_td_alloc_timer_cb; |
| |
| if (ipc_imem_config(ipc_imem)) { |
| dev_err(ipc_imem->dev, "failed to initialize the imem"); |
| goto imem_config_fail; |
| } |
| |
| stage = ipc_mmio_get_exec_stage(ipc_imem->mmio); |
| if (stage == IPC_MEM_EXEC_STAGE_BOOT) { |
| /* Alloc and Register devlink */ |
| ipc_imem->ipc_devlink = ipc_devlink_init(ipc_imem); |
| if (!ipc_imem->ipc_devlink) { |
| dev_err(ipc_imem->dev, "Devlink register failed"); |
| goto imem_config_fail; |
| } |
| |
| if (ipc_flash_link_establish(ipc_imem)) |
| goto devlink_channel_fail; |
| } |
| return ipc_imem; |
| devlink_channel_fail: |
| ipc_devlink_deinit(ipc_imem->ipc_devlink); |
| imem_config_fail: |
| hrtimer_cancel(&ipc_imem->td_alloc_timer); |
| hrtimer_cancel(&ipc_imem->fast_update_timer); |
| hrtimer_cancel(&ipc_imem->tdupdate_timer); |
| hrtimer_cancel(&ipc_imem->startup_timer); |
| protocol_init_fail: |
| cancel_work_sync(&ipc_imem->run_state_worker); |
| ipc_task_deinit(ipc_imem->ipc_task); |
| ipc_task_init_fail: |
| kfree(ipc_imem->ipc_task); |
| ipc_task_fail: |
| kfree(ipc_imem->mmio); |
| mmio_init_fail: |
| kfree(ipc_imem); |
| return NULL; |
| } |
| |
| void ipc_imem_irq_process(struct iosm_imem *ipc_imem, int irq) |
| { |
| /* Debounce IPC_EV_IRQ. */ |
| if (ipc_imem && !ipc_imem->ev_irq_pending[irq]) { |
| ipc_imem->ev_irq_pending[irq] = true; |
| ipc_task_queue_send_task(ipc_imem, ipc_imem_tq_irq_cb, irq, |
| NULL, 0, false); |
| } |
| } |
| |
| void ipc_imem_td_update_timer_suspend(struct iosm_imem *ipc_imem, bool suspend) |
| { |
| ipc_imem->td_update_timer_suspended = suspend; |
| } |
| |
| /* Verify the CP execution state, copy the chip info, |
| * change the execution phase to ROM |
| */ |
| static int ipc_imem_devlink_trigger_chip_info_cb(struct iosm_imem *ipc_imem, |
| int arg, void *msg, |
| size_t msgsize) |
| { |
| enum ipc_mem_exec_stage stage; |
| struct sk_buff *skb; |
| int rc = -EINVAL; |
| size_t size; |
| |
| /* Test the CP execution state. */ |
| stage = ipc_mmio_get_exec_stage(ipc_imem->mmio); |
| if (stage != IPC_MEM_EXEC_STAGE_BOOT) { |
| dev_err(ipc_imem->dev, |
| "Execution_stage: expected BOOT, received = %X", stage); |
| goto trigger_chip_info_fail; |
| } |
| /* Allocate a new sk buf for the chip info. */ |
| size = ipc_imem->mmio->chip_info_size; |
| if (size > IOSM_CHIP_INFO_SIZE_MAX) |
| goto trigger_chip_info_fail; |
| |
| skb = ipc_pcie_alloc_local_skb(ipc_imem->pcie, GFP_ATOMIC, size); |
| if (!skb) { |
| dev_err(ipc_imem->dev, "exhausted skbuf kernel DL memory"); |
| rc = -ENOMEM; |
| goto trigger_chip_info_fail; |
| } |
| /* Copy the chip info characters into the ipc_skb. */ |
| ipc_mmio_copy_chip_info(ipc_imem->mmio, skb_put(skb, size), size); |
| /* First change to the ROM boot phase. */ |
| dev_dbg(ipc_imem->dev, "execution_stage[%X] eq. BOOT", stage); |
| ipc_imem->phase = ipc_imem_phase_update(ipc_imem); |
| ipc_imem_sys_devlink_notify_rx(ipc_imem->ipc_devlink, skb); |
| rc = 0; |
| trigger_chip_info_fail: |
| return rc; |
| } |
| |
| int ipc_imem_devlink_trigger_chip_info(struct iosm_imem *ipc_imem) |
| { |
| return ipc_task_queue_send_task(ipc_imem, |
| ipc_imem_devlink_trigger_chip_info_cb, |
| 0, NULL, 0, true); |
| } |