/**
 * @file
 *
 * @brief Generic low-level inter-processor mailbox communication API.
 */

/*
 * Copyright (c) 2015 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef ZEPHYR_INCLUDE_DRIVERS_IPM_H_
#define ZEPHYR_INCLUDE_DRIVERS_IPM_H_

/**
 * @brief IPM Interface
 * @defgroup ipm_interface IPM Interface
 * @ingroup io_interfaces
 * @{
 */

#include <zephyr/kernel.h>
#include <zephyr/device.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @typedef ipm_callback_t
 * @brief Callback API for incoming IPM messages
 *
 * These callbacks execute in interrupt context. Therefore, use only
 * interrupt-safe APIS. Registration of callbacks is done via
 * @a ipm_register_callback
 *
 * @param ipmdev Driver instance
 * @param user_data Pointer to some private data provided at registration
 *        time.
 * @param id Message type identifier.
 * @param data Message data pointer. The correct amount of data to read out
 *        must be inferred using the message id/upper level protocol.
 */
typedef void (*ipm_callback_t)(const struct device *ipmdev, void *user_data,
			       uint32_t id, volatile void *data);

/**
 * @typedef ipm_send_t
 * @brief Callback API to send IPM messages
 *
 * See @a ipm_send() for argument definitions.
 */
typedef int (*ipm_send_t)(const struct device *ipmdev, int wait, uint32_t id,
			  const void *data, int size);
/**
 * @typedef ipm_max_data_size_get_t
 * @brief Callback API to get maximum data size
 *
 * See @a ipm_max_data_size_get() for argument definitions.
 */
typedef int (*ipm_max_data_size_get_t)(const struct device *ipmdev);

/**
 * @typedef ipm_max_id_val_get_t
 * @brief Callback API to get the ID's maximum value
 *
 * See @a ipm_max_id_val_get() for argument definitions.
 */
typedef uint32_t (*ipm_max_id_val_get_t)(const struct device *ipmdev);

/**
 * @typedef ipm_register_callback_t
 * @brief Callback API upon registration
 *
 * See @a ipm_register_callback() for argument definitions.
 */
typedef void (*ipm_register_callback_t)(const struct device *port,
					ipm_callback_t cb,
					void *user_data);

/**
 * @typedef ipm_set_enabled_t
 * @brief Callback API upon enablement of interrupts
 *
 * See @a ipm_set_enabled() for argument definitions.
 */
typedef int (*ipm_set_enabled_t)(const struct device *ipmdev, int enable);

/**
 * @typedef ipm_complete_t
 * @brief Callback API upon command completion
 *
 * See @a ipm_complete() for argument definitions.
 */
typedef void (*ipm_complete_t)(const struct device *ipmdev);

__subsystem struct ipm_driver_api {
	ipm_send_t send;
	ipm_register_callback_t register_callback;
	ipm_max_data_size_get_t max_data_size_get;
	ipm_max_id_val_get_t max_id_val_get;
	ipm_set_enabled_t set_enabled;
#ifdef CONFIG_IPM_CALLBACK_ASYNC
	ipm_complete_t complete;
#endif
};

/**
 * @brief Try to send a message over the IPM device.
 *
 * A message is considered consumed once the remote interrupt handler
 * finishes. If there is deferred processing on the remote side,
 * or if outgoing messages must be queued and wait on an
 * event/semaphore, a high-level driver can implement that.
 *
 * There are constraints on how much data can be sent or the maximum value
 * of id. Use the @a ipm_max_data_size_get and @a ipm_max_id_val_get routines
 * to determine them.
 *
 * The @a size parameter is used only on the sending side to determine
 * the amount of data to put in the message registers. It is not passed along
 * to the receiving side. The upper-level protocol dictates the amount of
 * data read back.
 *
 * @param ipmdev Driver instance
 * @param wait If nonzero, busy-wait for remote to consume the message. The
 *	       message is considered consumed once the remote interrupt handler
 *	       finishes. If there is deferred processing on the remote side,
 *	       or you would like to queue outgoing messages and wait on an
 *	       event/semaphore, you can implement that in a high-level driver
 * @param id Message identifier. Values are constrained by
 *        @a ipm_max_data_size_get since many boards only allow for a
 *        subset of bits in a 32-bit register to store the ID.
 * @param data Pointer to the data sent in the message.
 * @param size Size of the data.
 *
 * @retval -EBUSY    If the remote hasn't yet read the last data sent.
 * @retval -EMSGSIZE If the supplied data size is unsupported by the driver.
 * @retval -EINVAL   If there was a bad parameter, such as: too-large id value.
 *                   or the device isn't an outbound IPM channel.
 * @retval 0         On success.
 */
__syscall int ipm_send(const struct device *ipmdev, int wait, uint32_t id,
		       const void *data, int size);

static inline int z_impl_ipm_send(const struct device *ipmdev, int wait,
				  uint32_t id,
				  const void *data, int size)
{
	const struct ipm_driver_api *api =
		(const struct ipm_driver_api *)ipmdev->api;

	return api->send(ipmdev, wait, id, data, size);
}

/**
 * @brief Register a callback function for incoming messages.
 *
 * @param ipmdev Driver instance pointer.
 * @param cb Callback function to execute on incoming message interrupts.
 * @param user_data Application-specific data pointer which will be passed
 *        to the callback function when executed.
 */
static inline void ipm_register_callback(const struct device *ipmdev,
					 ipm_callback_t cb, void *user_data)
{
	const struct ipm_driver_api *api =
		(const struct ipm_driver_api *)ipmdev->api;

	api->register_callback(ipmdev, cb, user_data);
}

/**
 * @brief Return the maximum number of bytes possible in an outbound message.
 *
 * IPM implementations vary on the amount of data that can be sent in a
 * single message since the data payload is typically stored in registers.
 *
 * @param ipmdev Driver instance pointer.
 *
 * @return Maximum possible size of a message in bytes.
 */
__syscall int ipm_max_data_size_get(const struct device *ipmdev);

static inline int z_impl_ipm_max_data_size_get(const struct device *ipmdev)
{
	const struct ipm_driver_api *api =
		(const struct ipm_driver_api *)ipmdev->api;

	return api->max_data_size_get(ipmdev);
}


/**
 * @brief Return the maximum id value possible in an outbound message.
 *
 * Many IPM implementations store the message's ID in a register with
 * some bits reserved for other uses.
 *
 * @param ipmdev Driver instance pointer.
 *
 * @return Maximum possible value of a message ID.
 */
__syscall uint32_t ipm_max_id_val_get(const struct device *ipmdev);

static inline uint32_t z_impl_ipm_max_id_val_get(const struct device *ipmdev)
{
	const struct ipm_driver_api *api =
		(const struct ipm_driver_api *)ipmdev->api;

	return api->max_id_val_get(ipmdev);
}

/**
 * @brief Enable interrupts and callbacks for inbound channels.
 *
 * @param ipmdev Driver instance pointer.
 * @param enable Set to 0 to disable and to nonzero to enable.
 *
 * @retval 0       On success.
 * @retval -EINVAL If it isn't an inbound channel.
 */
__syscall int ipm_set_enabled(const struct device *ipmdev, int enable);

static inline int z_impl_ipm_set_enabled(const struct device *ipmdev,
					 int enable)
{
	const struct ipm_driver_api *api =
		(const struct ipm_driver_api *)ipmdev->api;

	return api->set_enabled(ipmdev, enable);
}

/**
 * @brief Signal asynchronous command completion
 *
 * Some IPM backends have an ability to deliver a command
 * asynchronously.  The callback will be invoked in interrupt context,
 * but the message (including the provided data pointer) will stay
 * "active" and unacknowledged until later code (presumably in thread
 * mode) calls ipm_complete().
 *
 * This function is, obviously, a noop on drivers without async
 * support.
 *
 * @param ipmdev Driver instance pointer.
 */
__syscall void ipm_complete(const struct device *ipmdev);

static inline void z_impl_ipm_complete(const struct device *ipmdev)
{
#ifdef CONFIG_IPM_CALLBACK_ASYNC
	const struct ipm_driver_api *api =
		(const struct ipm_driver_api *)ipmdev->api;

	if (api->complete != NULL) {
		api->complete(ipmdev);
	}
#endif
}

#ifdef __cplusplus
}
#endif

/**
 * @}
 */

#include <syscalls/ipm.h>

#endif /* ZEPHYR_INCLUDE_DRIVERS_IPM_H_ */
