diff options
Diffstat (limited to 'drivers/net/wwan/iosm/iosm_ipc_protocol.c')
| -rw-r--r-- | drivers/net/wwan/iosm/iosm_ipc_protocol.c | 283 | 
1 files changed, 283 insertions, 0 deletions
diff --git a/drivers/net/wwan/iosm/iosm_ipc_protocol.c b/drivers/net/wwan/iosm/iosm_ipc_protocol.c new file mode 100644 index 000000000000..834d8b146a94 --- /dev/null +++ b/drivers/net/wwan/iosm/iosm_ipc_protocol.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020-21 Intel Corporation. + */ + +#include "iosm_ipc_imem.h" +#include "iosm_ipc_protocol.h" +#include "iosm_ipc_protocol_ops.h" +#include "iosm_ipc_pm.h" +#include "iosm_ipc_task_queue.h" + +int ipc_protocol_tq_msg_send(struct iosm_protocol *ipc_protocol, +			     enum ipc_msg_prep_type msg_type, +			     union ipc_msg_prep_args *prep_args, +			     struct ipc_rsp *response) +{ +	int index = ipc_protocol_msg_prep(ipc_protocol->imem, msg_type, +					  prep_args); + +	/* Store reference towards caller specified response in response ring +	 * and signal CP +	 */ +	if (index >= 0 && index < IPC_MEM_MSG_ENTRIES) { +		ipc_protocol->rsp_ring[index] = response; +		ipc_protocol_msg_hp_update(ipc_protocol->imem); +	} + +	return index; +} + +/* Callback for message send */ +static int ipc_protocol_tq_msg_send_cb(struct iosm_imem *ipc_imem, int arg, +				       void *msg, size_t size) +{ +	struct ipc_call_msg_send_args *send_args = msg; +	struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol; + +	return ipc_protocol_tq_msg_send(ipc_protocol, send_args->msg_type, +					send_args->prep_args, +					send_args->response); +} + +/* Remove reference to a response. This is typically used when a requestor timed + * out and is no longer interested in the response. + */ +static int ipc_protocol_tq_msg_remove(struct iosm_imem *ipc_imem, int arg, +				      void *msg, size_t size) +{ +	struct iosm_protocol *ipc_protocol = ipc_imem->ipc_protocol; + +	ipc_protocol->rsp_ring[arg] = NULL; +	return 0; +} + +int ipc_protocol_msg_send(struct iosm_protocol *ipc_protocol, +			  enum ipc_msg_prep_type prep, +			  union ipc_msg_prep_args *prep_args) +{ +	struct ipc_call_msg_send_args send_args; +	unsigned int exec_timeout; +	struct ipc_rsp response; +	int index; + +	exec_timeout = (ipc_protocol_get_ap_exec_stage(ipc_protocol) == +					IPC_MEM_EXEC_STAGE_RUN ? +				IPC_MSG_COMPLETE_RUN_DEFAULT_TIMEOUT : +				IPC_MSG_COMPLETE_BOOT_DEFAULT_TIMEOUT); + +	/* Trap if called from non-preemptible context */ +	might_sleep(); + +	response.status = IPC_MEM_MSG_CS_INVALID; +	init_completion(&response.completion); + +	send_args.msg_type = prep; +	send_args.prep_args = prep_args; +	send_args.response = &response; + +	/* Allocate and prepare message to be sent in tasklet context. +	 * A positive index returned form tasklet_call references the message +	 * in case it needs to be cancelled when there is a timeout. +	 */ +	index = ipc_task_queue_send_task(ipc_protocol->imem, +					 ipc_protocol_tq_msg_send_cb, 0, +					 &send_args, 0, true); + +	if (index < 0) { +		dev_err(ipc_protocol->dev, "msg %d failed", prep); +		return index; +	} + +	/* Wait for the device to respond to the message */ +	switch (wait_for_completion_timeout(&response.completion, +					    msecs_to_jiffies(exec_timeout))) { +	case 0: +		/* Timeout, there was no response from the device. +		 * Remove the reference to the local response completion +		 * object as we are no longer interested in the response. +		 */ +		ipc_task_queue_send_task(ipc_protocol->imem, +					 ipc_protocol_tq_msg_remove, index, +					 NULL, 0, true); +		dev_err(ipc_protocol->dev, "msg timeout"); +		ipc_uevent_send(ipc_protocol->pcie->dev, UEVENT_MDM_TIMEOUT); +		break; +	default: +		/* We got a response in time; check completion status: */ +		if (response.status != IPC_MEM_MSG_CS_SUCCESS) { +			dev_err(ipc_protocol->dev, +				"msg completion status error %d", +				response.status); +			return -EIO; +		} +	} + +	return 0; +} + +static int ipc_protocol_msg_send_host_sleep(struct iosm_protocol *ipc_protocol, +					    u32 state) +{ +	union ipc_msg_prep_args prep_args = { +		.sleep.target = 0, +		.sleep.state = state, +	}; + +	return ipc_protocol_msg_send(ipc_protocol, IPC_MSG_PREP_SLEEP, +				     &prep_args); +} + +void ipc_protocol_doorbell_trigger(struct iosm_protocol *ipc_protocol, +				   u32 identifier) +{ +	ipc_pm_signal_hpda_doorbell(&ipc_protocol->pm, identifier, true); +} + +bool ipc_protocol_pm_dev_sleep_handle(struct iosm_protocol *ipc_protocol) +{ +	u32 ipc_status = ipc_protocol_get_ipc_status(ipc_protocol); +	u32 requested; + +	if (ipc_status != IPC_MEM_DEVICE_IPC_RUNNING) { +		dev_err(ipc_protocol->dev, +			"irq ignored, CP IPC state is %d, should be RUNNING", +			ipc_status); + +		/* Stop further processing. */ +		return false; +	} + +	/* Get a copy of the requested PM state by the device and the local +	 * device PM state. +	 */ +	requested = ipc_protocol_pm_dev_get_sleep_notification(ipc_protocol); + +	return ipc_pm_dev_slp_notification(&ipc_protocol->pm, requested); +} + +static int ipc_protocol_tq_wakeup_dev_slp(struct iosm_imem *ipc_imem, int arg, +					  void *msg, size_t size) +{ +	struct iosm_pm *ipc_pm = &ipc_imem->ipc_protocol->pm; + +	/* Wakeup from device sleep if it is not ACTIVE */ +	ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_HS, true); + +	ipc_pm_trigger(ipc_pm, IPC_PM_UNIT_HS, false); + +	return 0; +} + +void ipc_protocol_s2idle_sleep(struct iosm_protocol *ipc_protocol, bool sleep) +{ +	ipc_pm_set_s2idle_sleep(&ipc_protocol->pm, sleep); +} + +bool ipc_protocol_suspend(struct iosm_protocol *ipc_protocol) +{ +	if (!ipc_pm_prepare_host_sleep(&ipc_protocol->pm)) +		goto err; + +	ipc_task_queue_send_task(ipc_protocol->imem, +				 ipc_protocol_tq_wakeup_dev_slp, 0, NULL, 0, +				 true); + +	if (!ipc_pm_wait_for_device_active(&ipc_protocol->pm)) { +		ipc_uevent_send(ipc_protocol->pcie->dev, UEVENT_MDM_TIMEOUT); +		goto err; +	} + +	/* Send the sleep message for sync sys calls. */ +	dev_dbg(ipc_protocol->dev, "send TARGET_HOST, ENTER_SLEEP"); +	if (ipc_protocol_msg_send_host_sleep(ipc_protocol, +					     IPC_HOST_SLEEP_ENTER_SLEEP)) { +		/* Sending ENTER_SLEEP message failed, we are still active */ +		ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_ACTIVE; +		goto err; +	} + +	ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_SLEEP; +	return true; +err: +	return false; +} + +bool ipc_protocol_resume(struct iosm_protocol *ipc_protocol) +{ +	if (!ipc_pm_prepare_host_active(&ipc_protocol->pm)) +		return false; + +	dev_dbg(ipc_protocol->dev, "send TARGET_HOST, EXIT_SLEEP"); +	if (ipc_protocol_msg_send_host_sleep(ipc_protocol, +					     IPC_HOST_SLEEP_EXIT_SLEEP)) { +		ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_SLEEP; +		return false; +	} + +	ipc_protocol->pm.host_pm_state = IPC_MEM_HOST_PM_ACTIVE; + +	return true; +} + +struct iosm_protocol *ipc_protocol_init(struct iosm_imem *ipc_imem) +{ +	struct iosm_protocol *ipc_protocol = +		kzalloc(sizeof(*ipc_protocol), GFP_KERNEL); +	struct ipc_protocol_context_info *p_ci; +	u64 addr; + +	if (!ipc_protocol) +		return NULL; + +	ipc_protocol->dev = ipc_imem->dev; +	ipc_protocol->pcie = ipc_imem->pcie; +	ipc_protocol->imem = ipc_imem; +	ipc_protocol->p_ap_shm = NULL; +	ipc_protocol->phy_ap_shm = 0; + +	ipc_protocol->old_msg_tail = 0; + +	ipc_protocol->p_ap_shm = +		pci_alloc_consistent(ipc_protocol->pcie->pci, +				     sizeof(*ipc_protocol->p_ap_shm), +				     &ipc_protocol->phy_ap_shm); + +	if (!ipc_protocol->p_ap_shm) { +		dev_err(ipc_protocol->dev, "pci shm alloc error"); +		kfree(ipc_protocol); +		return NULL; +	} + +	/* Prepare the context info for CP. */ +	addr = ipc_protocol->phy_ap_shm; +	p_ci = &ipc_protocol->p_ap_shm->ci; +	p_ci->device_info_addr = +		addr + offsetof(struct ipc_protocol_ap_shm, device_info); +	p_ci->head_array = +		addr + offsetof(struct ipc_protocol_ap_shm, head_array); +	p_ci->tail_array = +		addr + offsetof(struct ipc_protocol_ap_shm, tail_array); +	p_ci->msg_head = addr + offsetof(struct ipc_protocol_ap_shm, msg_head); +	p_ci->msg_tail = addr + offsetof(struct ipc_protocol_ap_shm, msg_tail); +	p_ci->msg_ring_addr = +		addr + offsetof(struct ipc_protocol_ap_shm, msg_ring); +	p_ci->msg_ring_entries = cpu_to_le16(IPC_MEM_MSG_ENTRIES); +	p_ci->msg_irq_vector = IPC_MSG_IRQ_VECTOR; +	p_ci->device_info_irq_vector = IPC_DEVICE_IRQ_VECTOR; + +	ipc_mmio_set_contex_info_addr(ipc_imem->mmio, addr); + +	ipc_pm_init(ipc_protocol); + +	return ipc_protocol; +} + +void ipc_protocol_deinit(struct iosm_protocol *proto) +{ +	pci_free_consistent(proto->pcie->pci, sizeof(*proto->p_ap_shm), +			    proto->p_ap_shm, proto->phy_ap_shm); + +	ipc_pm_deinit(proto); +	kfree(proto); +}  | 
