blob: 6471202906b880f7c17b7d0a34073b4600f9c0ba [file] [log] [blame]
/*
* Copyright (c) 2020 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/drivers/ipm.h>
#include <zephyr/arch/common/sys_io.h>
#include <soc.h>
#include "ipm_cavs_idc.h"
#ifdef CONFIG_SCHED_IPI_SUPPORTED
extern void z_sched_ipi(void);
#endif
struct cavs_idc_data {
ipm_callback_t cb;
void *user_data;
};
static struct cavs_idc_data cavs_idc_device_data;
static void cavs_idc_isr(const struct device *dev)
{
struct cavs_idc_data *drv_data = dev->data;
uint32_t i, id;
void *ext;
uint32_t idctfc;
uint32_t curr_cpu_id = arch_curr_cpu()->id;
#ifdef CONFIG_SCHED_IPI_SUPPORTED
bool do_sched_ipi = false;
#endif
for (i = 0; i < CONFIG_MP_NUM_CPUS; i++) {
if (i == curr_cpu_id) {
/* skip current core */
continue;
}
idctfc = idc_read(IPC_IDCTFC(i), curr_cpu_id);
if ((idctfc & IPC_IDCTFC_BUSY) == 0) {
/* No message from this core */
continue;
}
/* Extract the message */
id = idctfc & IPC_IDCTFC_MSG_MASK;
switch (id) {
#ifdef CONFIG_SCHED_IPI_SUPPORTED
case IPM_CAVS_IDC_MSG_SCHED_IPI_ID:
do_sched_ipi = true;
break;
#endif
default:
if (drv_data->cb != NULL) {
ext = UINT_TO_POINTER(
idc_read(IPC_IDCTEFC(i), curr_cpu_id) &
IPC_IDCTEFC_MSG_MASK);
drv_data->cb(dev, drv_data->user_data, id, ext);
}
break;
}
/* Reset busy bit by writing to it */
idctfc |= IPC_IDCTFC_BUSY;
idc_write(IPC_IDCTFC(i), curr_cpu_id, idctfc);
}
#ifdef CONFIG_SCHED_IPI_SUPPORTED
if (do_sched_ipi) {
z_sched_ipi();
}
#endif
}
static int cavs_idc_send(const struct device *dev, int wait, uint32_t id,
const void *data, int size)
{
uint32_t curr_cpu_id = arch_curr_cpu()->id;
uint32_t ext = POINTER_TO_UINT(data);
uint32_t reg;
bool busy;
int i;
if ((wait != 0) || (size != 0)) {
return -ENOTSUP;
}
/* Check if any core is still busy */
busy = false;
for (i = 0; i < CONFIG_MP_NUM_CPUS; i++) {
if (i == curr_cpu_id) {
/* skip current core */
continue;
}
reg = idc_read(IPC_IDCITC(i), curr_cpu_id);
if ((reg & IPC_IDCITC_BUSY) != 0) {
busy = true;
break;
}
}
/* Can't send if busy */
if (busy) {
return -EBUSY;
}
id &= IPC_IDCITC_MSG_MASK;
ext &= IPC_IDCIETC_MSG_MASK;
ext |= IPC_IDCIETC_DONE; /* always clear DONE bit */
for (i = 0; i < CONFIG_MP_NUM_CPUS; i++) {
if (i == curr_cpu_id) {
/* skip current core */
continue;
}
idc_write(IPC_IDCIETC(i), curr_cpu_id, ext);
idc_write(IPC_IDCITC(i), curr_cpu_id, id | IPC_IDCITC_BUSY);
}
return 0;
}
static int cavs_idc_max_data_size_get(const struct device *dev)
{
ARG_UNUSED(dev);
/* IDC can send an ID (of 31 bits, the header) and
* another data of 30 bits (the extension). It cannot
* send a whole message over. Best we can do is send
* a 4-byte aligned pointer.
*
* So return 0 here for max data size.
*/
return 0;
}
static uint32_t cavs_idc_max_id_val_get(const struct device *dev)
{
ARG_UNUSED(dev);
return IPM_CAVS_IDC_ID_MASK;
}
static void cavs_idc_register_callback(const struct device *dev,
ipm_callback_t cb,
void *user_data)
{
struct cavs_idc_data *drv_data = dev->data;
drv_data->cb = cb;
drv_data->user_data = user_data;
}
static int cavs_idc_set_enabled(const struct device *dev, int enable)
{
int i, j;
uint32_t mask;
#ifdef CONFIG_SCHED_IPI_SUPPORTED
/* With scheduler IPI, IDC must always be enabled. */
if (enable == 0) {
return -ENOTSUP;
}
#endif
for (i = 0; i < CONFIG_MP_NUM_CPUS; i++) {
mask = 0;
if (enable) {
for (j = 0; j < CONFIG_MP_NUM_CPUS; j++) {
if (i == j) {
continue;
}
mask |= IPC_IDCCTL_IDCTBIE(j);
}
}
idc_write(IPC_IDCCTL, i, mask);
/* FIXME: when we have API to enable IRQ on specific core. */
sys_set_bit(DT_REG_ADDR(DT_NODELABEL(cavs_intc0)) + 0x04 +
CAVS_ICTL_INT_CPU_OFFSET(i),
CAVS_IRQ_NUMBER(DT_INST_IRQN(0)));
}
return 0;
}
static int cavs_idc_init(const struct device *dev)
{
IRQ_CONNECT(DT_INST_IRQN(0),
DT_INST_IRQ(0, priority),
cavs_idc_isr, DEVICE_DT_INST_GET(0), 0);
irq_enable(DT_INST_IRQN(0));
return 0;
}
static const struct ipm_driver_api cavs_idc_driver_api = {
.send = cavs_idc_send,
.register_callback = cavs_idc_register_callback,
.max_data_size_get = cavs_idc_max_data_size_get,
.max_id_val_get = cavs_idc_max_id_val_get,
.set_enabled = cavs_idc_set_enabled,
};
DEVICE_DT_INST_DEFINE(0, &cavs_idc_init, NULL,
&cavs_idc_device_data, NULL,
PRE_KERNEL_2, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&cavs_idc_driver_api);
#ifdef CONFIG_SCHED_IPI_SUPPORTED
int cavs_idc_smp_init(const struct device *dev)
{
/* Enable IDC for scheduler IPI */
cavs_idc_set_enabled(dev, 1);
return 0;
}
#ifndef CONFIG_SMP_BOOT_DELAY
SYS_INIT(cavs_idc_smp_init, SMP, 0);
#endif
#endif