blob: ad24007f760af970dbaacda58aba0008ccfcd14d [file] [log] [blame]
/*
* Copyright (c) 2018, Intel Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Intel Corporation nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Author: Tomasz Lauda <tomasz.lauda@linux.intel.com>
*/
/**
* \file arch/xtensa/smp/include/arch/idc.h
* \brief Xtensa SMP architecture IDC header file
* \authors Tomasz Lauda <tomasz.lauda@linux.intel.com>
*/
#ifndef __ARCH_IDC_H__
#define __ARCH_IDC_H__
#include <xtos-structs.h>
#include <arch/cpu.h>
#include <platform/interrupt.h>
#include <platform/platform.h>
#include <sof/alloc.h>
#include <sof/lock.h>
#include <sof/trace.h>
/** \brief IDC trace function. */
#define trace_idc(__e) trace_event(TRACE_CLASS_IDC, __e)
/** \brief IDC trace value function. */
#define tracev_idc(__e) tracev_event(TRACE_CLASS_IDC, __e)
/** \brief IDC trace error function. */
#define trace_idc_error(__e) trace_error(TRACE_CLASS_IDC, __e)
/** \brief IDC send blocking flag. */
#define IDC_BLOCKING 0
/** \brief IDC send non-blocking flag. */
#define IDC_NON_BLOCKING 1
/** \brief IDC send timeout in cycles. */
#define IDC_TIMEOUT 800000
/** \brief ROM wake version parsed by ROM during core wake up. */
#define IDC_ROM_WAKE_VERSION 0x2
/** \brief ROM control version parsed by ROM during core wake up. */
#define IDC_ROM_CONTROL_VERSION 0x1
// TODO: refactor below defines after universal IDC message template
// will be defined and ready
/** \brief Power up message header. */
#define IDC_POWER_UP_MESSAGE \
(IDC_ROM_WAKE_VERSION | (IDC_ROM_CONTROL_VERSION << 24))
/** \brief Power up message extension. */
#define IDC_POWER_UP_EXTENSION (SOF_TEXT_START >> 2)
/** \brief Power down message header. */
#define IDC_POWER_DOWN_MESSAGE 0x7FFFFFFF
/** \brief IDC message. */
struct idc_msg {
uint32_t header; /**< header value */
uint32_t extension; /**< extension value */
uint32_t core; /**< core id */
};
/** \brief IDC data. */
struct idc {
spinlock_t lock; /**< lock mechanism */
uint32_t busy_bit_mask; /**< busy interrupt mask */
uint32_t done_bit_mask; /**< done interrupt mask */
uint32_t msg_pending; /**< is message pending */
struct idc_msg received_msg; /**< received message */
};
extern void cpu_power_down_core(void);
/**
* \brief Returns IDC data.
* \return Pointer to pointer of IDC data.
*/
static inline struct idc **idc_get(void)
{
struct core_context *ctx = (struct core_context *)cpu_read_threadptr();
return &ctx->idc;
}
static inline void idc_enable_interrupts(int target_core, int source_core)
{
idc_write(IPC_IDCCTL, target_core,
IPC_IDCCTL_IDCTBIE(source_core));
platform_interrupt_unmask(PLATFORM_IDC_INTERRUPT(target_core), 0);
}
/**
* \brief IDC interrupt handler.
* \param[in,out] arg Pointer to IDC data.
*/
static void idc_irq_handler(void *arg)
{
struct idc *idc = arg;
int core = arch_cpu_get_id();
uint32_t idctfc;
uint32_t idctefc;
uint32_t idcietc;
uint32_t i;
tracev_idc("IRQ");
for (i = 0; i < PLATFORM_CORE_COUNT; i++) {
idctfc = idc_read(IPC_IDCTFC(i), core);
if (idctfc & IPC_IDCTFC_BUSY) {
trace_idc("Nms");
/* disable BUSY interrupt */
idc_write(IPC_IDCCTL, core, idc->done_bit_mask);
idc->received_msg.core = i;
idc->received_msg.header =
idctfc & IPC_IDCTFC_MSG_MASK;
idctefc = idc_read(IPC_IDCTEFC(i), core);
idc->received_msg.extension =
idctefc & IPC_IDCTEFC_MSG_MASK;
idc->msg_pending = 1;
break;
}
}
for (i = 0; i < PLATFORM_CORE_COUNT; i++) {
idcietc = idc_read(IPC_IDCIETC(i), core);
if (idcietc & IPC_IDCIETC_DONE) {
tracev_idc("Rpy");
idc_write(IPC_IDCIETC(i), core,
idcietc | IPC_IDCIETC_DONE);
break;
}
}
}
/**
* \brief Sends IDC message.
* \param[in,out] msg Pointer to IDC message.
* \param[in] mode Is message blocking or not.
* \return Error code.
*/
static inline int arch_idc_send_msg(struct idc_msg *msg, uint32_t mode)
{
struct idc *idc = *idc_get();
int core = arch_cpu_get_id();
int ret = 0;
uint32_t timeout = 0;
uint32_t idcietc;
uint32_t flags;
tracev_idc("Msg");
spin_lock_irq(&idc->lock, flags);
idc_write(IPC_IDCIETC(msg->core), core, msg->extension);
idc_write(IPC_IDCITC(msg->core), core, msg->header | IPC_IDCITC_BUSY);
if (mode == IDC_BLOCKING) {
do {
idelay(PLATFORM_DEFAULT_DELAY);
timeout += PLATFORM_DEFAULT_DELAY;
idcietc = idc_read(IPC_IDCIETC(msg->core), core);
} while (!(idcietc & IPC_IDCIETC_DONE) &&
timeout < IDC_TIMEOUT);
if (timeout >= IDC_TIMEOUT) {
trace_idc_error("eS0");
ret = -ETIME;
}
}
spin_unlock_irq(&idc->lock, flags);
return ret;
}
/**
* \brief Executes IDC message based on type.
* \param[in,out] msg Pointer to IDC message.
* \return Error status.
*/
static inline int32_t idc_cmd(struct idc_msg *msg)
{
/* right now we only handle power down */
/* TODO: universal implementation */
if (msg->header == IDC_POWER_DOWN_MESSAGE)
cpu_power_down_core();
return 0;
}
/**
* \brief Handles received IDC message.
* \param[in,out] idc Pointer to IDC data.
*/
static inline void idc_do_cmd(struct idc *idc)
{
int core = arch_cpu_get_id();
int initiator = idc->received_msg.core;
trace_idc("Cmd");
idc_cmd(&idc->received_msg);
idc->msg_pending = 0;
/* clear BUSY bit */
idc_write(IPC_IDCTFC(initiator), core,
idc_read(IPC_IDCTFC(initiator), core) | IPC_IDCTFC_BUSY);
/* enable BUSY interrupt */
idc_write(IPC_IDCCTL, core, idc->busy_bit_mask | idc->done_bit_mask);
}
/**
* \brief Checks for pending IDC messages.
*/
static inline void arch_idc_process_msg_queue(void)
{
struct idc *idc = *idc_get();
if (idc->msg_pending)
idc_do_cmd(idc);
}
/**
* \brief Returns BUSY interrupt mask based on core id.
* \param[in] core Core id.
* \return BUSY interrupt mask.
*/
static inline uint32_t idc_get_busy_bit_mask(int core)
{
uint32_t busy_mask = 0;
int i;
if (core == PLATFORM_MASTER_CORE_ID) {
for (i = 0; i < PLATFORM_CORE_COUNT; i++) {
if (i != PLATFORM_MASTER_CORE_ID)
busy_mask |= IPC_IDCCTL_IDCTBIE(i);
}
} else {
busy_mask = IPC_IDCCTL_IDCTBIE(PLATFORM_MASTER_CORE_ID);
}
return busy_mask;
}
/**
* \brief Returns DONE interrupt mask based on core id.
* \param[in] core Core id.
* \return DONE interrupt mask.
*/
static inline uint32_t idc_get_done_bit_mask(int core)
{
uint32_t done_mask = 0;
int i;
if (core == PLATFORM_MASTER_CORE_ID) {
for (i = 0; i < PLATFORM_CORE_COUNT; i++) {
if (i != PLATFORM_MASTER_CORE_ID)
done_mask |= IPC_IDCCTL_IDCIDIE(i);
}
} else {
done_mask = 0;
}
return done_mask;
}
/**
* \brief Initializes IDC data and registers for interrupt.
*/
static inline void arch_idc_init(void)
{
int core = arch_cpu_get_id();
trace_idc("IDI");
/* initialize idc data */
struct idc **idc = idc_get();
*idc = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(**idc));
spinlock_init(&((*idc)->lock));
(*idc)->busy_bit_mask = idc_get_busy_bit_mask(core);
(*idc)->done_bit_mask = idc_get_done_bit_mask(core);
/* configure interrupt */
interrupt_register(PLATFORM_IDC_INTERRUPT(core),
idc_irq_handler, *idc);
interrupt_enable(PLATFORM_IDC_INTERRUPT(core));
/* enable BUSY and DONE (only for master core) interrupts */
idc_write(IPC_IDCCTL, core,
(*idc)->busy_bit_mask | (*idc)->done_bit_mask);
}
/**
* \brief Frees IDC data and unregisters interrupt.
*/
static inline void idc_free(void)
{
int core = arch_cpu_get_id();
int i = 0;
uint32_t idctfc;
trace_idc("IDF");
/* disable and unregister interrupt */
interrupt_disable(PLATFORM_IDC_INTERRUPT(core));
interrupt_unregister(PLATFORM_IDC_INTERRUPT(core));
/* clear BUSY bits */
for (i = 0; i < PLATFORM_CORE_COUNT; i++) {
idctfc = idc_read(IPC_IDCTFC(i), core);
if (idctfc & IPC_IDCTFC_BUSY)
idc_write(IPC_IDCTFC(i), core, idctfc);
}
rfree(*idc_get());
}
#endif