blob: 7251af9171ff58434b84185bf5916060d1327022 [file] [log] [blame]
/*
* Copyright (c) 2016, NVIDIA CORPORATION.
* Copright 2019, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the GNU General Public License version 2. Note that NO WARRANTY is provided.
* See "LICENSE_GPLv2.txt" for details.
*
* @TAG(DATA61_GPL)
*/
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <platsupport/pmem.h>
#include <platsupport/fdt.h>
#include <tx2bpmp/hsp.h>
#include <utils/util.h>
/* Register holds information about the number of
* shared mailboxes, shared semaphores etc. */
#define HSP_INT_DIMENSION_OFFSET 0x380
#define HSP_INT_DIMENSION_SM_SHIFT 0
#define HSP_INT_DIMENSION_SS_SHIFT 4
#define HSP_INT_DIMENSION_AS_SHIFT 8
#define HSP_INT_DIMENSION_NUM_MASK 0xf
#define HSP_DOORBELL_BLOCK_STRIDE 0x100
#define HSP_BITMAP_TZ_SECURE_SHFIT 0
#define HSP_BITMAP_TZ_NONSECURE_SHIFT 16
typedef struct tx2_hsp_priv {
ps_io_ops_t *io_ops;
void *hsp_base;
void *doorbell_base;
pmem_region_t tx2_hsp_region;
} tx2_hsp_priv_t;
enum dbell_reg_offset {
DBELL_TRIGGER = 0x0,
DBELL_ENABLE = 0x4,
DBELL_RAW = 0x8,
DBELL_PENDING = 0xc
};
enum dbell_bitmap_offset {
CCPLEX_BIT = BIT(1),
DPMU_BIT = BIT(2),
BPMP_BIT = BIT(3),
SPE_BIT = BIT(4),
CPE_BIT = BIT(5),
SCE_BIT = CPE_BIT,
DMA_BIT = BIT(6),
TSECA_BIT = BIT(7),
TSECB_BIT = BIT(8),
JTAGM_BIT = BIT(9),
CSITE_BIT = BIT(10),
APE_BIT = BIT(11)
};
static bool check_doorbell_id_is_valid(enum tx2_doorbell_id db_id)
{
if (CCPLEX_PM_DBELL <= db_id && db_id <= APE_DBELL) {
return true;
}
return false;
}
static uint32_t *hsp_get_doorbell_register(tx2_hsp_priv_t *hsp, enum tx2_doorbell_id db_id,
enum dbell_reg_offset offset)
{
assert(hsp);
assert(DBELL_TRIGGER <= offset && offset <= DBELL_PENDING);
return hsp->doorbell_base + db_id * HSP_DOORBELL_BLOCK_STRIDE + offset;
}
static int hsp_destroy(void *data)
{
tx2_hsp_priv_t *hsp_priv = data;
/* The doorbell base is just an offset from the hsp base, so we only need
* to deallocate the hsp base */
if (hsp_priv->hsp_base) {
ps_io_unmap(&hsp_priv->io_ops->io_mapper, hsp_priv->hsp_base, hsp_priv->tx2_hsp_region.length);
}
ps_io_ops_t *temp_ops = hsp_priv->io_ops;
ZF_LOGF_IF(ps_free(&temp_ops->malloc_ops, sizeof(*hsp_priv), hsp_priv),
"Failed to de-allocate the private data for HSP");
return 0;
}
static int hsp_doorbell_ring(void *data, enum tx2_doorbell_id db_id)
{
if (!check_doorbell_id_is_valid(db_id)) {
ZF_LOGE("Invalid doorbell ID!");
return -EINVAL;
}
tx2_hsp_priv_t *hsp_priv = data;
/* Write any value to the trigger register to 'ring' the doorbell */
uint32_t *trigger_reg = hsp_get_doorbell_register(hsp_priv, db_id, DBELL_TRIGGER);
assert(trigger_reg);
*trigger_reg = 1;
return 0;
}
static int hsp_doorbell_check(void *data, enum tx2_doorbell_id db_id)
{
if (!check_doorbell_id_is_valid(db_id)) {
ZF_LOGE("Invalid doorbell ID!");
return -EINVAL;
}
tx2_hsp_priv_t *hsp_priv = data;
/* Checking if the doorbell has been 'rung' requires checking for proper
* bit in the bitfield. The bitfield is also split into TrustZone secure
* and TZ non-secure. Refer to Figure 75 in Section 14.8.5 for further details. */
uint32_t *pending_reg = hsp_get_doorbell_register(hsp_priv, db_id, DBELL_PENDING);
enum dbell_bitmap_offset bitmap_offset;
switch (db_id) {
case CCPLEX_PM_DBELL:
case CCPLEX_TZ_UNSECURE_DBELL:
case CCPLEX_TZ_SECURE_DBELL:
bitmap_offset = CCPLEX_BIT;
break;
case BPMP_DBELL:
bitmap_offset = BPMP_BIT;
break;
case SPE_DBELL:
bitmap_offset = SPE_BIT;
break;
case SCE_DBELL:
bitmap_offset = SCE_BIT;
break;
case APE_DBELL:
bitmap_offset = APE_BIT;
break;
default:
ZF_LOGF("We shouldn't get here, doorbell ID is %d", db_id);
}
/* Usermode isn't in TrustZone secure, so we just default to TZ non-secure */
int is_pending = *pending_reg & (bitmap_offset << HSP_BITMAP_TZ_NONSECURE_SHIFT);
if (is_pending) {
*pending_reg &= ~(bitmap_offset << HSP_BITMAP_TZ_NONSECURE_SHIFT);
}
return (is_pending != 0);
}
static int allocate_register_callback(pmem_region_t pmem, unsigned curr_num, size_t num_regs, void *token)
{
assert(token != NULL);
tx2_hsp_priv_t *hsp_priv = token;
/* There's only one register region to map, map it in */
assert(num_regs == 1 && curr_num == 0);
hsp_priv->hsp_base = ps_pmem_map(hsp_priv->io_ops, pmem, false, PS_MEM_NORMAL);
if (hsp_priv->hsp_base == NULL) {
return -EIO;
}
hsp_priv->tx2_hsp_region = pmem;
return 0;
}
int tx2_hsp_init(ps_io_ops_t *io_ops, tx2_hsp_t *hsp, const char *path)
{
if (!io_ops || !hsp) {
ZF_LOGE("Arguments are NULL!");
return -EINVAL;
}
int error = 0;
tx2_hsp_priv_t *hsp_priv = NULL;
error = ps_calloc(&io_ops->malloc_ops, 1, sizeof(*hsp_priv), (void **) &hsp_priv);
if (error) {
ZF_LOGE("Failed to allocate memory for private data for the HSP");
return -ENOMEM;
}
hsp_priv->io_ops = io_ops;
ps_fdt_cookie_t *cookie = NULL;
error = ps_fdt_read_path(&io_ops->io_fdt, &io_ops->malloc_ops, path, &cookie);
if (error) {
ZF_LOGE("Failed to find %s in device tree", path);
return -ENODEV;
}
/* walk the registers and allocate them */
error = ps_fdt_walk_registers(&io_ops->io_fdt, cookie, allocate_register_callback, hsp_priv);
if (error) {
ZF_LOGE("Failed to walk fdt node");
return -ENODEV;
}
if (!hsp_priv->hsp_base) {
ZF_LOGE("Failed to map tx2 HSP module");
ZF_LOGF_IF(ps_free(&io_ops->malloc_ops, sizeof(*hsp_priv), hsp_priv),
"Failed to clean-up after a failed initialisation for the HSP");
return -ENOMEM;
}
error = ps_fdt_cleanup_cookie(&io_ops->malloc_ops, cookie);
if (error) {
return -ENODEV;
}
/* Get the base addr of the doorbell
* Section 14.8.5: All doorbell registers are in a single page, doorbell
* {db} has a register range starting at DB{db}_BASE = HSP_{inst}_BASE +
* (1+ nSM/2 + nSS + nAS) * 64 KiB + {db} * 0x100. */
int num_sm = 0, num_ss = 0, num_as = 0;
uint32_t *int_dim_reg = hsp_priv->hsp_base + HSP_INT_DIMENSION_OFFSET;
num_sm = (*int_dim_reg >> HSP_INT_DIMENSION_SM_SHIFT) & HSP_INT_DIMENSION_NUM_MASK;
num_ss = (*int_dim_reg >> HSP_INT_DIMENSION_SS_SHIFT) & HSP_INT_DIMENSION_NUM_MASK;
num_as = (*int_dim_reg >> HSP_INT_DIMENSION_AS_SHIFT) & HSP_INT_DIMENSION_NUM_MASK;
hsp_priv->doorbell_base = hsp_priv->hsp_base + (1 + (num_sm / 2) + num_ss + num_as) * 0x10000;
hsp->data = hsp_priv;
hsp->ring = hsp_doorbell_ring;
hsp->check = hsp_doorbell_check;
hsp->destroy = hsp_destroy;
return 0;
}