forked from rrcarlosr/Jetpack
1502 lines
43 KiB
C
1502 lines
43 KiB
C
/*
|
|
* t19x-nvlink-endpt.c:
|
|
* This is the NVLINK endpoint driver for 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/of.h>
|
|
#include <linux/device.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/module.h>
|
|
#include <linux/io.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/platform/tegra/mc.h>
|
|
#include <linux/platform/tegra/mc-regs-t19x.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/reset.h>
|
|
#include <soc/tegra/fuse.h>
|
|
|
|
#include "t19x-nvlink-endpt.h"
|
|
#include "nvlink-hw.h"
|
|
|
|
#define PLLNVHS_FREQ_150MHZ (150 * 1000 * 1000)
|
|
|
|
/* NVLINK TOM is the top of the NVLINK aperture */
|
|
#define NVLINK_TOM_GB 512
|
|
|
|
/* Convert the NVLINK TOM value from GB to MB for register programming */
|
|
#define NVLINK_TOM_MB (((NVLINK_TOM_GB) * 1024) - 1)
|
|
|
|
static struct of_device_id t19x_nvlink_controller_of_match[] = {
|
|
{
|
|
.compatible = "nvidia,t19x-nvlink-controller",
|
|
}, {
|
|
},
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, t19x_nvlink_controller_of_match);
|
|
|
|
u32 nvlw_tioctrl_readl(struct tnvlink_dev *tdev, u32 reg)
|
|
{
|
|
return readl(tdev->nvlw_tioctrl_base + reg);
|
|
}
|
|
|
|
void nvlw_tioctrl_writel(struct tnvlink_dev *tdev, u32 reg, u32 val)
|
|
{
|
|
writel(val, tdev->nvlw_tioctrl_base + reg);
|
|
}
|
|
|
|
u32 nvlw_nvlipt_readl(struct tnvlink_dev *tdev, u32 reg)
|
|
{
|
|
return readl(tdev->nvlw_nvlipt_base + reg);
|
|
}
|
|
|
|
void nvlw_nvlipt_writel(struct tnvlink_dev *tdev, u32 reg, u32 val)
|
|
{
|
|
writel(val, tdev->nvlw_nvlipt_base + reg);
|
|
}
|
|
|
|
u32 nvlw_minion_readl(struct tnvlink_dev *tdev, u32 reg)
|
|
{
|
|
return readl(tdev->nvlw_minion_base + reg);
|
|
}
|
|
|
|
void nvlw_minion_writel(struct tnvlink_dev *tdev, u32 reg, u32 val)
|
|
{
|
|
writel(val, tdev->nvlw_minion_base + reg);
|
|
}
|
|
|
|
u32 nvlw_nvl_readl(struct tnvlink_dev *tdev, u32 reg)
|
|
{
|
|
return readl(tdev->tlink.nvlw_nvl_base + reg);
|
|
}
|
|
|
|
void nvlw_nvl_writel(struct tnvlink_dev *tdev, u32 reg, u32 val)
|
|
{
|
|
writel(val, tdev->tlink.nvlw_nvl_base + reg);
|
|
}
|
|
|
|
u32 nvlw_sync2x_readl(struct tnvlink_dev *tdev, u32 reg)
|
|
{
|
|
return readl(tdev->nvlw_sync2x_base + reg);
|
|
}
|
|
|
|
void nvlw_sync2x_writel(struct tnvlink_dev *tdev, u32 reg, u32 val)
|
|
{
|
|
writel(val, tdev->nvlw_sync2x_base + reg);
|
|
}
|
|
|
|
u32 nvlw_nvltlc_readl(struct tnvlink_dev *tdev, u32 reg)
|
|
{
|
|
return readl(tdev->tlink.nvlw_nvltlc_base + reg);
|
|
}
|
|
|
|
void nvlw_nvltlc_writel(struct tnvlink_dev *tdev, u32 reg, u32 val)
|
|
{
|
|
writel(val, tdev->tlink.nvlw_nvltlc_base + reg);
|
|
}
|
|
|
|
static inline u32 mssnvlink_0_readl(struct tnvlink_dev *tdev, u32 reg)
|
|
{
|
|
return readl(tdev->mssnvlink_0_base + reg);
|
|
}
|
|
|
|
static inline void mssnvlink_0_writel(struct tnvlink_dev *tdev, u32 reg,
|
|
u32 val)
|
|
{
|
|
writel(val, tdev->mssnvlink_0_base + reg);
|
|
}
|
|
|
|
/* TODO: Remove all non-NVLINK MMIO register writes from the driver */
|
|
static inline void non_nvlink_writel(u32 reg, u32 val)
|
|
{
|
|
void __iomem *ptr = ioremap(reg, 0x4);
|
|
|
|
__raw_writel(val, ptr);
|
|
iounmap(ptr);
|
|
}
|
|
|
|
/*
|
|
* Wait for a bit to be set or cleared in an NVLINK register. If the desired bit
|
|
* condition doesn't happen in a certain amount of time, a timeout will happen.
|
|
*/
|
|
int wait_for_reg_cond_nvlink(
|
|
struct tnvlink_dev *tdev,
|
|
u32 reg,
|
|
u32 bit,
|
|
bool check_for_bit_set,
|
|
char *bit_name,
|
|
u32 (*reg_readl)(struct tnvlink_dev *, u32),
|
|
u32 *reg_val,
|
|
u32 timeout_us)
|
|
{
|
|
u32 elapsed_us = 0;
|
|
|
|
do {
|
|
usleep_range(DEFAULT_LOOP_SLEEP_US, DEFAULT_LOOP_SLEEP_US*2);
|
|
elapsed_us += DEFAULT_LOOP_SLEEP_US;
|
|
|
|
*reg_val = reg_readl(tdev, reg);
|
|
if ((check_for_bit_set && (*reg_val & BIT(bit))) ||
|
|
(!check_for_bit_set && ((*reg_val & BIT(bit)) == 0)))
|
|
break;
|
|
} while (elapsed_us < timeout_us);
|
|
if (elapsed_us >= timeout_us) {
|
|
if (check_for_bit_set) {
|
|
nvlink_err("Timeout waiting for the %s bit to get set",
|
|
bit_name);
|
|
} else {
|
|
nvlink_err(
|
|
"Timeout waiting for the %s bit to get cleared",
|
|
bit_name);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* tegra_nvlink_car_init(): initializes UPHY mgmt and sys clk
|
|
* clears the resets to uphy
|
|
*/
|
|
static int tegra_nvlink_car_enable(struct tnvlink_dev *tdev)
|
|
{
|
|
int ret;
|
|
unsigned long clk_rate = 0;
|
|
u32 reg_val = 0;
|
|
|
|
/* enable the Management clock */
|
|
ret = clk_prepare_enable(tdev->clk_nvhs_pll0_mgmt);
|
|
if (ret < 0) {
|
|
nvlink_err("nvlink mgmt clock enable failed : %d", ret);
|
|
goto fail;
|
|
}
|
|
/* Clear reset for UPHY PM */
|
|
reset_control_deassert(tdev->rst_nvhs_uphy_pm);
|
|
|
|
/* Enable clock for nvlink_sys */
|
|
ret = clk_prepare_enable(tdev->clk_nvlink_sys);
|
|
if (ret < 0) {
|
|
nvlink_err("nvlink sys clock enable failed : %d", ret);
|
|
goto nvlink_sys_enable_fail;
|
|
}
|
|
ret = clk_set_parent(tdev->clk_nvlink_sys,
|
|
tdev->clk_pllrefe_vcoout_gated);
|
|
if (ret < 0) {
|
|
nvlink_err("nvlink sys clock set parent failed : %d", ret);
|
|
goto nvlink_sys_set_parent_fail;
|
|
}
|
|
|
|
reset_control_reset(tdev->rst_nvlink);
|
|
|
|
/* Take link out of reset */
|
|
reg_val = nvlw_tioctrl_readl(tdev, NVLW_RESET) |
|
|
BIT(NVLW_RESET_LINKRESET);
|
|
nvlw_tioctrl_writel(tdev, NVLW_RESET, reg_val);
|
|
udelay(NVLW_POST_RESET_DELAY_US);
|
|
|
|
/* Reset persistent HW state for this link */
|
|
reg_val = nvlw_tioctrl_readl(tdev, NVLW_DEBUG_RESET) &
|
|
~BIT(NVLW_DEBUG_RESET_LINK) &
|
|
~BIT(NVLW_DEBUG_RESET_COMMON);
|
|
nvlw_tioctrl_writel(tdev, NVLW_DEBUG_RESET, reg_val);
|
|
udelay(NVLW_POST_RESET_DELAY_US);
|
|
reg_val = nvlw_tioctrl_readl(tdev, NVLW_DEBUG_RESET) |
|
|
BIT(NVLW_DEBUG_RESET_LINK) |
|
|
BIT(NVLW_DEBUG_RESET_COMMON);
|
|
nvlw_tioctrl_writel(tdev, NVLW_DEBUG_RESET, reg_val);
|
|
udelay(NVLW_POST_RESET_DELAY_US);
|
|
|
|
reset_control_deassert(tdev->rst_nvhs_uphy_l0);
|
|
reset_control_deassert(tdev->rst_nvhs_uphy_l1);
|
|
reset_control_deassert(tdev->rst_nvhs_uphy_l2);
|
|
reset_control_deassert(tdev->rst_nvhs_uphy_l3);
|
|
reset_control_deassert(tdev->rst_nvhs_uphy_l4);
|
|
reset_control_deassert(tdev->rst_nvhs_uphy_l5);
|
|
reset_control_deassert(tdev->rst_nvhs_uphy_l6);
|
|
reset_control_deassert(tdev->rst_nvhs_uphy_l7);
|
|
reset_control_deassert(tdev->rst_nvhs_uphy_pll0);
|
|
reset_control_deassert(tdev->rst_nvhs_uphy);
|
|
|
|
if (tdev->refclk == NVLINK_REFCLK_150) {
|
|
ret = clk_set_rate(tdev->clk_pllnvhs,
|
|
PLLNVHS_FREQ_150MHZ);
|
|
if (ret < 0) {
|
|
nvlink_err("nvlink pllnvhs setrate failed : %d", ret);
|
|
goto pllnvhs_fail;
|
|
} else {
|
|
clk_rate = clk_get_rate(tdev->clk_pllnvhs);
|
|
if (clk_rate != PLLNVHS_FREQ_150MHZ) {
|
|
nvlink_err("clk_pllnvhs rate = %lu", clk_rate);
|
|
ret = -EINVAL;
|
|
goto pllnvhs_fail;
|
|
}
|
|
}
|
|
ret = clk_prepare_enable(tdev->clk_pllnvhs);
|
|
if (ret < 0) {
|
|
nvlink_err("pllnvhs clock enable failed : %d", ret);
|
|
goto pllnvhs_fail;
|
|
}
|
|
}
|
|
|
|
/* De-assert MSSNVLINK0 reset */
|
|
reset_control_deassert(tdev->rst_mssnvl);
|
|
|
|
return ret;
|
|
|
|
pllnvhs_fail:
|
|
nvlink_sys_set_parent_fail:
|
|
clk_disable_unprepare(tdev->clk_nvlink_sys);
|
|
nvlink_sys_enable_fail:
|
|
clk_disable_unprepare(tdev->clk_nvhs_pll0_mgmt);
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
static void init_tlc_buffers(struct tnvlink_dev *tdev)
|
|
{
|
|
nvlink_dbg("Initializing TLC buffers");
|
|
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_CREDITS_VC0, 0x10000C0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_CREDITS_VC1, 0x0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_CREDITS_VC2, 0x0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_CREDITS_VC3, 0x0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_CREDITS_VC4, 0x0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_CREDITS_VC5, 0x10000C0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_CREDITS_VC6, 0x0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_CREDITS_VC7, 0x0);
|
|
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_SZ_VC0, 0xff00bf);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_SZ_VC1, 0xff00bf);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_SZ_VC2, 0xff00bf);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_SZ_VC3, 0xff00bf);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_SZ_VC4, 0xff00bf);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_SZ_VC5, 0x1ff017f);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_SZ_VC6, 0x1ff017f);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_SZ_VC7, 0x1ff017f);
|
|
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_CREDITS_VC0, 0x800040);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_CREDITS_VC1, 0x0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_CREDITS_VC2, 0x0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_CREDITS_VC3, 0x0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_CREDITS_VC4, 0x0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_CREDITS_VC5, 0x800040);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_CREDITS_VC6, 0x0);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_CREDITS_VC7, 0x0);
|
|
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_SZ_VC0, 0x7f003f);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_SZ_VC1, 0x7f003f);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_SZ_VC2, 0x7f003f);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_SZ_VC3, 0x7f003f);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_SZ_VC4, 0x7f003f);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_SZ_VC5, 0xff007f);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_SZ_VC6, 0xff007f);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_SZ_VC7, 0xff007f);
|
|
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_ERR_LOG_EN_0, 0x3ffffff);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_ERR_REPORT_EN_0, 0x3ffffff);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_ERR_CONTAIN_EN_0, 0x3ffffff);
|
|
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_LOG_EN_0, 0xffffff);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_REPORT_EN_0, 0xffffff);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_CONTAIN_EN_0, 0xffffff);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_LOG_EN_1, 0x3fffff);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_REPORT_EN_1, 0x3fffff);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_ERR_CONTAIN_EN_1, 0x3fffff);
|
|
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_TX_CTRL_BUFFER_READY, 0x1);
|
|
nvlw_nvltlc_writel(tdev, NVLTLC_RX_CTRL_BUFFER_READY, 0x1);
|
|
}
|
|
|
|
static void init_tlc(struct tnvlink_dev *tdev)
|
|
{
|
|
struct nvlink_device *ndev = tdev->ndev;
|
|
|
|
init_tlc_buffers(tdev);
|
|
|
|
if (ndev->link.is_sl_supported)
|
|
init_single_lane_params(tdev);
|
|
}
|
|
|
|
/*
|
|
* Enable the MSSNVLINK core master and slave clocks.
|
|
*/
|
|
static void disable_nvlink_slcg(struct tnvlink_dev *tdev)
|
|
{
|
|
u32 val;
|
|
|
|
val = mssnvlink_0_readl(tdev, MSSNVLINK_CLK_SLCG);
|
|
val |= BIT(MSSNVLINK_CLK_SLCG_MCF_MASTER_CLK_ENBL);
|
|
val |= BIT(MSSNVLINK_CLK_SLCG_MCF_SLAVE_CLK_ENBL);
|
|
val |= BIT(MSSNVLINK_CLK_SLCG_CORE_CLK_UPDATE_ENBL);
|
|
mssnvlink_0_writel(tdev, MSSNVLINK_CLK_SLCG, val);
|
|
}
|
|
|
|
static void mssnvlink_lock_ila(struct tnvlink_dev *tdev)
|
|
{
|
|
u32 val;
|
|
|
|
/* Enable PM clocks */
|
|
val = mssnvlink_0_readl(tdev, MSSNVLINK_CLK_CONTROL);
|
|
val |= BIT(MSSNVLINK_CLK_CONTROL_PM_CLK_ENBL);
|
|
mssnvlink_0_writel(tdev, MSSNVLINK_CLK_CONTROL, val);
|
|
|
|
/* Set ILA LOCK bit */
|
|
val = mssnvlink_0_readl(tdev, MSSNVLINK_PM_MODE);
|
|
val |= BIT(MSSNVLINK_PM_MODE_LOCK_ILA);
|
|
mssnvlink_0_writel(tdev, MSSNVLINK_PM_MODE, val);
|
|
|
|
/* Disable PM clocks */
|
|
val = mssnvlink_0_readl(tdev, MSSNVLINK_CLK_CONTROL);
|
|
val &= ~BIT(MSSNVLINK_CLK_CONTROL_PM_CLK_ENBL);
|
|
mssnvlink_0_writel(tdev, MSSNVLINK_CLK_CONTROL, val);
|
|
}
|
|
|
|
/*
|
|
* mssnvlink_init:
|
|
* Do the folllwing to initialize MSSNVLINK. This initialization is required to
|
|
* allow traffic to flow between the NVLINK controller and MSSNVLINK:
|
|
* - Enable the MSSNVLINK core master and slave clocks.
|
|
* - Program the upper limit of the NVLINK aperture in MSSNVLINK. The bottom
|
|
* of the aperture is fixed at 128 GB. So we don't need to program that.
|
|
* - Release MSSNVLINK header and data credits to the NVLINK controller.
|
|
*
|
|
* TODO: Convert the magic values being programmed below into something that's
|
|
* more understandable.
|
|
*/
|
|
static void mssnvlink_init(struct tnvlink_dev *tdev)
|
|
{
|
|
disable_nvlink_slcg(tdev);
|
|
|
|
/* Program the upper limit of the NVLINK aperture in MSSNVLINK */
|
|
nvlink_dbg("Programming MSSNVLINK_TOM to %u GB", NVLINK_TOM_GB);
|
|
non_nvlink_writel(MCB_BASE + MC_MSSNVLINK_TOM, NVLINK_TOM_MB);
|
|
non_nvlink_writel(MCB_BASE + MC_MSSNVLINK_REG_CTRL, 0x1);
|
|
|
|
/* MSSNVLINK credit programming */
|
|
mssnvlink_0_writel(tdev, MSSNVLINK_MASTER_CREDIT_TRANSINFO, 0x15455000);
|
|
mssnvlink_0_writel(tdev, MSSNVLINK_MASTER_CREDIT_INGR_DATA, 0x8020000);
|
|
mssnvlink_0_writel(tdev, MSSNVLINK_SLAVE_CREDIT_TRANSINFO, 0x14050000);
|
|
mssnvlink_0_writel(tdev, MSSNVLINK_SLAVE_CREDIT_INGR_DATA, 0x300c0000);
|
|
|
|
/*
|
|
* Performance settings for balancing request and response bandwidth
|
|
* across NVLINK
|
|
*/
|
|
non_nvlink_writel(MCB_BASE + MC_MCF_IREQX_VCARB_CONFIG, 0x8f0);
|
|
non_nvlink_writel(MCB_BASE + MC_MCF_OREQX_VCARB_CONFIG, 0x8f0);
|
|
|
|
mssnvlink_lock_ila(tdev);
|
|
|
|
}
|
|
|
|
/*
|
|
* Program the upper limit of the NVLINK aperture in SCF.
|
|
* The bottom of the aperture is fixed at 128 GB. So we don't need to program
|
|
* that.
|
|
*/
|
|
static inline void program_scf_tom(void)
|
|
{
|
|
u32 reg_val = SCF_NVLINK_CFG_TOM_MB_F(NVLINK_TOM_MB) |
|
|
BIT(SCF_NVLINK_CFG_EN);
|
|
|
|
nvlink_dbg("Programming SCF TOM to %u GB", NVLINK_TOM_GB);
|
|
asm volatile("msr s3_0_c15_c0_3, %0" : : "r" (reg_val));
|
|
}
|
|
|
|
/*
|
|
* Performs device level initialization like setting up the clocks and
|
|
* resets, booting the minion and configuring the device level interrupts.
|
|
*/
|
|
int t19x_nvlink_dev_early_init(struct nvlink_device *ndev)
|
|
{
|
|
struct tnvlink_dev *tdev = NULL;
|
|
int ret = 0;
|
|
bool fuse_clk_enabled = false;
|
|
|
|
if (!ndev) {
|
|
nvlink_err("Invalid device struct pointer");
|
|
return -EINVAL;
|
|
}
|
|
tdev = (struct tnvlink_dev *)ndev->priv;
|
|
if (!tdev) {
|
|
nvlink_err("Invalid tnvlink_dev struct pointer");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* WAR for Bug 2301575: enable fuse clocks */
|
|
ret = tegra_fuse_clock_enable();
|
|
if (ret < 0) {
|
|
nvlink_err("failed to enable fuse clocks");
|
|
goto fail;
|
|
} else {
|
|
fuse_clk_enabled = true;
|
|
nvlink_dbg("fuse clocks are turned ON");
|
|
}
|
|
|
|
ret = tegra_nvlink_car_enable(tdev);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
/*
|
|
* Make sure clocks and resets are enabled before installing
|
|
* interrupt handler. It will prevent from accessing clock
|
|
* gated block in case of spurious interrupt.
|
|
*/
|
|
ret = devm_request_threaded_irq(tdev->dev, tdev->irq,
|
|
NULL, t19x_nvlink_endpt_isr,
|
|
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
|
|
dev_name(tdev->dev), tdev);
|
|
if (ret < 0) {
|
|
nvlink_err("Failed to register irq %d", tdev->irq);
|
|
goto fail;
|
|
}
|
|
|
|
ret = minion_boot(tdev);
|
|
if (ret < 0)
|
|
goto fail;
|
|
nvlink_config_common_intr(tdev);
|
|
|
|
nvlink_dbg("Device early init done for dev%u", ndev->device_id);
|
|
goto success;
|
|
fail:
|
|
nvlink_err("Device early init failed for dev%u", ndev->device_id);
|
|
if (fuse_clk_enabled) {
|
|
if (tegra_fuse_clock_disable()) {
|
|
nvlink_err("failed to disable fuse clocks");
|
|
} else {
|
|
nvlink_dbg("fuse clocks are turned OFF");
|
|
}
|
|
}
|
|
success:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Performs link level initialization like phy_init, setting up the link
|
|
* interrupts and enabling the AN0 packets.
|
|
*/
|
|
int t19x_nvlink_link_early_init(struct nvlink_device *ndev)
|
|
{
|
|
struct tnvlink_dev *tdev = NULL;
|
|
int ret = 0;
|
|
|
|
if (!ndev) {
|
|
nvlink_err("Invalid device struct pointer");
|
|
return -EINVAL;
|
|
}
|
|
tdev = (struct tnvlink_dev *)ndev->priv;
|
|
|
|
nvlink_enable_AN0_packets(tdev);
|
|
ret = init_nvhs_phy(tdev);
|
|
if (ret < 0)
|
|
goto fail;
|
|
nvlink_enable_link_interrupts(tdev);
|
|
init_tlc(tdev);
|
|
/* 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");
|
|
minion_dump_pc_trace(tdev);
|
|
goto fail;
|
|
}
|
|
nvlink_dbg("Link early init done for dev%u", ndev->device_id);
|
|
goto success;
|
|
fail:
|
|
nvlink_err("link early init failed for dev%u", ndev->device_id);
|
|
success:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Performs memory interface initialization
|
|
*/
|
|
int t19x_nvlink_dev_interface_init(struct nvlink_device *ndev)
|
|
{
|
|
struct tnvlink_dev *tdev = NULL;
|
|
int ret = 0;
|
|
|
|
if (!ndev) {
|
|
nvlink_err("Invalid device struct pointer");
|
|
return -EINVAL;
|
|
}
|
|
tdev = (struct tnvlink_dev *)ndev->priv;
|
|
|
|
mssnvlink_init(tdev);
|
|
program_scf_tom();
|
|
|
|
ret = t19x_nvlink_config_tp_counters(tdev);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
ret = t19x_nvlink_reset_tp_counters(tdev);
|
|
if (ret < 0)
|
|
return -EINVAL;
|
|
nvlink_dbg("Link interface init done for dev%u", ndev->device_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Reg_init programs the prod-setting if any.
|
|
*/
|
|
int t19x_nvlink_dev_reg_init(struct nvlink_device *ndev)
|
|
{
|
|
struct tnvlink_dev *tdev = NULL;
|
|
int ret = 0;
|
|
|
|
if (!ndev) {
|
|
nvlink_err("Invalid device struct pointer");
|
|
return -EINVAL;
|
|
}
|
|
tdev = (struct tnvlink_dev *)ndev->priv;
|
|
|
|
if (tdev->prod_list) {
|
|
ret = tegra_prod_set_by_name(&tdev->mssnvlink_0_base,
|
|
"prod", tdev->prod_list);
|
|
if (ret < 0) {
|
|
/* prod setting failures should not stop nvlink init */
|
|
ret = 0;
|
|
goto fail;
|
|
}
|
|
}
|
|
nvlink_dbg("Device reg init done for dev%u", ndev->device_id);
|
|
goto success;
|
|
fail:
|
|
nvlink_err("Device reg init failed for dev%u", ndev->device_id);
|
|
success:
|
|
return ret;
|
|
}
|
|
|
|
/* Disable the NVLINK aperture in SCF blocks */
|
|
static void disable_nvlink_aperture(void)
|
|
{
|
|
u32 reg_val;
|
|
|
|
/* Read SCF TOM value */
|
|
asm volatile("mrs %0, s3_0_c15_c0_3" : "=r"(reg_val));
|
|
|
|
/* Clear the enable bit */
|
|
reg_val &= ~BIT(SCF_NVLINK_CFG_EN);
|
|
|
|
/* Write SCF TOM value */
|
|
asm volatile("msr s3_0_c15_c0_3, %0" : : "r" (reg_val));
|
|
}
|
|
|
|
/* Check there are no pending requests on NVLink */
|
|
static bool is_nvlink_idle(struct tnvlink_dev *tdev)
|
|
{
|
|
u32 reg;
|
|
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_MASTER_ACT_TRANSINFO);
|
|
if (reg & MSSNVLINK_MASTER_ACT_TRANSINFO_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_MASTER_ACT_TRANSINFO_CNT_CURRENT is not"
|
|
" zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_MASTER_ACT_TRANSINFO_RMW);
|
|
if (reg & MSSNVLINK_MASTER_ACT_TRANSINFO_RMW_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_MASTER_ACT_TRANSINFO_RMW_CNT_CURRENT is"
|
|
" not zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_MASTER_ACT_INGR_DATA);
|
|
if (reg & MSSNVLINK_MASTER_ACT_INGR_DATA_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_MASTER_ACT_INGR_DATA_CNT_CURRENT is not"
|
|
" zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_MASTER_ACT_EGR_DATA);
|
|
if (reg & MSSNVLINK_MASTER_ACT_EGR_DATA_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_MASTER_ACT_EGR_DATA_CNT_CURRENT is not"
|
|
" zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_MASTER_RESERVED_EGR_DATA);
|
|
if (reg & MSSNVLINK_MASTER_RESERVED_EGR_DATA_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_MASTER_RESERVED_EGR_DATA_CNT_CURRENT is"
|
|
" not zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_MASTER_ACT_TRANSDONE);
|
|
if (reg & MSSNVLINK_MASTER_ACT_TRANSDONE_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_MASTER_ACT_TRANSDONE_CNT_CURRENT is not"
|
|
" zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_MASTER_INGR_CMD_QUEUE);
|
|
if (reg & MSSNVLINK_MASTER_INGR_CMD_QUEUE_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_MASTER_INGR_CMD_QUEUE_CNT_CURRENT is not"
|
|
" zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_MASTER_EGR_CMD_QUEUE);
|
|
if (reg & MSSNVLINK_MASTER_EGR_CMD_QUEUE_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_MASTER_EGR_CMD_QUEUE_CNT_CURRENT is not"
|
|
" zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_SLAVE_ACT_TRANSINFO);
|
|
if (reg & MSSNVLINK_SLAVE_ACT_TRANSINFO_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_SLAVE_ACT_TRANSINFO_CNT_CURRENT is not"
|
|
" zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_SLAVE_ACT_INGR_DATA);
|
|
if (reg & MSSNVLINK_SLAVE_ACT_INGR_DATA_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_SLAVE_ACT_INGR_DATA_CNT_CURRENT is not"
|
|
" zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_SLAVE_ACT_EGR_DATA);
|
|
if (reg & MSSNVLINK_SLAVE_ACT_EGR_DATA_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_SLAVE_ACT_EGR_DATA_CNT_CURRENT is not"
|
|
" zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_SLAVE_ACT_TAG_TABLE);
|
|
if (reg & MSSNVLINK_SLAVE_ACT_TAG_TABLE_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_SLAVE_ACT_TAG_TABLE_CNT_CURRENT is not"
|
|
" zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_SLAVE_TRANSDONE_MCF_REQ);
|
|
if (reg & MSSNVLINK_SLAVE_TRANSDONE_MCF_REQ_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_SLAVE_TRANSDONE_MCF_REQ_CNT_CURRENT is"
|
|
" not zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
reg = mssnvlink_0_readl(tdev, MSSNVLINK_SLAVE_TRANSDONE_MCF_DAT);
|
|
if (reg & MSSNVLINK_SLAVE_TRANSDONE_MCF_DAT_CNT_CURRENT_MASK) {
|
|
nvlink_dbg("MSSNVLINK_SLAVE_TRANSDONE_MCF_DAT_CNT_CURRENT is"
|
|
" not zero and nvlink is not idle");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void enable_nvlink_slcg(struct tnvlink_dev *tdev)
|
|
{
|
|
u32 val;
|
|
|
|
val = mssnvlink_0_readl(tdev, MSSNVLINK_CLK_SLCG);
|
|
val &= ~(BIT(MSSNVLINK_CLK_SLCG_MCF_MASTER_CLK_ENBL));
|
|
val &= ~(BIT(MSSNVLINK_CLK_SLCG_MCF_SLAVE_CLK_ENBL));
|
|
val |= BIT(MSSNVLINK_CLK_SLCG_CORE_CLK_UPDATE_ENBL);
|
|
mssnvlink_0_writel(tdev, MSSNVLINK_CLK_SLCG, val);
|
|
}
|
|
|
|
int t19x_nvlink_dev_interface_disable(struct nvlink_device *ndev)
|
|
{
|
|
struct tnvlink_dev *tdev = NULL;
|
|
|
|
if (!ndev) {
|
|
nvlink_err("Invalid nvlink_device struct pointer");
|
|
return -EINVAL;
|
|
}
|
|
tdev = (struct tnvlink_dev *)ndev->priv;
|
|
|
|
/* Ensure there is no pending request in MSSNVLINK */
|
|
/* FIXME : If is_nvlink_idle() returns true, there's no guarantee
|
|
* that NVLINK will remain idle.
|
|
*/
|
|
if (!is_nvlink_idle(tdev)) {
|
|
nvlink_err("Can't shutdown nvlink, link still active.");
|
|
return -EPERM;
|
|
}
|
|
|
|
disable_nvlink_aperture();
|
|
enable_nvlink_slcg(tdev);
|
|
|
|
nvlink_dbg("nvlink dev interface disable successful");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int t19x_nvlink_dev_car_disable(struct nvlink_device *ndev)
|
|
{
|
|
struct tnvlink_dev *tdev = NULL;
|
|
int ret = 0;
|
|
|
|
if (!ndev) {
|
|
nvlink_err("Invalid nvlink_device struct pointer");
|
|
return -EINVAL;
|
|
}
|
|
tdev = (struct tnvlink_dev *)ndev->priv;
|
|
|
|
/* 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");
|
|
goto fail;
|
|
}
|
|
|
|
ret = reset_control_assert(tdev->rst_mssnvl);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed for mssnvl");
|
|
goto fail;
|
|
}
|
|
|
|
if (tdev->refclk == NVLINK_REFCLK_150)
|
|
clk_disable_unprepare(tdev->clk_pllnvhs);
|
|
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed nvhs_uphy");
|
|
goto fail;
|
|
}
|
|
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy_pll0);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed for nvhs_uphy_pll0");
|
|
goto fail;
|
|
}
|
|
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy_l7);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed for nvhs_uphy_l7");
|
|
goto fail;
|
|
}
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy_l6);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed for nvhs_uphy_l6");
|
|
goto fail;
|
|
}
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy_l5);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed for nvhs_uphy_l5");
|
|
goto fail;
|
|
}
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy_l4);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed for nvhs_uphy_l4");
|
|
goto fail;
|
|
}
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy_l3);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed for nvhs_uphy_l3");
|
|
goto fail;
|
|
}
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy_l2);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed for nvhs_uphy_l2");
|
|
goto fail;
|
|
}
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy_l1);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed for nvhs_uphy_l1");
|
|
goto fail;
|
|
}
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy_l0);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed for nvhs_uphy_l0");
|
|
goto fail;
|
|
}
|
|
|
|
clk_disable_unprepare(tdev->clk_nvlink_sys);
|
|
|
|
ret = reset_control_assert(tdev->rst_nvhs_uphy_pm);
|
|
if (ret < 0) {
|
|
nvlink_err("Reset assert failed nvhs_uphy_pm");
|
|
goto fail;
|
|
}
|
|
|
|
clk_disable_unprepare(tdev->clk_nvhs_pll0_mgmt);
|
|
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_nvlink_clk_rst_init(struct tnvlink_dev *tdev)
|
|
{
|
|
/* clocks */
|
|
tdev->clk_nvhs_pll0_mgmt = devm_clk_get(tdev->dev,
|
|
"nvhs_pll0_mgmt");
|
|
if (IS_ERR(tdev->clk_nvhs_pll0_mgmt)) {
|
|
nvlink_err("missing mgmt clock");
|
|
return PTR_ERR(tdev->clk_nvhs_pll0_mgmt);
|
|
}
|
|
|
|
tdev->clk_pllrefe_vcoout_gated = devm_clk_get(tdev->dev,
|
|
"pllrefe_vcoout_gated");
|
|
if (IS_ERR(tdev->clk_pllrefe_vcoout_gated)) {
|
|
nvlink_err("missing pllrefe clock");
|
|
return PTR_ERR(tdev->clk_pllrefe_vcoout_gated);
|
|
}
|
|
|
|
tdev->clk_nvlink_sys = devm_clk_get(tdev->dev,
|
|
"nvlink_sys");
|
|
if (IS_ERR(tdev->clk_nvlink_sys)) {
|
|
nvlink_err("missing sys clock");
|
|
return PTR_ERR(tdev->clk_nvlink_sys);
|
|
}
|
|
|
|
tdev->clk_pllnvhs = devm_clk_get(tdev->dev,
|
|
"pllnvhs");
|
|
if (IS_ERR(tdev->clk_pllnvhs)) {
|
|
nvlink_err("missing pllnvhs clock");
|
|
return PTR_ERR(tdev->clk_pllnvhs);
|
|
}
|
|
|
|
tdev->clk_m = devm_clk_get(tdev->dev, "clk_m");
|
|
if (IS_ERR(tdev->clk_m)) {
|
|
nvlink_err("missing clk_m clock");
|
|
return PTR_ERR(tdev->clk_m);
|
|
}
|
|
|
|
tdev->clk_nvlink_pll_txclk = devm_clk_get(tdev->dev,
|
|
"nvlink_pll_txclk");
|
|
if (IS_ERR(tdev->clk_nvlink_pll_txclk)) {
|
|
nvlink_err("missing nvlink_pll_txclk clock");
|
|
return PTR_ERR(tdev->clk_nvlink_pll_txclk);
|
|
}
|
|
|
|
tdev->clk_nvlink_tx = devm_clk_get(tdev->dev, "nvlink_tx");
|
|
if (IS_ERR(tdev->clk_nvlink_tx)) {
|
|
nvlink_err("missing nvlink_tx clock");
|
|
return PTR_ERR(tdev->clk_nvlink_tx);
|
|
}
|
|
|
|
/* Resets */
|
|
tdev->rst_mssnvl = devm_reset_control_get(tdev->dev, "mssnvl");
|
|
if (IS_ERR(tdev->rst_mssnvl)) {
|
|
nvlink_err("missing rst_mssnvl reset");
|
|
return PTR_ERR(tdev->rst_mssnvl);
|
|
}
|
|
tdev->rst_nvhs_uphy_pm = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy_pm");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy_pm)) {
|
|
nvlink_err("missing rst_nvhs_uphy_pm reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy_pm);
|
|
}
|
|
tdev->rst_nvhs_uphy = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy)) {
|
|
nvlink_err("missing rst_nvhs_uphy reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy);
|
|
}
|
|
tdev->rst_nvhs_uphy_pll0 = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy_pll0");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy_pll0)) {
|
|
nvlink_err("missing rst_nvhs_uphy_pll0 reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy_pll0);
|
|
}
|
|
tdev->rst_nvhs_uphy_l0 = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy_l0");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy_l0)) {
|
|
nvlink_err("missing rst_nvhs_uphy_l0 reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy_l0);
|
|
}
|
|
tdev->rst_nvhs_uphy_l1 = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy_l1");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy_l1)) {
|
|
nvlink_err("missing rst_nvhs_uphy_l1 reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy_l1);
|
|
}
|
|
tdev->rst_nvhs_uphy_l2 = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy_l2");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy_l2)) {
|
|
nvlink_err("missing rst_nvhs_uphy_l2 reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy_l2);
|
|
}
|
|
tdev->rst_nvhs_uphy_l3 = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy_l3");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy_l3)) {
|
|
nvlink_err("missing rst_nvhs_uphy_l3 reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy_l3);
|
|
}
|
|
tdev->rst_nvhs_uphy_l4 = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy_l4");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy_l4)) {
|
|
nvlink_err("missing rst_nvhs_uphy_l4 reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy_l4);
|
|
}
|
|
tdev->rst_nvhs_uphy_l5 = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy_l5");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy_l5)) {
|
|
nvlink_err("missing rst_nvhs_uphy_l5 reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy_l5);
|
|
}
|
|
tdev->rst_nvhs_uphy_l6 = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy_l6");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy_l6)) {
|
|
nvlink_err("missing rst_nvhs_uphy_l6 reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy_l6);
|
|
}
|
|
tdev->rst_nvhs_uphy_l7 = devm_reset_control_get(tdev->dev,
|
|
"nvhs_uphy_l7");
|
|
if (IS_ERR(tdev->rst_nvhs_uphy_l7)) {
|
|
nvlink_err("missing rst_nvhs_uphy_l7 reset");
|
|
return PTR_ERR(tdev->rst_nvhs_uphy_l7);
|
|
}
|
|
tdev->rst_nvlink = devm_reset_control_get(tdev->dev,
|
|
"nvlink");
|
|
if (IS_ERR(tdev->rst_nvlink)) {
|
|
nvlink_err("missing rst_nvlink reset");
|
|
return PTR_ERR(tdev->rst_nvlink);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_PM_SLEEP)
|
|
/* This function invokes core driver's nvlink shutdown api */
|
|
int t19x_nvlink_suspend(struct device *dev)
|
|
{
|
|
struct tnvlink_dev *tdev = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
if (!tdev) {
|
|
nvlink_err("Invalid tnvlink_dev struct pointer");
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
ret = nvlink_shutdown(tdev->ndev);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
nvlink_dbg("t19x nvlink suspend successful");
|
|
goto exit;
|
|
|
|
fail:
|
|
nvlink_err("t19x nvlink suspend failed!");
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static const struct dev_pm_ops tegra_nvlink_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(t19x_nvlink_suspend, NULL)
|
|
};
|
|
#endif
|
|
|
|
static void tegra_nvlink_clk_rst_deinit(struct tnvlink_dev *tdev)
|
|
{
|
|
/* clocks */
|
|
if (tdev->clk_nvhs_pll0_mgmt)
|
|
devm_clk_put(tdev->dev, tdev->clk_nvhs_pll0_mgmt);
|
|
|
|
if (tdev->clk_pllrefe_vcoout_gated)
|
|
devm_clk_put(tdev->dev, tdev->clk_pllrefe_vcoout_gated);
|
|
|
|
if (tdev->clk_nvlink_sys)
|
|
devm_clk_put(tdev->dev, tdev->clk_nvlink_sys);
|
|
|
|
if (tdev->clk_pllnvhs)
|
|
devm_clk_put(tdev->dev, tdev->clk_pllnvhs);
|
|
|
|
if (tdev->clk_m)
|
|
devm_clk_put(tdev->dev, tdev->clk_m);
|
|
|
|
if (tdev->clk_nvlink_pll_txclk)
|
|
devm_clk_put(tdev->dev, tdev->clk_nvlink_pll_txclk);
|
|
|
|
if (tdev->clk_nvlink_tx)
|
|
devm_clk_put(tdev->dev, tdev->clk_nvlink_tx);
|
|
|
|
reset_control_assert(tdev->rst_nvhs_uphy_pm);
|
|
reset_control_assert(tdev->rst_nvhs_uphy);
|
|
reset_control_assert(tdev->rst_nvhs_uphy_pll0);
|
|
reset_control_assert(tdev->rst_nvhs_uphy_l0);
|
|
reset_control_assert(tdev->rst_nvhs_uphy_l1);
|
|
reset_control_assert(tdev->rst_nvhs_uphy_l2);
|
|
reset_control_assert(tdev->rst_nvhs_uphy_l3);
|
|
reset_control_assert(tdev->rst_nvhs_uphy_l4);
|
|
reset_control_assert(tdev->rst_nvhs_uphy_l5);
|
|
reset_control_assert(tdev->rst_nvhs_uphy_l6);
|
|
reset_control_assert(tdev->rst_nvhs_uphy_l7);
|
|
|
|
}
|
|
|
|
static int t19x_nvlink_check_fuse(void)
|
|
{
|
|
u32 fuse_val;
|
|
int ret = 0;
|
|
|
|
/* fuse clock is enabled by fuse driver before reading fuse */
|
|
ret = tegra_fuse_readl(FUSE_IP_DISABLE_0, &fuse_val);
|
|
if (ret) {
|
|
nvlink_err("nvlink ip fuse cannot be read");
|
|
} else {
|
|
nvlink_dbg("fuse_ip_disable_0: 0x%x", fuse_val);
|
|
}
|
|
|
|
if (!ret) {
|
|
/* fuse is read. check if ip is enabled */
|
|
if (fuse_val & FUSE_IP_DISABLE_0_NVLINK_MASK) {
|
|
nvlink_err("nvlink ip is disabled");
|
|
ret = -EPERM;
|
|
}
|
|
}
|
|
if (!ret) {
|
|
if (tegra_fuse_readl(FUSE_UCODE_MINION_REV_0, &fuse_val)) {
|
|
nvlink_err("ucode minion rev fuse cannot be read");
|
|
} else {
|
|
nvlink_dbg("fuse_ucode_minion_rev_0: 0x%x", fuse_val &
|
|
FUSE_UCODE_MINION_REV_0_MASK);
|
|
}
|
|
|
|
if (tegra_fuse_readl(FUSE_SECURE_MINION_DEBUG_DIS_0,
|
|
&fuse_val)) {
|
|
nvlink_err("minion debug dis fuse cannot be read");
|
|
} else {
|
|
nvlink_dbg("fuse_secure_minion_debug_dis_0: 0x%x",
|
|
fuse_val & FUSE_SECURE_MINION_DEBUG_DIS_0_MASK);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int t19x_nvlink_endpt_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
struct tnvlink_dev *tdev;
|
|
struct nvlink_device *ndev;
|
|
struct device_node *np = pdev->dev.of_node;
|
|
struct device_node *endpt_dt_node = NULL;
|
|
struct device *dev = NULL;
|
|
struct nvlink_device_pci_info *local_pci_info;
|
|
struct nvlink_device_pci_info *remote_pci_info;
|
|
struct tegra_prod *nvlink_prod;
|
|
|
|
if (!np) {
|
|
nvlink_err("Invalid device_node");
|
|
ret = -ENODEV;
|
|
goto fail;
|
|
}
|
|
|
|
ret = t19x_nvlink_check_fuse();
|
|
/* proceed only if fuse is read and nvlink ip is not disabled */
|
|
if (ret) {
|
|
ret = -ENODEV;
|
|
goto fail;
|
|
}
|
|
|
|
ndev = kzalloc(sizeof(struct nvlink_device), GFP_KERNEL);
|
|
if (!ndev) {
|
|
nvlink_err("Couldn't allocate memory for nvlink device struct");
|
|
ret = -ENOMEM;
|
|
goto err_alloc_ndev;
|
|
}
|
|
|
|
tdev = kzalloc(sizeof(struct tnvlink_dev), GFP_KERNEL);
|
|
if (!tdev) {
|
|
nvlink_err("Couldn't allocate memory for tegra nvlink device");
|
|
ret = -ENOMEM;
|
|
goto err_alloc_tdev;
|
|
}
|
|
|
|
/*
|
|
* By default the RM shim driver is disabled. The shim driver can be
|
|
* enabled if userspace calls the ENABLE_SHIM_DRIVER IOCTL.
|
|
*/
|
|
tdev->rm_shim_enabled = false;
|
|
|
|
/* Map NVLINK apertures listed in device tree node */
|
|
tdev->nvlw_tioctrl_base =
|
|
of_io_request_and_map(np, 0,
|
|
"NVLW_TIOCTRL aperture");
|
|
if (IS_ERR(tdev->nvlw_tioctrl_base)) {
|
|
nvlink_err("Couldn't map the NVLW_TIOCTRL aperture");
|
|
ret = PTR_ERR(tdev->nvlw_tioctrl_base);
|
|
goto err_mapping;
|
|
}
|
|
|
|
tdev->nvlw_nvlipt_base =
|
|
of_io_request_and_map(np, 1,
|
|
"NVLW_NVLIPT aperture");
|
|
if (IS_ERR(tdev->nvlw_nvlipt_base)) {
|
|
nvlink_err("Couldn't map the NVLW_NVLIPT aperture");
|
|
ret = PTR_ERR(tdev->nvlw_nvlipt_base);
|
|
goto err_mapping;
|
|
}
|
|
|
|
tdev->nvlw_minion_base =
|
|
of_io_request_and_map(np, 2,
|
|
"NVLW_MINION aperture");
|
|
if (IS_ERR(tdev->nvlw_minion_base)) {
|
|
nvlink_err("Couldn't map the NVLW_MINION aperture");
|
|
ret = PTR_ERR(tdev->nvlw_minion_base);
|
|
goto err_mapping;
|
|
}
|
|
|
|
tdev->tlink.nvlw_nvl_base =
|
|
of_io_request_and_map(np, 3,
|
|
"NVLW_NVL aperture");
|
|
if (IS_ERR(tdev->tlink.nvlw_nvl_base)) {
|
|
nvlink_err("Couldn't map the NVLW_NVL aperture");
|
|
ret = PTR_ERR(tdev->tlink.nvlw_nvl_base);
|
|
goto err_mapping;
|
|
}
|
|
|
|
tdev->nvlw_sync2x_base = of_io_request_and_map(np, 4,
|
|
"NVLW_SYNC2X aperture");
|
|
if (IS_ERR(tdev->nvlw_sync2x_base)) {
|
|
nvlink_err("Couldn't map the NVLW_SYNC2X aperture");
|
|
ret = PTR_ERR(tdev->nvlw_sync2x_base);
|
|
goto err_mapping;
|
|
}
|
|
|
|
tdev->tlink.nvlw_nvltlc_base =
|
|
of_io_request_and_map(np, 5,
|
|
"NVLW_NVLTLC aperture");
|
|
if (IS_ERR(tdev->tlink.nvlw_nvltlc_base)) {
|
|
nvlink_err("Couldn't map the NVLW_NVLTLC aperture");
|
|
ret = PTR_ERR(tdev->tlink.nvlw_nvltlc_base);
|
|
goto err_mapping;
|
|
}
|
|
|
|
tdev->mssnvlink_0_base = of_io_request_and_map(np, 6,
|
|
"MSSNVLINK_0 aperture");
|
|
if (IS_ERR(tdev->mssnvlink_0_base)) {
|
|
nvlink_err("Couldn't map the MSSNVLINK_0 aperture");
|
|
ret = PTR_ERR(tdev->mssnvlink_0_base);
|
|
goto err_mapping;
|
|
}
|
|
|
|
tdev->is_nea = DEFAULT_IS_NEA;
|
|
tdev->is_tp_cntr_running = false;
|
|
tdev->irq = platform_get_irq(pdev, 0);
|
|
if (tdev->irq < 0) {
|
|
nvlink_err("Couldn't get interrupt listed in device tree");
|
|
ret = -EINVAL;
|
|
goto err_mapping;
|
|
}
|
|
|
|
tdev->dev = &pdev->dev;
|
|
tdev->class.owner = THIS_MODULE;
|
|
tdev->class.name = NVLINK_MODULE_NAME;
|
|
|
|
/* Create device node */
|
|
ret = class_register(&tdev->class);
|
|
if (ret) {
|
|
nvlink_err("Failed to register class");
|
|
goto err_mapping;
|
|
}
|
|
|
|
ret = alloc_chrdev_region(&tdev->dev_t, 0, 1, dev_name(tdev->dev));
|
|
if (ret) {
|
|
nvlink_err("Failed to allocate dev_t");
|
|
goto err_chrdev_region;
|
|
}
|
|
|
|
cdev_init(&tdev->cdev, &t19x_nvlink_endpt_ops);
|
|
tdev->cdev.owner = THIS_MODULE;
|
|
|
|
ret = cdev_add(&tdev->cdev, tdev->dev_t, 1);
|
|
if (ret) {
|
|
nvlink_err("Failed to add cdev");
|
|
goto err_cdev;
|
|
}
|
|
|
|
dev = device_create(&tdev->class,
|
|
NULL,
|
|
tdev->dev_t,
|
|
NULL,
|
|
NVLINK_MODULE_NAME);
|
|
if (IS_ERR(dev)) {
|
|
nvlink_err("Failed to create device");
|
|
ret = PTR_ERR(dev);
|
|
goto err_device;
|
|
}
|
|
|
|
ret = tegra_nvlink_clk_rst_init(tdev);
|
|
if (ret)
|
|
goto err_clk_rst;
|
|
|
|
nvlink_prod = devm_tegra_prod_get(tdev->dev);
|
|
if (IS_ERR_OR_NULL(nvlink_prod)) {
|
|
nvlink_err("Prod-setting not available");
|
|
nvlink_prod = NULL;
|
|
}
|
|
tdev->prod_list = nvlink_prod;
|
|
tdev->refclk = NVLINK_REFCLK_156;
|
|
ndev->speed = NVLINK_SPEED_20;
|
|
ndev->link_bitrate = LINK_BITRATE_156MHZ_20GBPS;
|
|
tdev->ndev = ndev;
|
|
|
|
tdev->tlink.sl_params = entry_100us_sl_params;
|
|
tdev->tlink.tdev = tdev;
|
|
tdev->tlink.nlink = &(ndev->link);
|
|
t19x_nvlink_endpt_debugfs_init(tdev);
|
|
|
|
/* Fill in the nvlink_device struct */
|
|
/* Read NVLINK topology information in device tree */
|
|
endpt_dt_node = of_get_child_by_name(np, "endpoint");
|
|
if (endpt_dt_node == NULL) {
|
|
nvlink_err("Topology information is missing from the"
|
|
" device tree!");
|
|
ret = -ENODATA;
|
|
goto err_topology;
|
|
}
|
|
ret = of_property_read_u32(endpt_dt_node, "local_dev_id",
|
|
&ndev->device_id);
|
|
if (ret < 0) {
|
|
nvlink_dbg("Couldn't read the \"local_dev_id\" device tree"
|
|
" property. Choosing default value"
|
|
" (i.e. NVLINK_ENDPT_T19X).");
|
|
ndev->device_id = NVLINK_ENDPT_T19X;
|
|
}
|
|
ret = of_property_read_u32(endpt_dt_node, "local_link_id",
|
|
&ndev->link.link_id);
|
|
if (ret < 0) {
|
|
nvlink_dbg("Couldn't read the \"local_link_id\" device tree"
|
|
" property. Choosing default value (i.e. 0).");
|
|
ndev->link.link_id = 0;
|
|
}
|
|
ndev->is_master = of_property_read_bool(endpt_dt_node, "is_master");
|
|
ret = of_property_read_u32(endpt_dt_node, "remote_dev_id",
|
|
&ndev->link.remote_dev_info.device_id);
|
|
if (ret < 0) {
|
|
nvlink_err("The \"remote_dev_id\" property is missing from the"
|
|
" device tree!");
|
|
ret = -ENODATA;
|
|
goto err_topology;
|
|
}
|
|
ret = of_property_read_u32(endpt_dt_node, "remote_link_id",
|
|
&ndev->link.remote_dev_info.link_id);
|
|
if (ret < 0) {
|
|
nvlink_err("The \"remote_link_id\" property is missing from the"
|
|
" device tree!");
|
|
ret = -ENODATA;
|
|
goto err_topology;
|
|
}
|
|
|
|
nvlink_dbg("Device Tree Topology Information:");
|
|
nvlink_dbg(" - Local Device: Device ID = %d, Link ID = %d, Is master? = %s",
|
|
ndev->device_id,
|
|
ndev->link.link_id,
|
|
ndev->is_master ? "True" : "False");
|
|
nvlink_dbg(" - Remote Device: Device ID = %d, Link ID = %d",
|
|
ndev->link.remote_dev_info.device_id,
|
|
ndev->link.remote_dev_info.link_id);
|
|
|
|
/*
|
|
* MODS/RM identifies devices using PCI device information. Tegra
|
|
* however, doesn't have any valid PCI information because Tegra
|
|
* is not a PCI device. Therefore we use the following bogus PCI
|
|
* info for Tegra. We're hoping that this bogus PCI info doesn't
|
|
* clash with the PCI info of an actual PCI device.
|
|
*/
|
|
local_pci_info = &ndev->pci_info;
|
|
remote_pci_info = &ndev->link.remote_dev_info.pci_info;
|
|
local_pci_info->domain = 0;
|
|
local_pci_info->bus = ~0;
|
|
local_pci_info->device = ~0;
|
|
local_pci_info->function = ~0;
|
|
local_pci_info->pci_device_id = ~0;
|
|
|
|
if (ndev->device_id == ndev->link.remote_dev_info.device_id) {
|
|
/* Tegra loopback topology */
|
|
/* Local and remote PCI info should be identical */
|
|
memcpy(remote_pci_info,
|
|
local_pci_info,
|
|
sizeof(struct nvlink_device_pci_info));
|
|
} else {
|
|
/* Non-loopback topology */
|
|
/* Set all fields to 0xffff... */
|
|
memset(remote_pci_info,
|
|
0xff,
|
|
sizeof(struct nvlink_device_pci_info));
|
|
}
|
|
|
|
ndev->dev_ops.dev_early_init = t19x_nvlink_dev_early_init;
|
|
ndev->dev_ops.dev_interface_init = t19x_nvlink_dev_interface_init;
|
|
ndev->dev_ops.dev_reg_init = t19x_nvlink_dev_reg_init;
|
|
ndev->dev_ops.dev_interface_disable = t19x_nvlink_dev_interface_disable;
|
|
/* Point priv of ndev to the tegra nvlink endpoint device struct */
|
|
ndev->priv = (void *) tdev;
|
|
|
|
/* Fill in the nvlink_link struct */
|
|
ndev->link.device_id = ndev->device_id;
|
|
ndev->link.link_ops.get_link_mode = t19x_nvlink_get_link_mode;
|
|
ndev->link.link_ops.set_link_mode = t19x_nvlink_set_link_mode;
|
|
ndev->link.link_ops.get_sublink_mode = t19x_nvlink_get_sublink_mode;
|
|
ndev->link.link_ops.set_sublink_mode = t19x_nvlink_set_sublink_mode;
|
|
ndev->link.link_ops.get_link_state = t19x_nvlink_get_link_state;
|
|
ndev->link.link_ops.get_tx_sublink_state =
|
|
t19x_nvlink_get_tx_sublink_state;
|
|
ndev->link.link_ops.get_rx_sublink_state =
|
|
t19x_nvlink_get_rx_sublink_state;
|
|
ndev->link.link_ops.link_early_init =
|
|
t19x_nvlink_link_early_init;
|
|
ndev->link.ndev = ndev;
|
|
/* Point priv of ndev->link to the tegra nvlink endpoint link struct */
|
|
ndev->link.priv = (void *)&(tdev->tlink);
|
|
ndev->link.is_sl_supported = false;
|
|
|
|
platform_set_drvdata(pdev, tdev);
|
|
|
|
/* Register device with the Tegra NVLINK core driver */
|
|
ret = nvlink_register_device(ndev);
|
|
if (ret < 0) {
|
|
goto err_ndev_register;
|
|
}
|
|
|
|
/* Register link with the Tegra NVLINK core driver */
|
|
ret = nvlink_register_link(&ndev->link);
|
|
if (ret < 0) {
|
|
goto err_nlink_register;
|
|
}
|
|
|
|
nvlink_dbg("Probe successful!");
|
|
goto success;
|
|
|
|
err_nlink_register:
|
|
nvlink_unregister_device(ndev);
|
|
err_ndev_register:
|
|
tegra_nvlink_clk_rst_deinit(tdev);
|
|
err_topology:
|
|
t19x_nvlink_endpt_debugfs_deinit(tdev);
|
|
err_clk_rst:
|
|
device_destroy(&tdev->class, tdev->dev_t);
|
|
err_device:
|
|
cdev_del(&tdev->cdev);
|
|
err_cdev:
|
|
unregister_chrdev_region(tdev->dev_t, 1);
|
|
err_chrdev_region:
|
|
class_unregister(&tdev->class);
|
|
|
|
err_mapping:
|
|
if (!IS_ERR(tdev->nvlw_tioctrl_base))
|
|
iounmap(tdev->nvlw_tioctrl_base);
|
|
|
|
if (!IS_ERR(tdev->nvlw_nvlipt_base))
|
|
iounmap(tdev->nvlw_nvlipt_base);
|
|
|
|
if (!IS_ERR(tdev->nvlw_minion_base))
|
|
iounmap(tdev->nvlw_minion_base);
|
|
|
|
if (!IS_ERR(tdev->tlink.nvlw_nvl_base))
|
|
iounmap(tdev->tlink.nvlw_nvl_base);
|
|
|
|
if (!IS_ERR(tdev->nvlw_sync2x_base))
|
|
iounmap(tdev->nvlw_sync2x_base);
|
|
|
|
if (!IS_ERR(tdev->tlink.nvlw_nvltlc_base))
|
|
iounmap(tdev->tlink.nvlw_nvltlc_base);
|
|
|
|
if (!IS_ERR(tdev->mssnvlink_0_base))
|
|
iounmap(tdev->mssnvlink_0_base);
|
|
|
|
kfree(tdev);
|
|
err_alloc_tdev:
|
|
kfree(ndev);
|
|
err_alloc_ndev:
|
|
fail:
|
|
nvlink_err("Probe failed!");
|
|
success:
|
|
return ret;
|
|
}
|
|
|
|
static int t19x_nvlink_endpt_remove(struct platform_device *pdev)
|
|
{
|
|
struct nvlink_device *ndev = NULL;
|
|
struct tnvlink_dev *tdev = NULL;
|
|
|
|
tdev = platform_get_drvdata(pdev);
|
|
ndev = tdev->ndev;
|
|
|
|
if (!tdev->rm_shim_enabled) {
|
|
nvlink_unregister_link(&ndev->link);
|
|
nvlink_unregister_device(ndev);
|
|
}
|
|
|
|
t19x_nvlink_endpt_debugfs_deinit(tdev);
|
|
tegra_nvlink_clk_rst_deinit(tdev);
|
|
device_destroy(&tdev->class, tdev->dev_t);
|
|
cdev_del(&tdev->cdev);
|
|
unregister_chrdev_region(tdev->dev_t, 1);
|
|
class_unregister(&tdev->class);
|
|
iounmap(tdev->nvlw_tioctrl_base);
|
|
iounmap(tdev->nvlw_nvlipt_base);
|
|
iounmap(tdev->nvlw_minion_base);
|
|
iounmap(tdev->tlink.nvlw_nvl_base);
|
|
iounmap(tdev->nvlw_sync2x_base);
|
|
iounmap(tdev->tlink.nvlw_nvltlc_base);
|
|
iounmap(tdev->mssnvlink_0_base);
|
|
kfree(tdev);
|
|
kfree(ndev);
|
|
return 0;
|
|
}
|
|
|
|
static void t19x_nvlink_endpt_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct tnvlink_dev *tdev = platform_get_drvdata(pdev);
|
|
int ret = 0;
|
|
|
|
if (!tdev) {
|
|
nvlink_err("Invalid tnvlink_dev struct pointer");
|
|
nvlink_err("t19x nvlink shutdown failed!");
|
|
return;
|
|
}
|
|
|
|
ret = nvlink_shutdown(tdev->ndev);
|
|
if (ret < 0) {
|
|
nvlink_err("t19x nvlink shutdown failed!");
|
|
return;
|
|
}
|
|
|
|
nvlink_dbg("t19x nvlink shutdown successful");
|
|
}
|
|
|
|
static struct platform_driver t19x_nvlink_endpt_pdrv = {
|
|
.probe = t19x_nvlink_endpt_probe,
|
|
.remove = t19x_nvlink_endpt_remove,
|
|
.driver = {
|
|
.name = NVLINK_MODULE_NAME,
|
|
#if IS_ENABLED(CONFIG_PM_SLEEP)
|
|
.pm = &tegra_nvlink_pm_ops,
|
|
#endif
|
|
.of_match_table = of_match_ptr(t19x_nvlink_controller_of_match),
|
|
},
|
|
.shutdown = t19x_nvlink_endpt_shutdown,
|
|
};
|
|
|
|
static int __init t19x_nvlink_endpt_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = platform_driver_register(&t19x_nvlink_endpt_pdrv);
|
|
if (ret < 0)
|
|
nvlink_err("Platform driver register failed");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit t19x_nvlink_endpt_exit(void)
|
|
{
|
|
nvlink_dbg("Unloading the T19x NVLINK endpoint driver");
|
|
platform_driver_unregister(&t19x_nvlink_endpt_pdrv);
|
|
}
|
|
|
|
module_init(t19x_nvlink_endpt_init);
|
|
module_exit(t19x_nvlink_endpt_exit);
|
|
|
|
MODULE_ALIAS(NVLINK_MODULE_NAME);
|
|
MODULE_DESCRIPTION("T19x NVLINK Endpoint Driver");
|
|
MODULE_LICENSE("GPL v2");
|