blob: 04a48029c6db4b1c97eae37d939440a749abc42e [file] [log] [blame]
/*
* 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)