| /* |
| * 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. |
| */ |
| |
| #include <subdev/clk.h> |
| #include <subdev/volt.h> |
| #include <subdev/timer.h> |
| #include <core/device.h> |
| #include <core/tegra.h> |
| |
| #include "priv.h" |
| #include "gk20a.h" |
| |
| #define GPCPLL_CFG_SYNC_MODE BIT(2) |
| |
| #define BYPASSCTRL_SYS (SYS_GPCPLL_CFG_BASE + 0x340) |
| #define BYPASSCTRL_SYS_GPCPLL_SHIFT 0 |
| #define BYPASSCTRL_SYS_GPCPLL_WIDTH 1 |
| |
| #define GPCPLL_CFG2_SDM_DIN_SHIFT 0 |
| #define GPCPLL_CFG2_SDM_DIN_WIDTH 8 |
| #define GPCPLL_CFG2_SDM_DIN_MASK \ |
| (MASK(GPCPLL_CFG2_SDM_DIN_WIDTH) << GPCPLL_CFG2_SDM_DIN_SHIFT) |
| #define GPCPLL_CFG2_SDM_DIN_NEW_SHIFT 8 |
| #define GPCPLL_CFG2_SDM_DIN_NEW_WIDTH 15 |
| #define GPCPLL_CFG2_SDM_DIN_NEW_MASK \ |
| (MASK(GPCPLL_CFG2_SDM_DIN_NEW_WIDTH) << GPCPLL_CFG2_SDM_DIN_NEW_SHIFT) |
| #define GPCPLL_CFG2_SETUP2_SHIFT 16 |
| #define GPCPLL_CFG2_PLL_STEPA_SHIFT 24 |
| |
| #define GPCPLL_DVFS0 (SYS_GPCPLL_CFG_BASE + 0x10) |
| #define GPCPLL_DVFS0_DFS_COEFF_SHIFT 0 |
| #define GPCPLL_DVFS0_DFS_COEFF_WIDTH 7 |
| #define GPCPLL_DVFS0_DFS_COEFF_MASK \ |
| (MASK(GPCPLL_DVFS0_DFS_COEFF_WIDTH) << GPCPLL_DVFS0_DFS_COEFF_SHIFT) |
| #define GPCPLL_DVFS0_DFS_DET_MAX_SHIFT 8 |
| #define GPCPLL_DVFS0_DFS_DET_MAX_WIDTH 7 |
| #define GPCPLL_DVFS0_DFS_DET_MAX_MASK \ |
| (MASK(GPCPLL_DVFS0_DFS_DET_MAX_WIDTH) << GPCPLL_DVFS0_DFS_DET_MAX_SHIFT) |
| |
| #define GPCPLL_DVFS1 (SYS_GPCPLL_CFG_BASE + 0x14) |
| #define GPCPLL_DVFS1_DFS_EXT_DET_SHIFT 0 |
| #define GPCPLL_DVFS1_DFS_EXT_DET_WIDTH 7 |
| #define GPCPLL_DVFS1_DFS_EXT_STRB_SHIFT 7 |
| #define GPCPLL_DVFS1_DFS_EXT_STRB_WIDTH 1 |
| #define GPCPLL_DVFS1_DFS_EXT_CAL_SHIFT 8 |
| #define GPCPLL_DVFS1_DFS_EXT_CAL_WIDTH 7 |
| #define GPCPLL_DVFS1_DFS_EXT_SEL_SHIFT 15 |
| #define GPCPLL_DVFS1_DFS_EXT_SEL_WIDTH 1 |
| #define GPCPLL_DVFS1_DFS_CTRL_SHIFT 16 |
| #define GPCPLL_DVFS1_DFS_CTRL_WIDTH 12 |
| #define GPCPLL_DVFS1_EN_SDM_SHIFT 28 |
| #define GPCPLL_DVFS1_EN_SDM_WIDTH 1 |
| #define GPCPLL_DVFS1_EN_SDM_BIT BIT(28) |
| #define GPCPLL_DVFS1_EN_DFS_SHIFT 29 |
| #define GPCPLL_DVFS1_EN_DFS_WIDTH 1 |
| #define GPCPLL_DVFS1_EN_DFS_BIT BIT(29) |
| #define GPCPLL_DVFS1_EN_DFS_CAL_SHIFT 30 |
| #define GPCPLL_DVFS1_EN_DFS_CAL_WIDTH 1 |
| #define GPCPLL_DVFS1_EN_DFS_CAL_BIT BIT(30) |
| #define GPCPLL_DVFS1_DFS_CAL_DONE_SHIFT 31 |
| #define GPCPLL_DVFS1_DFS_CAL_DONE_WIDTH 1 |
| #define GPCPLL_DVFS1_DFS_CAL_DONE_BIT BIT(31) |
| |
| #define GPC_BCAST_GPCPLL_DVFS2 (GPC_BCAST_GPCPLL_CFG_BASE + 0x20) |
| #define GPC_BCAST_GPCPLL_DVFS2_DFS_EXT_STROBE_BIT BIT(16) |
| |
| #define GPCPLL_CFG3_PLL_DFS_TESTOUT_SHIFT 24 |
| #define GPCPLL_CFG3_PLL_DFS_TESTOUT_WIDTH 7 |
| |
| #define DFS_DET_RANGE 6 /* -2^6 ... 2^6-1 */ |
| #define SDM_DIN_RANGE 12 /* -2^12 ... 2^12-1 */ |
| |
| struct gm20b_clk_dvfs_params { |
| s32 coeff_slope; |
| s32 coeff_offs; |
| u32 vco_ctrl; |
| }; |
| |
| static const struct gm20b_clk_dvfs_params gm20b_dvfs_params = { |
| .coeff_slope = -165230, |
| .coeff_offs = 214007, |
| .vco_ctrl = 0x7 << 3, |
| }; |
| |
| /* |
| * 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; |
| }; |
| |
| struct gm20b_clk_dvfs { |
| u32 dfs_coeff; |
| s32 dfs_det_max; |
| s32 dfs_ext_cal; |
| }; |
| |
| struct gm20b_clk { |
| /* currently applied parameters */ |
| struct gk20a_clk base; |
| struct gm20b_clk_dvfs dvfs; |
| u32 uv; |
| |
| /* new parameters to apply */ |
| struct gk20a_pll new_pll; |
| struct gm20b_clk_dvfs new_dvfs; |
| u32 new_uv; |
| |
| const struct gm20b_clk_dvfs_params *dvfs_params; |
| |
| /* fused parameters */ |
| s32 uvdet_slope; |
| s32 uvdet_offs; |
| |
| /* safe frequency we can use at minimum voltage */ |
| u32 safe_fmax_vmin; |
| }; |
| #define gm20b_clk(p) container_of((gk20a_clk(p)), struct gm20b_clk, base) |
| |
| static u32 pl_to_div(u32 pl) |
| { |
| return pl; |
| } |
| |
| static u32 div_to_pl(u32 div) |
| { |
| return div; |
| } |
| |
| static const struct gk20a_clk_pllg_params gm20b_pllg_params = { |
| .min_vco = 1300000, .max_vco = 2600000, |
| .min_u = 12000, .max_u = 38400, |
| .min_m = 1, .max_m = 255, |
| .min_n = 8, .max_n = 255, |
| .min_pl = 1, .max_pl = 31, |
| }; |
| |
| static void |
| gm20b_pllg_read_mnp(struct gm20b_clk *clk, struct gm20b_pll *pll) |
| { |
| struct nvkm_subdev *subdev = &clk->base.base.subdev; |
| struct nvkm_device *device = subdev->device; |
| u32 val; |
| |
| gk20a_pllg_read_mnp(&clk->base, &pll->base); |
| val = nvkm_rd32(device, GPCPLL_CFG2); |
| pll->sdm_din = (val >> GPCPLL_CFG2_SDM_DIN_SHIFT) & |
| MASK(GPCPLL_CFG2_SDM_DIN_WIDTH); |
| } |
| |
| static void |
| gm20b_pllg_write_mnp(struct gm20b_clk *clk, const struct gm20b_pll *pll) |
| { |
| struct nvkm_device *device = clk->base.base.subdev.device; |
| |
| nvkm_mask(device, GPCPLL_CFG2, GPCPLL_CFG2_SDM_DIN_MASK, |
| pll->sdm_din << GPCPLL_CFG2_SDM_DIN_SHIFT); |
| gk20a_pllg_write_mnp(&clk->base, &pll->base); |
| } |
| |
| /* |
| * 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). |
| */ |
| static void |
| gm20b_dvfs_calc_det_coeff(struct gm20b_clk *clk, s32 uv, |
| struct gm20b_clk_dvfs *dvfs) |
| { |
| struct nvkm_subdev *subdev = &clk->base.base.subdev; |
| const struct 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); |
| |
| /* coeff = slope * voltage + offset */ |
| coeff = DIV_ROUND_CLOSEST(mv * p->coeff_slope, 1000) + p->coeff_offs; |
| coeff = DIV_ROUND_CLOSEST(coeff, 1000); |
| dvfs->dfs_coeff = min_t(u32, coeff, MASK(GPCPLL_DVFS0_DFS_COEFF_WIDTH)); |
| |
| dvfs->dfs_ext_cal = DIV_ROUND_CLOSEST(uv - clk->uvdet_offs, |
| clk->uvdet_slope); |
| /* should never happen */ |
| if (abs(dvfs->dfs_ext_cal) >= BIT(DFS_DET_RANGE)) |
| nvkm_error(subdev, "dfs_ext_cal overflow!\n"); |
| |
| dvfs->dfs_det_max = 0; |
| |
| nvkm_debug(subdev, "%s uv: %d coeff: %x, ext_cal: %d, det_max: %d\n", |
| __func__, uv, dvfs->dfs_coeff, dvfs->dfs_ext_cal, |
| dvfs->dfs_det_max); |
| } |
| |
| /* |
| * 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. |
| */ |
| static void |
| 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; |
| const struct 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); |
| |
| nvkm_debug(subdev, "%s n_eff: %d, n_int: %d, sdm_din: %d\n", __func__, |
| n_eff, *n_int, *sdm_din); |
| } |
| |
| static int |
| gm20b_pllg_slide(struct gm20b_clk *clk, u32 n) |
| { |
| struct nvkm_subdev *subdev = &clk->base.base.subdev; |
| struct nvkm_device *device = subdev->device; |
| struct gm20b_pll pll; |
| u32 n_int, sdm_din; |
| int ret = 0; |
| |
| /* 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; |
| |
| /* pll slowdown mode */ |
| nvkm_mask(device, GPCPLL_NDIV_SLOWDOWN, |
| BIT(GPCPLL_NDIV_SLOWDOWN_SLOWDOWN_USING_PLL_SHIFT), |
| BIT(GPCPLL_NDIV_SLOWDOWN_SLOWDOWN_USING_PLL_SHIFT)); |
| |
| /* 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 complete SDM update */ |
| nvkm_mask(device, GPCPLL_CFG2, GPCPLL_CFG2_SDM_DIN_MASK, |
| sdm_din << GPCPLL_CFG2_SDM_DIN_SHIFT); |
| |
| /* exit slowdown mode */ |
| nvkm_mask(device, GPCPLL_NDIV_SLOWDOWN, |
| BIT(GPCPLL_NDIV_SLOWDOWN_SLOWDOWN_USING_PLL_SHIFT) | |
| BIT(GPCPLL_NDIV_SLOWDOWN_EN_DYNRAMP_SHIFT), 0); |
| nvkm_rd32(device, GPCPLL_NDIV_SLOWDOWN); |
| |
| return ret; |
| } |
| |
| static int |
| gm20b_pllg_enable(struct gm20b_clk *clk) |
| { |
| struct nvkm_device *device = clk->base.base.subdev.device; |
| |
| nvkm_mask(device, GPCPLL_CFG, GPCPLL_CFG_ENABLE, GPCPLL_CFG_ENABLE); |
| nvkm_rd32(device, GPCPLL_CFG); |
| |
| /* 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)); |
| |
| return 0; |
| } |
| |
| static void |
| gm20b_pllg_disable(struct gm20b_clk *clk) |
| { |
| struct nvkm_device *device = clk->base.base.subdev.device; |
| |
| /* put PLL in bypass before disabling it */ |
| nvkm_mask(device, SEL_VCO, BIT(SEL_VCO_GPC2CLK_OUT_SHIFT), 0); |
| |
| /* clear SYNC_MODE before disabling PLL */ |
| nvkm_mask(device, GPCPLL_CFG, GPCPLL_CFG_SYNC_MODE, 0); |
| |
| nvkm_mask(device, GPCPLL_CFG, GPCPLL_CFG_ENABLE, 0); |
| nvkm_rd32(device, GPCPLL_CFG); |
| } |
| |
| static int |
| gm20b_pllg_program_mnp(struct gm20b_clk *clk, const struct gk20a_pll *pll) |
| { |
| struct nvkm_subdev *subdev = &clk->base.base.subdev; |
| struct nvkm_device *device = subdev->device; |
| struct gm20b_pll cur_pll; |
| u32 n_int, sdm_din; |
| /* if we only change pdiv, we can do a glitchless transition */ |
| bool pdiv_only; |
| int ret; |
| |
| gm20b_dvfs_calc_ndiv(clk, pll->n, &n_int, &sdm_din); |
| gm20b_pllg_read_mnp(clk, &cur_pll); |
| pdiv_only = cur_pll.base.n == n_int && cur_pll.sdm_din == sdm_din && |
| cur_pll.base.m == pll->m; |
| |
| /* 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); |
| |
| cur_pll.base = *pll; |
| cur_pll.base.n = n_int; |
| cur_pll.sdm_din = sdm_din; |
| gm20b_pllg_write_mnp(clk, &cur_pll); |
| |
| ret = gm20b_pllg_enable(clk); |
| if (ret) |
| return ret; |
| } |
| |
| /* restore out divider 1:1 */ |
| udelay(2); |
| nvkm_mask(device, GPC2CLK_OUT, GPC2CLK_OUT_VCODIV_MASK, |
| GPC2CLK_OUT_VCODIV1 << GPC2CLK_OUT_VCODIV_SHIFT); |
| /* Intentional 2nd write to assure linear divider operation */ |
| nvkm_mask(device, GPC2CLK_OUT, GPC2CLK_OUT_VCODIV_MASK, |
| GPC2CLK_OUT_VCODIV1 << GPC2CLK_OUT_VCODIV_SHIFT); |
| nvkm_rd32(device, GPC2CLK_OUT); |
| |
| return 0; |
| } |
| |
| static int |
| gm20b_pllg_program_mnp_slide(struct gm20b_clk *clk, const struct gk20a_pll *pll) |
| { |
| struct gk20a_pll cur_pll; |
| int ret; |
| |
| 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); |
| } |
| |
| static int |
| gm20b_clk_calc(struct nvkm_clk *base, struct nvkm_cstate *cstate) |
| { |
| struct gm20b_clk *clk = gm20b_clk(base); |
| struct nvkm_subdev *subdev = &base->subdev; |
| struct nvkm_volt *volt = base->subdev.device->volt; |
| int ret; |
| |
| ret = gk20a_pllg_calc_mnp(&clk->base, cstate->domain[nv_clk_src_gpc] * |
| GK20A_CLK_GPC_MDIV, &clk->new_pll); |
| if (ret) |
| return ret; |
| |
| clk->new_uv = volt->vid[cstate->voltage].uv; |
| gm20b_dvfs_calc_det_coeff(clk, clk->new_uv, &clk->new_dvfs); |
| |
| nvkm_debug(subdev, "%s uv: %d uv\n", __func__, clk->new_uv); |
| |
| return 0; |
| } |
| |
| /* |
| * Compute PLL parameters that are always safe for the current voltage |
| */ |
| static void |
| gm20b_dvfs_calc_safe_pll(struct gm20b_clk *clk, struct gk20a_pll *pll) |
| { |
| u32 rate = gk20a_pllg_calc_rate(&clk->base, pll) / KHZ; |
| u32 parent_rate = clk->base.parent_rate / KHZ; |
| u32 nmin, nsafe; |
| |
| /* remove a safe margin of 10% */ |
| if (rate > clk->safe_fmax_vmin) |
| rate = rate * (100 - 10) / 100; |
| |
| /* gpc2clk */ |
| rate *= 2; |
| |
| nmin = DIV_ROUND_UP(pll->m * clk->base.params->min_vco, parent_rate); |
| nsafe = pll->m * rate / (clk->base.parent_rate); |
| |
| if (nsafe < nmin) { |
| pll->pl = DIV_ROUND_UP(nmin * parent_rate, pll->m * rate); |
| nsafe = nmin; |
| } |
| |
| pll->n = nsafe; |
| } |
| |
| static void |
| gm20b_dvfs_program_coeff(struct gm20b_clk *clk, u32 coeff) |
| { |
| struct nvkm_device *device = clk->base.base.subdev.device; |
| |
| /* strobe to read external DFS coefficient */ |
| nvkm_mask(device, GPC_BCAST_GPCPLL_DVFS2, |
| GPC_BCAST_GPCPLL_DVFS2_DFS_EXT_STROBE_BIT, |
| GPC_BCAST_GPCPLL_DVFS2_DFS_EXT_STROBE_BIT); |
| |
| nvkm_mask(device, GPCPLL_DVFS0, GPCPLL_DVFS0_DFS_COEFF_MASK, |
| coeff << GPCPLL_DVFS0_DFS_COEFF_SHIFT); |
| |
| udelay(1); |
| nvkm_mask(device, GPC_BCAST_GPCPLL_DVFS2, |
| GPC_BCAST_GPCPLL_DVFS2_DFS_EXT_STROBE_BIT, 0); |
| } |
| |
| static void |
| gm20b_dvfs_program_ext_cal(struct gm20b_clk *clk, u32 dfs_det_cal) |
| { |
| struct nvkm_device *device = clk->base.base.subdev.device; |
| u32 val; |
| |
| nvkm_mask(device, GPC_BCAST_GPCPLL_DVFS2, MASK(DFS_DET_RANGE + 1), |
| dfs_det_cal); |
| udelay(1); |
| |
| 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); |
| } |
| } |
| |
| static void |
| gm20b_dvfs_program_dfs_detection(struct gm20b_clk *clk, |
| struct gm20b_clk_dvfs *dvfs) |
| { |
| struct nvkm_device *device = clk->base.base.subdev.device; |
| |
| /* strobe to read external DFS coefficient */ |
| nvkm_mask(device, GPC_BCAST_GPCPLL_DVFS2, |
| GPC_BCAST_GPCPLL_DVFS2_DFS_EXT_STROBE_BIT, |
| GPC_BCAST_GPCPLL_DVFS2_DFS_EXT_STROBE_BIT); |
| |
| nvkm_mask(device, GPCPLL_DVFS0, |
| GPCPLL_DVFS0_DFS_COEFF_MASK | GPCPLL_DVFS0_DFS_DET_MAX_MASK, |
| dvfs->dfs_coeff << GPCPLL_DVFS0_DFS_COEFF_SHIFT | |
| dvfs->dfs_det_max << GPCPLL_DVFS0_DFS_DET_MAX_SHIFT); |
| |
| udelay(1); |
| nvkm_mask(device, GPC_BCAST_GPCPLL_DVFS2, |
| GPC_BCAST_GPCPLL_DVFS2_DFS_EXT_STROBE_BIT, 0); |
| |
| gm20b_dvfs_program_ext_cal(clk, dvfs->dfs_ext_cal); |
| } |
| |
| static int |
| gm20b_clk_prog(struct nvkm_clk *base) |
| { |
| struct gm20b_clk *clk = gm20b_clk(base); |
| u32 cur_freq; |
| int ret; |
| |
| /* 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 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); |
| |
| prog: |
| clk->uv = clk->new_uv; |
| clk->dvfs = clk->new_dvfs; |
| clk->base.pll = clk->new_pll; |
| |
| return gm20b_pllg_program_mnp_slide(clk, &clk->base.pll); |
| } |
| |
| static struct nvkm_pstate |
| gm20b_pstates[] = { |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 76800, |
| .voltage = 0, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 153600, |
| .voltage = 1, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 230400, |
| .voltage = 2, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 307200, |
| .voltage = 3, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 384000, |
| .voltage = 4, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 460800, |
| .voltage = 5, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 537600, |
| .voltage = 6, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 614400, |
| .voltage = 7, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 691200, |
| .voltage = 8, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 768000, |
| .voltage = 9, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 844800, |
| .voltage = 10, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 921600, |
| .voltage = 11, |
| }, |
| }, |
| { |
| .base = { |
| .domain[nv_clk_src_gpc] = 998400, |
| .voltage = 12, |
| }, |
| }, |
| }; |
| |
| static void |
| gm20b_clk_fini(struct nvkm_clk *base) |
| { |
| struct nvkm_device *device = base->subdev.device; |
| struct gm20b_clk *clk = gm20b_clk(base); |
| |
| /* slide to VCO min */ |
| if (gk20a_pllg_is_enabled(&clk->base)) { |
| struct gk20a_pll pll; |
| u32 n_lo; |
| |
| gk20a_pllg_read_mnp(&clk->base, &pll); |
| n_lo = gk20a_pllg_n_lo(&clk->base, &pll); |
| gm20b_pllg_slide(clk, n_lo); |
| } |
| |
| gm20b_pllg_disable(clk); |
| |
| /* set IDDQ */ |
| nvkm_mask(device, GPCPLL_CFG, GPCPLL_CFG_IDDQ, 1); |
| } |
| |
| static int |
| gm20b_clk_init_dvfs(struct gm20b_clk *clk) |
| { |
| struct nvkm_subdev *subdev = &clk->base.base.subdev; |
| struct nvkm_device *device = subdev->device; |
| bool fused = clk->uvdet_offs && clk->uvdet_slope; |
| static const s32 ADC_SLOPE_UV = 10000; /* default ADC detection slope */ |
| u32 data; |
| int ret; |
| |
| /* Enable NA DVFS */ |
| nvkm_mask(device, GPCPLL_DVFS1, GPCPLL_DVFS1_EN_DFS_BIT, |
| GPCPLL_DVFS1_EN_DFS_BIT); |
| |
| /* Set VCO_CTRL */ |
| if (clk->dvfs_params->vco_ctrl) |
| nvkm_mask(device, GPCPLL_CFG3, GPCPLL_CFG3_VCO_CTRL_MASK, |
| clk->dvfs_params->vco_ctrl << GPCPLL_CFG3_VCO_CTRL_SHIFT); |
| |
| if (fused) { |
| /* Start internal calibration, but ignore results */ |
| nvkm_mask(device, GPCPLL_DVFS1, GPCPLL_DVFS1_EN_DFS_CAL_BIT, |
| GPCPLL_DVFS1_EN_DFS_CAL_BIT); |
| |
| /* got uvdev parameters from fuse, skip calibration */ |
| goto calibrated; |
| } |
| |
| /* |
| * 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; |
| |
| nvkm_debug(subdev, "calibrated DVFS parameters: offs %d, slope %d\n", |
| clk->uvdet_offs, clk->uvdet_slope); |
| |
| calibrated: |
| /* Compute and apply initial DVFS parameters */ |
| gm20b_dvfs_calc_det_coeff(clk, clk->uv, &clk->dvfs); |
| gm20b_dvfs_program_coeff(clk, 0); |
| gm20b_dvfs_program_ext_cal(clk, clk->dvfs.dfs_ext_cal); |
| gm20b_dvfs_program_coeff(clk, clk->dvfs.dfs_coeff); |
| gm20b_dvfs_program_dfs_detection(clk, &clk->new_dvfs); |
| |
| return 0; |
| } |
| |
| /* Forward declaration to detect speedo >=1 in gm20b_clk_init() */ |
| static const struct nvkm_clk_func gm20b_clk; |
| |
| static int |
| gm20b_clk_init(struct nvkm_clk *base) |
| { |
| struct gk20a_clk *clk = gk20a_clk(base); |
| struct nvkm_subdev *subdev = &clk->base.subdev; |
| struct nvkm_device *device = subdev->device; |
| int ret; |
| u32 data; |
| |
| /* get out from IDDQ */ |
| nvkm_mask(device, GPCPLL_CFG, GPCPLL_CFG_IDDQ, 0); |
| nvkm_rd32(device, GPCPLL_CFG); |
| udelay(5); |
| |
| nvkm_mask(device, GPC2CLK_OUT, GPC2CLK_OUT_INIT_MASK, |
| GPC2CLK_OUT_INIT_VAL); |
| |
| /* 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); |
| |
| /* speedo >= 1? */ |
| if (clk->base.func == &gm20b_clk) { |
| struct gm20b_clk *_clk = gm20b_clk(base); |
| struct nvkm_volt *volt = device->volt; |
| |
| /* Get current voltage */ |
| _clk->uv = nvkm_volt_get(volt); |
| |
| /* Initialize DVFS */ |
| ret = gm20b_clk_init_dvfs(_clk); |
| if (ret) |
| return ret; |
| } |
| |
| /* Start with lowest frequency */ |
| base->func->calc(base, &base->func->pstates[0].base); |
| ret = base->func->prog(base); |
| if (ret) { |
| nvkm_error(subdev, "cannot initialize clock\n"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static const struct nvkm_clk_func |
| gm20b_clk_speedo0 = { |
| .init = gm20b_clk_init, |
| .fini = gk20a_clk_fini, |
| .read = gk20a_clk_read, |
| .calc = gk20a_clk_calc, |
| .prog = gk20a_clk_prog, |
| .tidy = gk20a_clk_tidy, |
| .pstates = gm20b_pstates, |
| /* Speedo 0 only supports 12 voltages */ |
| .nr_pstates = ARRAY_SIZE(gm20b_pstates) - 1, |
| .domains = { |
| { nv_clk_src_crystal, 0xff }, |
| { nv_clk_src_gpc, 0xff, 0, "core", GK20A_CLK_GPC_MDIV }, |
| { nv_clk_src_max }, |
| }, |
| }; |
| |
| static const struct nvkm_clk_func |
| gm20b_clk = { |
| .init = gm20b_clk_init, |
| .fini = gm20b_clk_fini, |
| .read = gk20a_clk_read, |
| .calc = gm20b_clk_calc, |
| .prog = gm20b_clk_prog, |
| .tidy = gk20a_clk_tidy, |
| .pstates = gm20b_pstates, |
| .nr_pstates = ARRAY_SIZE(gm20b_pstates), |
| .domains = { |
| { nv_clk_src_crystal, 0xff }, |
| { nv_clk_src_gpc, 0xff, 0, "core", GK20A_CLK_GPC_MDIV }, |
| { nv_clk_src_max }, |
| }, |
| }; |
| |
| static int |
| gm20b_clk_new_speedo0(struct nvkm_device *device, enum nvkm_subdev_type type, int inst, |
| struct nvkm_clk **pclk) |
| { |
| struct gk20a_clk *clk; |
| int ret; |
| |
| clk = kzalloc(sizeof(*clk), GFP_KERNEL); |
| if (!clk) |
| return -ENOMEM; |
| *pclk = &clk->base; |
| |
| ret = gk20a_clk_ctor(device, type, inst, &gm20b_clk_speedo0, &gm20b_pllg_params, clk); |
| clk->pl_to_div = pl_to_div; |
| clk->div_to_pl = div_to_pl; |
| return ret; |
| } |
| |
| /* FUSE register */ |
| #define FUSE_RESERVED_CALIB0 0x204 |
| #define FUSE_RESERVED_CALIB0_INTERCEPT_FRAC_SHIFT 0 |
| #define FUSE_RESERVED_CALIB0_INTERCEPT_FRAC_WIDTH 4 |
| #define FUSE_RESERVED_CALIB0_INTERCEPT_INT_SHIFT 4 |
| #define FUSE_RESERVED_CALIB0_INTERCEPT_INT_WIDTH 10 |
| #define FUSE_RESERVED_CALIB0_SLOPE_FRAC_SHIFT 14 |
| #define FUSE_RESERVED_CALIB0_SLOPE_FRAC_WIDTH 10 |
| #define FUSE_RESERVED_CALIB0_SLOPE_INT_SHIFT 24 |
| #define FUSE_RESERVED_CALIB0_SLOPE_INT_WIDTH 6 |
| #define FUSE_RESERVED_CALIB0_FUSE_REV_SHIFT 30 |
| #define FUSE_RESERVED_CALIB0_FUSE_REV_WIDTH 2 |
| |
| static int |
| gm20b_clk_init_fused_params(struct gm20b_clk *clk) |
| { |
| struct nvkm_subdev *subdev = &clk->base.base.subdev; |
| u32 val = 0; |
| u32 rev = 0; |
| |
| #if IS_ENABLED(CONFIG_ARCH_TEGRA) |
| tegra_fuse_readl(FUSE_RESERVED_CALIB0, &val); |
| rev = (val >> FUSE_RESERVED_CALIB0_FUSE_REV_SHIFT) & |
| MASK(FUSE_RESERVED_CALIB0_FUSE_REV_WIDTH); |
| #endif |
| |
| /* No fused parameters, we will calibrate later */ |
| if (rev == 0) |
| return -EINVAL; |
| |
| /* Integer part in mV + fractional part in uV */ |
| clk->uvdet_slope = ((val >> FUSE_RESERVED_CALIB0_SLOPE_INT_SHIFT) & |
| MASK(FUSE_RESERVED_CALIB0_SLOPE_INT_WIDTH)) * 1000 + |
| ((val >> FUSE_RESERVED_CALIB0_SLOPE_FRAC_SHIFT) & |
| MASK(FUSE_RESERVED_CALIB0_SLOPE_FRAC_WIDTH)); |
| |
| /* Integer part in mV + fractional part in 100uV */ |
| clk->uvdet_offs = ((val >> FUSE_RESERVED_CALIB0_INTERCEPT_INT_SHIFT) & |
| MASK(FUSE_RESERVED_CALIB0_INTERCEPT_INT_WIDTH)) * 1000 + |
| ((val >> FUSE_RESERVED_CALIB0_INTERCEPT_FRAC_SHIFT) & |
| MASK(FUSE_RESERVED_CALIB0_INTERCEPT_FRAC_WIDTH)) * 100; |
| |
| nvkm_debug(subdev, "fused calibration data: slope %d, offs %d\n", |
| clk->uvdet_slope, clk->uvdet_offs); |
| return 0; |
| } |
| |
| static int |
| 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); |
| |
| /* Speedo >= 1, use NAPLL */ |
| clk = kzalloc(sizeof(*clk) + sizeof(*clk_params), GFP_KERNEL); |
| if (!clk) |
| return -ENOMEM; |
| *pclk = &clk->base.base; |
| subdev = &clk->base.base.subdev; |
| |
| /* 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); |
| } |
| |
| clk->base.pl_to_div = pl_to_div; |
| clk->base.div_to_pl = div_to_pl; |
| |
| clk->dvfs_params = &gm20b_dvfs_params; |
| |
| 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; |
| } |