/* * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE.
*/
/* * base.n is now the *integer* part of the N factor. * sdm_din contains n's decimal part.
*/ struct gm20b_pll { struct gk20a_pll base;
u32 sdm_din;
};
/* * Determine DFS_COEFF for the requested voltage. Always select external * calibration override equal to the voltage, and set maximum detection * limit "0" (to make sure that PLL output remains under F/V curve when * voltage increases).
*/ staticvoid
gm20b_dvfs_calc_det_coeff(struct gm20b_clk *clk, s32 uv, struct gm20b_clk_dvfs *dvfs)
{ struct nvkm_subdev *subdev = &clk->base.base.subdev; conststruct gm20b_clk_dvfs_params *p = clk->dvfs_params;
u32 coeff; /* Work with mv as uv would likely trigger an overflow */
s32 mv = DIV_ROUND_CLOSEST(uv, 1000);
/* * Solve equation for integer and fractional part of the effective NDIV: * * n_eff = n_int + 1/2 + (SDM_DIN / 2^(SDM_DIN_RANGE + 1)) + * (DVFS_COEFF * DVFS_DET_DELTA) / 2^DFS_DET_RANGE * * The SDM_DIN LSB is finally shifted out, since it is not accessible by sw.
*/ staticvoid
gm20b_dvfs_calc_ndiv(struct gm20b_clk *clk, u32 n_eff, u32 *n_int, u32 *sdm_din)
{ struct nvkm_subdev *subdev = &clk->base.base.subdev; conststruct gk20a_clk_pllg_params *p = clk->base.params;
u32 n;
s32 det_delta;
u32 rem, rem_range;
/* calculate current ext_cal and subtract previous one */
det_delta = DIV_ROUND_CLOSEST(((s32)clk->uv) - clk->uvdet_offs,
clk->uvdet_slope);
det_delta -= clk->dvfs.dfs_ext_cal;
det_delta = min(det_delta, clk->dvfs.dfs_det_max);
det_delta *= clk->dvfs.dfs_coeff;
/* integer part of n */
n = (n_eff << DFS_DET_RANGE) - det_delta; /* should never happen! */ if (n <= 0) {
nvkm_error(subdev, "ndiv <= 0 - setting to 1...\n");
n = 1 << DFS_DET_RANGE;
} if (n >> DFS_DET_RANGE > p->max_n) {
nvkm_error(subdev, "ndiv > max_n - setting to max_n...\n");
n = p->max_n << DFS_DET_RANGE;
}
*n_int = n >> DFS_DET_RANGE;
/* fractional part of n */
rem = ((u32)n) & MASK(DFS_DET_RANGE);
rem_range = SDM_DIN_RANGE + 1 - DFS_DET_RANGE; /* subtract 2^SDM_DIN_RANGE to account for the 1/2 of the equation */
rem = (rem << rem_range) - BIT(SDM_DIN_RANGE); /* lose 8 LSB and clip - sdm_din only keeps the most significant byte */
*sdm_din = (rem >> BITS_PER_BYTE) & MASK(GPCPLL_CFG2_SDM_DIN_WIDTH);
/* calculate the new n_int/sdm_din for this n/uv */
gm20b_dvfs_calc_ndiv(clk, n, &n_int, &sdm_din);
/* get old coefficients */
gm20b_pllg_read_mnp(clk, &pll); /* do nothing if NDIV is the same */ if (n_int == pll.base.n && sdm_din == pll.sdm_din) return 0;
/* new ndiv ready for ramp */ /* in DVFS mode SDM is updated via "new" field */
nvkm_mask(device, GPCPLL_CFG2, GPCPLL_CFG2_SDM_DIN_NEW_MASK,
sdm_din << GPCPLL_CFG2_SDM_DIN_NEW_SHIFT);
pll.base.n = n_int;
udelay(1);
gk20a_pllg_write_mnp(&clk->base, &pll.base);
/* dynamic ramp to new ndiv */
udelay(1);
nvkm_mask(device, GPCPLL_NDIV_SLOWDOWN,
BIT(GPCPLL_NDIV_SLOWDOWN_EN_DYNRAMP_SHIFT),
BIT(GPCPLL_NDIV_SLOWDOWN_EN_DYNRAMP_SHIFT));
/* wait for ramping to complete */ if (nvkm_wait_usec(device, 500, GPC_BCAST_NDIV_SLOWDOWN_DEBUG,
GPC_BCAST_NDIV_SLOWDOWN_DEBUG_PLL_DYNRAMP_DONE_SYNCED_MASK,
GPC_BCAST_NDIV_SLOWDOWN_DEBUG_PLL_DYNRAMP_DONE_SYNCED_MASK) < 0)
ret = -ETIMEDOUT;
/* In DVFS mode lock cannot be used - so just delay */
udelay(40);
/* set SYNC_MODE for glitchless switch out of bypass */
nvkm_mask(device, GPCPLL_CFG, GPCPLL_CFG_SYNC_MODE,
GPCPLL_CFG_SYNC_MODE);
nvkm_rd32(device, GPCPLL_CFG);
/* switch to VCO mode */
nvkm_mask(device, SEL_VCO, BIT(SEL_VCO_GPC2CLK_OUT_SHIFT),
BIT(SEL_VCO_GPC2CLK_OUT_SHIFT));
/* need full sequence if clock not enabled yet */ if (!gk20a_pllg_is_enabled(&clk->base))
pdiv_only = false;
/* split VCO-to-bypass jump in half by setting out divider 1:2 */
nvkm_mask(device, GPC2CLK_OUT, GPC2CLK_OUT_VCODIV_MASK,
GPC2CLK_OUT_VCODIV2 << GPC2CLK_OUT_VCODIV_SHIFT); /* Intentional 2nd write to assure linear divider operation */
nvkm_mask(device, GPC2CLK_OUT, GPC2CLK_OUT_VCODIV_MASK,
GPC2CLK_OUT_VCODIV2 << GPC2CLK_OUT_VCODIV_SHIFT);
nvkm_rd32(device, GPC2CLK_OUT);
udelay(2);
if (pdiv_only) {
u32 old = cur_pll.base.pl;
u32 new = pll->pl;
/* * we can do a glitchless transition only if the old and new PL * parameters share at least one bit set to 1. If this is not * the case, calculate and program an interim PL that will allow * us to respect that rule.
*/ if ((old & new) == 0) {
cur_pll.base.pl = min(old | BIT(ffs(new) - 1), new | BIT(ffs(old) - 1));
gk20a_pllg_write_mnp(&clk->base, &cur_pll.base);
}
cur_pll.base.pl = new;
gk20a_pllg_write_mnp(&clk->base, &cur_pll.base);
} else { /* disable before programming if more than pdiv changes */
gm20b_pllg_disable(clk);
if (gk20a_pllg_is_enabled(&clk->base)) {
gk20a_pllg_read_mnp(&clk->base, &cur_pll);
/* just do NDIV slide if there is no change to M and PL */ if (pll->m == cur_pll.m && pll->pl == cur_pll.pl) return gm20b_pllg_slide(clk, pll->n);
/* slide down to current NDIV_LO */
cur_pll.n = gk20a_pllg_n_lo(&clk->base, &cur_pll);
ret = gm20b_pllg_slide(clk, cur_pll.n); if (ret) return ret;
}
/* program MNP with the new clock parameters and new NDIV_LO */
cur_pll = *pll;
cur_pll.n = gk20a_pllg_n_lo(&clk->base, &cur_pll);
ret = gm20b_pllg_program_mnp(clk, &cur_pll); if (ret) return ret;
/* slide up to new NDIV */ return gm20b_pllg_slide(clk, pll->n);
}
val = nvkm_rd32(device, GPCPLL_DVFS1); if (!(val & BIT(25))) { /* Use external value to overwrite calibration value */
val |= BIT(25) | BIT(16);
nvkm_wr32(device, GPCPLL_DVFS1, val);
}
}
/* No change in DVFS settings? */ if (clk->uv == clk->new_uv) goto prog;
/* * Interim step for changing DVFS detection settings: low enough * frequency to be safe at DVFS coeff = 0. * * 1. If voltage is increasing: * - safe frequency target matches the lowest - old - frequency * - DVFS settings are still old * - Voltage already increased to new level by volt, but maximum * detection limit assures PLL output remains under F/V curve * * 2. If voltage is decreasing: * - safe frequency target matches the lowest - new - frequency * - DVFS settings are still old * - Voltage is also old, it will be lowered by volt afterwards * * Interim step can be skipped if old frequency is below safe minimum, * i.e., it is low enough to be safe at any voltage in operating range * with zero DVFS coefficient.
*/
cur_freq = nvkm_clk_read(&clk->base.base, nv_clk_src_gpc); if (cur_freq > clk->safe_fmax_vmin) { struct gk20a_pll pll_safe;
if (clk->uv < clk->new_uv) /* voltage will raise: safe frequency is current one */
pll_safe = clk->base.pll; else /* voltage will drop: safe frequency is new one */
pll_safe = clk->new_pll;
gm20b_dvfs_calc_safe_pll(clk, &pll_safe);
ret = gm20b_pllg_program_mnp_slide(clk, &pll_safe); if (ret) return ret;
}
/* * DVFS detection settings transition: * - Set DVFS coefficient zero * - Set calibration level to new voltage * - Set DVFS coefficient to match new voltage
*/
gm20b_dvfs_program_coeff(clk, 0);
gm20b_dvfs_program_ext_cal(clk, clk->new_dvfs.dfs_ext_cal);
gm20b_dvfs_program_coeff(clk, clk->new_dvfs.dfs_coeff);
gm20b_dvfs_program_dfs_detection(clk, &clk->new_dvfs);
/* * If calibration parameters are not fused, start internal calibration, * wait for completion, and use results along with default slope to * calculate ADC offset during boot.
*/
nvkm_mask(device, GPCPLL_DVFS1, GPCPLL_DVFS1_EN_DFS_CAL_BIT,
GPCPLL_DVFS1_EN_DFS_CAL_BIT);
/* Wait for internal calibration done (spec < 2us). */
ret = nvkm_wait_usec(device, 10, GPCPLL_DVFS1,
GPCPLL_DVFS1_DFS_CAL_DONE_BIT,
GPCPLL_DVFS1_DFS_CAL_DONE_BIT); if (ret < 0) {
nvkm_error(subdev, "GPCPLL calibration timeout\n"); return -ETIMEDOUT;
}
data = nvkm_rd32(device, GPCPLL_CFG3) >>
GPCPLL_CFG3_PLL_DFS_TESTOUT_SHIFT;
data &= MASK(GPCPLL_CFG3_PLL_DFS_TESTOUT_WIDTH);
clk->uvdet_slope = ADC_SLOPE_UV;
clk->uvdet_offs = ((s32)clk->uv) - data * ADC_SLOPE_UV;
/* Set the global bypass control to VCO */
nvkm_mask(device, BYPASSCTRL_SYS,
MASK(BYPASSCTRL_SYS_GPCPLL_WIDTH) << BYPASSCTRL_SYS_GPCPLL_SHIFT,
0);
ret = gk20a_clk_setup_slide(clk); if (ret) return ret;
/* If not fused, set RAM SVOP PDP data 0x2, and enable fuse override */
data = nvkm_rd32(device, 0x021944); if (!(data & 0x3)) {
data |= 0x2;
nvkm_wr32(device, 0x021944, data);
data = nvkm_rd32(device, 0x021948);
data |= 0x1;
nvkm_wr32(device, 0x021948, data);
}
/* Disable idle slow down */
nvkm_mask(device, 0x20160, 0x003f0000, 0x0);
staticint
gm20b_clk_init_safe_fmax(struct gm20b_clk *clk)
{ struct nvkm_subdev *subdev = &clk->base.base.subdev; struct nvkm_volt *volt = subdev->device->volt; struct nvkm_pstate *pstates = clk->base.base.func->pstates; int nr_pstates = clk->base.base.func->nr_pstates; int vmin, id = 0;
u32 fmax = 0; int i;
/* find lowest voltage we can use */
vmin = volt->vid[0].uv; for (i = 1; i < volt->vid_nr; i++) { if (volt->vid[i].uv <= vmin) {
vmin = volt->vid[i].uv;
id = volt->vid[i].vid;
}
}
/* find max frequency at this voltage */ for (i = 0; i < nr_pstates; i++) if (pstates[i].base.voltage == id)
fmax = max(fmax,
pstates[i].base.domain[nv_clk_src_gpc]);
if (!fmax) {
nvkm_error(subdev, "failed to evaluate safe fmax\n"); return -EINVAL;
}
/* we are safe at 90% of the max frequency */
clk->safe_fmax_vmin = fmax * (100 - 10) / 100;
nvkm_debug(subdev, "safe fmax @ vmin = %u Khz\n", clk->safe_fmax_vmin);
return 0;
}
int
gm20b_clk_new(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, struct nvkm_clk **pclk)
{ struct nvkm_device_tegra *tdev = device->func->tegra(device); struct gm20b_clk *clk; struct nvkm_subdev *subdev; struct gk20a_clk_pllg_params *clk_params; int ret;
/* Speedo 0 GPUs cannot use noise-aware PLL */ if (tdev->gpu_speedo_id == 0) return gm20b_clk_new_speedo0(device, type, inst, pclk);
/* duplicate the clock parameters since we will patch them below */
clk_params = (void *) (clk + 1);
*clk_params = gm20b_pllg_params;
ret = gk20a_clk_ctor(device, type, inst, &gm20b_clk, clk_params, &clk->base); if (ret) return ret;
/* * NAPLL can only work with max_u, clamp the m range so * gk20a_pllg_calc_mnp always uses it
*/
clk_params->max_m = clk_params->min_m = DIV_ROUND_UP(clk_params->max_u,
(clk->base.parent_rate / KHZ)); if (clk_params->max_m == 0) {
nvkm_warn(subdev, "cannot use NAPLL, using legacy clock...\n");
kfree(clk); return gm20b_clk_new_speedo0(device, type, inst, pclk);
}
ret = gm20b_clk_init_fused_params(clk); /* * we will calibrate during init - should never happen on * prod parts
*/ if (ret)
nvkm_warn(subdev, "no fused calibration parameters\n");
ret = gm20b_clk_init_safe_fmax(clk); if (ret) return ret;
return 0;
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.