blob: b360739175065f3052a1dc0ab41a3cab693470ac [file] [log] [blame]
/* ipm_quark_se.c - Quark SE mailbox driver */
/*
* Copyright (c) 2015 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <kernel.h>
#include <zephyr/types.h>
#include <string.h>
#include <device.h>
#include <init.h>
#include <ipm.h>
#include <arch/cpu.h>
#include <misc/printk.h>
#include <misc/__assert.h>
#include <errno.h>
#include "ipm_quark_se.h"
/* We have a single ISR for all channels, so in order to properly handle
* messages we need to figure out which device object corresponds to
* in incoming channel
*/
static struct device *device_by_channel[QUARK_SE_IPM_CHANNELS];
static u32_t inbound_channels;
const struct ipm_driver_api ipm_quark_se_api_funcs;
static u32_t quark_se_ipm_sts_get(void)
{
return sys_read32(QUARK_SE_IPM_CHALL_STS) & inbound_channels;
}
static void set_channel_irq_state(int channel, int enable)
{
mem_addr_t addr = QUARK_SE_IPM_MASK;
int bit = channel + QUARK_SE_IPM_MASK_START_BIT;
if (enable) {
sys_clear_bit(addr, bit);
} else {
sys_set_bit(addr, bit);
}
}
/* Interrupt handler, gets messages on all incoming enabled mailboxes */
void quark_se_ipm_isr(void *param)
{
int channel;
int sts, bit;
struct device *d;
const struct quark_se_ipm_config_info *config;
struct quark_se_ipm_driver_data *driver_data;
volatile struct quark_se_ipm *ipm;
unsigned int key;
ARG_UNUSED(param);
while ((sts = quark_se_ipm_sts_get())) {
__ASSERT(sts, "spurious IPM interrupt");
bit = find_msb_set(sts) - 1;
channel = bit / 2;
d = device_by_channel[channel];
__ASSERT(d, "got IRQ on channel with no IPM device");
config = d->config->config_info;
driver_data = d->driver_data;
ipm = config->ipm;
__ASSERT(driver_data->callback,
"enabled IPM channel with no callback");
driver_data->callback(driver_data->callback_ctx,
ipm->ctrl & QUARK_SE_IPM_CTRL_CTRL_MASK,
&ipm->data);
key = irq_lock();
/* Clear the interrupt bit */
ipm->sts = QUARK_SE_IPM_STS_IRQ_BIT;
/* Clear channel status bit */
ipm->sts = QUARK_SE_IPM_STS_STS_BIT;
/* Wait for the above register writes to clear the channel
* to propagate to the global channel status register
*/
while (quark_se_ipm_sts_get() & (0x3 << (channel * 2))) {
/* Busy-wait */
}
irq_unlock(key);
}
}
static int quark_se_ipm_send(struct device *d, int wait, u32_t id,
const void *data, int size)
{
const struct quark_se_ipm_config_info *config = d->config->config_info;
volatile struct quark_se_ipm *ipm = config->ipm;
u32_t data32[4]; /* Until we change API to u32_t array */
unsigned int flags;
int i;
if (id > QUARK_SE_IPM_MAX_ID_VAL) {
return -EINVAL;
}
if (config->direction != QUARK_SE_IPM_OUTBOUND) {
return -EINVAL;
}
if (size > QUARK_SE_IPM_DATA_REGS * sizeof(u32_t)) {
return -EMSGSIZE;
}
flags = irq_lock();
if (ipm->sts & QUARK_SE_IPM_STS_STS_BIT) {
irq_unlock(flags);
return -EBUSY;
}
/* Actual message is passing using 32 bits registers */
memcpy(data32, data, size);
for (i = 0; i < ARRAY_SIZE(data32); ++i) {
ipm->data[i] = data32[i];
}
ipm->ctrl = id | QUARK_SE_IPM_CTRL_IRQ_BIT;
/* Wait for HW to set the sts bit */
while (!(ipm->sts & QUARK_SE_IPM_STS_STS_BIT)) {
}
irq_unlock(flags);
if (wait) {
/* Loop until remote clears the status bit */
while (ipm->sts & QUARK_SE_IPM_STS_STS_BIT) {
}
}
return 0;
}
static int quark_se_ipm_max_data_size_get(struct device *d)
{
ARG_UNUSED(d);
return QUARK_SE_IPM_DATA_REGS * sizeof(u32_t);
}
static u32_t quark_se_ipm_max_id_val_get(struct device *d)
{
ARG_UNUSED(d);
return QUARK_SE_IPM_MAX_ID_VAL;
}
static void quark_se_ipm_register_callback(struct device *d, ipm_callback_t cb,
void *context)
{
struct quark_se_ipm_driver_data *driver_data = d->driver_data;
driver_data->callback = cb;
driver_data->callback_ctx = context;
}
static int quark_se_ipm_set_enabled(struct device *d, int enable)
{
const struct quark_se_ipm_config_info *config_info =
d->config->config_info;
if (config_info->direction != QUARK_SE_IPM_INBOUND) {
return -EINVAL;
}
set_channel_irq_state(config_info->channel, enable);
return 0;
}
const struct ipm_driver_api ipm_quark_se_api_funcs = {
.send = quark_se_ipm_send,
.register_callback = quark_se_ipm_register_callback,
.max_data_size_get = quark_se_ipm_max_data_size_get,
.max_id_val_get = quark_se_ipm_max_id_val_get,
.set_enabled = quark_se_ipm_set_enabled
};
int quark_se_ipm_controller_initialize(struct device *d)
{
const struct quark_se_ipm_controller_config_info *config =
d->config->config_info;
#if CONFIG_IPM_QUARK_SE_MASTER
int i;
/* Mask all mailbox interrupts, we'll enable them
* individually later. Clear out any pending messages
*/
sys_write32(0xFFFFFFFF, QUARK_SE_IPM_MASK);
for (i = 0; i < QUARK_SE_IPM_CHANNELS; ++i) {
volatile struct quark_se_ipm *ipm = QUARK_SE_IPM(i);
ipm->sts = 0U;
}
#endif
if (config->controller_init) {
return config->controller_init();
}
return 0;
}
int quark_se_ipm_initialize(struct device *d)
{
const struct quark_se_ipm_config_info *config = d->config->config_info;
device_by_channel[config->channel] = d;
if (config->direction == QUARK_SE_IPM_INBOUND) {
inbound_channels |= (0x3 << (config->channel * 2));
}
return 0;
}