Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2020, Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT intel_adsp_mailbox

#include <device.h>
#include <soc.h>
#include <drivers/ipm.h>

#include <platform/mailbox.h>
#include <platform/shim.h>

#include <logging/log.h>
LOG_MODULE_REGISTER(ipm_adsp, CONFIG_IPM_LOG_LEVEL);

/*
 * With IPM data might be transferred by using ID field for simple
 * messages or via shared memory. Following parameters specify maximum
 * values for ID and DATA.
 */
#define IPM_INTEL_ADSP_MAX_DATA_SIZE		256
#define IPM_INTEL_ADSP_MAX_ID_VAL		IPC_DIPCI_MSG_MASK

/* Mailbox ADSP -> Host */
#define IPM_INTEL_ADSP_MAILBOX_OUT		MAILBOX_DSPBOX_BASE
#define IPM_INTEL_ADSP_MAILBOX_OUT_SIZE	MAILBOX_DSPBOX_SIZE

BUILD_ASSERT(IPM_INTEL_ADSP_MAILBOX_OUT_SIZE >= IPM_INTEL_ADSP_MAX_DATA_SIZE);

/* Mailbox Host -> ADSP */
#define IPM_INTEL_ADSP_MAILBOX_IN		MAILBOX_HOSTBOX_BASE
#define IPM_INTEL_ADSP_MAILBOX_IN_SIZE	MAILBOX_HOSTBOX_SIZE

BUILD_ASSERT(IPM_INTEL_ADSP_MAILBOX_IN_SIZE >= IPM_INTEL_ADSP_MAX_DATA_SIZE);

struct ipm_adsp_config {
	void (*irq_config_func)(const struct device *dev);
};

struct ipm_adsp_data {
	ipm_callback_t callback;
	void *user_data;
};

static void ipm_adsp_isr(const struct device *dev)
{
	const struct ipm_adsp_data *data = dev->data;
	uint32_t dipcctl, dipcie, dipct;

	dipct = ipc_read(IPC_DIPCT);
	dipcie = ipc_read(IPC_DIPCIE);
	dipcctl = ipc_read(IPC_DIPCCTL);

	LOG_DBG("dipct 0x%x dipcie 0x%x dipcctl 0x%x", dipct, dipcie, dipcctl);

	/*
	 * DSP core has received a message from IPC initiator (HOST).
	 * Initiator set Doorbel mechanism (HIPCI_BUSY bit).
	 */
	if (dipct & IPC_DIPCT_BUSY && dipcctl & IPC_DIPCCTL_IPCTBIE) {
		/* Mask BUSY interrupt */
		ipc_write(IPC_DIPCCTL, dipcctl & ~IPC_DIPCCTL_IPCTBIE);

		if (data->callback) {
			SOC_DCACHE_INVALIDATE((void *)IPM_INTEL_ADSP_MAILBOX_IN,
					      IPM_INTEL_ADSP_MAILBOX_IN_SIZE);
			/* Use zero copy */
			data->callback(dev, data->user_data,
				       dipct & IPC_DIPCI_MSG_MASK,
				       (void *)IPM_INTEL_ADSP_MAILBOX_IN);
		}

		/*
		 * Clear BUSY indicating to the Host that the message is
		 * received, and DSP is ready to accept another message
		 */
		ipc_write(IPC_DIPCT, ipc_read(IPC_DIPCT) | IPC_DIPCT_BUSY);

		/* Unmask BUSY interrupts */
		ipc_write(IPC_DIPCCTL,
			  ipc_read(IPC_DIPCCTL) | IPC_DIPCCTL_IPCTBIE);
	}

	/*
	 * DSP Initiator DONE indicates that we got reply from HOST that message
	 * is received and we can send another message.
	 */
	if (dipcie & IPC_DIPCIE_DONE && dipcctl & IPC_DIPCCTL_IPCIDIE) {
		/* Mask DONE interrupt */
		ipc_write(IPC_DIPCCTL,
			  ipc_read(IPC_DIPCCTL) & ~IPC_DIPCCTL_IPCIDIE);

		/* Clear DONE bit, Notify HOST that operation is completed */
		ipc_write(IPC_DIPCIE,
			  ipc_read(IPC_DIPCIE) | IPC_DIPCIE_DONE);

		/* Unmask DONE interrupt */
		ipc_write(IPC_DIPCCTL,
			  ipc_read(IPC_DIPCCTL) | IPC_DIPCCTL_IPCIDIE);

		LOG_DBG("Not handled: IPC_DIPCCTL_IPCIDIE");

		/* TODO: implement queued message sending if needed */
	}
}

static int ipm_adsp_send(const struct device *dev, int wait, uint32_t id,
			 const void *data, int size)
{
	LOG_DBG("Send: id %d data %p size %d", id, data, size);
	LOG_HEXDUMP_DBG(data, size, "send");

	if (id > IPM_INTEL_ADSP_MAX_ID_VAL) {
		return -EINVAL;
	}

	if (size > IPM_INTEL_ADSP_MAX_DATA_SIZE) {
		return -EMSGSIZE;
	}

	if (wait) {
		while (ipc_read(IPC_DIPCI) & IPC_DIPCI_BUSY) {
		}
	} else {
		if (ipc_read(IPC_DIPCI) & IPC_DIPCI_BUSY) {
			LOG_DBG("Busy: previous message is not handled");
			return -EBUSY;
		}
	}

	memcpy((void *)IPM_INTEL_ADSP_MAILBOX_OUT, data, size);
	SOC_DCACHE_FLUSH((void *)IPM_INTEL_ADSP_MAILBOX_OUT, size);

	ipc_write(IPC_DIPCIE, 0);
	ipc_write(IPC_DIPCI, IPC_DIPCI_BUSY | id);

	return 0;
}

static void ipm_adsp_register_callback(const struct device *dev,
				       ipm_callback_t cb,
				       void *user_data)
{
	struct ipm_adsp_data *data = dev->data;

	data->callback = cb;
	data->user_data = user_data;
}

static int ipm_adsp_max_data_size_get(const struct device *dev)
{
	ARG_UNUSED(dev);

	LOG_DBG("dev %p", dev);

	return IPM_INTEL_ADSP_MAX_DATA_SIZE;
}

static uint32_t ipm_adsp_max_id_val_get(const struct device *dev)
{
	ARG_UNUSED(dev);

	LOG_DBG("dev %p", dev);

	return IPM_INTEL_ADSP_MAX_ID_VAL;
}

static int ipm_adsp_set_enabled(const struct device *dev, int enable)
{
	LOG_DBG("dev %p", dev);

	/* enable IPC interrupts from host */
	ipc_write(IPC_DIPCCTL, IPC_DIPCCTL_IPCIDIE | IPC_DIPCCTL_IPCTBIE);

	return 0;
}

static int ipm_adsp_init(const struct device *dev)
{
	const struct ipm_adsp_config *config = dev->config;

	LOG_DBG("dev %p", dev);

	config->irq_config_func(dev);

	return 0;
}

static const struct ipm_driver_api ipm_adsp_driver_api = {
	.send = ipm_adsp_send,
	.register_callback = ipm_adsp_register_callback,
	.max_data_size_get = ipm_adsp_max_data_size_get,
	.max_id_val_get = ipm_adsp_max_id_val_get,
	.set_enabled = ipm_adsp_set_enabled,
};

static void ipm_adsp_config_func(const struct device *dev);

static const struct ipm_adsp_config ipm_adsp_config = {
	.irq_config_func = ipm_adsp_config_func,
};

static struct ipm_adsp_data ipm_adsp_data;

DEVICE_DT_INST_DEFINE(0,
		    &ipm_adsp_init,
		    NULL,
		    &ipm_adsp_data, &ipm_adsp_config,
		    PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
		    &ipm_adsp_driver_api);

static void ipm_adsp_config_func(const struct device *dev)
{
	IRQ_CONNECT(DT_INST_IRQN(0),
		    DT_INST_IRQ(0, priority),
		    ipm_adsp_isr, DEVICE_DT_INST_GET(0), 0);

	irq_enable(DT_INST_IRQN(0));
}