diff --git a/drivers/gpu/nvgpu/Makefile.nvgpu-t18x b/drivers/gpu/nvgpu/Makefile.nvgpu-t18x index eb9d59773..17b33959c 100644 --- a/drivers/gpu/nvgpu/Makefile.nvgpu-t18x +++ b/drivers/gpu/nvgpu/Makefile.nvgpu-t18x @@ -42,7 +42,8 @@ nvgpu-y += \ $(nvgpu-t18x)/clk/clk.o \ $(nvgpu-t18x)/gp106/clk_gp106.o \ $(nvgpu-t18x)/gp106/gp106_gating_reglist.o \ - $(nvgpu-t18x)/gp106/therm_gp106.o + $(nvgpu-t18x)/gp106/therm_gp106.o \ + $(nvgpu-t18x)/gp106/xve_gp106.o nvgpu-$(CONFIG_TEGRA_GK20A) += $(nvgpu-t18x)/gp10b/platform_gp10b_tegra.o diff --git a/drivers/gpu/nvgpu/gp106/hal_gp106.c b/drivers/gpu/nvgpu/gp106/hal_gp106.c index d07da835e..2217dfead 100644 --- a/drivers/gpu/nvgpu/gp106/hal_gp106.c +++ b/drivers/gpu/nvgpu/gp106/hal_gp106.c @@ -29,6 +29,7 @@ #include "gp10b/regops_gp10b.h" #include "gp10b/cde_gp10b.h" #include "gp106/therm_gp106.h" +#include "gp106/xve_gp106.h" #include "gm206/bios_gm206.h" @@ -210,6 +211,8 @@ int gp106_init_hal(struct gk20a *g) #endif gm206_init_bios(gops); gp106_init_therm_ops(gops); + gp106_init_xve_ops(gops); + gops->name = "gp10x"; gops->get_litter_value = gp106_get_litter_value; gops->chip_init_gpu_characteristics = gk20a_init_gpu_characteristics; diff --git a/drivers/gpu/nvgpu/gp106/hw_xp_gp106.h b/drivers/gpu/nvgpu/gp106/hw_xp_gp106.h new file mode 100644 index 000000000..40b14da14 --- /dev/null +++ b/drivers/gpu/nvgpu/gp106/hw_xp_gp106.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2016, 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 . + */ +/* + * Function naming determines intended use: + * + * _r(void) : Returns the offset for register . + * + * _o(void) : Returns the offset for element . + * + * _w(void) : Returns the word offset for word (4 byte) element . + * + * __s(void) : Returns size of field of register in bits. + * + * __f(u32 v) : Returns a value based on 'v' which has been shifted + * and masked to place it at field of register . This value + * can be |'d with others to produce a full register value for + * register . + * + * __m(void) : Returns a mask for field of register . This + * value can be ~'d and then &'d to clear the value of field for + * register . + * + * ___f(void) : Returns the constant value after being shifted + * to place it at field of register . This value can be |'d + * with others to produce a full register value for . + * + * __v(u32 r) : Returns the value of field from a full register + * value 'r' after being shifted to place its LSB at bit 0. + * This value is suitable for direct comparison with other unshifted + * values appropriate for use in field of register . + * + * ___v(void) : Returns the constant value for defined for + * field of register . This value is suitable for direct + * comparison with unshifted values appropriate for use in field + * of register . + */ +#ifndef _hw_xp_gp106_h_ +#define _hw_xp_gp106_h_ + +static inline u32 xp_dl_mgr_r(u32 i) +{ + return 0x0008b8c0 + i*4; +} +static inline u32 xp_dl_mgr_safe_timing_f(u32 v) +{ + return (v & 0x1) << 2; +} +static inline u32 xp_pl_link_config_r(u32 i) +{ + return 0x0008c040 + i*4; +} +static inline u32 xp_pl_link_config_ltssm_status_f(u32 v) +{ + return (v & 0x1) << 4; +} +static inline u32 xp_pl_link_config_ltssm_status_idle_v(void) +{ + return 0x00000000; +} +static inline u32 xp_pl_link_config_ltssm_directive_f(u32 v) +{ + return (v & 0xf) << 0; +} +static inline u32 xp_pl_link_config_ltssm_directive_m(void) +{ + return 0xf << 0; +} +static inline u32 xp_pl_link_config_ltssm_directive_normal_operations_v(void) +{ + return 0x00000000; +} +static inline u32 xp_pl_link_config_ltssm_directive_change_speed_v(void) +{ + return 0x00000001; +} +static inline u32 xp_pl_link_config_max_link_rate_f(u32 v) +{ + return (v & 0x3) << 18; +} +static inline u32 xp_pl_link_config_max_link_rate_m(void) +{ + return 0x3 << 18; +} +static inline u32 xp_pl_link_config_max_link_rate_2500_mtps_v(void) +{ + return 0x00000002; +} +static inline u32 xp_pl_link_config_max_link_rate_5000_mtps_v(void) +{ + return 0x00000001; +} +static inline u32 xp_pl_link_config_max_link_rate_8000_mtps_v(void) +{ + return 0x00000000; +} +static inline u32 xp_pl_link_config_target_tx_width_f(u32 v) +{ + return (v & 0x7) << 20; +} +static inline u32 xp_pl_link_config_target_tx_width_m(void) +{ + return 0x7 << 20; +} +static inline u32 xp_pl_link_config_target_tx_width_x1_v(void) +{ + return 0x00000007; +} +static inline u32 xp_pl_link_config_target_tx_width_x2_v(void) +{ + return 0x00000006; +} +static inline u32 xp_pl_link_config_target_tx_width_x4_v(void) +{ + return 0x00000005; +} +static inline u32 xp_pl_link_config_target_tx_width_x8_v(void) +{ + return 0x00000004; +} +static inline u32 xp_pl_link_config_target_tx_width_x16_v(void) +{ + return 0x00000000; +} +#endif diff --git a/drivers/gpu/nvgpu/gp106/hw_xve_gp106.h b/drivers/gpu/nvgpu/gp106/hw_xve_gp106.h index 74b6cf7c6..24434ae0d 100644 --- a/drivers/gpu/nvgpu/gp106/hw_xve_gp106.h +++ b/drivers/gpu/nvgpu/gp106/hw_xve_gp106.h @@ -66,4 +66,84 @@ static inline u32 xve_rom_ctrl_rom_shadow_enabled_f(void) { return 0x1; } +static inline u32 xve_link_control_status_r(void) +{ + return 0x00000088; +} +static inline u32 xve_link_control_status_link_speed_m(void) +{ + return 0xf << 16; +} +static inline u32 xve_link_control_status_link_speed_v(u32 r) +{ + return (r >> 16) & 0xf; +} +static inline u32 xve_link_control_status_link_speed_link_speed_2p5_v(void) +{ + return 0x00000001; +} +static inline u32 xve_link_control_status_link_speed_link_speed_5p0_v(void) +{ + return 0x00000002; +} +static inline u32 xve_link_control_status_link_speed_link_speed_8p0_v(void) +{ + return 0x00000003; +} +static inline u32 xve_link_control_status_link_width_m(void) +{ + return 0x3f << 20; +} +static inline u32 xve_link_control_status_link_width_v(u32 r) +{ + return (r >> 20) & 0x3f; +} +static inline u32 xve_link_control_status_link_width_x1_v(void) +{ + return 0x00000001; +} +static inline u32 xve_link_control_status_link_width_x2_v(void) +{ + return 0x00000002; +} +static inline u32 xve_link_control_status_link_width_x4_v(void) +{ + return 0x00000004; +} +static inline u32 xve_link_control_status_link_width_x8_v(void) +{ + return 0x00000008; +} +static inline u32 xve_link_control_status_link_width_x16_v(void) +{ + return 0x00000010; +} +static inline u32 xve_priv_xv_r(void) +{ + return 0x00000150; +} +static inline u32 xve_priv_xv_cya_l0s_enable_f(u32 v) +{ + return (v & 0x1) << 7; +} +static inline u32 xve_priv_xv_cya_l0s_enable_m(void) +{ + return 0x1 << 7; +} +static inline u32 xve_priv_xv_cya_l0s_enable_v(u32 r) +{ + return (r >> 7) & 0x1; +} +static inline u32 xve_priv_xv_cya_l1_enable_f(u32 v) +{ + return (v & 0x1) << 8; +} +static inline u32 xve_priv_xv_cya_l1_enable_m(void) +{ + return 0x1 << 8; +} +static inline u32 xve_priv_xv_cya_l1_enable_v(u32 r) +{ + return (r >> 8) & 0x1; +} #endif diff --git a/drivers/gpu/nvgpu/gp106/xve_gp106.c b/drivers/gpu/nvgpu/gp106/xve_gp106.c new file mode 100644 index 000000000..23a02fbd0 --- /dev/null +++ b/drivers/gpu/nvgpu/gp106/xve_gp106.c @@ -0,0 +1,623 @@ +/* + * Copyright (c) 2016, 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 + +#include "gk20a/gk20a.h" +#include "gm206/bios_gm206.h" +#include "gp106/xve_gp106.h" + +#include "gp106/hw_xp_gp106.h" +#include "gp106/hw_xve_gp106.h" + +/** + * Init a timer and place the timeout data in @timeout. + */ +static void init_timeout(u32 timeout_ms, u32 *timeout) +{ + *timeout = jiffies + msecs_to_jiffies(timeout_ms); +} + +/** + * Returns 1 if the current time is after @timeout i.e: the timer timed + * out. Returns 0 if the timer still has time left. + */ +static int check_timeout(u32 *timeout) +{ + unsigned long now = jiffies; + unsigned long timeout_l = (unsigned long)*timeout; + + if (time_after(now, timeout_l)) + return 1; + + return 0; +} + +static void xve_xve_writel_gp106(struct gk20a *g, u32 reg, u32 val) +{ + gk20a_writel(g, NV_PCFG + reg, val); +} + +static u32 xve_xve_readl_gp106(struct gk20a *g, u32 reg) +{ + return gk20a_readl(g, NV_PCFG + reg); +} + +/** + * Places one of: + * + * %GPU_XVE_SPEED_2P5 + * %GPU_XVE_SPEED_5P0 + * %GPU_XVE_SPEED_8P0 + * + * in the u32 pointed to by @xve_link_speed. If for some reason an unknown PCIe + * bus speed is detected then *@xve_link_speed is not touched and -ENODEV is + * returned. + */ +static int xve_get_speed_gp106(struct gk20a *g, u32 *xve_link_speed) +{ + u32 status; + u32 link_speed, real_link_speed = 0; + + status = g->ops.xve.xve_readl(g, xve_link_control_status_r()); + + link_speed = xve_link_control_status_link_speed_v(status); + + /* + * Can't use a switch statement becuase switch statements dont work with + * function calls. + */ + if (link_speed == xve_link_control_status_link_speed_link_speed_2p5_v()) + real_link_speed = GPU_XVE_SPEED_2P5; + if (link_speed == xve_link_control_status_link_speed_link_speed_5p0_v()) + real_link_speed = GPU_XVE_SPEED_5P0; + if (link_speed == xve_link_control_status_link_speed_link_speed_8p0_v()) + real_link_speed = GPU_XVE_SPEED_8P0; + + if (!real_link_speed) { + pr_warn("%s: Unknown PCIe bus speed!\n", __func__); + return -ENODEV; + } + + *xve_link_speed = real_link_speed; + return 0; +} + +/** + * Set the mask for L0s in the XVE. + * + * When @status is non-zero the mask for L0s is set which _disables_ L0s. When + * @status is zero L0s is no longer masked and may be enabled. + */ +static void set_xve_l0s_mask(struct gk20a *g, bool status) +{ + u32 xve_priv; + u32 status_bit = status ? 1 : 0; + + xve_priv = g->ops.xve.xve_readl(g, xve_priv_xv_r()); + + xve_priv = set_field(xve_priv, + xve_priv_xv_cya_l0s_enable_m(), + xve_priv_xv_cya_l0s_enable_f(status_bit)); + + g->ops.xve.xve_writel(g, xve_priv_xv_r(), xve_priv); +} + +/** + * Set the mask for L1 in the XVE. + * + * When @status is non-zero the mask for L1 is set which _disables_ L0s. When + * @status is zero L1 is no longer masked and may be enabled. + */ +static void set_xve_l1_mask(struct gk20a *g, int status) +{ + u32 xve_priv; + u32 status_bit = status ? 1 : 0; + + xve_priv = g->ops.xve.xve_readl(g, xve_priv_xv_r()); + + xve_priv = set_field(xve_priv, + xve_priv_xv_cya_l1_enable_m(), + xve_priv_xv_cya_l1_enable_f(status_bit)); + + g->ops.xve.xve_writel(g, xve_priv_xv_r(), xve_priv); +} + +/** + * When doing the speed change disable power saving features. + */ +static void disable_aspm_gp106(struct gk20a *g) +{ + u32 xve_priv; + + xve_priv = g->ops.xve.xve_readl(g, xve_priv_xv_r()); + + /* + * Store prior ASPM state so we can restore it later on. + */ + g->xve_l0s = xve_priv_xv_cya_l0s_enable_v(xve_priv); + g->xve_l1 = xve_priv_xv_cya_l1_enable_v(xve_priv); + + set_xve_l0s_mask(g, true); + set_xve_l1_mask(g, true); +} + +/** + * Restore the state saved by disable_aspm_gp106(). + */ +static void enable_aspm_gp106(struct gk20a *g) +{ + set_xve_l0s_mask(g, g->xve_l0s); + set_xve_l1_mask(g, g->xve_l1); +} + +/* + * Error checking is done in xve_set_speed_gp106. + */ +static int __do_xve_set_speed_gp106(struct gk20a *g, u32 next_link_speed) +{ + u32 current_link_speed, new_link_speed; + u32 dl_mgr, saved_dl_mgr; + u32 pl_link_config; + u32 link_control_status, link_speed_setting, link_width; + u32 timeout; + int attempts = 10, err_status = 0; + + g->ops.xve.get_speed(g, ¤t_link_speed); + xv_sc_dbg(PRE_CHANGE, "Executing PCIe link change."); + xv_sc_dbg(PRE_CHANGE, " Current speed: %s", + xve_speed_to_str(current_link_speed)); + xv_sc_dbg(PRE_CHANGE, " Next speed: %s", + xve_speed_to_str(next_link_speed)); + xv_sc_dbg(PRE_CHANGE, " PL_LINK_CONFIG: 0x%08x", + gk20a_readl(g, xp_pl_link_config_r(0))); + + xv_sc_dbg(DISABLE_ASPM, "Disabling ASPM..."); + disable_aspm_gp106(g); + xv_sc_dbg(DISABLE_ASPM, " Done!"); + + xv_sc_dbg(DL_SAFE_MODE, "Putting DL in safe mode..."); + saved_dl_mgr = gk20a_readl(g, xp_dl_mgr_r(0)); + + /* + * Put the DL in safe mode. + */ + dl_mgr = saved_dl_mgr; + dl_mgr |= xp_dl_mgr_safe_timing_f(1); + gk20a_writel(g, xp_dl_mgr_r(0), dl_mgr); + xv_sc_dbg(DL_SAFE_MODE, " Done!"); + + init_timeout(GPU_XVE_TIMEOUT_MS, &timeout); + + xv_sc_dbg(CHECK_LINK, "Checking for link idle..."); + while (1) { + pl_link_config = gk20a_readl(g, xp_pl_link_config_r(0)); + if ((xp_pl_link_config_ltssm_status_f(pl_link_config) == + xp_pl_link_config_ltssm_status_idle_v()) && + (xp_pl_link_config_ltssm_directive_f(pl_link_config) == + xp_pl_link_config_ltssm_directive_normal_operations_v())) + break; + + if (check_timeout(&timeout)) { + err_status = -ETIMEDOUT; + break; + } + } + + if (err_status == -ETIMEDOUT) + /* TODO: debug message. */ + goto done; + + xv_sc_dbg(CHECK_LINK, " Done"); + + xv_sc_dbg(LINK_SETTINGS, "Preparing next link settings"); + pl_link_config &= ~xp_pl_link_config_max_link_rate_m(); + switch (next_link_speed) { + case GPU_XVE_SPEED_2P5: + link_speed_setting = + xve_link_control_status_link_speed_link_speed_2p5_v(); + pl_link_config |= xp_pl_link_config_max_link_rate_f( + xp_pl_link_config_max_link_rate_2500_mtps_v()); + break; + case GPU_XVE_SPEED_5P0: + link_speed_setting = + xve_link_control_status_link_speed_link_speed_5p0_v(); + pl_link_config |= xp_pl_link_config_max_link_rate_f( + xp_pl_link_config_max_link_rate_5000_mtps_v()); + break; + case GPU_XVE_SPEED_8P0: + link_speed_setting = + xve_link_control_status_link_speed_link_speed_8p0_v(); + pl_link_config |= xp_pl_link_config_max_link_rate_f( + xp_pl_link_config_max_link_rate_8000_mtps_v()); + break; + default: + BUG(); /* Should never be hit. */ + } + + link_control_status = + g->ops.xve.xve_readl(g, xve_link_control_status_r()); + link_width = xve_link_control_status_link_width_v(link_control_status); + + pl_link_config &= ~xp_pl_link_config_target_tx_width_m(); + + /* Can't use a switch due to oddities in register definitions. */ + if (link_width == xve_link_control_status_link_width_x1_v()) + pl_link_config |= xp_pl_link_config_target_tx_width_f( + xp_pl_link_config_target_tx_width_x1_v()); + else if (link_width == xve_link_control_status_link_width_x2_v()) + pl_link_config |= xp_pl_link_config_target_tx_width_f( + xp_pl_link_config_target_tx_width_x2_v()); + else if (link_width == xve_link_control_status_link_width_x4_v()) + pl_link_config |= xp_pl_link_config_target_tx_width_f( + xp_pl_link_config_target_tx_width_x4_v()); + else if (link_width == xve_link_control_status_link_width_x8_v()) + pl_link_config |= xp_pl_link_config_target_tx_width_f( + xp_pl_link_config_target_tx_width_x8_v()); + else if (link_width == xve_link_control_status_link_width_x16_v()) + pl_link_config |= xp_pl_link_config_target_tx_width_f( + xp_pl_link_config_target_tx_width_x16_v()); + else + BUG(); + + xv_sc_dbg(LINK_SETTINGS, " pl_link_config = 0x%08x", pl_link_config); + xv_sc_dbg(LINK_SETTINGS, " Done"); + + xv_sc_dbg(EXEC_CHANGE, "Running link speed change..."); + + init_timeout(GPU_XVE_TIMEOUT_MS, &timeout); + while (1) { + gk20a_writel(g, xp_pl_link_config_r(0), pl_link_config); + if (pl_link_config == + gk20a_readl(g, xp_pl_link_config_r(0))) + break; + + if (check_timeout(&timeout)) { + err_status = -ETIMEDOUT; + break; + } + } + + if (err_status == -ETIMEDOUT) + goto done; + + xv_sc_dbg(EXEC_CHANGE, " Wrote PL_LINK_CONFIG."); + + pl_link_config = gk20a_readl(g, xp_pl_link_config_r(0)); + + do { + pl_link_config = set_field(pl_link_config, + xp_pl_link_config_ltssm_directive_m(), + xp_pl_link_config_ltssm_directive_f( + xp_pl_link_config_ltssm_directive_change_speed_v())); + + xv_sc_dbg(EXEC_CHANGE, " Executing change (0x%08x)!", + pl_link_config); + gk20a_writel(g, xp_pl_link_config_r(0), pl_link_config); + + /* + * Read NV_XP_PL_LINK_CONFIG until the link has swapped to + * the target speed. + */ + init_timeout(GPU_XVE_TIMEOUT_MS, &timeout); + while (1) { + pl_link_config = gk20a_readl(g, xp_pl_link_config_r(0)); + if (pl_link_config != 0xfffffff && + (xp_pl_link_config_ltssm_status_f(pl_link_config) == + xp_pl_link_config_ltssm_status_idle_v()) && + (xp_pl_link_config_ltssm_directive_f(pl_link_config) == + xp_pl_link_config_ltssm_directive_normal_operations_v())) + break; + + if (check_timeout(&timeout)) { + err_status = -ETIMEDOUT; + xv_sc_dbg(EXEC_CHANGE, " timeout; pl_link_config = 0x%x", + pl_link_config); + break; + } + } + + xv_sc_dbg(EXEC_CHANGE, " Change done... Checking status"); + + if (pl_link_config == 0xffffffff) { + WARN(1, "GPU fell of PCI bus!?"); + + /* + * The rest of the driver is probably about to + * explode... + */ + BUG(); + } + + link_control_status = + g->ops.xve.xve_readl(g, xve_link_control_status_r()); + xv_sc_dbg(EXEC_CHANGE, " target %d vs current %d", + link_speed_setting, + xve_link_control_status_link_speed_v(link_control_status)); + + if (err_status == -ETIMEDOUT) + xv_sc_dbg(EXEC_CHANGE, " Oops timed out?"); + } while (attempts-- > 0 && + link_speed_setting != + xve_link_control_status_link_speed_v(link_control_status)); + + xv_sc_dbg(EXEC_VERIF, "Verifying speed change..."); + + /* + * Check that the new link speed is actually active. If we failed to + * change to the new link speed then return to the link speed setting + * pre-speed change. + */ + new_link_speed = xve_link_control_status_link_speed_v( + link_control_status); + if (link_speed_setting != new_link_speed) { + u32 link_config = gk20a_readl(g, xp_pl_link_config_r(0)); + + xv_sc_dbg(EXEC_VERIF, " Current and target speeds mismatch!"); + xv_sc_dbg(EXEC_VERIF, " LINK_CONTROL_STATUS: 0x%08x", + g->ops.xve.xve_readl(g, xve_link_control_status_r())); + xv_sc_dbg(EXEC_VERIF, " Link speed is %s - should be %s", + xve_speed_to_str(new_link_speed), + xve_speed_to_str(link_speed_setting)); + + link_config &= ~xp_pl_link_config_max_link_rate_m(); + if (new_link_speed == + xve_link_control_status_link_speed_link_speed_2p5_v()) + link_config |= xp_pl_link_config_max_link_rate_f( + xp_pl_link_config_max_link_rate_2500_mtps_v()); + else if (new_link_speed == + xve_link_control_status_link_speed_link_speed_5p0_v()) + link_config |= xp_pl_link_config_max_link_rate_f( + xp_pl_link_config_max_link_rate_5000_mtps_v()); + else if (new_link_speed == + xve_link_control_status_link_speed_link_speed_8p0_v()) + link_config |= xp_pl_link_config_max_link_rate_f( + xp_pl_link_config_max_link_rate_8000_mtps_v()); + else + link_config |= xp_pl_link_config_max_link_rate_f( + xp_pl_link_config_max_link_rate_2500_mtps_v()); + + gk20a_writel(g, xp_pl_link_config_r(0), link_config); + err_status = -ENODEV; + } else { + xv_sc_dbg(EXEC_VERIF, " Current and target speeds match!"); + err_status = 0; + } + +done: + /* Restore safe timings. */ + xv_sc_dbg(CLEANUP, "Restoring saved DL settings..."); + gk20a_writel(g, xp_dl_mgr_r(0), saved_dl_mgr); + xv_sc_dbg(CLEANUP, " Done"); + + xv_sc_dbg(CLEANUP, "Re-enabling ASPM settings..."); + enable_aspm_gp106(g); + xv_sc_dbg(CLEANUP, " Done"); + + return err_status; +} + +/** + * Sets the PCIe link speed to @xve_link_speed which must be one of: + * + * %GPU_XVE_SPEED_2P5 + * %GPU_XVE_SPEED_5P0 + * %GPU_XVE_SPEED_8P0 + * + * If an error is encountered an appropriate error will be returned. + */ +static int xve_set_speed_gp106(struct gk20a *g, u32 next_link_speed) +{ + u32 current_link_speed; + int err; + + if ((next_link_speed & GPU_XVE_SPEED_MASK) == 0) + return -EINVAL; + + err = g->ops.xve.get_speed(g, ¤t_link_speed); + if (err) + return err; + + /* No-op. */ + if (current_link_speed == next_link_speed) + return 0; + + return __do_xve_set_speed_gp106(g, next_link_speed); +} + +/** + * Places a bitmask of available speeds for gp106 in @speed_mask. + */ +static void xve_available_speeds_gp106(struct gk20a *g, u32 *speed_mask) +{ + *speed_mask = GPU_XVE_SPEED_2P5 | GPU_XVE_SPEED_5P0; +} + +static ssize_t xve_link_speed_write(struct file *filp, + const char __user *buff, + size_t len, loff_t *off) +{ + struct gk20a *g = ((struct seq_file *)filp->private_data)->private; + char kbuff[16]; + u32 buff_size, check_len; + u32 link_speed = 0; + int ret; + + buff_size = min_t(size_t, 16, len); + + memset(kbuff, 0, 16); + if (copy_from_user(kbuff, buff, buff_size)) + return -EFAULT; + + check_len = strlen("Gen1"); + if (strncmp(kbuff, "Gen1", check_len) == 0) + link_speed = GPU_XVE_SPEED_2P5; + else if (strncmp(kbuff, "Gen2", check_len) == 0) + link_speed = GPU_XVE_SPEED_5P0; + else if (strncmp(kbuff, "Gen3", check_len) == 0) + link_speed = GPU_XVE_SPEED_8P0; + else + gk20a_err(g->dev, "%s: Unknown PCIe speed: %s\n", + __func__, kbuff); + + if (!link_speed) + return -EINVAL; + + /* Brief pause... To help rate limit this. */ + msleep(250); + + /* + * And actually set the speed. Yay. + */ + ret = g->ops.xve.set_speed(g, link_speed); + if (ret) + return ret; + + return len; +} + +static int xve_link_speed_show(struct seq_file *s, void *unused) +{ + struct gk20a *g = s->private; + u32 speed; + int err; + + err = g->ops.xve.get_speed(g, &speed); + if (err) + return err; + + seq_printf(s, "Current PCIe speed:\n %s\n", xve_speed_to_str(speed)); + + return 0; +} + +static int xve_link_speed_open(struct inode *inode, struct file *file) +{ + return single_open(file, xve_link_speed_show, inode->i_private); +} + +static const struct file_operations xve_link_speed_fops = { + .open = xve_link_speed_open, + .read = seq_read, + .write = xve_link_speed_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static int xve_available_speeds_show(struct seq_file *s, void *unused) +{ + struct gk20a *g = s->private; + u32 available_speeds; + + g->ops.xve.available_speeds(g, &available_speeds); + + seq_puts(s, "Available PCIe bus speeds:\n"); + if (available_speeds & GPU_XVE_SPEED_2P5) + seq_puts(s, " Gen1\n"); + if (available_speeds & GPU_XVE_SPEED_5P0) + seq_puts(s, " Gen2\n"); + if (available_speeds & GPU_XVE_SPEED_8P0) + seq_puts(s, " Gen3\n"); + + return 0; +} + +static int xve_available_speeds_open(struct inode *inode, struct file *file) +{ + return single_open(file, xve_available_speeds_show, inode->i_private); +} + +static const struct file_operations xve_available_speeds_fops = { + .open = xve_available_speeds_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int xve_link_control_status_show(struct seq_file *s, void *unused) +{ + struct gk20a *g = s->private; + u32 link_status; + + link_status = g->ops.xve.xve_readl(g, xve_link_control_status_r()); + seq_printf(s, "0x%08x\n", link_status); + + return 0; +} + +static int xve_link_control_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, xve_link_control_status_show, inode->i_private); +} + +static const struct file_operations xve_link_control_status_fops = { + .open = xve_link_control_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int xve_sw_init_gp106(struct device *dev) +{ + int err = -ENODEV; +#ifdef CONFIG_DEBUG_FS + struct gk20a *g = get_gk20a(dev); + struct gk20a_platform *plat = gk20a_get_platform(dev); + struct dentry *gpu_root = plat->debugfs; + + g->debugfs_xve = debugfs_create_dir("xve", gpu_root); + if (IS_ERR_OR_NULL(g->debugfs_xve)) + goto fail; + + /* + * These are just debug nodes. If they fail to get made it's not worth + * worrying the higher level SW. + */ + debugfs_create_file("link_speed", S_IRUGO, + g->debugfs_xve, g, + &xve_link_speed_fops); + debugfs_create_file("available_speeds", S_IRUGO, + g->debugfs_xve, g, + &xve_available_speeds_fops); + debugfs_create_file("link_control_status", S_IRUGO, + g->debugfs_xve, g, + &xve_link_control_status_fops); + + err = 0; +fail: + return err; +#else + return err; +#endif +} + +/* + * Init the HAL functions and what not. xve_sw_init_gp106() is for initializing + * all the other stuff like debugfs nodes, etc. + */ +int gp106_init_xve_ops(struct gpu_ops *gops) +{ + gops->xve.sw_init = xve_sw_init_gp106; + gops->xve.get_speed = xve_get_speed_gp106; + gops->xve.set_speed = xve_set_speed_gp106; + gops->xve.available_speeds = xve_available_speeds_gp106; + gops->xve.xve_readl = xve_xve_readl_gp106; + gops->xve.xve_writel = xve_xve_writel_gp106; + + return 0; +} diff --git a/drivers/gpu/nvgpu/gp106/xve_gp106.h b/drivers/gpu/nvgpu/gp106/xve_gp106.h new file mode 100644 index 000000000..65c75bf03 --- /dev/null +++ b/drivers/gpu/nvgpu/gp106/xve_gp106.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016, 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 . + */ + +#ifndef __XVE_GP106_H__ +#define __XVE_GP106_H__ + +#include "gk20a/gk20a.h" + +int gp106_init_xve_ops(struct gpu_ops *gops); + +/* + * Best guess for a reasonable timeout. + */ +#define GPU_XVE_TIMEOUT_MS 500 + +/* + * For the available speeds bitmap. + */ +#define GPU_XVE_SPEED_2P5 (1 << 0) +#define GPU_XVE_SPEED_5P0 (1 << 1) +#define GPU_XVE_SPEED_8P0 (1 << 2) +#define GPU_XVE_NR_SPEEDS 3 + +#define GPU_XVE_SPEED_MASK (GPU_XVE_SPEED_2P5 | \ + GPU_XVE_SPEED_5P0 | \ + GPU_XVE_SPEED_8P0) + +/* + * The HW uses a 2 bit field where speed is defined by a number: + * + * NV_XVE_LINK_CONTROL_STATUS_LINK_SPEED_2P5 = 1 + * NV_XVE_LINK_CONTROL_STATUS_LINK_SPEED_5P0 = 2 + * NV_XVE_LINK_CONTROL_STATUS_LINK_SPEED_8P0 = 3 + * + * This isn't ideal for a bitmap with available speeds. So the external + * APIs think about speeds as a bit in a bitmap and this function converts + * from those bits to the actual HW speed setting. + * + * @speed_bit must have only 1 bit set and must be one of the 3 available + * HW speeds. Not all chips support all speeds so use available_speeds() to + * determine what a given chip supports. + */ +static inline u32 xve_speed_to_hw_speed_setting(u32 speed_bit) +{ + if (!speed_bit || + !is_power_of_2(speed_bit) || + !(speed_bit & GPU_XVE_SPEED_MASK)) + return -EINVAL; + + return ilog2(speed_bit) + 1; +} + +static inline const char *xve_speed_to_str(u32 speed) +{ + if (!speed || !is_power_of_2(speed) || + !(speed & GPU_XVE_SPEED_MASK)) + return "Unknown ???"; + + return speed & GPU_XVE_SPEED_2P5 ? "Gen1" : + speed & GPU_XVE_SPEED_5P0 ? "Gen2" : + speed & GPU_XVE_SPEED_8P0 ? "Gen3" : + "Unknown ???"; +} + +/* + * Debugging for the speed change. + */ +enum xv_speed_change_steps { + PRE_CHANGE = 0, + DISABLE_ASPM, + DL_SAFE_MODE, + CHECK_LINK, + LINK_SETTINGS, + EXEC_CHANGE, + EXEC_VERIF, + CLEANUP +}; + +#define xv_dbg(fmt, args...) \ + gk20a_dbg(gpu_dbg_xv, fmt, ##args) + +#define xv_sc_dbg(step, fmt, args...) \ + xv_dbg("[%d] %15s | " fmt, step, __stringify(step), ##args) + + +#endif