tegrakernel/kernel/nvidia/drivers/nvlink/t19x-nvlink-endpt-minion.c

951 lines
26 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* t19x-nvlink-endpt-minion.c:
* This file contains code for booting and interacting with the MINION
* microcontroller located inside the Tegra NVLINK controller.
*
* Copyright (c) 2017-2018, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/firmware.h>
#include <linux/clk.h>
#include "t19x-nvlink-endpt.h"
#include "nvlink-hw.h"
#define MINION_FW_PATH "nvlink/t194_minion_ucode.bin"
#define MINION_BYTES_PER_BLOCK 256
#define MINION_WORD_SIZE 4
/* Extract a WORD from the MINION ucode */
static inline u32 minion_extract_word(struct tnvlink_dev *tdev, int idx)
{
struct nvlink_device *ndev = tdev->ndev;
u32 out_data = 0;
u8 byte = 0;
int i = 0;
for (i = 0; i < 4; i++) {
byte = ndev->minion_fw->data[idx + i];
out_data |= ((u32)byte) << (8 * i);
}
return out_data;
}
/* Helper function for writing 1 word of data to the MINION's IMEM */
static inline void minion_write_imem(struct tnvlink_dev *tdev,
u32 byte_offs,
u32 word,
u32 tag)
{
/* Need to write tag at the start of each new 256B block */
if ((byte_offs % MINION_BYTES_PER_BLOCK) < 4)
nvlw_minion_writel(tdev, CMINION_FALCON_IMEMT, tag);
nvlw_minion_writel(tdev, CMINION_FALCON_IMEMD, word);
}
/* Helper function for writing 1 word of data to the MINION's DMEM */
static inline void minion_write_dmem(struct tnvlink_dev *tdev, u32 word)
{
nvlw_minion_writel(tdev, CMINION_FALCON_DMEMD, word);
}
/* Load a section of the MINION ucode into IMEM or DMEM */
static int minion_load_ucode_section(struct tnvlink_dev *tdev,
int is_imem,
u32 offset,
u32 size,
int use_app_tag,
u32 app_tag)
{
struct nvlink_device *ndev = tdev->ndev;
u32 i = offset;
u32 byte_pos = 0;
u8 byte = 0;
u32 word = 0;
u32 tag = 0;
if ((offset + size) > ndev->minion_hdr.ucode_data_size) {
nvlink_err("Section size is invalid");
return -1;
}
/* Extract bytes from ucode image and write them to IMEM or DMEM */
for (i = offset; i < (offset + size); i++) {
byte_pos = i % 4;
byte = ndev->minion_img[i];
/* Increment app tag at the start of each new 256B block */
if (use_app_tag &&
(i != offset) &&
((i % MINION_BYTES_PER_BLOCK) == 0)) {
app_tag++;
}
/* Last byte */
if (i == ndev->minion_hdr.ucode_data_size - 1) {
if (byte_pos != 0) {
if (is_imem) {
if (use_app_tag)
tag = app_tag;
else
tag = i/MINION_BYTES_PER_BLOCK;
minion_write_imem(tdev, i, word, tag);
} else {
minion_write_dmem(tdev, word);
}
}
} else {
if (byte_pos == 0)
word = 0;
word |= ((u32)byte) << (8 * byte_pos);
/* Write word to IMEM or DMEM */
if (byte_pos == 3) {
if (is_imem) {
if (use_app_tag)
tag = app_tag;
else
tag = i/MINION_BYTES_PER_BLOCK;
minion_write_imem(tdev, i, word, tag);
} else {
minion_write_dmem(tdev, word);
}
}
}
}
return 0;
}
/* Send a command to the MINION and wait for command completion */
int minion_send_cmd(struct tnvlink_dev *tdev,
u32 cmd,
u32 scratch0_val)
{
int err = 0;
u32 reg_val = 0;
/* Write to minion scratch if needed by command */
if (cmd == MINION_NVLINK_DL_CMD_COMMAND_CONFIGEOM)
nvlw_minion_writel(tdev, MINION_MISC, scratch0_val);
/* Send command to MINION */
reg_val = (cmd << MINION_NVLINK_DL_CMD_COMMAND_SHIFT) &
MINION_NVLINK_DL_CMD_COMMAND_MASK;
reg_val |= BIT(MINION_NVLINK_DL_CMD_FAULT);
nvlw_minion_writel(tdev, MINION_NVLINK_DL_CMD, reg_val);
/* Wait for MINION_NVLINK_DL_CMD_READY bit to be set */
err = wait_for_reg_cond_nvlink(tdev,
MINION_NVLINK_DL_CMD,
MINION_NVLINK_DL_CMD_READY,
true,
"MINION_NVLINK_DL_CMD_READY",
nvlw_minion_readl,
&reg_val,
DEFAULT_LOOP_TIMEOUT_US);
if (err < 0) {
nvlink_err("MINION command (cmd = %d) failed to complete", cmd);
return err;
}
if (reg_val & BIT(MINION_NVLINK_DL_CMD_FAULT)) {
nvlink_err("MINION command (cmd = %d) faulted!", cmd);
/* Clear the fault and return */
nvlw_minion_writel(tdev, MINION_NVLINK_DL_CMD, reg_val);
return -1;
}
nvlink_dbg("MINION command (cmd = %d) completed successfully!", cmd);
return 0;
}
/*
* minion_print_ucode: Dump the contents of the MINION's IMEM and DMEM
*
* TODO: Currently we're making assumptions about the ordering of the IMEM/DMEM
* sections. We need to do a run-time sort on the starting offsets of all
* the IMEM/DMEM sections and then dump out the contents of each section
* in the derived order.
*/
static void minion_print_ucode(struct tnvlink_dev *tdev)
{
struct nvlink_device *ndev = tdev->ndev;
struct minion_hdr *hdr = &(ndev->minion_hdr);
int i = 0;
int j = 0;
u32 reg_val = 0;
u32 byte_num = 0;
/* Dump the IMEM's contents */
nvlink_dbg("");
nvlink_dbg("MINION IMEM DUMP - START");
/* Initialize address of IMEM to 0x0 and set auto-increment on read */
nvlw_minion_writel(tdev,
CMINION_FALCON_IMEMC,
BIT(CMINION_FALCON_IMEMC_AINCR));
/* Dump the OS code section of the IMEM */
nvlink_dbg("");
nvlink_dbg("OS Code Section:");
for (i = 0; i < hdr->os_code_size/MINION_WORD_SIZE; i++) {
reg_val = nvlw_minion_readl(tdev, CMINION_FALCON_IMEMD);
nvlink_dbg("Byte 0x%x, Data = 0x%x", byte_num, reg_val);
byte_num += MINION_WORD_SIZE;
}
/* Dump the app code sections of the IMEM */
for (i = 0; i < hdr->num_apps; i++) {
nvlink_dbg("");
nvlink_dbg("App %d Code Section:", i);
for (j = 0; j < hdr->app_code_sizes[i]/MINION_WORD_SIZE; j++) {
reg_val = nvlw_minion_readl(tdev, CMINION_FALCON_IMEMD);
nvlink_dbg("Byte 0x%x, Data = 0x%x", byte_num, reg_val);
byte_num += MINION_WORD_SIZE;
}
}
nvlink_dbg("");
nvlink_dbg("MINION IMEM DUMP - END");
/* Dump the DMEM's contents */
nvlink_dbg("");
nvlink_dbg("MINION DMEM DUMP - START");
/* Initialize address of DMEM to 0x0 and set auto-increment on read */
nvlw_minion_writel(tdev,
CMINION_FALCON_DMEMC,
BIT(CMINION_FALCON_DMEMC_AINCR));
/* Dump the OS data section of the DMEM */
nvlink_dbg("");
nvlink_dbg("OS Data Section:");
byte_num = 0;
for (i = 0; i < hdr->os_data_size/MINION_WORD_SIZE; i++) {
reg_val = nvlw_minion_readl(tdev, CMINION_FALCON_DMEMD);
nvlink_dbg("Byte 0x%x, Data = 0x%x", byte_num, reg_val);
byte_num += MINION_WORD_SIZE;
}
/* Dump the app data sections of the DMEM */
for (i = 0; i < hdr->num_apps; i++) {
nvlink_dbg("");
nvlink_dbg("App %d Data Section:", i);
for (j = 0; j < hdr->app_data_sizes[i]/MINION_WORD_SIZE; j++) {
reg_val = nvlw_minion_readl(tdev, CMINION_FALCON_DMEMD);
nvlink_dbg("Byte 0x%x, Data = 0x%x", byte_num, reg_val);
byte_num += MINION_WORD_SIZE;
}
}
nvlink_dbg("");
nvlink_dbg("MINION DMEM DUMP - END");
nvlink_dbg("");
}
/*
* Dump the MINION PC trace. This is useful for debugging MINION
* errors/hangs/crashes.
*/
void minion_dump_pc_trace(struct tnvlink_dev *tdev)
{
u32 trace_pc_count = 0;
u32 pc = 0;
u32 traceidx = 0;
u32 tracepc = 0;
u32 i = 0;
u32 idx = 0;
u32 reg_val = 0;
reg_val = nvlw_minion_readl(tdev, CMINION_FALCON_SCTL);
nvlink_err("CMINION_FALCON_SCTL = 0x%x", reg_val);
if (reg_val & BIT(CMINION_FALCON_SCTL_HSMODE)) {
nvlink_err("MINION is in HS mode."
" MINION PC TRACE dump is not supported in HS mode.");
return;
}
nvlink_err("");
nvlink_err("MINION PC TRACE DUMP - START");
nvlink_err("");
/* Get total number of PC trace entries */
reg_val = nvlw_minion_readl(tdev, CMINION_FALCON_TRACEIDX);
nvlink_err("CMINION_FALCON_TRACEIDX = 0x%x", reg_val);
trace_pc_count = CMINION_FALCON_TRACEIDX_MAXIDX_V(reg_val);
nvlink_err("PC TRACE (Total entries = %d - "
"entry 0 is the most recent branch):",
trace_pc_count);
/* Print the entire PC trace */
for (i = 0; i < trace_pc_count; i++) {
idx = CMINION_FALCON_TRACEIDX_IDX_F(i);
nvlw_minion_writel(tdev, CMINION_FALCON_TRACEIDX, idx);
traceidx = nvlw_minion_readl(tdev, CMINION_FALCON_TRACEIDX);
tracepc = nvlw_minion_readl(tdev, CMINION_FALCON_TRACEPC);
pc = CMINION_FALCON_TRACEPC_PC_V(tracepc);
nvlink_err(" - PC(%d) = %#010x", idx, pc);
nvlink_err(" - CMINION_FALCON_TRACEIDX = 0x%x",
traceidx);
nvlink_err(" - CMINION_FALCON_TRACEPC = 0x%x",
tracepc);
}
nvlink_err("");
nvlink_err("MINION PC TRACE DUMP - END");
nvlink_err("");
}
/*
* Dump the MINION registers which are useful for debugging MINION
* errors/hangs/crashes.
*/
void minion_dump_registers(struct tnvlink_dev *tdev)
{
nvlink_err("");
nvlink_err("MINION REGISTER DUMP - START");
nvlink_err("");
nvlink_err("CMINION_FALCON_OS = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_OS));
nvlink_err("CMINION_FALCON_CPUCTL = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_CPUCTL));
nvlink_err("CMINION_FALCON_IDLESTATE = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_IDLESTATE));
nvlink_err("CMINION_FALCON_MAILBOX0 = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_MAILBOX0));
nvlink_err("CMINION_FALCON_MAILBOX1 = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_MAILBOX1));
nvlink_err("CMINION_FALCON_IRQSTAT = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_IRQSTAT));
nvlink_err("CMINION_FALCON_IRQMASK = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_IRQMASK));
nvlink_err("CMINION_FALCON_DEBUG1 = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_DEBUG1));
nvlink_err("CMINION_FALCON_DEBUGINFO = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_DEBUGINFO));
nvlink_err("CMINION_FALCON_BOOTVEC = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_BOOTVEC));
nvlink_err("CMINION_FALCON_HWCFG = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_HWCFG));
nvlink_err("CMINION_FALCON_ENGCTL = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_ENGCTL));
nvlink_err("CMINION_FALCON_CURCTX = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_CURCTX));
nvlink_err("CMINION_FALCON_NXTCTX = 0x%x",
nvlw_minion_readl(tdev, CMINION_FALCON_NXTCTX));
/* DL_CMD related registers */
nvlink_err("MINION_MINION_DEVICES = 0x%x",
nvlw_minion_readl(tdev, MINION_MINION_DEVICES));
nvlink_err("MINION_MINION_INTR = 0x%x",
nvlw_minion_readl(tdev, MINION_MINION_INTR));
nvlink_err("MINION_MINION_INTR_STALL_EN = 0x%x",
nvlw_minion_readl(tdev, MINION_MINION_INTR_STALL_EN));
nvlink_err("MINION_MINION_INTR_NONSTALL_EN = 0x%x",
nvlw_minion_readl(tdev, MINION_MINION_INTR_NONSTALL_EN));
nvlink_err("MINION_NVLINK_LINK_INTR = 0x%x",
nvlw_minion_readl(tdev, MINION_NVLINK_LINK_INTR));
nvlink_err("MINION_NVLINK_DL_CMD = 0x%x",
nvlw_minion_readl(tdev, MINION_NVLINK_DL_CMD));
nvlink_err("MINION_NVLINK_LINK_DL_STAT = 0x%x",
nvlw_minion_readl(tdev, MINION_NVLINK_LINK_DL_STAT));
nvlink_err("MINION_MINION_STATUS = 0x%x",
nvlw_minion_readl(tdev, MINION_MINION_STATUS));
nvlink_err("");
nvlink_err("MINION REGISTER DUMP - END");
nvlink_err("");
}
/*
* minion_boot:
* ------------
* Boot the MINION microcontroller by executing the following steps:
* - Get MINION ucode from the filesystem
* - Read ucode header
* - Load ucode image sections into MINION's IMEM and DMEM
* - Start MINION boot and wait for boot to complete
* - Send the SWINTR DLCMD to the MINION and poll for expected interrupt
*
* If all goes well, the MINION should be booted and ready to accept DLCMDs from
* SW.
*
* TODO: Currently we're making assumptions about the ordering of the IMEM/DMEM
* sections. We need to do a run-time sort on the starting offsets of all
* the IMEM/DMEM sections and then load each section in the derived order.
*/
int minion_boot(struct tnvlink_dev *tdev)
{
int ret = 0;
struct nvlink_device *ndev = tdev->ndev;
struct minion_hdr *hdr = &(ndev->minion_hdr);
int data_idx = 0;
int i = 0;
u32 elapsed_us = 0;
u32 reg_val = 0;
int dmem_scrub_pending = 1;
int imem_scrub_pending = 1;
int dump_ucode = 0;
u32 minion_status = 0;
u32 intr_code = 0;
/* Configure minion falcon Interrupts */
nvlink_config_minion_falcon_intr(tdev);
/* Get MINION ucode from the filesystem */
ret = request_firmware(&(ndev->minion_fw), MINION_FW_PATH, tdev->dev);
if (ret) {
nvlink_err("Can't get MINION ucode binary");
goto exit;
}
/* Read ucode header */
hdr->os_code_offset = minion_extract_word(tdev, data_idx);
data_idx += 4;
hdr->os_code_size = minion_extract_word(tdev, data_idx);
data_idx += 4;
hdr->os_data_offset = minion_extract_word(tdev, data_idx);
data_idx += 4;
hdr->os_data_size = minion_extract_word(tdev, data_idx);
data_idx += 4;
hdr->num_apps = minion_extract_word(tdev, data_idx);
data_idx += 4;
nvlink_dbg("MINION Ucode Header Info:");
nvlink_dbg("-------------------------");
nvlink_dbg(" - OS Code Offset = %u", hdr->os_code_offset);
nvlink_dbg(" - OS Code Size = %u", hdr->os_code_size);
nvlink_dbg(" - OS Data Offset = %u", hdr->os_data_offset);
nvlink_dbg(" - OS Data Size = %u", hdr->os_data_size);
nvlink_dbg(" - Num Apps = %u", hdr->num_apps);
/* Allocate offset/size arrays for all the ucode apps */
hdr->app_code_offsets = kcalloc(hdr->num_apps, sizeof(u32), GFP_KERNEL);
if (!hdr->app_code_offsets) {
nvlink_err("Couldn't allocate MINION app_code_offsets array");
ret = -ENOMEM;
goto cleanup;
}
hdr->app_code_sizes = kcalloc(hdr->num_apps, sizeof(u32), GFP_KERNEL);
if (!hdr->app_code_sizes) {
nvlink_err("Couldn't allocate MINION app_code_sizes array");
ret = -ENOMEM;
goto cleanup;
}
hdr->app_data_offsets = kcalloc(hdr->num_apps, sizeof(u32), GFP_KERNEL);
if (!hdr->app_data_offsets) {
nvlink_err("Couldn't allocate MINION app_data_offsets array");
ret = -ENOMEM;
goto cleanup;
}
hdr->app_data_sizes = kcalloc(hdr->num_apps, sizeof(u32), GFP_KERNEL);
if (!hdr->app_data_sizes) {
nvlink_err("Couldn't allocate MINION app_data_sizes array");
ret = -ENOMEM;
goto cleanup;
}
/* Get app code offsets and sizes */
for (i = 0; i < hdr->num_apps; i++) {
hdr->app_code_offsets[i] = minion_extract_word(tdev, data_idx);
data_idx += 4;
hdr->app_code_sizes[i] = minion_extract_word(tdev, data_idx);
data_idx += 4;
nvlink_dbg(" - App Code:");
nvlink_dbg(" - App #%d: Code Offset = %u, Code Size = %u",
i,
hdr->app_code_offsets[i],
hdr->app_code_sizes[i]);
}
/* Get app data offsets and sizes */
for (i = 0; i < hdr->num_apps; i++) {
hdr->app_data_offsets[i] = minion_extract_word(tdev, data_idx);
data_idx += 4;
hdr->app_data_sizes[i] = minion_extract_word(tdev, data_idx);
data_idx += 4;
nvlink_dbg(" - App Data:");
nvlink_dbg(" - App #%d: Data Offset = %u, Data Size = %u",
i,
hdr->app_data_offsets[i],
hdr->app_data_sizes[i]);
}
hdr->ovl_offset = minion_extract_word(tdev, data_idx);
data_idx += 4;
hdr->ovl_size = minion_extract_word(tdev, data_idx);
data_idx += 4;
ndev->minion_img = &(ndev->minion_fw->data[data_idx]);
hdr->ucode_data_size = ndev->minion_fw->size - data_idx;
nvlink_dbg(" - Overlay Offset = %u", hdr->ovl_offset);
nvlink_dbg(" - Overlay Size = %u", hdr->ovl_size);
nvlink_dbg(" - Ucode Data Size = %u", hdr->ucode_data_size);
/* Do memory scrub */
nvlw_minion_writel(tdev, CMINION_FALCON_DMACTL, 0);
while (dmem_scrub_pending || imem_scrub_pending) {
reg_val = nvlw_minion_readl(tdev, CMINION_FALCON_DMACTL);
dmem_scrub_pending =
reg_val & BIT(CMINION_FALCON_DMACTL_DMEM_SCRUBBING);
imem_scrub_pending =
reg_val & BIT(CMINION_FALCON_DMACTL_IMEM_SCRUBBING);
}
/* Initialize address of IMEM to 0x0 and set auto-increment on write */
nvlw_minion_writel(tdev,
CMINION_FALCON_IMEMC,
BIT(CMINION_FALCON_IMEMC_AINCW));
/* Load OS code into the IMEM */
nvlink_dbg("Loading OS code into the IMEM");
ret = minion_load_ucode_section(tdev,
1,
hdr->os_code_offset,
hdr->os_code_size,
0,
0);
if (ret < 0) {
nvlink_err("Unable to load MINION OS code into the IMEM");
goto cleanup;
}
/* Initialize address of DMEM to 0x0 and set auto-increment on write */
nvlw_minion_writel(tdev,
CMINION_FALCON_DMEMC,
BIT(CMINION_FALCON_DMEMC_AINCW));
/* Load OS data into the DMEM */
nvlink_dbg("Loading OS data into the DMEM");
ret = minion_load_ucode_section(tdev,
0,
hdr->os_data_offset,
hdr->os_data_size,
0,
0);
if (ret < 0) {
nvlink_err("Unable to load OS data into the DMEM");
goto cleanup;
}
/* Load the ucode apps */
for (i = 0; i < hdr->num_apps; i++) {
/* Mark the app code as secure */
reg_val = nvlw_minion_readl(tdev, CMINION_FALCON_IMEMC);
reg_val |= BIT(CMINION_FALCON_IMEMC_SECURE);
nvlw_minion_writel(tdev, CMINION_FALCON_IMEMC, reg_val);
/* Load app code into the IMEM */
nvlink_dbg("Loading app %d code into the IMEM", i);
ret = minion_load_ucode_section(tdev,
1,
hdr->app_code_offsets[i],
hdr->app_code_sizes[i],
1,
hdr->app_code_offsets[i] >> 8);
if (ret < 0) {
nvlink_err("Unable to load the app code"
" for app %d into the IMEM",
i);
goto cleanup;
}
/* Load app data into the DMEM */
nvlink_dbg("Loading app %d data into the DMEM", i);
ret = minion_load_ucode_section(tdev,
0,
hdr->app_data_offsets[i],
hdr->app_data_sizes[i],
0,
0);
if (ret < 0) {
nvlink_err("Unable to load the app data"
" for app %d into the DMEM",
i);
goto cleanup;
}
}
/*
* If needed dump out the contents of the MINION's IMEM and DMEM so that
* we can verify that we're loading the ucode correctly.
*/
if (dump_ucode)
minion_print_ucode(tdev);
/* Write boot vector */
nvlw_minion_writel(tdev, CMINION_FALCON_BOOTVEC, hdr->os_code_offset);
/* Start MINION CPU */
reg_val = nvlw_minion_readl(tdev, CMINION_FALCON_CPUCTL);
reg_val |= BIT(CMINION_FALCON_CPUCTL_STARTCPU);
nvlw_minion_writel(tdev, CMINION_FALCON_CPUCTL, reg_val);
/* Wait for MINION to boot */
while (1) {
usleep_range(DEFAULT_LOOP_SLEEP_US, DEFAULT_LOOP_SLEEP_US*2);
elapsed_us += DEFAULT_LOOP_SLEEP_US;
reg_val = nvlw_minion_readl(tdev, MINION_MINION_STATUS);
minion_status = (reg_val & MINION_MINION_STATUS_STATUS_MASK) >>
MINION_MINION_STATUS_STATUS_SHIFT;
if (minion_status != MINION_MINION_STATUS_STATUS_INIT) {
if (minion_status != MINION_MINION_STATUS_STATUS_BOOT) {
nvlink_err(
"MINION ucode initialization failed!");
nvlink_err("MINION_MINION_STATUS = 0x%x",
reg_val);
ret = -1;
goto err_dump;
} else {
u32 os = 0;
u32 os_maj_ver = 0;
u32 os_min_ver = 0;
u32 mbox = 0;
u32 sctl = 0;
nvlink_dbg("MINION booted successfully!");
os = nvlw_minion_readl(tdev,
CMINION_FALCON_OS);
os_maj_ver =
(os &
CMINION_FALCON_OS_MAJOR_VER_MASK) >>
CMINION_FALCON_OS_MAJOR_VER_SHIFT;
os_min_ver =
(os &
CMINION_FALCON_OS_MINOR_VER_MASK) >>
CMINION_FALCON_OS_MINOR_VER_SHIFT;
mbox = nvlw_minion_readl(tdev,
CMINION_FALCON_MAILBOX1);
sctl = nvlw_minion_readl(tdev,
CMINION_FALCON_SCTL);
/* Dump the ucode ID string epilog */
nvlink_dbg("MINION Falcon ucode version info:"
" Ucode v%d.%d, Phy v%d",
os_maj_ver,
os_min_ver,
mbox);
/* Display security level info */
nvlink_dbg("CMINION_FALCON_SCTL = 0x%x",
sctl);
break;
}
}
if (elapsed_us >= DEFAULT_LOOP_TIMEOUT_US) {
nvlink_err("Timeout waiting for MINION to boot!");
nvlink_err("MINION_MINION_STATUS = 0x%x", reg_val);
ret = -1;
goto err_dump;
}
/* Service any pending falcon interrupts */
minion_service_falcon_intr(tdev);
}
/* Wait until MINION is ready to accept commands */
ret = wait_for_reg_cond_nvlink(tdev,
MINION_NVLINK_DL_CMD,
MINION_NVLINK_DL_CMD_READY,
true,
"MINION_NVLINK_DL_CMD_READY",
nvlw_minion_readl,
&reg_val,
DEFAULT_LOOP_TIMEOUT_US);
if (ret < 0) {
nvlink_err("MINION booted but its not accepting commands");
goto err_dump;
}
/*
* Send a SWINTR DLCMD to MINION to test if its functioning
* properly
*/
ret = minion_send_cmd(tdev, MINION_NVLINK_DL_CMD_COMMAND_SWINTR, 0);
if (ret < 0) {
nvlink_err("MINION SWINTR DLCMD failed!");
goto err_dump;
}
/* Check interrupt register to see if interrupt was received */
reg_val = nvlw_minion_readl(tdev, MINION_NVLINK_LINK_INTR);
intr_code = (reg_val & MINION_NVLINK_LINK_INTR_CODE_MASK) >>
MINION_NVLINK_LINK_INTR_CODE_SHIFT;
if (intr_code == MINION_NVLINK_LINK_INTR_CODE_SWREQ) {
nvlink_dbg("MINION SWINTR DLCMD succeeded!");
} else {
nvlink_err("MINION SWINTR DLCMD failed!"
" SW requested interrupt was not received.");
nvlink_err("MINION_NVLINK_LINK_INTR: 0x%x", reg_val);
ret = -1;
goto err_dump;
}
goto cleanup;
err_dump:
/* Dump the PC trace and misc registers for error conditions */
minion_dump_pc_trace(tdev);
minion_dump_registers(tdev);
cleanup:
release_firmware(ndev->minion_fw);
kfree(hdr->app_code_offsets);
kfree(hdr->app_code_sizes);
kfree(hdr->app_data_offsets);
kfree(hdr->app_data_sizes);
memset(hdr, 0, sizeof(struct minion_hdr));
exit:
ndev->minion_fw = NULL;
ndev->minion_img = NULL;
return ret;
}
/*
* init_nvhs_phy:
* Initialize the NVHS PHY. This encompasses the following:
* - Sending MINION DLCMDs for various PHY init steps
* - Switching the TX clock from OSC to brick PLL
* - RX calibration of lanes
*/
int init_nvhs_phy(struct tnvlink_dev *tdev)
{
int ret = 0;
bool dump_minion = true;
u32 reg_val = 0;
u32 link_state = 0;
struct nvlink_device *ndev = tdev->ndev;
ret = minion_send_cmd(tdev,
MINION_NVLINK_DL_CMD_COMMAND_XAVIER_PLLOVERRIDE_ON,
0);
if (ret < 0) {
nvlink_err("Error sending XAVIER_PLLOVERRIDE_ON command to"
" MINION");
goto fail;
}
if (tdev->is_nea) {
ret = minion_send_cmd(tdev,
MINION_NVLINK_DL_CMD_COMMAND_SETNEA,
0);
if (ret < 0) {
nvlink_err("Error sending SETNEA command to MINION");
goto fail;
}
}
reg_val = nvlw_nvl_readl(tdev, NVL_LINK_STATE);
link_state = (reg_val & NVL_LINK_STATE_STATE_MASK) >>
NVL_LINK_STATE_STATE_SHIFT;
if (link_state != NVL_LINK_STATE_STATE_INIT) {
nvlink_err("Link is not in INIT state."
" INITPLL can only be executed in INIT state.");
ret = -1;
/*
* This is not a MINION error condition. We don't need a MINION
* debug dump.
*/
dump_minion = false;
goto fail;
} else if ((tdev->refclk == NVLINK_REFCLK_150) &&
(ndev->speed == NVLINK_SPEED_20)) {
ret = minion_send_cmd(tdev,
MINION_NVLINK_DL_CMD_COMMAND_INITPLL_5,
0);
if (ret < 0) {
nvlink_err("Error sending INITPLL_5 command to MINION");
goto fail;
}
ndev->link_bitrate = LINK_BITRATE_150MHZ_20GBPS;
} else if ((tdev->refclk == NVLINK_REFCLK_150) &&
(ndev->speed == NVLINK_SPEED_16)) {
ret = minion_send_cmd(tdev,
MINION_NVLINK_DL_CMD_COMMAND_INITPLL_9,
0);
if (ret < 0) {
nvlink_err("Error sending INITPLL_9 command to MINION");
goto fail;
}
ndev->link_bitrate = LINK_BITRATE_150MHZ_16GBPS;
} else if ((tdev->refclk == NVLINK_REFCLK_156) &&
(ndev->speed == NVLINK_SPEED_20)) {
ret = minion_send_cmd(tdev,
MINION_NVLINK_DL_CMD_COMMAND_INITPLL_4,
0);
if (ret < 0) {
nvlink_err("Error sending INITPLL_4 command to MINION");
goto fail;
}
ndev->link_bitrate = LINK_BITRATE_156MHZ_20GBPS;
} else if ((tdev->refclk == NVLINK_REFCLK_156) &&
(ndev->speed == NVLINK_SPEED_16)) {
ret = minion_send_cmd(tdev,
MINION_NVLINK_DL_CMD_COMMAND_INITPLL_8,
0);
if (ret < 0) {
nvlink_err("Error sending INITPLL_8 command to MINION");
goto fail;
}
ndev->link_bitrate = LINK_BITRATE_156MHZ_16GBPS;
} else {
nvlink_err("Invalid speed or refclk");
ret = -EINVAL;
goto fail;
}
ret = minion_send_cmd(tdev,
MINION_NVLINK_DL_CMD_COMMAND_XAVIER_CALIBRATEPLL,
0);
if (ret < 0) {
nvlink_err("Error sending XAVIER_CALIBRATEPLL command to"
" MINION");
goto fail;
}
ret = clk_set_rate(tdev->clk_nvlink_pll_txclk, ndev->link_bitrate / 16);
if (ret < 0) {
nvlink_err("clk_nvlink_pll_txclk's clk_set_rate() call failed");
/*
* This is not a MINION error condition. We don't need a MINION
* debug dump.
*/
dump_minion = false;
goto fail;
}
/* Switch the TX clock from OSC to brick PLL */
ret = clk_set_parent(tdev->clk_nvlink_tx, tdev->clk_nvlink_pll_txclk);
if (ret < 0) {
nvlink_err("clk_nvlink_tx's clk_set_parent() call failed");
/*
* This is not a MINION error condition. We don't need a MINION
* debug dump.
*/
dump_minion = false;
goto fail;
}
/* INITRXTERM is required if connected to nvlink 2.2 device.
* For RXDET functionality to succeed on 2.2 devices, the opposite
* endpoint must initialize the RX termination.It does no harm when
* connected to 2.0 device.
*/
ret = minion_send_cmd(tdev, MINION_NVLINK_DL_CMD_COMMAND_INITRXTERM,
0);
if (ret < 0) {
nvlink_err("Error sending INITRXTERM command to MINION");
goto fail;
}
ret = minion_send_cmd(tdev,
MINION_NVLINK_DL_CMD_COMMAND_INITPHY,
0);
if (ret < 0) {
nvlink_err("Error sending INITPHY command to MINION");
goto undo_clk;
}
/* RX calibration */
reg_val = BIT(NVL_BR0_CFG_CTL_CAL_RXCAL) |
BIT(NVL_BR0_CFG_CTL_CAL_INIT_TRAIN_DONE);
nvlw_nvl_writel(tdev, NVL_BR0_CFG_CTL_CAL, reg_val);
/* Wait for RXCAL_DONE bit to be set */
ret = wait_for_reg_cond_nvlink(tdev,
NVL_BR0_CFG_STATUS_CAL,
NVL_BR0_CFG_STATUS_CAL_RXCAL_DONE,
true,
"NVL_BR0_CFG_STATUS_CAL_RXCAL_DONE",
nvlw_nvl_readl,
&reg_val,
DEFAULT_LOOP_TIMEOUT_US);
if (ret < 0) {
nvlink_err("RX calibration failed!");
/*
* This is not a MINION error condition. We don't need a MINION
* debug dump.
*/
dump_minion = false;
goto undo_clk;
}
ret = minion_send_cmd(tdev,
MINION_NVLINK_DL_CMD_COMMAND_INITLANEENABLE,
0);
if (ret < 0) {
nvlink_err("Error sending INITLANEENABLE command to MINION");
goto undo_clk;
}
ret = minion_send_cmd(tdev,
MINION_NVLINK_DL_CMD_COMMAND_INITDLPL,
0);
if (ret < 0) {
nvlink_err("Error sending INITDLPL command to MINION");
goto undo_clk;
}
nvlink_dbg("NVHS PHY init succeeded!");
goto success;
undo_clk:
/* Switch the TX clock from brick PLL to OSC */
ret = clk_set_parent(tdev->clk_nvlink_tx, tdev->clk_m);
if (ret < 0)
nvlink_err("clk_nvlink_tx's clk_set_parent() call failed");
fail:
nvlink_err("NVHS PHY init failed!");
/*
* Dump the MINION PC trace and misc registers for MINION error
* conditions
*/
if (dump_minion) {
minion_dump_pc_trace(tdev);
minion_dump_registers(tdev);
}
success:
return ret;
}