/* * 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 . */ #include #include #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, ®_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, ®_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 it’s 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, ®_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; }