GK SDK 源码库: XMIPCLinuxV100R005C00SPC030 (kernel/tools/open_source excluded)

This commit is contained in:
lai
2026-09-06 03:52:57 +08:00
commit b1928b41c0
21813 changed files with 4413081 additions and 0 deletions
@@ -0,0 +1,231 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch_helpers.h>
#include <assert.h>
#include <bpmp.h>
#include <common/debug.h>
#include <drivers/delay_timer.h>
#include <errno.h>
#include <lib/mmio.h>
#include <plat/common/platform.h>
#include <stdbool.h>
#include <string.h>
#include <tegra_def.h>
#define BPMP_TIMEOUT 500 /* 500ms */
static uint32_t channel_base[NR_CHANNELS];
static uint32_t bpmp_init_state = BPMP_INIT_PENDING;
static uint32_t channel_field(unsigned int ch)
{
return mmio_read_32(TEGRA_RES_SEMA_BASE + STA_OFFSET) & CH_MASK(ch);
}
static bool master_free(unsigned int ch)
{
return channel_field(ch) == MA_FREE(ch);
}
static bool master_acked(unsigned int ch)
{
return channel_field(ch) == MA_ACKD(ch);
}
static void signal_slave(unsigned int ch)
{
mmio_write_32(TEGRA_RES_SEMA_BASE + CLR_OFFSET, CH_MASK(ch));
}
static void free_master(unsigned int ch)
{
mmio_write_32(TEGRA_RES_SEMA_BASE + CLR_OFFSET,
MA_ACKD(ch) ^ MA_FREE(ch));
}
/* should be called with local irqs disabled */
int32_t tegra_bpmp_send_receive_atomic(int mrq, const void *ob_data, int ob_sz,
void *ib_data, int ib_sz)
{
unsigned int ch = (unsigned int)plat_my_core_pos();
mb_data_t *p = (mb_data_t *)(uintptr_t)channel_base[ch];
int32_t ret = -ETIMEDOUT, timeout = 0;
if (bpmp_init_state == BPMP_INIT_COMPLETE) {
/* loop until BPMP is free */
for (timeout = 0; timeout < BPMP_TIMEOUT; timeout++) {
if (master_free(ch) == true) {
break;
}
mdelay(1);
}
if (timeout != BPMP_TIMEOUT) {
/* generate the command struct */
p->code = mrq;
p->flags = DO_ACK;
(void)memcpy((void *)p->data, ob_data, (size_t)ob_sz);
/* signal command ready to the BPMP */
signal_slave(ch);
mmio_write_32(TEGRA_PRI_ICTLR_BASE + CPU_IEP_FIR_SET,
(1U << INT_SHR_SEM_OUTBOX_FULL));
/* loop until the command is executed */
for (timeout = 0; timeout < BPMP_TIMEOUT; timeout++) {
if (master_acked(ch) == true) {
break;
}
mdelay(1);
}
if (timeout != BPMP_TIMEOUT) {
/* get the command response */
(void)memcpy(ib_data, (const void *)p->data,
(size_t)ib_sz);
/* return error code */
ret = p->code;
/* free this channel */
free_master(ch);
}
}
} else {
/* return error code */
ret = -EINVAL;
}
if (timeout == BPMP_TIMEOUT) {
ERROR("Timed out waiting for bpmp's response\n");
}
return ret;
}
int tegra_bpmp_init(void)
{
uint32_t val, base, timeout = BPMP_TIMEOUT;
unsigned int ch;
int ret = 0;
if (bpmp_init_state == BPMP_INIT_PENDING) {
/* check if the bpmp processor is alive. */
do {
val = mmio_read_32(TEGRA_RES_SEMA_BASE + STA_OFFSET);
if (val != SIGN_OF_LIFE) {
mdelay(1);
timeout--;
}
} while ((val != SIGN_OF_LIFE) && (timeout > 0U));
if (val == SIGN_OF_LIFE) {
/* check if clock for the atomics block is enabled */
val = mmio_read_32(TEGRA_CAR_RESET_BASE + TEGRA_CLK_ENB_V);
if ((val & CAR_ENABLE_ATOMICS) == 0) {
ERROR("Clock to the atomics block is disabled\n");
}
/* check if the atomics block is out of reset */
val = mmio_read_32(TEGRA_CAR_RESET_BASE + TEGRA_RST_DEV_CLR_V);
if ((val & CAR_ENABLE_ATOMICS) == CAR_ENABLE_ATOMICS) {
ERROR("Reset to the atomics block is asserted\n");
}
/* base address to get the result from Atomics */
base = TEGRA_ATOMICS_BASE + RESULT0_REG_OFFSET;
/* channel area is setup by BPMP before signaling handshake */
for (ch = 0; ch < NR_CHANNELS; ch++) {
/* issue command to get the channel base address */
mmio_write_32(base, (ch << TRIGGER_ID_SHIFT) |
ATOMIC_CMD_GET);
/* get the base address for the channel */
channel_base[ch] = mmio_read_32(base);
/* increment result register offset */
base += 4U;
}
/* mark state as "initialized" */
bpmp_init_state = BPMP_INIT_COMPLETE;
/* the channel values have to be visible across all cpus */
flush_dcache_range((uint64_t)channel_base,
sizeof(channel_base));
flush_dcache_range((uint64_t)&bpmp_init_state,
sizeof(bpmp_init_state));
INFO("%s: done\n", __func__);
} else {
ERROR("BPMP not powered on\n");
/* bpmp is not present in the system */
bpmp_init_state = BPMP_NOT_PRESENT;
/* communication timed out */
ret = -ETIMEDOUT;
}
}
return ret;
}
void tegra_bpmp_suspend(void)
{
/* freeze the interface */
if (bpmp_init_state == BPMP_INIT_COMPLETE) {
bpmp_init_state = BPMP_SUSPEND_ENTRY;
flush_dcache_range((uint64_t)&bpmp_init_state,
sizeof(bpmp_init_state));
}
}
void tegra_bpmp_resume(void)
{
uint32_t val, timeout = 0;
if (bpmp_init_state == BPMP_SUSPEND_ENTRY) {
/* check if the bpmp processor is alive. */
do {
val = mmio_read_32(TEGRA_RES_SEMA_BASE + STA_OFFSET);
if (val != SIGN_OF_LIFE) {
mdelay(1);
timeout++;
}
} while ((val != SIGN_OF_LIFE) && (timeout < BPMP_TIMEOUT));
if (val == SIGN_OF_LIFE) {
INFO("%s: BPMP took %d ms to resume\n", __func__, timeout);
/* mark state as "initialized" */
bpmp_init_state = BPMP_INIT_COMPLETE;
/* state has to be visible across all cpus */
flush_dcache_range((uint64_t)&bpmp_init_state,
sizeof(bpmp_init_state));
} else {
ERROR("BPMP not powered on\n");
}
}
}
@@ -0,0 +1,345 @@
/*
* Copyright (c) 2017-2020, NVIDIA CORPORATION. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <bpmp_ipc.h>
#include <common/debug.h>
#include <drivers/delay_timer.h>
#include <errno.h>
#include <lib/mmio.h>
#include <lib/utils_def.h>
#include <stdbool.h>
#include <string.h>
#include <tegra_def.h>
#include "intf.h"
#include "ivc.h"
/**
* Holds IVC channel data
*/
struct ccplex_bpmp_channel_data {
/* Buffer for incoming data */
struct frame_data *ib;
/* Buffer for outgoing data */
struct frame_data *ob;
};
static struct ccplex_bpmp_channel_data s_channel;
static struct ivc ivc_ccplex_bpmp_channel;
/*
* Helper functions to access the HSP doorbell registers
*/
static inline uint32_t hsp_db_read(uint32_t reg)
{
return mmio_read_32((uint32_t)(TEGRA_HSP_DBELL_BASE + reg));
}
static inline void hsp_db_write(uint32_t reg, uint32_t val)
{
mmio_write_32((uint32_t)(TEGRA_HSP_DBELL_BASE + reg), val);
}
/*******************************************************************************
* IVC wrappers for CCPLEX <-> BPMP communication.
******************************************************************************/
static void tegra_bpmp_ring_bpmp_doorbell(void);
/*
* Get the next frame where data can be written.
*/
static struct frame_data *tegra_bpmp_get_next_out_frame(void)
{
struct frame_data *frame;
const struct ivc *ch = &ivc_ccplex_bpmp_channel;
frame = (struct frame_data *)tegra_ivc_write_get_next_frame(ch);
if (frame == NULL) {
ERROR("%s: Error in getting next frame, exiting\n", __func__);
} else {
s_channel.ob = frame;
}
return frame;
}
static void tegra_bpmp_signal_slave(void)
{
(void)tegra_ivc_write_advance(&ivc_ccplex_bpmp_channel);
tegra_bpmp_ring_bpmp_doorbell();
}
static int32_t tegra_bpmp_free_master(void)
{
return tegra_ivc_read_advance(&ivc_ccplex_bpmp_channel);
}
static bool tegra_bpmp_slave_acked(void)
{
struct frame_data *frame;
bool ret = true;
frame = (struct frame_data *)tegra_ivc_read_get_next_frame(&ivc_ccplex_bpmp_channel);
if (frame == NULL) {
ret = false;
} else {
s_channel.ib = frame;
}
return ret;
}
static struct frame_data *tegra_bpmp_get_cur_in_frame(void)
{
return s_channel.ib;
}
/*
* Enables BPMP to ring CCPlex doorbell
*/
static void tegra_bpmp_enable_ccplex_doorbell(void)
{
uint32_t reg;
reg = hsp_db_read(HSP_DBELL_1_ENABLE);
reg |= HSP_MASTER_BPMP_BIT;
hsp_db_write(HSP_DBELL_1_ENABLE, reg);
}
/*
* CCPlex rings the BPMP doorbell
*/
static void tegra_bpmp_ring_bpmp_doorbell(void)
{
/*
* Any writes to this register has the same effect,
* uses master ID of the write transaction and set
* corresponding flag.
*/
hsp_db_write(HSP_DBELL_3_TRIGGER, HSP_MASTER_CCPLEX_BIT);
}
/*
* Returns true if CCPLex can ring BPMP doorbell, otherwise false.
* This also signals that BPMP is up and ready.
*/
static bool tegra_bpmp_can_ccplex_ring_doorbell(void)
{
uint32_t reg;
/* check if ccplex can communicate with bpmp */
reg = hsp_db_read(HSP_DBELL_3_ENABLE);
return ((reg & HSP_MASTER_CCPLEX_BIT) != 0U);
}
static int32_t tegra_bpmp_wait_for_slave_ack(void)
{
uint32_t timeout = TIMEOUT_RESPONSE_FROM_BPMP_US;
while (!tegra_bpmp_slave_acked() && (timeout != 0U)) {
udelay(1);
timeout--;
};
return ((timeout == 0U) ? -ETIMEDOUT : 0);
}
/*
* Notification from the ivc layer
*/
static void tegra_bpmp_ivc_notify(const struct ivc *ivc)
{
(void)(ivc);
tegra_bpmp_ring_bpmp_doorbell();
}
/*
* Atomic send/receive API, which means it waits until slave acks
*/
static int32_t tegra_bpmp_ipc_send_req_atomic(uint32_t mrq, void *p_out,
uint32_t size_out, void *p_in, uint32_t size_in)
{
struct frame_data *frame = tegra_bpmp_get_next_out_frame();
const struct frame_data *f_in = NULL;
int32_t ret = 0;
void *p_fdata;
if ((p_out == NULL) || (size_out > IVC_DATA_SZ_BYTES) ||
(frame == NULL)) {
ERROR("%s: invalid parameters, exiting\n", __func__);
return -EINVAL;
}
/* prepare the command frame */
frame->mrq = mrq;
frame->flags = FLAG_DO_ACK;
p_fdata = frame->data;
(void)memcpy(p_fdata, p_out, (size_t)size_out);
/* signal the slave */
tegra_bpmp_signal_slave();
/* wait for slave to ack */
ret = tegra_bpmp_wait_for_slave_ack();
if (ret < 0) {
ERROR("%s: wait for slave failed (%d)\n", __func__, ret);
return ret;
}
/* retrieve the response frame */
if ((size_in <= IVC_DATA_SZ_BYTES) && (p_in != NULL)) {
f_in = tegra_bpmp_get_cur_in_frame();
if (f_in != NULL) {
ERROR("Failed to get next input frame!\n");
} else {
(void)memcpy(p_in, p_fdata, (size_t)size_in);
}
}
ret = tegra_bpmp_free_master();
if (ret < 0) {
ERROR("%s: free master failed (%d)\n", __func__, ret);
}
return ret;
}
/*
* Initializes the BPMP<--->CCPlex communication path.
*/
int32_t tegra_bpmp_ipc_init(void)
{
size_t msg_size;
uint32_t frame_size, timeout;
int32_t error = 0;
/* allow bpmp to ring CCPLEX's doorbell */
tegra_bpmp_enable_ccplex_doorbell();
/* wait for BPMP to actually ring the doorbell */
timeout = TIMEOUT_RESPONSE_FROM_BPMP_US;
while ((timeout != 0U) && !tegra_bpmp_can_ccplex_ring_doorbell()) {
udelay(1); /* bpmp turn-around time */
timeout--;
}
if (timeout == 0U) {
ERROR("%s: BPMP firmware is not ready\n", __func__);
return -ENOTSUP;
}
INFO("%s: BPMP handshake completed\n", __func__);
msg_size = tegra_ivc_align(IVC_CMD_SZ_BYTES);
frame_size = (uint32_t)tegra_ivc_total_queue_size(msg_size);
if (frame_size > TEGRA_BPMP_IPC_CH_MAP_SIZE) {
ERROR("%s: carveout size is not sufficient\n", __func__);
return -EINVAL;
}
error = tegra_ivc_init(&ivc_ccplex_bpmp_channel,
(uint32_t)TEGRA_BPMP_IPC_RX_PHYS_BASE,
(uint32_t)TEGRA_BPMP_IPC_TX_PHYS_BASE,
1U, frame_size, tegra_bpmp_ivc_notify);
if (error != 0) {
ERROR("%s: IVC init failed (%d)\n", __func__, error);
} else {
/* reset channel */
tegra_ivc_channel_reset(&ivc_ccplex_bpmp_channel);
/* wait for notification from BPMP */
while (tegra_ivc_channel_notified(&ivc_ccplex_bpmp_channel) != 0) {
/*
* Interrupt BPMP with doorbell each time after
* tegra_ivc_channel_notified() returns non zero
* value.
*/
tegra_bpmp_ring_bpmp_doorbell();
}
INFO("%s: All communication channels initialized\n", __func__);
}
return error;
}
/* Handler to reset a hardware module */
int32_t tegra_bpmp_ipc_reset_module(uint32_t rst_id)
{
int32_t ret;
struct mrq_reset_request req = {
.cmd = (uint32_t)CMD_RESET_MODULE,
.reset_id = rst_id
};
/* only GPCDMA/XUSB_PADCTL resets are supported */
assert((rst_id == TEGRA_RESET_ID_XUSB_PADCTL) ||
(rst_id == TEGRA_RESET_ID_GPCDMA));
ret = tegra_bpmp_ipc_send_req_atomic(MRQ_RESET, &req,
(uint32_t)sizeof(req), NULL, 0);
if (ret != 0) {
ERROR("%s: failed for module %d with error %d\n", __func__,
rst_id, ret);
}
return ret;
}
int tegra_bpmp_ipc_enable_clock(uint32_t clk_id)
{
int ret;
struct mrq_clk_request req;
/* only SE clocks are supported */
if (clk_id != TEGRA_CLK_SE) {
return -ENOTSUP;
}
/* prepare the MRQ_CLK command */
req.cmd_and_id = make_mrq_clk_cmd(CMD_CLK_ENABLE, clk_id);
ret = tegra_bpmp_ipc_send_req_atomic(MRQ_CLK, &req, (uint32_t)sizeof(req),
NULL, 0);
if (ret != 0) {
ERROR("%s: failed for module %d with error %d\n", __func__,
clk_id, ret);
}
return ret;
}
int tegra_bpmp_ipc_disable_clock(uint32_t clk_id)
{
int ret;
struct mrq_clk_request req;
/* only SE clocks are supported */
if (clk_id != TEGRA_CLK_SE) {
return -ENOTSUP;
}
/* prepare the MRQ_CLK command */
req.cmd_and_id = make_mrq_clk_cmd(CMD_CLK_DISABLE, clk_id);
ret = tegra_bpmp_ipc_send_req_atomic(MRQ_CLK, &req, (uint32_t)sizeof(req),
NULL, 0);
if (ret != 0) {
ERROR("%s: failed for module %d with error %d\n", __func__,
clk_id, ret);
}
return ret;
}
@@ -0,0 +1,127 @@
/*
* Copyright (c) 2017-2020, NVIDIA CORPORATION. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef BPMP_INTF_H
#define BPMP_INTF_H
/**
* Flags used in IPC req
*/
#define FLAG_DO_ACK (U(1) << 0)
#define FLAG_RING_DOORBELL (U(1) << 1)
/* Bit 1 is designated for CCPlex in secure world */
#define HSP_MASTER_CCPLEX_BIT (U(1) << 1)
/* Bit 19 is designated for BPMP in non-secure world */
#define HSP_MASTER_BPMP_BIT (U(1) << 19)
/* Timeout to receive response from BPMP is 1 sec */
#define TIMEOUT_RESPONSE_FROM_BPMP_US U(1000000) /* in microseconds */
/**
* IVC protocol defines and command/response frame
*/
/**
* IVC specific defines
*/
#define IVC_CMD_SZ_BYTES U(128)
#define IVC_DATA_SZ_BYTES U(120)
/**
* Holds frame data for an IPC request
*/
struct frame_data {
/* Identification as to what kind of data is being transmitted */
uint32_t mrq;
/* Flags for slave as to how to respond back */
uint32_t flags;
/* Actual data being sent */
uint8_t data[IVC_DATA_SZ_BYTES];
};
/**
* Commands send to the BPMP firmware
*/
/**
* MRQ command codes
*/
#define MRQ_RESET U(20)
#define MRQ_CLK U(22)
/**
* Reset sub-commands
*/
#define CMD_RESET_ASSERT U(1)
#define CMD_RESET_DEASSERT U(2)
#define CMD_RESET_MODULE U(3)
/**
* Used by the sender of an #MRQ_RESET message to request BPMP to
* assert or deassert a given reset line.
*/
struct __attribute__((packed)) mrq_reset_request {
/* reset action to perform (mrq_reset_commands) */
uint32_t cmd;
/* id of the reset to affected */
uint32_t reset_id;
};
/**
* MRQ_CLK sub-commands
*
*/
enum {
CMD_CLK_GET_RATE = U(1),
CMD_CLK_SET_RATE = U(2),
CMD_CLK_ROUND_RATE = U(3),
CMD_CLK_GET_PARENT = U(4),
CMD_CLK_SET_PARENT = U(5),
CMD_CLK_IS_ENABLED = U(6),
CMD_CLK_ENABLE = U(7),
CMD_CLK_DISABLE = U(8),
CMD_CLK_GET_ALL_INFO = U(14),
CMD_CLK_GET_MAX_CLK_ID = U(15),
CMD_CLK_MAX,
};
/**
* Used by the sender of an #MRQ_CLK message to control clocks. The
* clk_request is split into several sub-commands. Some sub-commands
* require no additional data. Others have a sub-command specific
* payload
*
* |sub-command |payload |
* |----------------------------|-----------------------|
* |CMD_CLK_GET_RATE |- |
* |CMD_CLK_SET_RATE |clk_set_rate |
* |CMD_CLK_ROUND_RATE |clk_round_rate |
* |CMD_CLK_GET_PARENT |- |
* |CMD_CLK_SET_PARENT |clk_set_parent |
* |CMD_CLK_IS_ENABLED |- |
* |CMD_CLK_ENABLE |- |
* |CMD_CLK_DISABLE |- |
* |CMD_CLK_GET_ALL_INFO |- |
* |CMD_CLK_GET_MAX_CLK_ID |- |
*
*/
struct mrq_clk_request {
/**
* sub-command and clock id concatenated to 32-bit word.
* - bits[31..24] is the sub-cmd.
* - bits[23..0] is the clock id
*/
uint32_t cmd_and_id;
};
/**
* Macro to prepare the MRQ_CLK sub-command
*/
#define make_mrq_clk_cmd(cmd, id) (((cmd) << 24) | (id & 0xFFFFFF))
#endif /* BPMP_INTF_H */
@@ -0,0 +1,654 @@
/*
* Copyright (c) 2017-2020, NVIDIA CORPORATION. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch_helpers.h>
#include <assert.h>
#include <common/debug.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "ivc.h"
/*
* IVC channel reset protocol.
*
* Each end uses its tx_channel.state to indicate its synchronization state.
*/
enum {
/*
* This value is zero for backwards compatibility with services that
* assume channels to be initially zeroed. Such channels are in an
* initially valid state, but cannot be asynchronously reset, and must
* maintain a valid state at all times.
*
* The transmitting end can enter the established state from the sync or
* ack state when it observes the receiving endpoint in the ack or
* established state, indicating that has cleared the counters in our
* rx_channel.
*/
ivc_state_established = U(0),
/*
* If an endpoint is observed in the sync state, the remote endpoint is
* allowed to clear the counters it owns asynchronously with respect to
* the current endpoint. Therefore, the current endpoint is no longer
* allowed to communicate.
*/
ivc_state_sync = U(1),
/*
* When the transmitting end observes the receiving end in the sync
* state, it can clear the w_count and r_count and transition to the ack
* state. If the remote endpoint observes us in the ack state, it can
* return to the established state once it has cleared its counters.
*/
ivc_state_ack = U(2)
};
/*
* This structure is divided into two-cache aligned parts, the first is only
* written through the tx_channel pointer, while the second is only written
* through the rx_channel pointer. This delineates ownership of the cache lines,
* which is critical to performance and necessary in non-cache coherent
* implementations.
*/
struct ivc_channel_header {
struct {
/* fields owned by the transmitting end */
uint32_t w_count;
uint32_t state;
uint32_t w_rsvd[IVC_CHHDR_TX_FIELDS - 2];
};
struct {
/* fields owned by the receiving end */
uint32_t r_count;
uint32_t r_rsvd[IVC_CHHDR_RX_FIELDS - 1];
};
};
static inline bool ivc_channel_empty(const struct ivc *ivc,
volatile const struct ivc_channel_header *ch)
{
/*
* This function performs multiple checks on the same values with
* security implications, so sample the counters' current values in
* shared memory to ensure that these checks use the same values.
*/
uint32_t wr_count = ch->w_count;
uint32_t rd_count = ch->r_count;
bool ret = false;
(void)ivc;
/*
* Perform an over-full check to prevent denial of service attacks where
* a server could be easily fooled into believing that there's an
* extremely large number of frames ready, since receivers are not
* expected to check for full or over-full conditions.
*
* Although the channel isn't empty, this is an invalid case caused by
* a potentially malicious peer, so returning empty is safer, because it
* gives the impression that the channel has gone silent.
*/
if (((wr_count - rd_count) > ivc->nframes) || (wr_count == rd_count)) {
ret = true;
}
return ret;
}
static inline bool ivc_channel_full(const struct ivc *ivc,
volatile const struct ivc_channel_header *ch)
{
uint32_t wr_count = ch->w_count;
uint32_t rd_count = ch->r_count;
(void)ivc;
/*
* Invalid cases where the counters indicate that the queue is over
* capacity also appear full.
*/
return ((wr_count - rd_count) >= ivc->nframes);
}
static inline uint32_t ivc_channel_avail_count(const struct ivc *ivc,
volatile const struct ivc_channel_header *ch)
{
uint32_t wr_count = ch->w_count;
uint32_t rd_count = ch->r_count;
(void)ivc;
/*
* This function isn't expected to be used in scenarios where an
* over-full situation can lead to denial of service attacks. See the
* comment in ivc_channel_empty() for an explanation about special
* over-full considerations.
*/
return (wr_count - rd_count);
}
static inline void ivc_advance_tx(struct ivc *ivc)
{
ivc->tx_channel->w_count++;
if (ivc->w_pos == (ivc->nframes - (uint32_t)1U)) {
ivc->w_pos = 0U;
} else {
ivc->w_pos++;
}
}
static inline void ivc_advance_rx(struct ivc *ivc)
{
ivc->rx_channel->r_count++;
if (ivc->r_pos == (ivc->nframes - (uint32_t)1U)) {
ivc->r_pos = 0U;
} else {
ivc->r_pos++;
}
}
static inline int32_t ivc_check_read(const struct ivc *ivc)
{
/*
* tx_channel->state is set locally, so it is not synchronized with
* state from the remote peer. The remote peer cannot reset its
* transmit counters until we've acknowledged its synchronization
* request, so no additional synchronization is required because an
* asynchronous transition of rx_channel->state to ivc_state_ack is not
* allowed.
*/
if (ivc->tx_channel->state != ivc_state_established) {
return -ECONNRESET;
}
/*
* Avoid unnecessary invalidations when performing repeated accesses to
* an IVC channel by checking the old queue pointers first.
* Synchronization is only necessary when these pointers indicate empty
* or full.
*/
if (!ivc_channel_empty(ivc, ivc->rx_channel)) {
return 0;
}
return ivc_channel_empty(ivc, ivc->rx_channel) ? -ENOMEM : 0;
}
static inline int32_t ivc_check_write(const struct ivc *ivc)
{
if (ivc->tx_channel->state != ivc_state_established) {
return -ECONNRESET;
}
if (!ivc_channel_full(ivc, ivc->tx_channel)) {
return 0;
}
return ivc_channel_full(ivc, ivc->tx_channel) ? -ENOMEM : 0;
}
bool tegra_ivc_can_read(const struct ivc *ivc)
{
return ivc_check_read(ivc) == 0;
}
bool tegra_ivc_can_write(const struct ivc *ivc)
{
return ivc_check_write(ivc) == 0;
}
bool tegra_ivc_tx_empty(const struct ivc *ivc)
{
return ivc_channel_empty(ivc, ivc->tx_channel);
}
static inline uintptr_t calc_frame_offset(uint32_t frame_index,
uint32_t frame_size, uint32_t frame_offset)
{
return ((uintptr_t)frame_index * (uintptr_t)frame_size) +
(uintptr_t)frame_offset;
}
static void *ivc_frame_pointer(const struct ivc *ivc,
volatile const struct ivc_channel_header *ch,
uint32_t frame)
{
assert(frame < ivc->nframes);
return (void *)((uintptr_t)(&ch[1]) +
calc_frame_offset(frame, ivc->frame_size, 0));
}
int32_t tegra_ivc_read(struct ivc *ivc, void *buf, size_t max_read)
{
const void *src;
int32_t result;
if (buf == NULL) {
return -EINVAL;
}
if (max_read > ivc->frame_size) {
return -E2BIG;
}
result = ivc_check_read(ivc);
if (result != 0) {
return result;
}
/*
* Order observation of w_pos potentially indicating new data before
* data read.
*/
dmbish();
src = ivc_frame_pointer(ivc, ivc->rx_channel, ivc->r_pos);
(void)memcpy(buf, src, max_read);
ivc_advance_rx(ivc);
/*
* Ensure our write to r_pos occurs before our read from w_pos.
*/
dmbish();
/*
* Notify only upon transition from full to non-full.
* The available count can only asynchronously increase, so the
* worst possible side-effect will be a spurious notification.
*/
if (ivc_channel_avail_count(ivc, ivc->rx_channel) == (ivc->nframes - (uint32_t)1U)) {
ivc->notify(ivc);
}
return (int32_t)max_read;
}
/* directly peek at the next frame rx'ed */
void *tegra_ivc_read_get_next_frame(const struct ivc *ivc)
{
if (ivc_check_read(ivc) != 0) {
return NULL;
}
/*
* Order observation of w_pos potentially indicating new data before
* data read.
*/
dmbld();
return ivc_frame_pointer(ivc, ivc->rx_channel, ivc->r_pos);
}
int32_t tegra_ivc_read_advance(struct ivc *ivc)
{
/*
* No read barriers or synchronization here: the caller is expected to
* have already observed the channel non-empty. This check is just to
* catch programming errors.
*/
int32_t result = ivc_check_read(ivc);
if (result != 0) {
return result;
}
ivc_advance_rx(ivc);
/*
* Ensure our write to r_pos occurs before our read from w_pos.
*/
dmbish();
/*
* Notify only upon transition from full to non-full.
* The available count can only asynchronously increase, so the
* worst possible side-effect will be a spurious notification.
*/
if (ivc_channel_avail_count(ivc, ivc->rx_channel) == (ivc->nframes - (uint32_t)1U)) {
ivc->notify(ivc);
}
return 0;
}
int32_t tegra_ivc_write(struct ivc *ivc, const void *buf, size_t size)
{
void *p;
int32_t result;
if ((buf == NULL) || (ivc == NULL)) {
return -EINVAL;
}
if (size > ivc->frame_size) {
return -E2BIG;
}
result = ivc_check_write(ivc);
if (result != 0) {
return result;
}
p = ivc_frame_pointer(ivc, ivc->tx_channel, ivc->w_pos);
(void)memset(p, 0, ivc->frame_size);
(void)memcpy(p, buf, size);
/*
* Ensure that updated data is visible before the w_pos counter
* indicates that it is ready.
*/
dmbst();
ivc_advance_tx(ivc);
/*
* Ensure our write to w_pos occurs before our read from r_pos.
*/
dmbish();
/*
* Notify only upon transition from empty to non-empty.
* The available count can only asynchronously decrease, so the
* worst possible side-effect will be a spurious notification.
*/
if (ivc_channel_avail_count(ivc, ivc->tx_channel) == 1U) {
ivc->notify(ivc);
}
return (int32_t)size;
}
/* directly poke at the next frame to be tx'ed */
void *tegra_ivc_write_get_next_frame(const struct ivc *ivc)
{
if (ivc_check_write(ivc) != 0) {
return NULL;
}
return ivc_frame_pointer(ivc, ivc->tx_channel, ivc->w_pos);
}
/* advance the tx buffer */
int32_t tegra_ivc_write_advance(struct ivc *ivc)
{
int32_t result = ivc_check_write(ivc);
if (result != 0) {
return result;
}
/*
* Order any possible stores to the frame before update of w_pos.
*/
dmbst();
ivc_advance_tx(ivc);
/*
* Ensure our write to w_pos occurs before our read from r_pos.
*/
dmbish();
/*
* Notify only upon transition from empty to non-empty.
* The available count can only asynchronously decrease, so the
* worst possible side-effect will be a spurious notification.
*/
if (ivc_channel_avail_count(ivc, ivc->tx_channel) == (uint32_t)1U) {
ivc->notify(ivc);
}
return 0;
}
void tegra_ivc_channel_reset(const struct ivc *ivc)
{
ivc->tx_channel->state = ivc_state_sync;
ivc->notify(ivc);
}
/*
* ===============================================================
* IVC State Transition Table - see tegra_ivc_channel_notified()
* ===============================================================
*
* local remote action
* ----- ------ -----------------------------------
* SYNC EST <none>
* SYNC ACK reset counters; move to EST; notify
* SYNC SYNC reset counters; move to ACK; notify
* ACK EST move to EST; notify
* ACK ACK move to EST; notify
* ACK SYNC reset counters; move to ACK; notify
* EST EST <none>
* EST ACK <none>
* EST SYNC reset counters; move to ACK; notify
*
* ===============================================================
*/
int32_t tegra_ivc_channel_notified(struct ivc *ivc)
{
uint32_t peer_state;
/* Copy the receiver's state out of shared memory. */
peer_state = ivc->rx_channel->state;
if (peer_state == (uint32_t)ivc_state_sync) {
/*
* Order observation of ivc_state_sync before stores clearing
* tx_channel.
*/
dmbld();
/*
* Reset tx_channel counters. The remote end is in the SYNC
* state and won't make progress until we change our state,
* so the counters are not in use at this time.
*/
ivc->tx_channel->w_count = 0U;
ivc->rx_channel->r_count = 0U;
ivc->w_pos = 0U;
ivc->r_pos = 0U;
/*
* Ensure that counters appear cleared before new state can be
* observed.
*/
dmbst();
/*
* Move to ACK state. We have just cleared our counters, so it
* is now safe for the remote end to start using these values.
*/
ivc->tx_channel->state = ivc_state_ack;
/*
* Notify remote end to observe state transition.
*/
ivc->notify(ivc);
} else if ((ivc->tx_channel->state == (uint32_t)ivc_state_sync) &&
(peer_state == (uint32_t)ivc_state_ack)) {
/*
* Order observation of ivc_state_sync before stores clearing
* tx_channel.
*/
dmbld();
/*
* Reset tx_channel counters. The remote end is in the ACK
* state and won't make progress until we change our state,
* so the counters are not in use at this time.
*/
ivc->tx_channel->w_count = 0U;
ivc->rx_channel->r_count = 0U;
ivc->w_pos = 0U;
ivc->r_pos = 0U;
/*
* Ensure that counters appear cleared before new state can be
* observed.
*/
dmbst();
/*
* Move to ESTABLISHED state. We know that the remote end has
* already cleared its counters, so it is safe to start
* writing/reading on this channel.
*/
ivc->tx_channel->state = ivc_state_established;
/*
* Notify remote end to observe state transition.
*/
ivc->notify(ivc);
} else if (ivc->tx_channel->state == (uint32_t)ivc_state_ack) {
/*
* At this point, we have observed the peer to be in either
* the ACK or ESTABLISHED state. Next, order observation of
* peer state before storing to tx_channel.
*/
dmbld();
/*
* Move to ESTABLISHED state. We know that we have previously
* cleared our counters, and we know that the remote end has
* cleared its counters, so it is safe to start writing/reading
* on this channel.
*/
ivc->tx_channel->state = ivc_state_established;
/*
* Notify remote end to observe state transition.
*/
ivc->notify(ivc);
} else {
/*
* There is no need to handle any further action. Either the
* channel is already fully established, or we are waiting for
* the remote end to catch up with our current state. Refer
* to the diagram in "IVC State Transition Table" above.
*/
}
return ((ivc->tx_channel->state == (uint32_t)ivc_state_established) ? 0 : -EAGAIN);
}
size_t tegra_ivc_align(size_t size)
{
return (size + (IVC_ALIGN - 1U)) & ~(IVC_ALIGN - 1U);
}
size_t tegra_ivc_total_queue_size(size_t queue_size)
{
if ((queue_size & (IVC_ALIGN - 1U)) != 0U) {
ERROR("queue_size (%d) must be %d-byte aligned\n",
(int32_t)queue_size, IVC_ALIGN);
return 0;
}
return queue_size + sizeof(struct ivc_channel_header);
}
static int32_t check_ivc_params(uintptr_t queue_base1, uintptr_t queue_base2,
uint32_t nframes, uint32_t frame_size)
{
assert((offsetof(struct ivc_channel_header, w_count)
& (IVC_ALIGN - 1U)) == 0U);
assert((offsetof(struct ivc_channel_header, r_count)
& (IVC_ALIGN - 1U)) == 0U);
assert((sizeof(struct ivc_channel_header) & (IVC_ALIGN - 1U)) == 0U);
if (((uint64_t)nframes * (uint64_t)frame_size) >= 0x100000000ULL) {
ERROR("nframes * frame_size overflows\n");
return -EINVAL;
}
/*
* The headers must at least be aligned enough for counters
* to be accessed atomically.
*/
if ((queue_base1 & (IVC_ALIGN - 1U)) != 0U) {
ERROR("ivc channel start not aligned: %lx\n", queue_base1);
return -EINVAL;
}
if ((queue_base2 & (IVC_ALIGN - 1U)) != 0U) {
ERROR("ivc channel start not aligned: %lx\n", queue_base2);
return -EINVAL;
}
if ((frame_size & (IVC_ALIGN - 1U)) != 0U) {
ERROR("frame size not adequately aligned: %u\n",
frame_size);
return -EINVAL;
}
if (queue_base1 < queue_base2) {
if ((queue_base1 + ((uint64_t)frame_size * nframes)) > queue_base2) {
ERROR("queue regions overlap: %lx + %x, %x\n",
queue_base1, frame_size,
frame_size * nframes);
return -EINVAL;
}
} else {
if ((queue_base2 + ((uint64_t)frame_size * nframes)) > queue_base1) {
ERROR("queue regions overlap: %lx + %x, %x\n",
queue_base2, frame_size,
frame_size * nframes);
return -EINVAL;
}
}
return 0;
}
int32_t tegra_ivc_init(struct ivc *ivc, uintptr_t rx_base, uintptr_t tx_base,
uint32_t nframes, uint32_t frame_size,
ivc_notify_function notify)
{
int32_t result;
/* sanity check input params */
if ((ivc == NULL) || (notify == NULL)) {
return -EINVAL;
}
result = check_ivc_params(rx_base, tx_base, nframes, frame_size);
if (result != 0) {
return result;
}
/*
* All sizes that can be returned by communication functions should
* fit in a 32-bit integer.
*/
if (frame_size > (1u << 31)) {
return -E2BIG;
}
ivc->rx_channel = (struct ivc_channel_header *)rx_base;
ivc->tx_channel = (struct ivc_channel_header *)tx_base;
ivc->notify = notify;
ivc->frame_size = frame_size;
ivc->nframes = nframes;
ivc->w_pos = 0U;
ivc->r_pos = 0U;
INFO("%s: done\n", __func__);
return 0;
}
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2017-2020, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef BPMP_IVC_H
#define BPMP_IVC_H
#include <lib/utils_def.h>
#include <stdint.h>
#include <stddef.h>
#define IVC_ALIGN U(64)
#define IVC_CHHDR_TX_FIELDS U(16)
#define IVC_CHHDR_RX_FIELDS U(16)
struct ivc_channel_header;
struct ivc {
struct ivc_channel_header *rx_channel;
struct ivc_channel_header *tx_channel;
uint32_t w_pos;
uint32_t r_pos;
void (*notify)(const struct ivc *);
uint32_t nframes;
uint32_t frame_size;
};
/* callback handler for notify on receiving a response */
typedef void (* ivc_notify_function)(const struct ivc *);
int32_t tegra_ivc_init(struct ivc *ivc, uintptr_t rx_base, uintptr_t tx_base,
uint32_t nframes, uint32_t frame_size,
ivc_notify_function notify);
size_t tegra_ivc_total_queue_size(size_t queue_size);
size_t tegra_ivc_align(size_t size);
int32_t tegra_ivc_channel_notified(struct ivc *ivc);
void tegra_ivc_channel_reset(const struct ivc *ivc);
int32_t tegra_ivc_write_advance(struct ivc *ivc);
void *tegra_ivc_write_get_next_frame(const struct ivc *ivc);
int32_t tegra_ivc_write(struct ivc *ivc, const void *buf, size_t size);
int32_t tegra_ivc_read_advance(struct ivc *ivc);
void *tegra_ivc_read_get_next_frame(const struct ivc *ivc);
int32_t tegra_ivc_read(struct ivc *ivc, void *buf, size_t max_read);
bool tegra_ivc_tx_empty(const struct ivc *ivc);
bool tegra_ivc_can_write(const struct ivc *ivc);
bool tegra_ivc_can_read(const struct ivc *ivc);
#endif /* BPMP_IVC_H */
@@ -0,0 +1,322 @@
/*
* Copyright (c) 2015-2018, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <arch_helpers.h>
#include <cortex_a53.h>
#include <common/debug.h>
#include <drivers/delay_timer.h>
#include <lib/mmio.h>
#include <flowctrl.h>
#include <lib/utils_def.h>
#include <pmc.h>
#include <tegra_def.h>
#define CLK_RST_DEV_L_SET 0x300
#define CLK_RST_DEV_L_CLR 0x304
#define CLK_BPMP_RST (1 << 1)
#define EVP_BPMP_RESET_VECTOR 0x200
static const uint64_t flowctrl_offset_cpu_csr[4] = {
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_CPU0_CSR),
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_CPU1_CSR),
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_CPU1_CSR + 8),
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_CPU1_CSR + 16)
};
static const uint64_t flowctrl_offset_halt_cpu[4] = {
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_HALT_CPU0_EVENTS),
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_HALT_CPU1_EVENTS),
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_HALT_CPU1_EVENTS + 8),
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_HALT_CPU1_EVENTS + 16)
};
static const uint64_t flowctrl_offset_cc4_ctrl[4] = {
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_CC4_CORE0_CTRL),
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_CC4_CORE0_CTRL + 4),
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_CC4_CORE0_CTRL + 8),
(TEGRA_FLOWCTRL_BASE + FLOWCTRL_CC4_CORE0_CTRL + 12)
};
static inline void tegra_fc_cc4_ctrl(int cpu_id, uint32_t val)
{
mmio_write_32(flowctrl_offset_cc4_ctrl[cpu_id], val);
val = mmio_read_32(flowctrl_offset_cc4_ctrl[cpu_id]);
}
static inline void tegra_fc_cpu_csr(int cpu_id, uint32_t val)
{
mmio_write_32(flowctrl_offset_cpu_csr[cpu_id], val);
val = mmio_read_32(flowctrl_offset_cpu_csr[cpu_id]);
}
static inline void tegra_fc_halt_cpu(int cpu_id, uint32_t val)
{
mmio_write_32(flowctrl_offset_halt_cpu[cpu_id], val);
val = mmio_read_32(flowctrl_offset_halt_cpu[cpu_id]);
}
static void tegra_fc_prepare_suspend(int cpu_id, uint32_t csr)
{
uint32_t val;
val = FLOWCTRL_HALT_GIC_IRQ | FLOWCTRL_HALT_GIC_FIQ |
FLOWCTRL_HALT_LIC_IRQ | FLOWCTRL_HALT_LIC_FIQ |
FLOWCTRL_WAITEVENT;
tegra_fc_halt_cpu(cpu_id, val);
val = FLOWCTRL_CSR_INTR_FLAG | FLOWCTRL_CSR_EVENT_FLAG |
FLOWCTRL_CSR_ENABLE | (FLOWCTRL_WAIT_WFI_BITMAP << cpu_id);
tegra_fc_cpu_csr(cpu_id, val | csr);
}
/*******************************************************************************
* After this, no core can wake from C7 until the action is reverted.
* If a wake up event is asserted, the FC state machine will stall until
* the action is reverted.
******************************************************************************/
void tegra_fc_ccplex_pgexit_lock(void)
{
unsigned int i, cpu = read_mpidr() & MPIDR_CPU_MASK;
uint32_t flags = tegra_fc_read_32(FLOWCTRL_FC_SEQ_INTERCEPT) & ~INTERCEPT_IRQ_PENDING;;
uint32_t icept_cpu_flags[] = {
INTERCEPT_EXIT_PG_CORE0,
INTERCEPT_EXIT_PG_CORE1,
INTERCEPT_EXIT_PG_CORE2,
INTERCEPT_EXIT_PG_CORE3
};
/* set the intercept flags */
for (i = 0; i < ARRAY_SIZE(icept_cpu_flags); i++) {
/* skip current CPU */
if (i == cpu)
continue;
/* enable power gate exit intercept locks */
flags |= icept_cpu_flags[i];
}
tegra_fc_write_32(FLOWCTRL_FC_SEQ_INTERCEPT, flags);
(void)tegra_fc_read_32(FLOWCTRL_FC_SEQ_INTERCEPT);
}
/*******************************************************************************
* Revert the ccplex powergate exit locks
******************************************************************************/
void tegra_fc_ccplex_pgexit_unlock(void)
{
/* clear lock bits, clear pending interrupts */
tegra_fc_write_32(FLOWCTRL_FC_SEQ_INTERCEPT, INTERCEPT_IRQ_PENDING);
(void)tegra_fc_read_32(FLOWCTRL_FC_SEQ_INTERCEPT);
}
/*******************************************************************************
* Powerdn the current CPU
******************************************************************************/
void tegra_fc_cpu_powerdn(uint32_t mpidr)
{
int cpu = mpidr & MPIDR_CPU_MASK;
VERBOSE("CPU%d powering down...\n", cpu);
tegra_fc_prepare_suspend(cpu, 0);
}
/*******************************************************************************
* Suspend the current CPU cluster
******************************************************************************/
void tegra_fc_cluster_idle(uint32_t mpidr)
{
int cpu = mpidr & MPIDR_CPU_MASK;
uint32_t val;
VERBOSE("Entering cluster idle state...\n");
tegra_fc_cc4_ctrl(cpu, 0);
/* hardware L2 flush is faster for A53 only */
tegra_fc_write_32(FLOWCTRL_L2_FLUSH_CONTROL,
!!MPIDR_AFFLVL1_VAL(mpidr));
/* suspend the CPU cluster */
val = FLOWCTRL_PG_CPU_NONCPU << FLOWCTRL_ENABLE_EXT;
tegra_fc_prepare_suspend(cpu, val);
}
/*******************************************************************************
* Power down the current CPU cluster
******************************************************************************/
void tegra_fc_cluster_powerdn(uint32_t mpidr)
{
int cpu = mpidr & MPIDR_CPU_MASK;
uint32_t val;
VERBOSE("Entering cluster powerdn state...\n");
tegra_fc_cc4_ctrl(cpu, 0);
/* hardware L2 flush is faster for A53 only */
tegra_fc_write_32(FLOWCTRL_L2_FLUSH_CONTROL,
read_midr() == CORTEX_A53_MIDR);
/* power down the CPU cluster */
val = FLOWCTRL_TURNOFF_CPURAIL << FLOWCTRL_ENABLE_EXT;
tegra_fc_prepare_suspend(cpu, val);
}
/*******************************************************************************
* Check if cluster idle or power down state is allowed from this CPU
******************************************************************************/
bool tegra_fc_is_ccx_allowed(void)
{
unsigned int i, cpu = read_mpidr() & MPIDR_CPU_MASK;
uint32_t val;
bool ccx_allowed = true;
for (i = 0; i < ARRAY_SIZE(flowctrl_offset_cpu_csr); i++) {
/* skip current CPU */
if (i == cpu)
continue;
/* check if all other CPUs are already halted */
val = mmio_read_32(flowctrl_offset_cpu_csr[i]);
if ((val & FLOWCTRL_CSR_HALT_MASK) == 0U) {
ccx_allowed = false;
}
}
return ccx_allowed;
}
/*******************************************************************************
* Suspend the entire SoC
******************************************************************************/
void tegra_fc_soc_powerdn(uint32_t mpidr)
{
int cpu = mpidr & MPIDR_CPU_MASK;
uint32_t val;
VERBOSE("Entering SoC powerdn state...\n");
tegra_fc_cc4_ctrl(cpu, 0);
tegra_fc_write_32(FLOWCTRL_L2_FLUSH_CONTROL, 1);
val = FLOWCTRL_TURNOFF_CPURAIL << FLOWCTRL_ENABLE_EXT;
tegra_fc_prepare_suspend(cpu, val);
/* overwrite HALT register */
tegra_fc_halt_cpu(cpu, FLOWCTRL_WAITEVENT);
}
/*******************************************************************************
* Power up the CPU
******************************************************************************/
void tegra_fc_cpu_on(int cpu)
{
tegra_fc_cpu_csr(cpu, FLOWCTRL_CSR_ENABLE);
tegra_fc_halt_cpu(cpu, FLOWCTRL_WAITEVENT | FLOWCTRL_HALT_SCLK);
}
/*******************************************************************************
* Power down the CPU
******************************************************************************/
void tegra_fc_cpu_off(int cpu)
{
uint32_t val;
/*
* Flow controller powers down the CPU during wfi. The CPU would be
* powered on when it receives any interrupt.
*/
val = FLOWCTRL_CSR_INTR_FLAG | FLOWCTRL_CSR_EVENT_FLAG |
FLOWCTRL_CSR_ENABLE | (FLOWCTRL_WAIT_WFI_BITMAP << cpu);
tegra_fc_cpu_csr(cpu, val);
tegra_fc_halt_cpu(cpu, FLOWCTRL_WAITEVENT);
tegra_fc_cc4_ctrl(cpu, 0);
}
/*******************************************************************************
* Inform the BPMP that we have completed the cluster power up
******************************************************************************/
void tegra_fc_lock_active_cluster(void)
{
uint32_t val;
val = tegra_fc_read_32(FLOWCTRL_BPMP_CLUSTER_CONTROL);
val |= FLOWCTRL_BPMP_CLUSTER_PWRON_LOCK;
tegra_fc_write_32(FLOWCTRL_BPMP_CLUSTER_CONTROL, val);
val = tegra_fc_read_32(FLOWCTRL_BPMP_CLUSTER_CONTROL);
}
/*******************************************************************************
* Power ON BPMP processor
******************************************************************************/
void tegra_fc_bpmp_on(uint32_t entrypoint)
{
/* halt BPMP */
tegra_fc_write_32(FLOWCTRL_HALT_BPMP_EVENTS, FLOWCTRL_WAITEVENT);
/* Assert BPMP reset */
mmio_write_32(TEGRA_CAR_RESET_BASE + CLK_RST_DEV_L_SET, CLK_BPMP_RST);
/* Set reset address (stored in PMC_SCRATCH39) */
mmio_write_32(TEGRA_EVP_BASE + EVP_BPMP_RESET_VECTOR, entrypoint);
while (entrypoint != mmio_read_32(TEGRA_EVP_BASE + EVP_BPMP_RESET_VECTOR))
; /* wait till value reaches EVP_BPMP_RESET_VECTOR */
/* Wait for 2us before de-asserting the reset signal. */
udelay(2);
/* De-assert BPMP reset */
mmio_write_32(TEGRA_CAR_RESET_BASE + CLK_RST_DEV_L_CLR, CLK_BPMP_RST);
/* Un-halt BPMP */
tegra_fc_write_32(FLOWCTRL_HALT_BPMP_EVENTS, 0);
}
/*******************************************************************************
* Power OFF BPMP processor
******************************************************************************/
void tegra_fc_bpmp_off(void)
{
/* halt BPMP */
tegra_fc_write_32(FLOWCTRL_HALT_BPMP_EVENTS, FLOWCTRL_WAITEVENT);
/* Assert BPMP reset */
mmio_write_32(TEGRA_CAR_RESET_BASE + CLK_RST_DEV_L_SET, CLK_BPMP_RST);
/* Clear reset address */
mmio_write_32(TEGRA_EVP_BASE + EVP_BPMP_RESET_VECTOR, 0);
while (0 != mmio_read_32(TEGRA_EVP_BASE + EVP_BPMP_RESET_VECTOR))
; /* wait till value reaches EVP_BPMP_RESET_VECTOR */
}
/*******************************************************************************
* Route legacy FIQ to the GICD
******************************************************************************/
void tegra_fc_enable_fiq_to_ccplex_routing(void)
{
uint32_t val = tegra_fc_read_32(FLOW_CTLR_FLOW_DBG_QUAL);
/* set the bit to pass FIQs to the GICD */
tegra_fc_write_32(FLOW_CTLR_FLOW_DBG_QUAL, val | FLOWCTRL_FIQ2CCPLEX_ENABLE);
}
/*******************************************************************************
* Disable routing legacy FIQ to the GICD
******************************************************************************/
void tegra_fc_disable_fiq_to_ccplex_routing(void)
{
uint32_t val = tegra_fc_read_32(FLOW_CTLR_FLOW_DBG_QUAL);
/* clear the bit to pass FIQs to the GICD */
tegra_fc_write_32(FLOW_CTLR_FLOW_DBG_QUAL, val & ~FLOWCTRL_FIQ2CCPLEX_ENABLE);
}
@@ -0,0 +1,188 @@
/*
* Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/delay_timer.h>
#include <errno.h>
#include <gpcdma.h>
#include <lib/mmio.h>
#include <lib/utils_def.h>
#include <platform_def.h>
#include <stdbool.h>
#include <tegra_def.h>
/* DMA channel registers */
#define DMA_CH_CSR U(0x0)
#define DMA_CH_CSR_WEIGHT_SHIFT U(10)
#define DMA_CH_CSR_XFER_MODE_SHIFT U(21)
#define DMA_CH_CSR_DMA_MODE_MEM2MEM U(4)
#define DMA_CH_CSR_DMA_MODE_FIXEDPATTERN U(6)
#define DMA_CH_CSR_IRQ_MASK_ENABLE (U(1) << 15)
#define DMA_CH_CSR_RUN_ONCE (U(1) << 27)
#define DMA_CH_CSR_ENABLE (U(1) << 31)
#define DMA_CH_STAT U(0x4)
#define DMA_CH_STAT_BUSY (U(1) << 31)
#define DMA_CH_SRC_PTR U(0xC)
#define DMA_CH_DST_PTR U(0x10)
#define DMA_CH_HI_ADR_PTR U(0x14)
#define DMA_CH_HI_ADR_PTR_SRC_MASK U(0xFF)
#define DMA_CH_HI_ADR_PTR_DST_SHIFT U(16)
#define DMA_CH_HI_ADR_PTR_DST_MASK U(0xFF)
#define DMA_CH_MC_SEQ U(0x18)
#define DMA_CH_MC_SEQ_REQ_CNT_SHIFT U(25)
#define DMA_CH_MC_SEQ_REQ_CNT_VAL U(0x10)
#define DMA_CH_MC_SEQ_BURST_SHIFT U(23)
#define DMA_CH_MC_SEQ_BURST_16_WORDS U(0x3)
#define DMA_CH_WORD_COUNT U(0x20)
#define DMA_CH_FIXED_PATTERN U(0x34)
#define DMA_CH_TZ U(0x38)
#define DMA_CH_TZ_ACCESS_ENABLE U(0)
#define DMA_CH_TZ_ACCESS_DISABLE U(3)
#define MAX_TRANSFER_SIZE (1U*1024U*1024U*1024U) /* 1GB */
#define GPCDMA_TIMEOUT_MS U(100)
#define GPCDMA_RESET_BIT (U(1) << 1)
static bool init_done;
static void tegra_gpcdma_write32(uint32_t offset, uint32_t val)
{
mmio_write_32(TEGRA_GPCDMA_BASE + offset, val);
}
static uint32_t tegra_gpcdma_read32(uint32_t offset)
{
return mmio_read_32(TEGRA_GPCDMA_BASE + offset);
}
static void tegra_gpcdma_init(void)
{
/* assert reset for DMA engine */
mmio_write_32(TEGRA_CAR_RESET_BASE + TEGRA_GPCDMA_RST_SET_REG_OFFSET,
GPCDMA_RESET_BIT);
udelay(2);
/* de-assert reset for DMA engine */
mmio_write_32(TEGRA_CAR_RESET_BASE + TEGRA_GPCDMA_RST_CLR_REG_OFFSET,
GPCDMA_RESET_BIT);
}
static void tegra_gpcdma_memcpy_priv(uint64_t dst_addr, uint64_t src_addr,
uint32_t num_bytes, uint32_t mode)
{
uint32_t val, timeout = 0;
int32_t ret = 0;
/* sanity check byte count */
if ((num_bytes > MAX_TRANSFER_SIZE) || ((num_bytes & 0x3U) != U(0))) {
ret = -EINVAL;
}
/* initialise GPCDMA block */
if (!init_done) {
tegra_gpcdma_init();
init_done = true;
}
/* make sure channel isn't busy */
val = tegra_gpcdma_read32(DMA_CH_STAT);
if ((val & DMA_CH_STAT_BUSY) == DMA_CH_STAT_BUSY) {
ERROR("DMA channel is busy\n");
ret = -EBUSY;
}
if (ret == 0) {
/* disable any DMA transfers */
tegra_gpcdma_write32(DMA_CH_CSR, 0);
/* enable DMA access to TZDRAM */
tegra_gpcdma_write32(DMA_CH_TZ, DMA_CH_TZ_ACCESS_ENABLE);
/* configure MC sequencer */
val = (DMA_CH_MC_SEQ_REQ_CNT_VAL << DMA_CH_MC_SEQ_REQ_CNT_SHIFT) |
(DMA_CH_MC_SEQ_BURST_16_WORDS << DMA_CH_MC_SEQ_BURST_SHIFT);
tegra_gpcdma_write32(DMA_CH_MC_SEQ, val);
/* reset fixed pattern */
tegra_gpcdma_write32(DMA_CH_FIXED_PATTERN, 0);
/* populate src and dst address registers */
tegra_gpcdma_write32(DMA_CH_SRC_PTR, (uint32_t)src_addr);
tegra_gpcdma_write32(DMA_CH_DST_PTR, (uint32_t)dst_addr);
val = (uint32_t)((src_addr >> 32) & DMA_CH_HI_ADR_PTR_SRC_MASK);
val |= (uint32_t)(((dst_addr >> 32) & DMA_CH_HI_ADR_PTR_DST_MASK) <<
DMA_CH_HI_ADR_PTR_DST_SHIFT);
tegra_gpcdma_write32(DMA_CH_HI_ADR_PTR, val);
/* transfer size (in words) */
tegra_gpcdma_write32(DMA_CH_WORD_COUNT, ((num_bytes >> 2) - 1U));
/* populate value for CSR */
val = (mode << DMA_CH_CSR_XFER_MODE_SHIFT) |
DMA_CH_CSR_RUN_ONCE | (U(1) << DMA_CH_CSR_WEIGHT_SHIFT) |
DMA_CH_CSR_IRQ_MASK_ENABLE;
tegra_gpcdma_write32(DMA_CH_CSR, val);
/* enable transfer */
val = tegra_gpcdma_read32(DMA_CH_CSR);
val |= DMA_CH_CSR_ENABLE;
tegra_gpcdma_write32(DMA_CH_CSR, val);
/* wait till transfer completes */
do {
/* read the status */
val = tegra_gpcdma_read32(DMA_CH_STAT);
if ((val & DMA_CH_STAT_BUSY) != DMA_CH_STAT_BUSY) {
break;
}
mdelay(1);
timeout++;
} while (timeout < GPCDMA_TIMEOUT_MS);
/* flag timeout error */
if (timeout == GPCDMA_TIMEOUT_MS) {
ERROR("DMA transfer timed out\n");
}
dsbsy();
/* disable DMA access to TZDRAM */
tegra_gpcdma_write32(DMA_CH_TZ, DMA_CH_TZ_ACCESS_DISABLE);
isb();
}
}
/*******************************************************************************
* Memcpy using GPCDMA block (Mem2Mem copy)
******************************************************************************/
void tegra_gpcdma_memcpy(uint64_t dst_addr, uint64_t src_addr,
uint32_t num_bytes)
{
tegra_gpcdma_memcpy_priv(dst_addr, src_addr, num_bytes,
DMA_CH_CSR_DMA_MODE_MEM2MEM);
}
/*******************************************************************************
* Memset using GPCDMA block (Fixed pattern write)
******************************************************************************/
void tegra_gpcdma_zeromem(uint64_t dst_addr, uint32_t num_bytes)
{
tegra_gpcdma_memcpy_priv(dst_addr, 0, num_bytes,
DMA_CH_CSR_DMA_MODE_FIXEDPATTERN);
}
@@ -0,0 +1,212 @@
/*
* Copyright (c) 2015-2019, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2020, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <string.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <lib/mmio.h>
#include <lib/utils.h>
#include <lib/xlat_tables/xlat_tables_v2.h>
#include <memctrl.h>
#include <memctrl_v1.h>
#include <tegra_def.h>
/* Video Memory base and size (live values) */
static uint64_t video_mem_base;
static uint64_t video_mem_size;
/*
* Init SMMU.
*/
void tegra_memctrl_setup(void)
{
/*
* Setup the Memory controller to allow only secure accesses to
* the TZDRAM carveout
*/
INFO("Tegra Memory Controller (v1)\n");
/* allow translations for all MC engines */
tegra_mc_write_32(MC_SMMU_TRANSLATION_ENABLE_0_0,
(unsigned int)MC_SMMU_TRANSLATION_ENABLE);
tegra_mc_write_32(MC_SMMU_TRANSLATION_ENABLE_1_0,
(unsigned int)MC_SMMU_TRANSLATION_ENABLE);
tegra_mc_write_32(MC_SMMU_TRANSLATION_ENABLE_2_0,
(unsigned int)MC_SMMU_TRANSLATION_ENABLE);
tegra_mc_write_32(MC_SMMU_TRANSLATION_ENABLE_3_0,
(unsigned int)MC_SMMU_TRANSLATION_ENABLE);
tegra_mc_write_32(MC_SMMU_TRANSLATION_ENABLE_4_0,
(unsigned int)MC_SMMU_TRANSLATION_ENABLE);
tegra_mc_write_32(MC_SMMU_ASID_SECURITY_0, MC_SMMU_ASID_SECURITY);
tegra_mc_write_32(MC_SMMU_TLB_CONFIG_0, MC_SMMU_TLB_CONFIG_0_RESET_VAL);
tegra_mc_write_32(MC_SMMU_PTC_CONFIG_0, MC_SMMU_PTC_CONFIG_0_RESET_VAL);
/* flush PTC and TLB */
tegra_mc_write_32(MC_SMMU_PTC_FLUSH_0, MC_SMMU_PTC_FLUSH_ALL);
(void)tegra_mc_read_32(MC_SMMU_CONFIG_0); /* read to flush writes */
tegra_mc_write_32(MC_SMMU_TLB_FLUSH_0, MC_SMMU_TLB_FLUSH_ALL);
/* enable SMMU */
tegra_mc_write_32(MC_SMMU_CONFIG_0,
MC_SMMU_CONFIG_0_SMMU_ENABLE_ENABLE);
(void)tegra_mc_read_32(MC_SMMU_CONFIG_0); /* read to flush writes */
/* video memory carveout */
tegra_mc_write_32(MC_VIDEO_PROTECT_BASE_HI,
(uint32_t)(video_mem_base >> 32));
tegra_mc_write_32(MC_VIDEO_PROTECT_BASE_LO, (uint32_t)video_mem_base);
tegra_mc_write_32(MC_VIDEO_PROTECT_SIZE_MB, video_mem_size);
}
/*
* Restore Memory Controller settings after "System Suspend"
*/
void tegra_memctrl_restore_settings(void)
{
tegra_memctrl_setup();
}
/*
* Secure the BL31 DRAM aperture.
*
* phys_base = physical base of TZDRAM aperture
* size_in_bytes = size of aperture in bytes
*/
void tegra_memctrl_tzdram_setup(uint64_t phys_base, uint32_t size_in_bytes)
{
/*
* Setup the Memory controller to allow only secure accesses to
* the TZDRAM carveout
*/
INFO("Configuring TrustZone DRAM Memory Carveout\n");
tegra_mc_write_32(MC_SECURITY_CFG0_0, phys_base);
tegra_mc_write_32(MC_SECURITY_CFG1_0, size_in_bytes >> 20);
}
static void tegra_clear_videomem(uintptr_t non_overlap_area_start,
unsigned long long non_overlap_area_size)
{
int ret;
/*
* Map the NS memory first, clean it and then unmap it.
*/
ret = mmap_add_dynamic_region(non_overlap_area_start, /* PA */
non_overlap_area_start, /* VA */
non_overlap_area_size, /* size */
MT_NS | MT_RW | MT_EXECUTE_NEVER |
MT_NON_CACHEABLE); /* attrs */
assert(ret == 0);
zeromem((void *)non_overlap_area_start, non_overlap_area_size);
flush_dcache_range(non_overlap_area_start, non_overlap_area_size);
mmap_remove_dynamic_region(non_overlap_area_start,
non_overlap_area_size);
}
/*
* Program the Video Memory carveout region
*
* phys_base = physical base of aperture
* size_in_bytes = size of aperture in bytes
*/
void tegra_memctrl_videomem_setup(uint64_t phys_base, uint32_t size_in_bytes)
{
uintptr_t vmem_end_old = video_mem_base + (video_mem_size << 20);
uintptr_t vmem_end_new = phys_base + size_in_bytes;
unsigned long long non_overlap_area_size;
/*
* Setup the Memory controller to restrict CPU accesses to the Video
* Memory region
*/
INFO("Configuring Video Memory Carveout\n");
/*
* Configure Memory Controller directly for the first time.
*/
if (video_mem_base == 0)
goto done;
/*
* Clear the old regions now being exposed. The following cases
* can occur -
*
* 1. clear whole old region (no overlap with new region)
* 2. clear old sub-region below new base
* 3. clear old sub-region above new end
*/
INFO("Cleaning previous Video Memory Carveout\n");
if (phys_base > vmem_end_old || video_mem_base > vmem_end_new) {
tegra_clear_videomem(video_mem_base, video_mem_size << 20);
} else {
if (video_mem_base < phys_base) {
non_overlap_area_size = phys_base - video_mem_base;
tegra_clear_videomem(video_mem_base, non_overlap_area_size);
}
if (vmem_end_old > vmem_end_new) {
non_overlap_area_size = vmem_end_old - vmem_end_new;
tegra_clear_videomem(vmem_end_new, non_overlap_area_size);
}
}
done:
tegra_mc_write_32(MC_VIDEO_PROTECT_BASE_HI, (uint32_t)(phys_base >> 32));
tegra_mc_write_32(MC_VIDEO_PROTECT_BASE_LO, (uint32_t)phys_base);
tegra_mc_write_32(MC_VIDEO_PROTECT_SIZE_MB, size_in_bytes >> 20);
/* store new values */
video_mem_base = phys_base;
video_mem_size = size_in_bytes >> 20;
}
/*
* During boot, USB3 and flash media (SDMMC/SATA) devices need access to
* IRAM. Because these clients connect to the MC and do not have a direct
* path to the IRAM, the MC implements AHB redirection during boot to allow
* path to IRAM. In this mode, accesses to a programmed memory address aperture
* are directed to the AHB bus, allowing access to the IRAM. The AHB aperture
* is defined by the IRAM_BASE_LO and IRAM_BASE_HI registers, which are
* initialized to disable this aperture.
*
* Once bootup is complete, we must program IRAM base to 0xffffffff and
* IRAM top to 0x00000000, thus disabling access to IRAM. DRAM is then
* potentially accessible in this address range. These aperture registers
* also have an access_control/lock bit. After disabling the aperture, the
* access_control register should be programmed to lock the registers.
*/
void tegra_memctrl_disable_ahb_redirection(void)
{
/* program the aperture registers */
tegra_mc_write_32(MC_IRAM_BASE_LO, 0xFFFFFFFF);
tegra_mc_write_32(MC_IRAM_TOP_LO, 0);
tegra_mc_write_32(MC_IRAM_BASE_TOP_HI, 0);
/* lock the aperture registers */
tegra_mc_write_32(MC_IRAM_REG_CTRL, MC_DISABLE_IRAM_CFG_WRITES);
}
void tegra_memctrl_clear_pending_interrupts(void)
{
uint32_t mcerr;
/* check if there are any pending interrupts */
mcerr = mmio_read_32(TEGRA_MC_BASE + MC_INTSTATUS);
if (mcerr != (uint32_t)0U) { /* should not see error here */
WARN("MC_INTSTATUS = 0x%x (should be zero)\n", mcerr);
mmio_write_32((TEGRA_MC_BASE + MC_INTSTATUS), mcerr);
}
}
@@ -0,0 +1,354 @@
/*
* Copyright (c) 2015-2017, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2019-2020, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <string.h>
#include <arch_helpers.h>
#include <common/bl_common.h>
#include <common/debug.h>
#include <lib/mmio.h>
#include <lib/utils.h>
#include <lib/xlat_tables/xlat_tables_v2.h>
#include <mce.h>
#include <memctrl.h>
#include <memctrl_v2.h>
#include <smmu.h>
#include <tegra_def.h>
#include <tegra_platform.h>
#include <tegra_private.h>
/* Video Memory base and size (live values) */
static uint64_t video_mem_base;
static uint64_t video_mem_size_mb;
/*
* Init Memory controller during boot.
*/
void tegra_memctrl_setup(void)
{
INFO("Tegra Memory Controller (v2)\n");
/* Initialize the System memory management unit */
tegra_smmu_init();
/* allow platforms to program custom memory controller settings */
plat_memctrl_setup();
/*
* All requests at boot time, and certain requests during
* normal run time, are physically addressed and must bypass
* the SMMU. The client hub logic implements a hardware bypass
* path around the Translation Buffer Units (TBU). During
* boot-time, the SMMU_BYPASS_CTRL register (which defaults to
* TBU_BYPASS mode) will be used to steer all requests around
* the uninitialized TBUs. During normal operation, this register
* is locked into TBU_BYPASS_SID config, which routes requests
* with special StreamID 0x7f on the bypass path and all others
* through the selected TBU. This is done to disable SMMU Bypass
* mode, as it could be used to circumvent SMMU security checks.
*/
tegra_mc_write_32(MC_SMMU_BYPASS_CONFIG,
MC_SMMU_BYPASS_CONFIG_SETTINGS);
}
/*
* Restore Memory Controller settings after "System Suspend"
*/
void tegra_memctrl_restore_settings(void)
{
/* restore platform's memory controller settings */
plat_memctrl_restore();
/* video memory carveout region */
if (video_mem_base != 0ULL) {
tegra_mc_write_32(MC_VIDEO_PROTECT_BASE_LO,
(uint32_t)video_mem_base);
assert(tegra_mc_read_32(MC_VIDEO_PROTECT_BASE_LO)
== (uint32_t)video_mem_base);
tegra_mc_write_32(MC_VIDEO_PROTECT_BASE_HI,
(uint32_t)(video_mem_base >> 32));
assert(tegra_mc_read_32(MC_VIDEO_PROTECT_BASE_HI)
== (uint32_t)(video_mem_base >> 32));
tegra_mc_write_32(MC_VIDEO_PROTECT_SIZE_MB,
(uint32_t)video_mem_size_mb);
assert(tegra_mc_read_32(MC_VIDEO_PROTECT_SIZE_MB)
== (uint32_t)video_mem_size_mb);
/*
* MCE propagates the VideoMem configuration values across the
* CCPLEX.
*/
mce_update_gsc_videomem();
}
}
/*
* Secure the BL31 DRAM aperture.
*
* phys_base = physical base of TZDRAM aperture
* size_in_bytes = size of aperture in bytes
*/
void tegra_memctrl_tzdram_setup(uint64_t phys_base, uint32_t size_in_bytes)
{
/*
* Perform platform specific steps.
*/
plat_memctrl_tzdram_setup(phys_base, size_in_bytes);
}
/*
* Secure the BL31 TZRAM aperture.
*
* phys_base = physical base of TZRAM aperture
* size_in_bytes = size of aperture in bytes
*/
void tegra_memctrl_tzram_setup(uint64_t phys_base, uint32_t size_in_bytes)
{
; /* do nothing */
}
/*
* Save MC settings before "System Suspend" to TZDRAM
*/
void tegra_mc_save_context(uint64_t mc_ctx_addr)
{
uint32_t i, num_entries = 0;
mc_regs_t *mc_ctx_regs;
const plat_params_from_bl2_t *params_from_bl2 = bl31_get_plat_params();
uint64_t tzdram_base = params_from_bl2->tzdram_base;
uint64_t tzdram_end = tzdram_base + params_from_bl2->tzdram_size;
assert((mc_ctx_addr >= tzdram_base) && (mc_ctx_addr <= tzdram_end));
/* get MC context table */
mc_ctx_regs = plat_memctrl_get_sys_suspend_ctx();
assert(mc_ctx_regs != NULL);
/*
* mc_ctx_regs[0].val contains the size of the context table minus
* the last entry. Sanity check the table size before we start with
* the context save operation.
*/
while (mc_ctx_regs[num_entries].reg != 0xFFFFFFFFU) {
num_entries++;
}
/* panic if the sizes do not match */
if (num_entries != mc_ctx_regs[0].val) {
ERROR("MC context size mismatch!");
panic();
}
/* save MC register values */
for (i = 1U; i < num_entries; i++) {
mc_ctx_regs[i].val = mmio_read_32(mc_ctx_regs[i].reg);
}
/* increment by 1 to take care of the last entry */
num_entries++;
/* Save MC config settings */
(void)memcpy((void *)mc_ctx_addr, mc_ctx_regs,
sizeof(mc_regs_t) * num_entries);
/* save the MC table address */
mmio_write_32(TEGRA_SCRATCH_BASE + SCRATCH_MC_TABLE_ADDR_LO,
(uint32_t)mc_ctx_addr);
assert(mmio_read_32(TEGRA_SCRATCH_BASE + SCRATCH_MC_TABLE_ADDR_LO)
== (uint32_t)mc_ctx_addr);
mmio_write_32(TEGRA_SCRATCH_BASE + SCRATCH_MC_TABLE_ADDR_HI,
(uint32_t)(mc_ctx_addr >> 32));
assert(mmio_read_32(TEGRA_SCRATCH_BASE + SCRATCH_MC_TABLE_ADDR_HI)
== (uint32_t)(mc_ctx_addr >> 32));
}
static void tegra_lock_videomem_nonoverlap(uint64_t phys_base,
uint64_t size_in_bytes)
{
uint32_t index;
uint64_t total_128kb_blocks = size_in_bytes >> 17;
uint64_t residual_4kb_blocks = (size_in_bytes & (uint32_t)0x1FFFF) >> 12;
uint64_t val;
/*
* Reset the access configuration registers to restrict access to
* old Videomem aperture
*/
for (index = MC_VIDEO_PROTECT_CLEAR_ACCESS_CFG0;
index < ((uint32_t)MC_VIDEO_PROTECT_CLEAR_ACCESS_CFG0 + (uint32_t)MC_GSC_CONFIG_REGS_SIZE);
index += 4U) {
tegra_mc_write_32(index, 0);
}
/*
* Set the base. It must be 4k aligned, at least.
*/
assert((phys_base & (uint64_t)0xFFF) == 0U);
tegra_mc_write_32(MC_VIDEO_PROTECT_CLEAR_BASE_LO, (uint32_t)phys_base);
tegra_mc_write_32(MC_VIDEO_PROTECT_CLEAR_BASE_HI,
(uint32_t)(phys_base >> 32) & (uint32_t)MC_GSC_BASE_HI_MASK);
/*
* Set the aperture size
*
* total size = (number of 128KB blocks) + (number of remaining 4KB
* blocks)
*
*/
val = (uint32_t)((residual_4kb_blocks << MC_GSC_SIZE_RANGE_4KB_SHIFT) |
total_128kb_blocks);
tegra_mc_write_32(MC_VIDEO_PROTECT_CLEAR_SIZE, (uint32_t)val);
/*
* Lock the configuration settings by enabling TZ-only lock and
* locking the configuration against any future changes from NS
* world.
*/
tegra_mc_write_32(MC_VIDEO_PROTECT_CLEAR_CFG,
(uint32_t)MC_GSC_ENABLE_TZ_LOCK_BIT);
/*
* MCE propagates the GSC configuration values across the
* CCPLEX.
*/
}
static void tegra_unlock_videomem_nonoverlap(void)
{
/* Clear the base */
tegra_mc_write_32(MC_VIDEO_PROTECT_CLEAR_BASE_LO, 0);
tegra_mc_write_32(MC_VIDEO_PROTECT_CLEAR_BASE_HI, 0);
/* Clear the size */
tegra_mc_write_32(MC_VIDEO_PROTECT_CLEAR_SIZE, 0);
}
static void tegra_clear_videomem(uintptr_t non_overlap_area_start,
unsigned long long non_overlap_area_size)
{
int ret;
INFO("Cleaning previous Video Memory Carveout\n");
/*
* Map the NS memory first, clean it and then unmap it.
*/
ret = mmap_add_dynamic_region(non_overlap_area_start, /* PA */
non_overlap_area_start, /* VA */
non_overlap_area_size, /* size */
MT_DEVICE | MT_RW | MT_NS); /* attrs */
assert(ret == 0);
zeromem((void *)non_overlap_area_start, non_overlap_area_size);
flush_dcache_range(non_overlap_area_start, non_overlap_area_size);
ret = mmap_remove_dynamic_region(non_overlap_area_start,
non_overlap_area_size);
assert(ret == 0);
}
static void tegra_clear_videomem_nonoverlap(uintptr_t phys_base,
unsigned long size_in_bytes)
{
uintptr_t vmem_end_old = video_mem_base + (video_mem_size_mb << 20);
uintptr_t vmem_end_new = phys_base + size_in_bytes;
unsigned long long non_overlap_area_size;
/*
* Clear the old regions now being exposed. The following cases
* can occur -
*
* 1. clear whole old region (no overlap with new region)
* 2. clear old sub-region below new base
* 3. clear old sub-region above new end
*/
if ((phys_base > vmem_end_old) || (video_mem_base > vmem_end_new)) {
tegra_clear_videomem(video_mem_base,
video_mem_size_mb << 20U);
} else {
if (video_mem_base < phys_base) {
non_overlap_area_size = phys_base - video_mem_base;
tegra_clear_videomem(video_mem_base, non_overlap_area_size);
}
if (vmem_end_old > vmem_end_new) {
non_overlap_area_size = vmem_end_old - vmem_end_new;
tegra_clear_videomem(vmem_end_new, non_overlap_area_size);
}
}
}
/*
* Program the Video Memory carveout region
*
* phys_base = physical base of aperture
* size_in_bytes = size of aperture in bytes
*/
void tegra_memctrl_videomem_setup(uint64_t phys_base, uint32_t size_in_bytes)
{
/*
* Setup the Memory controller to restrict CPU accesses to the Video
* Memory region
*/
INFO("Configuring Video Memory Carveout\n");
if (video_mem_base != 0U) {
/*
* Lock the non overlapping memory being cleared so that
* other masters do not accidently write to it. The memory
* would be unlocked once the non overlapping region is
* cleared and the new memory settings take effect.
*/
tegra_lock_videomem_nonoverlap(video_mem_base,
video_mem_size_mb << 20);
}
/* program the Videomem aperture */
tegra_mc_write_32(MC_VIDEO_PROTECT_BASE_LO, (uint32_t)phys_base);
tegra_mc_write_32(MC_VIDEO_PROTECT_BASE_HI,
(uint32_t)(phys_base >> 32));
tegra_mc_write_32(MC_VIDEO_PROTECT_SIZE_MB, size_in_bytes >> 20);
/* Redundancy check for Video Protect setting */
assert(tegra_mc_read_32(MC_VIDEO_PROTECT_BASE_LO)
== (uint32_t)phys_base);
assert(tegra_mc_read_32(MC_VIDEO_PROTECT_BASE_HI)
== (uint32_t)(phys_base >> 32));
assert(tegra_mc_read_32(MC_VIDEO_PROTECT_SIZE_MB)
== (size_in_bytes >> 20));
/*
* MCE propagates the VideoMem configuration values across the
* CCPLEX.
*/
(void)mce_update_gsc_videomem();
/* Clear the non-overlapping memory */
if (video_mem_base != 0U) {
tegra_clear_videomem_nonoverlap(phys_base, size_in_bytes);
tegra_unlock_videomem_nonoverlap();
}
/* store new values */
video_mem_base = phys_base;
video_mem_size_mb = (uint64_t)size_in_bytes >> 20;
}
/*
* This feature exists only for v1 of the Tegra Memory Controller.
*/
void tegra_memctrl_disable_ahb_redirection(void)
{
; /* do nothing */
}
void tegra_memctrl_clear_pending_interrupts(void)
{
; /* do nothing */
}
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2015, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <arch_helpers.h>
#include <common/debug.h>
#include <lib/mmio.h>
#include <pmc.h>
#include <tegra_def.h>
#define RESET_ENABLE 0x10U
/* Module IDs used during power ungate procedure */
static const uint32_t pmc_cpu_powergate_id[4] = {
14, /* CPU 0 */
9, /* CPU 1 */
10, /* CPU 2 */
11 /* CPU 3 */
};
/*******************************************************************************
* Power ungate CPU to start the boot process. CPU reset vectors must be
* populated before calling this function.
******************************************************************************/
void tegra_pmc_cpu_on(int32_t cpu)
{
uint32_t val;
/*
* Check if CPU is already power ungated
*/
val = tegra_pmc_read_32(PMC_PWRGATE_STATUS);
if ((val & (1U << pmc_cpu_powergate_id[cpu])) == 0U) {
/*
* The PMC deasserts the START bit when it starts the power
* ungate process. Loop till no power toggle is in progress.
*/
do {
val = tegra_pmc_read_32(PMC_PWRGATE_TOGGLE);
} while ((val & PMC_TOGGLE_START) != 0U);
/*
* Start the power ungate procedure
*/
val = pmc_cpu_powergate_id[cpu] | PMC_TOGGLE_START;
tegra_pmc_write_32(PMC_PWRGATE_TOGGLE, val);
/*
* The PMC deasserts the START bit when it starts the power
* ungate process. Loop till powergate START bit is asserted.
*/
do {
val = tegra_pmc_read_32(PMC_PWRGATE_TOGGLE);
} while ((val & (1U << 8)) != 0U);
/* loop till the CPU is power ungated */
do {
val = tegra_pmc_read_32(PMC_PWRGATE_STATUS);
} while ((val & (1U << pmc_cpu_powergate_id[cpu])) == 0U);
}
}
/*******************************************************************************
* Setup CPU vectors for resume from deep sleep
******************************************************************************/
void tegra_pmc_cpu_setup(uint64_t reset_addr)
{
uint32_t val;
tegra_pmc_write_32(PMC_SECURE_SCRATCH34,
((uint32_t)reset_addr & 0xFFFFFFFFU) | 1U);
val = (uint32_t)(reset_addr >> 32U);
tegra_pmc_write_32(PMC_SECURE_SCRATCH35, val & 0x7FFU);
}
/*******************************************************************************
* Lock CPU vectors to restrict further writes
******************************************************************************/
void tegra_pmc_lock_cpu_vectors(void)
{
uint32_t val;
/* lock PMC_SECURE_SCRATCH22 */
val = tegra_pmc_read_32(PMC_SECURE_DISABLE2);
val |= PMC_SECURE_DISABLE2_WRITE22_ON;
tegra_pmc_write_32(PMC_SECURE_DISABLE2, val);
/* lock PMC_SECURE_SCRATCH34/35 */
val = tegra_pmc_read_32(PMC_SECURE_DISABLE3);
val |= (PMC_SECURE_DISABLE3_WRITE34_ON |
PMC_SECURE_DISABLE3_WRITE35_ON);
tegra_pmc_write_32(PMC_SECURE_DISABLE3, val);
}
/*******************************************************************************
* Find out if this is the last standing CPU
******************************************************************************/
bool tegra_pmc_is_last_on_cpu(void)
{
int i, cpu = read_mpidr() & MPIDR_CPU_MASK;
uint32_t val = tegra_pmc_read_32(PMC_PWRGATE_STATUS);;
bool status = true;
/* check if this is the last standing CPU */
for (i = 0; i < PLATFORM_MAX_CPUS_PER_CLUSTER; i++) {
/* skip the current CPU */
if (i == cpu)
continue;
/* are other CPUs already power gated? */
if ((val & ((uint32_t)1 << pmc_cpu_powergate_id[i])) != 0U) {
status = false;
}
}
return status;
}
/*******************************************************************************
* Handler to be called on exiting System suspend. Right now only DPD registers
* are cleared.
******************************************************************************/
void tegra_pmc_resume(void)
{
/* Clear DPD sample */
mmio_write_32((TEGRA_PMC_BASE + PMC_IO_DPD_SAMPLE), 0x0);
/* Clear DPD Enable */
mmio_write_32((TEGRA_PMC_BASE + PMC_DPD_ENABLE_0), 0x0);
}
/*******************************************************************************
* Restart the system
******************************************************************************/
__dead2 void tegra_pmc_system_reset(void)
{
uint32_t reg;
reg = tegra_pmc_read_32(PMC_CONFIG);
reg |= RESET_ENABLE; /* restart */
tegra_pmc_write_32(PMC_CONFIG, reg);
wfi();
ERROR("Tegra System Reset: operation not handled.\n");
panic();
}
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2016-2018, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2020, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <assert.h>
#include <string.h>
#include <platform_def.h>
#include <common/bl_common.h>
#include <common/debug.h>
#include <smmu.h>
#include <tegra_platform.h>
#include <tegra_private.h>
extern void memcpy16(void *dest, const void *src, unsigned int length);
#define SMMU_NUM_CONTEXTS 64U
#define SMMU_CONTEXT_BANK_MAX_IDX 64U
#define MISMATCH_DETECTED 0x55AA55AAU
/*
* Init SMMU during boot or "System Suspend" exit
*/
void tegra_smmu_init(void)
{
uint32_t val, cb_idx, smmu_id, ctx_base;
uint32_t num_smmu_devices = plat_get_num_smmu_devices();
for (smmu_id = 0U; smmu_id < num_smmu_devices; smmu_id++) {
/* Program the SMMU pagesize and reset CACHE_LOCK bit */
val = tegra_smmu_read_32(smmu_id, SMMU_GSR0_SECURE_ACR);
val |= SMMU_GSR0_PGSIZE_64K;
val &= (uint32_t)~SMMU_ACR_CACHE_LOCK_ENABLE_BIT;
tegra_smmu_write_32(smmu_id, SMMU_GSR0_SECURE_ACR, val);
/* reset CACHE LOCK bit for NS Aux. Config. Register */
val = tegra_smmu_read_32(smmu_id, SMMU_GNSR_ACR);
val &= (uint32_t)~SMMU_ACR_CACHE_LOCK_ENABLE_BIT;
tegra_smmu_write_32(smmu_id, SMMU_GNSR_ACR, val);
/* disable TCU prefetch for all contexts */
ctx_base = (SMMU_GSR0_PGSIZE_64K * SMMU_NUM_CONTEXTS)
+ SMMU_CBn_ACTLR;
for (cb_idx = 0U; cb_idx < SMMU_CONTEXT_BANK_MAX_IDX; cb_idx++) {
val = tegra_smmu_read_32(smmu_id,
ctx_base + (SMMU_GSR0_PGSIZE_64K * cb_idx));
val &= (uint32_t)~SMMU_CBn_ACTLR_CPRE_BIT;
tegra_smmu_write_32(smmu_id, ctx_base +
(SMMU_GSR0_PGSIZE_64K * cb_idx), val);
}
/* set CACHE LOCK bit for NS Aux. Config. Register */
val = tegra_smmu_read_32(smmu_id, SMMU_GNSR_ACR);
val |= (uint32_t)SMMU_ACR_CACHE_LOCK_ENABLE_BIT;
tegra_smmu_write_32(smmu_id, SMMU_GNSR_ACR, val);
/* set CACHE LOCK bit for S Aux. Config. Register */
val = tegra_smmu_read_32(smmu_id, SMMU_GSR0_SECURE_ACR);
val |= (uint32_t)SMMU_ACR_CACHE_LOCK_ENABLE_BIT;
tegra_smmu_write_32(smmu_id, SMMU_GSR0_SECURE_ACR, val);
}
}
/*
* Verify SMMU settings have not been altered during boot
*/
void tegra_smmu_verify(void)
{
uint32_t cb_idx, ctx_base, smmu_id, val;
uint32_t num_smmu_devices = plat_get_num_smmu_devices();
uint32_t mismatch = 0U;
for (smmu_id = 0U; smmu_id < num_smmu_devices; smmu_id++) {
/* check PGSIZE_64K bit inr S Aux. Config. Register */
val = tegra_smmu_read_32(smmu_id, SMMU_GSR0_SECURE_ACR);
if (0U == (val & SMMU_GSR0_PGSIZE_64K)) {
ERROR("%s: PGSIZE_64K Mismatch - smmu_id=%d, GSR0_SECURE_ACR=%x\n",
__func__, smmu_id, val);
mismatch = MISMATCH_DETECTED;
}
/* check CACHE LOCK bit in S Aux. Config. Register */
if (0U == (val & SMMU_ACR_CACHE_LOCK_ENABLE_BIT)) {
ERROR("%s: CACHE_LOCK Mismatch - smmu_id=%d, GSR0_SECURE_ACR=%x\n",
__func__, smmu_id, val);
mismatch = MISMATCH_DETECTED;
}
/* check CACHE LOCK bit in NS Aux. Config. Register */
val = tegra_smmu_read_32(smmu_id, SMMU_GNSR_ACR);
if (0U == (val & SMMU_ACR_CACHE_LOCK_ENABLE_BIT)) {
ERROR("%s: Mismatch - smmu_id=%d, GNSR_ACR=%x\n",
__func__, smmu_id, val);
mismatch = MISMATCH_DETECTED;
}
/* verify TCU prefetch for all contexts is disabled */
ctx_base = (SMMU_GSR0_PGSIZE_64K * SMMU_NUM_CONTEXTS) +
SMMU_CBn_ACTLR;
for (cb_idx = 0U; cb_idx < SMMU_CONTEXT_BANK_MAX_IDX; cb_idx++) {
val = tegra_smmu_read_32(smmu_id,
ctx_base + (SMMU_GSR0_PGSIZE_64K * cb_idx));
if (0U != (val & SMMU_CBn_ACTLR_CPRE_BIT)) {
ERROR("%s: Mismatch - smmu_id=%d, cb_idx=%d, GSR0_PGSIZE_64K=%x\n",
__func__, smmu_id, cb_idx, val);
mismatch = MISMATCH_DETECTED;
}
}
}
/* Treat configuration mismatch as fatal */
if ((mismatch == MISMATCH_DETECTED) && tegra_platform_is_silicon()) {
panic();
}
}
@@ -0,0 +1,187 @@
/*
* Copyright (c) 2017-2020, ARM Limited and Contributors. All rights reserved.
* Copyright (c) 2020, NVIDIA Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <asm_macros.S>
#include <assert_macros.S>
#include <console_macros.S>
#define CONSOLE_NUM_BYTES_SHIFT 24
#define CONSOLE_FLUSH_DATA_TO_PORT (1 << 26)
#define CONSOLE_RING_DOORBELL (1 << 31)
#define CONSOLE_IS_BUSY (1 << 31)
#define CONSOLE_TIMEOUT 0xC000 /* 50 ms */
/*
* This file contains a driver implementation to make use of the
* real console implementation provided by the SPE firmware running
* SoCs after Tegra186.
*
* This console is shared by multiple components and the SPE firmware
* finally displays everything on the UART port.
*/
.globl console_spe_core_init
.globl console_spe_core_putc
.globl console_spe_core_getc
.globl console_spe_core_flush
.globl console_spe_putc
.globl console_spe_getc
.globl console_spe_flush
.globl console_spe_register
.macro check_if_console_is_ready base, tmp1, tmp2, label
/* wait until spe is ready or timeout expires */
mrs \tmp2, cntps_tval_el1
1: ldr \tmp1, [\base]
and \tmp1, \tmp1, #CONSOLE_IS_BUSY
cbz \tmp1, 2f
mrs \tmp1, cntps_tval_el1
sub \tmp1, \tmp2, \tmp1
cmp \tmp1, #CONSOLE_TIMEOUT
b.lt 1b
b \label
2:
.endm
/* -------------------------------------------------
* int console_spe_register(uintptr_t baseaddr,
* uint32_t clock, uint32_t baud,
* console_t *console);
* Function to initialize and register a new spe
* console. Storage passed in for the console struct
* *must* be persistent (i.e. not from the stack).
* In: x0 - UART register base address
* w1 - UART clock in Hz
* w2 - Baud rate
* x3 - pointer to empty console_t struct
* Out: return 1 on success, 0 on error
* Clobber list : x0, x1, x2, x6, x7, x14
* -------------------------------------------------
*/
func console_spe_register
/* Check the input base address */
cbz x0, register_fail
/* Dont use clock or baud rate, so ok to overwrite them */
check_if_console_is_ready x0, x1, x2, register_fail
cbz x3, register_fail
str x0, [x3, #CONSOLE_T_BASE]
mov x0, x3
finish_console_register spe putc=1, getc=1, flush=1
register_fail:
mov w0, wzr
ret
endfunc console_spe_register
/* --------------------------------------------------------
* int console_spe_core_putc(int c, uintptr_t base_addr)
* Function to output a character over the console. It
* returns the character printed on success or -1 on error.
* In : w0 - character to be printed
* x1 - console base address
* Out : return -1 on error else return character.
* Clobber list : x2, x3
* --------------------------------------------------------
*/
func console_spe_core_putc
/* Check the input parameter */
cbz x1, putc_error
/* Prepend '\r' to '\n' */
cmp w0, #0xA
b.ne not_eol
check_if_console_is_ready x1, x2, x3, putc_error
/* spe is ready */
mov w2, #0xD /* '\r' */
and w2, w2, #0xFF
mov w3, #(CONSOLE_RING_DOORBELL | (1 << CONSOLE_NUM_BYTES_SHIFT))
orr w2, w2, w3
str w2, [x1]
not_eol:
check_if_console_is_ready x1, x2, x3, putc_error
/* spe is ready */
mov w2, w0
and w2, w2, #0xFF
mov w3, #(CONSOLE_RING_DOORBELL | (1 << CONSOLE_NUM_BYTES_SHIFT))
orr w2, w2, w3
str w2, [x1]
ret
putc_error:
mov w0, #-1
ret
endfunc console_spe_core_putc
/* --------------------------------------------------------
* int console_spe_putc(int c, console_t *console)
* Function to output a character over the console. It
* returns the character printed on success or -1 on error.
* In : w0 - character to be printed
* x1 - pointer to console_t structure
* Out : return -1 on error else return character.
* Clobber list : x2
* --------------------------------------------------------
*/
func console_spe_putc
ldr x1, [x1, #CONSOLE_T_BASE]
b console_spe_core_putc
endfunc console_spe_putc
/* ---------------------------------------------
* int console_spe_getc(console_t *console)
* Function to get a character from the console.
* It returns the character grabbed on success
* or -1 if no character is available.
* In : x0 - pointer to console_t structure
* Out: w0 - character if available, else -1
* Clobber list : x0, x1
* ---------------------------------------------
*/
func console_spe_getc
mov w0, #-1
ret
endfunc console_spe_getc
/* -------------------------------------------------
* void console_spe_core_flush(uintptr_t base_addr)
* Function to force a write of all buffered
* data that hasn't been output.
* In : x0 - console base address
* Out : void.
* Clobber list : x0, x1
* -------------------------------------------------
*/
func console_spe_core_flush
#if ENABLE_ASSERTIONS
cmp x0, #0
ASM_ASSERT(ne)
#endif /* ENABLE_ASSERTIONS */
/* flush console */
mov w1, #(CONSOLE_RING_DOORBELL | CONSOLE_FLUSH_DATA_TO_PORT)
str w1, [x0]
ret
endfunc console_spe_core_flush
/* ---------------------------------------------
* void console_spe_flush(console_t *console)
* Function to force a write of all buffered
* data that hasn't been output.
* In : x0 - pointer to console_t structure
* Out : void.
* Clobber list : x0, x1
* ---------------------------------------------
*/
func console_spe_flush
ldr x0, [x0, #CONSOLE_T_BASE]
b console_spe_core_flush
endfunc console_spe_flush