|  | /* | 
|  | * Copyright (c) 2020-2023 Intel Corporation | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT intel_sedi_ipm | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/device.h> | 
|  | #include <zephyr/pm/device.h> | 
|  | #include <zephyr/drivers/ipm.h> | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(ipm_sedi, CONFIG_IPM_LOG_LEVEL); | 
|  |  | 
|  | #include "ipm_sedi.h" | 
|  |  | 
|  | extern void sedi_ipc_isr(IN sedi_ipc_t ipc_device); | 
|  |  | 
|  | static void set_ipm_dev_busy(const struct device *dev, bool is_write) | 
|  | { | 
|  | struct ipm_sedi_context *ipm = dev->data; | 
|  | unsigned int key = irq_lock(); | 
|  |  | 
|  | atomic_set_bit(&ipm->status, is_write ? IPM_WRITE_BUSY_BIT : IPM_READ_BUSY_BIT); | 
|  | pm_device_busy_set(dev); | 
|  | irq_unlock(key); | 
|  | } | 
|  |  | 
|  | static void clear_ipm_dev_busy(const struct device *dev, bool is_write) | 
|  | { | 
|  | struct ipm_sedi_context *ipm = dev->data; | 
|  | unsigned int key = irq_lock(); | 
|  |  | 
|  | atomic_clear_bit(&ipm->status, is_write ? IPM_WRITE_BUSY_BIT : IPM_READ_BUSY_BIT); | 
|  | if ((!atomic_test_bit(&ipm->status, IPM_WRITE_BUSY_BIT)) | 
|  | && (!atomic_test_bit(&ipm->status, IPM_READ_BUSY_BIT))) { | 
|  | pm_device_busy_clear(dev); | 
|  | } | 
|  | irq_unlock(key); | 
|  | } | 
|  |  | 
|  | static void ipm_event_dispose(IN sedi_ipc_t device, IN uint32_t event, INOUT void *params) | 
|  | { | 
|  | const struct device *dev = (const struct device *)params; | 
|  | struct ipm_sedi_context *ipm = dev->data; | 
|  | uint32_t drbl_in = 0, len; | 
|  |  | 
|  | LOG_DBG("dev: %u, event: %u", device, event); | 
|  | switch (event) { | 
|  | case SEDI_IPC_EVENT_MSG_IN: | 
|  | if (ipm->rx_msg_notify_cb != NULL) { | 
|  | set_ipm_dev_busy(dev, false); | 
|  | sedi_ipc_read_dbl(device, &drbl_in); | 
|  | len = IPC_HEADER_GET_LENGTH(drbl_in); | 
|  | sedi_ipc_read_msg(device, ipm->incoming_data_buf, len); | 
|  | ipm->rx_msg_notify_cb(dev, | 
|  | ipm->rx_msg_notify_cb_data, | 
|  | drbl_in, ipm->incoming_data_buf); | 
|  | } else { | 
|  | LOG_WRN("no handler for ipm new msg"); | 
|  | } | 
|  | break; | 
|  | case SEDI_IPC_EVENT_MSG_PEER_ACKED: | 
|  | if (atomic_test_bit(&ipm->status, IPM_WRITE_IN_PROC_BIT)) { | 
|  | k_sem_give(&ipm->device_write_msg_sem); | 
|  | } else { | 
|  | LOG_WRN("no sending in progress, got an ack"); | 
|  | } | 
|  | break; | 
|  | default: | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int ipm_init(const struct device *dev) | 
|  | { | 
|  | /* allocate resource and context*/ | 
|  | const struct ipm_sedi_config_t *info = dev->config; | 
|  | sedi_ipc_t device = info->ipc_device; | 
|  | struct ipm_sedi_context *ipm = dev->data; | 
|  |  | 
|  | info->irq_config(); | 
|  | k_sem_init(&ipm->device_write_msg_sem, 0, 1); | 
|  | k_mutex_init(&ipm->device_write_lock); | 
|  | ipm->status = 0; | 
|  |  | 
|  | sedi_ipc_init(device, ipm_event_dispose, (void *)dev); | 
|  | atomic_set_bit(&ipm->status, IPM_PEER_READY_BIT); | 
|  | LOG_DBG("ipm driver initialized on device: %p", dev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ipm_send_isr(const struct device *dev, | 
|  | uint32_t drbl, | 
|  | const void *msg, | 
|  | int msg_size) | 
|  | { | 
|  | const struct ipm_sedi_config_t *info = dev->config; | 
|  | sedi_ipc_t device = info->ipc_device; | 
|  | uint32_t drbl_acked = 0; | 
|  |  | 
|  | sedi_ipc_write_msg(device, (uint8_t *)msg, | 
|  | (uint32_t)msg_size); | 
|  | sedi_ipc_write_dbl(device, drbl); | 
|  | do { | 
|  | sedi_ipc_read_ack_drbl(device, &drbl_acked); | 
|  | } while ((drbl_acked & BIT(IPC_BUSY_BIT)) == 0); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int ipm_sedi_send(const struct device *dev, | 
|  | int wait, | 
|  | uint32_t drbl, | 
|  | const void *msg, | 
|  | int msg_size) | 
|  | { | 
|  | __ASSERT((dev != NULL), "bad params\n"); | 
|  | const struct ipm_sedi_config_t *info = dev->config; | 
|  | struct ipm_sedi_context *ipm = dev->data; | 
|  | sedi_ipc_t device = info->ipc_device; | 
|  | int ret, sedi_ret; | 
|  |  | 
|  | /* check params, check status */ | 
|  | if ((msg_size > IPC_DATA_LEN_MAX) || ((msg_size > 0) && (msg == NULL)) || | 
|  | ((drbl & BIT(IPC_BUSY_BIT)) == 0)) { | 
|  | LOG_ERR("bad params when sending ipm msg on device: %p", dev); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (wait == 0) { | 
|  | LOG_ERR("not support no wait mode when sending ipm msg"); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | if (k_is_in_isr()) { | 
|  | return ipm_send_isr(dev, drbl, msg, msg_size); | 
|  | } | 
|  |  | 
|  | k_mutex_lock(&ipm->device_write_lock, K_FOREVER); | 
|  | set_ipm_dev_busy(dev, true); | 
|  |  | 
|  | if (!atomic_test_bit(&ipm->status, IPM_PEER_READY_BIT)) { | 
|  | LOG_WRN("peer is not ready"); | 
|  | ret = -EBUSY; | 
|  | goto write_err; | 
|  | } | 
|  |  | 
|  | /* write data regs */ | 
|  | if (msg_size > 0) { | 
|  | sedi_ret = sedi_ipc_write_msg(device, (uint8_t *)msg, | 
|  | (uint32_t)msg_size); | 
|  | if (sedi_ret != SEDI_DRIVER_OK) { | 
|  | LOG_ERR("ipm write data fail on device: %p", dev); | 
|  | ret = -EBUSY; | 
|  | goto write_err; | 
|  | } | 
|  | } | 
|  |  | 
|  | atomic_set_bit(&ipm->status, IPM_WRITE_IN_PROC_BIT); | 
|  | /* write drbl regs to interrupt peer*/ | 
|  | sedi_ret = sedi_ipc_write_dbl(device, drbl); | 
|  |  | 
|  | if (sedi_ret != SEDI_DRIVER_OK) { | 
|  | LOG_ERR("ipm write doorbell fail on device: %p", dev); | 
|  | ret = -EBUSY; | 
|  | goto func_out; | 
|  | } | 
|  |  | 
|  | /* wait for busy-bit-consumed interrupt */ | 
|  | ret = k_sem_take(&ipm->device_write_msg_sem, K_MSEC(IPM_TIMEOUT_MS)); | 
|  | if (ret) { | 
|  | LOG_WRN("ipm write timeout on device: %p", dev); | 
|  | sedi_ipc_write_dbl(device, 0); | 
|  | } | 
|  |  | 
|  | func_out: | 
|  | atomic_clear_bit(&ipm->status, IPM_WRITE_IN_PROC_BIT); | 
|  |  | 
|  | write_err: | 
|  | clear_ipm_dev_busy(dev, true); | 
|  | k_mutex_unlock(&ipm->device_write_lock); | 
|  | if (ret == 0) { | 
|  | LOG_DBG("ipm wrote a new message on device: %p, drbl=%08x", | 
|  | dev, drbl); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void ipm_sedi_register_callback(const struct device *dev, ipm_callback_t cb, | 
|  | void *user_data) | 
|  | { | 
|  | __ASSERT((dev != NULL), "bad params\n"); | 
|  |  | 
|  | struct ipm_sedi_context *ipm = dev->data; | 
|  |  | 
|  | if (cb == NULL) { | 
|  | LOG_ERR("bad params when add ipm callback on device: %p", dev); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (ipm->rx_msg_notify_cb == NULL) { | 
|  | ipm->rx_msg_notify_cb = cb; | 
|  | ipm->rx_msg_notify_cb_data = user_data; | 
|  | } else { | 
|  | LOG_ERR("ipm rx callback already exists on device: %p", dev); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void ipm_sedi_complete(const struct device *dev) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | __ASSERT((dev != NULL), "bad params\n"); | 
|  |  | 
|  | const struct ipm_sedi_config_t *info = dev->config; | 
|  | sedi_ipc_t device = info->ipc_device; | 
|  |  | 
|  | ret = sedi_ipc_send_ack_drbl(device, 0); | 
|  | if (ret != SEDI_DRIVER_OK) { | 
|  | LOG_ERR("ipm send ack drl fail on device: %p", dev); | 
|  | } | 
|  |  | 
|  | clear_ipm_dev_busy(dev, false); | 
|  | } | 
|  |  | 
|  | static int ipm_sedi_get_max_data_size(const struct device *ipmdev) | 
|  | { | 
|  | ARG_UNUSED(ipmdev); | 
|  | return IPC_DATA_LEN_MAX; | 
|  | } | 
|  |  | 
|  | static uint32_t ipm_sedi_get_max_id(const struct device *ipmdev) | 
|  | { | 
|  | ARG_UNUSED(ipmdev); | 
|  | return UINT32_MAX; | 
|  | } | 
|  |  | 
|  | static int ipm_sedi_set_enable(const struct device *dev, int enable) | 
|  | { | 
|  | __ASSERT((dev != NULL), "bad params\n"); | 
|  |  | 
|  | const struct ipm_sedi_config_t *info = dev->config; | 
|  |  | 
|  | if (enable) { | 
|  | irq_enable(info->irq_num); | 
|  | } else { | 
|  | irq_disable(info->irq_num); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_PM_DEVICE) | 
|  | static int ipm_power_ctrl(const struct device *dev, | 
|  | enum pm_device_action action) | 
|  | { | 
|  | return 0; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static const struct ipm_driver_api ipm_funcs = { | 
|  | .send = ipm_sedi_send, | 
|  | .register_callback = ipm_sedi_register_callback, | 
|  | .max_data_size_get = ipm_sedi_get_max_data_size, | 
|  | .max_id_val_get = ipm_sedi_get_max_id, | 
|  | .complete = ipm_sedi_complete, | 
|  | .set_enabled = ipm_sedi_set_enable | 
|  | }; | 
|  |  | 
|  | #define IPM_SEDI_DEV_DEFINE(n)						\ | 
|  | static struct ipm_sedi_context ipm_data_##n;			\ | 
|  | static void ipm_##n##_irq_config(void);				\ | 
|  | static const struct ipm_sedi_config_t ipm_config_##n = {	\ | 
|  | .ipc_device = DT_INST_PROP(n, peripheral_id),		\ | 
|  | .irq_num = DT_INST_IRQN(n),				\ | 
|  | .irq_config = ipm_##n##_irq_config,			\ | 
|  | };								\ | 
|  | static void ipm_##n##_irq_config(void)				\ | 
|  | {								\ | 
|  | IRQ_CONNECT(DT_INST_IRQN(n),				\ | 
|  | DT_INST_IRQ(n, priority), sedi_ipc_isr,	\ | 
|  | DT_INST_PROP(n, peripheral_id),		\ | 
|  | DT_INST_IRQ(n, sense));			\ | 
|  | }								\ | 
|  | PM_DEVICE_DT_DEFINE(DT_NODELABEL(ipm##n), ipm_power_ctrl);	\ | 
|  | DEVICE_DT_INST_DEFINE(n,					\ | 
|  | &ipm_init,				\ | 
|  | PM_DEVICE_DT_GET(DT_NODELABEL(ipm##n)),   \ | 
|  | &ipm_data_##n,				\ | 
|  | &ipm_config_##n,				\ | 
|  | POST_KERNEL,				\ | 
|  | 0,					\ | 
|  | &ipm_funcs); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(IPM_SEDI_DEV_DEFINE) |