summaryrefslogtreecommitdiff
path: root/arch/arm/mach-tegra/nvrm/core/ap15/ap15rm_clock_config.c
diff options
context:
space:
mode:
authorGary King <gking@nvidia.com>2009-12-11 14:12:23 -0800
committerGary King <gking@nvidia.com>2009-12-11 14:13:37 -0800
commitfb0d4cc020403e874193ab0b6ef463414e4957c1 (patch)
tree9cd2a41b458e9fcaf6838d32fe9fdc051cff9e7e /arch/arm/mach-tegra/nvrm/core/ap15/ap15rm_clock_config.c
parentca2b56d4a0d2c83b722819416dbd4387bac3c0b2 (diff)
tegra-rm: branch over Tegra and Tegra 2 RM from Perforce CL 5140548
Update all license grants to BSD/MIT-like Remove some deadcode from the kernel fork of the codebase Change-Id: I73c130a8094972497c7dc7dba65755bd16175819
Diffstat (limited to 'arch/arm/mach-tegra/nvrm/core/ap15/ap15rm_clock_config.c')
-rw-r--r--arch/arm/mach-tegra/nvrm/core/ap15/ap15rm_clock_config.c2689
1 files changed, 2689 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/nvrm/core/ap15/ap15rm_clock_config.c b/arch/arm/mach-tegra/nvrm/core/ap15/ap15rm_clock_config.c
new file mode 100644
index 000000000000..198a2b20ce3d
--- /dev/null
+++ b/arch/arm/mach-tegra/nvrm/core/ap15/ap15rm_clock_config.c
@@ -0,0 +1,2689 @@
+/*
+ * Copyright (c) 2007-2009 NVIDIA Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NVIDIA Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "nvcommon.h"
+#include "nvassert.h"
+#include "nvrm_clocks.h"
+#include "nvrm_hwintf.h"
+#include "nvrm_module.h"
+#include "nvrm_drf.h"
+#include "ap15/aremc.h"
+#include "ap15/arclk_rst.h"
+#include "ap15/arapb_misc.h"
+#include "ap15rm_clocks.h"
+#include "ap15rm_private.h"
+#include "nvrm_pmu_private.h"
+#include "nvodm_query_discovery.h"
+#include "nvodm_query_memc.h"
+#include "ap20/ap20rm_clocks.h"
+
+// TODO: CAR and EMC access macros for time critical access
+
+/*****************************************************************************/
+
+static const NvU32 s_Ap15OscFreqKHz[] = { 13000, 19200, 12000, 26000 };
+
+static void
+Ap15PllPConfigure(NvRmDeviceHandle hRmDevice);
+
+static void
+Ap15MioReconfigure(NvRmDeviceHandle hRmDevice, NvRmFreqKHz MioKHz);
+
+static void
+Ap15AudioSyncInit(NvRmDeviceHandle hRmDevice, NvRmFreqKHz AudioSyncKHz);
+
+static NvError
+NvRmPrivOscDoublerConfigure(NvRmDeviceHandle hRmDevice, NvRmFreqKHz OscKHz)
+{
+ switch (hRmDevice->ChipId.Id)
+ {
+ case 0x15:
+ case 0x16:
+ return NvRmPrivAp15OscDoublerConfigure(hRmDevice, OscKHz);
+ case 0x20:
+ return NvRmPrivAp20OscDoublerConfigure(hRmDevice, OscKHz);
+ default:
+ NV_ASSERT(!"Unsupported chip ID");
+ return NvError_NotSupported;
+ }
+}
+
+void
+NvRmPrivClockSourceFreqInit(
+ NvRmDeviceHandle hRmDevice,
+ NvU32* pClockSourceFreq)
+{
+ NvU32 reg;
+ const NvRmCoreClockInfo* pCore = NULL;
+
+ NV_ASSERT(hRmDevice);
+ NV_ASSERT(pClockSourceFreq);
+
+ /*
+ * Fixed clock sources: 32kHz, main oscillator and doubler
+ * (OSC control should be already configured by the boot code)
+ */
+ pClockSourceFreq[NvRmClockSource_ClkS] = 32;
+
+ reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_OSC_CTRL_0);
+ pClockSourceFreq[NvRmClockSource_ClkM] =
+ s_Ap15OscFreqKHz[NV_DRF_VAL(CLK_RST_CONTROLLER, OSC_CTRL, OSC_FREQ, reg)];
+
+ if (NvSuccess == NvRmPrivOscDoublerConfigure(
+ hRmDevice, pClockSourceFreq[NvRmClockSource_ClkM]))
+ {
+ pClockSourceFreq[NvRmClockSource_ClkD] =
+ pClockSourceFreq[NvRmClockSource_ClkM] << 1;
+ }
+ else
+ pClockSourceFreq[NvRmClockSource_ClkD] = 0;
+
+ /*
+ * PLLs and secondary PLL dividers
+ */
+ #define INIT_PLL_FREQ(PllId) \
+ do\
+ {\
+ pClockSourceFreq[NvRmClockSource_##PllId] = NvRmPrivAp15PllFreqGet( \
+ hRmDevice, NvRmPrivGetClockSourceHandle(NvRmClockSource_##PllId)->pInfo.pPll); \
+ } while(0)
+
+ // PLLX (check if present, keep boot settings
+ // and just init frequency table)
+ if (NvRmPrivGetClockSourceHandle(NvRmClockSource_PllX0))
+ {
+ INIT_PLL_FREQ(PllX0);
+ }
+ // PLLC with output divider (if enabled keep boot settings and just init
+ // frequency table, if disbled or bypassed - configure)
+ INIT_PLL_FREQ(PllC0);
+ if (pClockSourceFreq[NvRmClockSource_PllC0] <=
+ pClockSourceFreq[NvRmClockSource_ClkM])
+ {
+ NvRmFreqKHz f = NVRM_PLLC_DEFAULT_FREQ_KHZ;
+ NvRmPrivAp15PllConfigureSimple(hRmDevice, NvRmClockSource_PllC0, f, &f);
+ }
+ pClockSourceFreq[NvRmClockSource_PllC1] = NvRmPrivDividerFreqGet(hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllC1)->pInfo.pDivider);
+
+ // PLLM with output divider (keep boot settings
+ // and just init frequency)
+ INIT_PLL_FREQ(PllM0);
+ pClockSourceFreq[NvRmClockSource_PllM1] = NvRmPrivDividerFreqGet(hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllM1)->pInfo.pDivider);
+#if !NV_OAL
+ // PLLD and PLLU with no output dividers (keep boot settings
+ // and just init frequency table)
+ INIT_PLL_FREQ(PllD0);
+ INIT_PLL_FREQ(PllU0);
+#endif
+
+ // PLLP and output dividers: set PLLP fixed frequency and enable dividers
+ // with fixed settings in override mode, so they can be changed later, as
+ // necessary. Switch system clock to oscillator during PLLP reconfiguration
+ INIT_PLL_FREQ(PllP0);
+ if (pClockSourceFreq[NvRmClockSource_PllP0] != NVRM_PLLP_FIXED_FREQ_KHZ)
+ {
+ pCore = NvRmPrivGetClockSourceHandle(
+ NvRmClockSource_SystemBus)->pInfo.pCore;
+ reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ pCore->SelectorOffset);
+ NvRmPrivCoreClockSet(hRmDevice, pCore, NvRmClockSource_ClkM, 0, 0);
+ Ap15PllPConfigure(hRmDevice);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ pCore->SelectorOffset, reg);
+ }
+ NV_ASSERT(pClockSourceFreq[NvRmClockSource_PllP0] == NVRM_PLLP_FIXED_FREQ_KHZ);
+ NvRmPrivDividerSet(
+ hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllP1)->pInfo.pDivider,
+ NVRM_FIXED_PLLP1_SETTING);
+ NvRmPrivDividerSet(
+ hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllP2)->pInfo.pDivider,
+ NVRM_FIXED_PLLP2_SETTING);
+ NvRmPrivDividerSet(
+ hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllP3)->pInfo.pDivider,
+ NVRM_FIXED_PLLP3_SETTING);
+ NvRmPrivDividerSet(
+ hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllP4)->pInfo.pDivider,
+ NVRM_FIXED_PLLP4_SETTING);
+
+ // PLLA and output divider must be init after PLLP1, used as a
+ // reference (keep boot settings and just init frequency table)
+ INIT_PLL_FREQ(PllA1);
+ pClockSourceFreq[NvRmClockSource_PllA0] = NvRmPrivDividerFreqGet(hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllA0)->pInfo.pDivider);
+
+ #undef INIT_PLL_FREQ
+
+ /*
+ * Core and bus clock sources
+ * - Leave CPU bus as set by boot-loader
+ * - Leave System bus as set by boot-loader, make sure all bus dividers are 1:1
+ */
+ pCore = NvRmPrivGetClockSourceHandle(NvRmClockSource_CpuBus)->pInfo.pCore;
+ pClockSourceFreq[NvRmClockSource_CpuBus] =
+ NvRmPrivCoreClockFreqGet(hRmDevice, pCore);
+ if (NvRmPrivGetClockSourceHandle(NvRmClockSource_CpuBridge))
+ {
+ pClockSourceFreq[NvRmClockSource_CpuBridge] = NvRmPrivDividerFreqGet(hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_CpuBridge)->pInfo.pDivider);
+ }
+ pCore = NvRmPrivGetClockSourceHandle(NvRmClockSource_SystemBus)->pInfo.pCore;
+ pClockSourceFreq[NvRmClockSource_SystemBus] =
+ NvRmPrivCoreClockFreqGet(hRmDevice, pCore);
+ NvRmPrivBusClockInit(
+ hRmDevice, pClockSourceFreq[NvRmClockSource_SystemBus]);
+
+ /*
+ * Initialize AudioSync clocks (PLLA will be re-configured if necessary)
+ */
+ Ap15AudioSyncInit(hRmDevice, NVRM_AUDIO_SYNC_KHZ);
+}
+
+void
+NvRmPrivBusClockInit(NvRmDeviceHandle hRmDevice, NvRmFreqKHz SystemFreq)
+{
+ /*
+ * Set all bus clock frequencies equal to the system clock frequency,
+ * and clear AVP clock skipper i.e., set all bus clock dividers 1:1.
+ * If APB clock is limited below system clock for a particular SoC,
+ * set the APB divider to satisfy this limitation.
+ */
+ NvRmFreqKHz AhbFreq, ApbFreq;
+ NvRmFreqKHz ApbMaxFreq = SystemFreq;
+ if (hRmDevice->ChipId.Id == 0x20)
+ {
+ ApbMaxFreq = NVRM_AP20_APB_MAX_KHZ; // AP20 limitation
+ }
+ AhbFreq = SystemFreq;
+ ApbFreq = NV_MIN(SystemFreq, ApbMaxFreq);
+
+ NvRmPrivBusClockFreqSet(
+ hRmDevice, SystemFreq, &SystemFreq, &AhbFreq, &ApbFreq, ApbMaxFreq);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_COP_CLK_SKIP_POLICY_0, 0x0);
+}
+
+/*****************************************************************************/
+/*****************************************************************************/
+
+static const NvRmFreqKHz s_PllLpCpconSelectionTable[] =
+{
+ NVRM_PLL_LP_CPCON_SELECT_STEPS_KHZ
+};
+static const NvU32 s_PllLpCpconSelectionTableSize =
+NV_ARRAY_SIZE(s_PllLpCpconSelectionTable);
+
+static const NvU32 s_PllMipiCpconSelectionTable[] =
+{
+ NVRM_PLL_MIPI_CPCON_SELECT_STEPS_N_DIVIDER
+};
+static const NvU32 s_PllMipiCpconSelectionTableSize =
+NV_ARRAY_SIZE(s_PllMipiCpconSelectionTable);
+
+static void
+PllLpGetTypicalControls(
+ NvRmFreqKHz InputKHz,
+ NvU32 M,
+ NvU32 N,
+ NvU32* pCpcon)
+{
+ NvU32 i;
+ if (N >= NVRM_PLL_LP_MIN_N_FOR_CPCON_SELECTION)
+ {
+ // CPCON depends on comparison frequency
+ for (i = 0; i < s_PllLpCpconSelectionTableSize; i++)
+ {
+ if (InputKHz >= s_PllLpCpconSelectionTable[i] * M)
+ break;
+ }
+ *pCpcon = i + 1;
+ }
+ else // CPCON is 1, regardless of frequency
+ {
+ *pCpcon = 1;
+ }
+}
+
+static void
+PllMipiGetTypicalControls(
+ NvU32 N,
+ NvU32* pCpcon,
+ NvU32* pLfCon)
+{
+ NvU32 i;
+
+ // CPCON depends on feedback divider
+ for (i = 0; i < s_PllMipiCpconSelectionTableSize; i++)
+ {
+ if (N <= s_PllMipiCpconSelectionTable[i])
+ break;
+ }
+ *pCpcon = i + 1;
+ *pLfCon = (N >= NVRM_PLL_MIPI_LFCON_SELECT_N_DIVIDER) ? 1 : 0;
+}
+
+void
+NvRmPrivAp15PllSet(
+ NvRmDeviceHandle hRmDevice,
+ const NvRmPllClockInfo* pCinfo,
+ NvU32 M,
+ NvU32 N,
+ NvU32 P,
+ NvU32 StableDelayUs,
+ NvU32 cpcon,
+ NvU32 lfcon,
+ NvBool TypicalControls,
+ NvU32 flags)
+{
+ NvU32 base, misc;
+ NvU32 old_base, old_misc;
+ NvU32 delay = 0;
+ NvU32 override = 0;
+
+ NV_ASSERT(hRmDevice);
+ NV_ASSERT(pCinfo);
+ NV_ASSERT(pCinfo->PllBaseOffset);
+ NV_ASSERT(pCinfo->PllMiscOffset);
+
+ /*
+ * PLL control fields used below have the same layout for all PLLs with
+ * the following exceptions:
+ *
+ * a) PLLP base register OVERRIDE field has to be set in order to enable
+ * PLLP re-configuration in diagnostic mode. For other PLLs this field is
+ * "Don't care".
+ * b) PLLU HS P divider field is one bit, inverse logic field. Other control
+ * bits, that are mapped to P divider in common layout should be set to 0.
+ *
+ * PLLP h/w field definitions will be used in DRF macros to construct base
+ * values for all PLLs, with special care of a) and b). All base fields not
+ * explicitly used below are set to 0 for all PLLs.
+ *
+ * c) PLLD/PLLU miscellaneous register has a unique fields determined based
+ * on the input flags. For other PLLs these fields have different meaning,
+ * and will be preserved.
+ *
+ * PLLP h/w field definitions will be used in DRF macros to construct
+ * miscellaneous values with common layout. For unique fields PLLD h/w
+ * definitions will be used. All miscellaneous fields not explicitly used
+ * below are preserved for all PLLs.
+ */
+ base = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllBaseOffset);
+ old_base = base;
+
+ // Disable PLL if either input or feedback divider setting is zero
+ if ((M == 0) || (N == 0))
+ {
+ base = NV_FLD_SET_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BYPASS, DISABLE, base);
+ base = NV_FLD_SET_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_ENABLE, DISABLE, base);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllBaseOffset, base);
+ NvRmPrivPllFreqUpdate(hRmDevice, pCinfo);
+ return;
+ }
+
+ // Determine type-specific controls, construct new misc settings
+ misc = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllMiscOffset);
+ old_misc = misc;
+ if (pCinfo->PllType == NvRmPllType_MIPI)
+ {
+ if (flags & NvRmPllConfigFlags_SlowMode)
+ {
+ misc = NV_FLD_SET_DRF_NUM( // "1" = slow (/8) MIPI clock output
+ CLK_RST_CONTROLLER, PLLD_MISC, PLLD_FO_MODE, 1, misc);
+ }
+ else if (flags & NvRmPllConfigFlags_FastMode)
+ {
+ misc = NV_FLD_SET_DRF_NUM( // "0" = fast MIPI clock output
+ CLK_RST_CONTROLLER, PLLD_MISC, PLLD_FO_MODE, 0, misc);
+ }
+ if (flags & NvRmPllConfigFlags_DiffClkEnable)
+ {
+ misc = NV_FLD_SET_DRF_NUM( // Enable differential clocks
+ CLK_RST_CONTROLLER, PLLD_MISC, PLLD_CLKENABLE, 1, misc);
+ }
+ else if (flags & NvRmPllConfigFlags_DiffClkDisable)
+ {
+ misc = NV_FLD_SET_DRF_NUM( // Disable differential clocks
+ CLK_RST_CONTROLLER, PLLD_MISC, PLLD_CLKENABLE, 0, misc);
+ }
+ if (TypicalControls)
+ {
+ PllMipiGetTypicalControls(N, &cpcon, &lfcon);
+ }
+ delay = NVRM_PLL_MIPI_STABLE_DELAY_US;
+ }
+ else if (pCinfo->PllType == NvRmPllType_LP)
+ {
+ if (flags & NvRmPllConfigFlags_DccEnable)
+ {
+ misc = NV_FLD_SET_DRF_NUM( // "1" = enable DCC
+ CLK_RST_CONTROLLER, PLLP_MISC, PLLP_DCCON, 1, misc);
+ }
+ else if (flags & NvRmPllConfigFlags_DccDisable)
+ {
+ misc = NV_FLD_SET_DRF_NUM( // "0" = disable DCC
+ CLK_RST_CONTROLLER, PLLP_MISC, PLLP_DCCON, 0, misc);
+ }
+ if (TypicalControls)
+ {
+ NvRmFreqKHz InputKHz = NvRmPrivGetClockSourceFreq(pCinfo->InputId);
+ PllLpGetTypicalControls(InputKHz, M, N, &cpcon);
+ }
+ lfcon = 0; // always for LP PLL
+ delay = NVRM_PLL_LP_STABLE_DELAY_US;
+ }
+ else if (pCinfo->PllType == NvRmPllType_UHS)
+ {
+ if (TypicalControls) // Same as MIPI typical controls
+ {
+ PllMipiGetTypicalControls(N, &cpcon, &lfcon);
+ }
+ delay = NVRM_PLL_MIPI_STABLE_DELAY_US;
+ P = (P == 0) ? 1 : 0; // P-divider is 1 bit, inverse logic
+ }
+ else
+ {
+ NV_ASSERT(!"Invalid PLL type");
+ }
+ misc = NV_FLD_SET_DRF_NUM(CLK_RST_CONTROLLER, PLLP_MISC, PLLP_CPCON, cpcon, misc);
+ misc = NV_FLD_SET_DRF_NUM(CLK_RST_CONTROLLER, PLLP_MISC, PLLP_LFCON, lfcon, misc);
+
+ // Construct new base setting
+ // Override is PLLP specific, and it is just ignored by other PLLs;
+ override = ((flags & NvRmPllConfigFlags_Override) != 0) ?
+ CLK_RST_CONTROLLER_PLLP_BASE_0_PLLP_BASE_OVRRIDE_ENABLE :
+ CLK_RST_CONTROLLER_PLLP_BASE_0_PLLP_BASE_OVRRIDE_DISABLE;
+ { // Compiler failed to generate correct code for the base fields
+ // concatenation without the split below
+ volatile NvU32 prebase =
+ NV_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BYPASS, ENABLE) |
+ NV_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_ENABLE, ENABLE) |
+ NV_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_REF_DIS, REF_ENABLE);
+ base = prebase |
+ NV_DRF_NUM(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BASE_OVRRIDE, override) |
+ NV_DRF_NUM(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_DIVP, P) |
+ NV_DRF_NUM(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_DIVN, N) |
+ NV_DRF_NUM(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_DIVM, M);
+ }
+
+ // If PLL is not bypassed, and new configurations is the same as the old
+ // one - exit without overwriting h/w. Otherwise, bypass PLL before
+ // changing configuration.
+ if (NV_DRF_VAL(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BYPASS, old_base) ==
+ CLK_RST_CONTROLLER_PLLP_BASE_0_PLLP_BYPASS_DISABLE)
+ {
+ old_base = NV_FLD_SET_DRF_DEF(
+ CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BYPASS, ENABLE, old_base);
+ if ((base == old_base) && (misc == old_misc))
+ {
+ NvRmPrivPllFreqUpdate(hRmDevice, pCinfo);
+ return;
+ }
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ pCinfo->PllBaseOffset, old_base);
+ }
+
+ // Configure and enable PLL, keep it bypassed
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllMiscOffset, misc);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllBaseOffset, base);
+
+ // Wait for PLL to stabilize and switch to PLL output
+ NV_ASSERT(StableDelayUs);
+ if (StableDelayUs > delay)
+ StableDelayUs = delay;
+ NvOsWaitUS(StableDelayUs);
+
+ base = NV_FLD_SET_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BYPASS, DISABLE, base);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllBaseOffset, base);
+ NvRmPrivPllFreqUpdate(hRmDevice, pCinfo);
+}
+
+NvRmFreqKHz
+NvRmPrivAp15PllFreqGet(
+ NvRmDeviceHandle hRmDevice,
+ const NvRmPllClockInfo* pCinfo)
+{
+ NvU32 M, N, P;
+ NvU32 base, misc;
+ NvRmFreqKHz PllKHz;
+
+ NV_ASSERT(hRmDevice);
+ NV_ASSERT(pCinfo);
+ NV_ASSERT(pCinfo->PllBaseOffset);
+ NV_ASSERT(pCinfo->PllMiscOffset);
+
+ /*
+ * PLL control fields used below have the same layout for all PLLs with
+ * the following exceptions:
+ *
+ * a) PLLP base register OVERRIDE field ("Don't care" for other PLLs).
+ * Respectively, PLLP h/w field definitions will be used in DRF macros
+ * to construct base values for all PLLs.
+ *
+ * b) PLLD/PLLU miscellaneous register fast/slow mode control (does not
+ * affect output frequency for other PLLs). Respectively, PLLD h/w field
+ * definitions will be used in DRF macros to construct miscellaneous values.
+ */
+ base = NV_REGR(
+ hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllBaseOffset);
+ PllKHz = NvRmPrivGetClockSourceFreq(pCinfo->InputId);
+ NV_ASSERT(PllKHz);
+ NV_ASSERT(NV_DRF_VAL(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_REF_DIS, base) ==
+ CLK_RST_CONTROLLER_PLLP_BASE_0_PLLP_REF_DIS_REF_ENABLE);
+
+ if (NV_DRF_VAL(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BYPASS, base) ==
+ CLK_RST_CONTROLLER_PLLP_BASE_0_PLLP_BYPASS_DISABLE)
+ {
+ // Special cases: PLL is disabled, or in fixed mode (PLLP only)
+ if (NV_DRF_VAL(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_ENABLE, base) ==
+ CLK_RST_CONTROLLER_PLLP_BASE_0_PLLP_ENABLE_DISABLE)
+ return 0;
+ if ((pCinfo->SourceId == NvRmClockSource_PllP0) &&
+ (NV_DRF_VAL(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BASE_OVRRIDE, base) ==
+ CLK_RST_CONTROLLER_PLLP_BASE_0_PLLP_BASE_OVRRIDE_DISABLE))
+ return NV_BOOT_PLLP_FIXED_FREQ_KHZ;
+
+ // PLL formula - Output F = (Reference F * N) / (M * 2^P)
+ M = NV_DRF_VAL(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_DIVM, base);
+ N = NV_DRF_VAL(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_DIVN, base);
+ P = NV_DRF_VAL(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_DIVP, base);
+ NV_ASSERT((M != 0) && (N != 0));
+
+ if (pCinfo->PllType == NvRmPllType_UHS)
+ {
+ // Adjust P divider field size and inverse logic for USB HS PLL
+ P = (P & 0x1) ? 0 : 1;
+ }
+ PllKHz = ((PllKHz * N) / M) >> P;
+
+ // Check slow/fast mode selection for MIPI PLLs
+ if (pCinfo->PllType == NvRmPllType_MIPI)
+ {
+ misc = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ pCinfo->PllMiscOffset);
+ if (NV_DRF_VAL(CLK_RST_CONTROLLER, PLLD_MISC, PLLD_FO_MODE, misc) == 1)
+ {
+ PllKHz = PllKHz >> 3; // In slow mode output is divided by 8
+ }
+ }
+ }
+ if (pCinfo->SourceId == NvRmClockSource_PllD0)
+ {
+ PllKHz = PllKHz >> 1; // PLLD output always divided by 2
+ }
+ return PllKHz;
+}
+
+static void
+Ap15PllControl(
+ NvRmDeviceHandle hRmDevice,
+ NvRmClockSource PllId,
+ NvBool Enable)
+{
+ NvU32 base;
+ NvU32 delay = 0;
+ const NvRmPllClockInfo* pCinfo =
+ NvRmPrivGetClockSourceHandle(PllId)->pInfo.pPll;
+
+ NV_ASSERT(hRmDevice);
+ NV_ASSERT(pCinfo->PllBaseOffset);
+
+ /*
+ * PLL control fields used below have the same layout for all PLLs.
+ * PLLP h/w field definitions will be used in DRF macros to construct base
+ * values for all PLLs.
+ */
+ base = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllBaseOffset);
+
+ if (Enable)
+ {
+ // No need to enable already enabled PLL - do nothing
+ if (NV_DRF_VAL(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_ENABLE, base) ==
+ CLK_RST_CONTROLLER_PLLP_BASE_0_PLLP_ENABLE_ENABLE)
+ return;
+
+ // Get ready stabilization delay
+ if ((pCinfo->PllType == NvRmPllType_MIPI) ||
+ (pCinfo->PllType == NvRmPllType_UHS))
+ delay = NVRM_PLL_MIPI_STABLE_DELAY_US;
+ else if (pCinfo->PllType == NvRmPllType_LP)
+ delay = NVRM_PLL_LP_STABLE_DELAY_US;
+ else
+ NV_ASSERT(!"Invalid PLL type");
+
+ // Bypass PLL => Enable PLL => wait for PLL to stabilize
+ // => switch to PLL output. All other settings preserved.
+ base = NV_FLD_SET_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BYPASS, ENABLE, base);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllBaseOffset, base);
+ base = NV_FLD_SET_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_ENABLE, ENABLE, base);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllBaseOffset, base);
+
+ NvOsWaitUS(delay);
+
+ base = NV_FLD_SET_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BYPASS, DISABLE, base);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllBaseOffset, base);
+ }
+ else
+ {
+ // Disable PLL, no bypass. All other settings preserved.
+ base = NV_FLD_SET_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_BYPASS, DISABLE, base);
+ base = NV_FLD_SET_DRF_DEF(CLK_RST_CONTROLLER, PLLP_BASE, PLLP_ENABLE, DISABLE, base);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0, pCinfo->PllBaseOffset, base);
+ }
+ NvRmPrivPllFreqUpdate(hRmDevice, pCinfo);
+}
+
+void
+NvRmPrivAp15PllConfigureSimple(
+ NvRmDeviceHandle hRmDevice,
+ NvRmClockSource PllId,
+ NvRmFreqKHz MaxOutKHz,
+ NvRmFreqKHz* pPllOutKHz)
+{
+#define NVRM_PLL_FCMP_1 (1000)
+#define NVRM_PLL_VCO_RANGE_1 (1000000)
+#define NVRM_PLL_FCMP_2 (2000)
+#define NVRM_PLL_VCO_RANGE_2 (2000000)
+
+ /*
+ * Simple PLL configuration (assuming that target output frequency is
+ * always in VCO range, and does not exceed 2GHz).
+ * - output divider is set 1:1
+ * - input divider is set to get comparison frequency equal or slightly
+ * above 1MHz if VCO is below 1GHz . Otherwise, input divider is set
+ * to get comparison frequency equal or slightly below 2MHz.
+ * - feedback divider is calculated based on target output frequency
+ * With simple configuration the absolute output frequency error does not
+ * exceed half of comparison frequency. It has been verified that simple
+ * configuration provides necessary accuracy for all display pixel clocks
+ * use cases.
+ */
+ NvU32 M, N, P;
+ NvRmFreqKHz RefKHz, VcoKHz;
+ const NvRmPllClockInfo* pCinfo =
+ NvRmPrivGetClockSourceHandle(PllId)->pInfo.pPll;
+ NvU32 flags = 0;
+
+ NV_ASSERT(hRmDevice);
+ VcoKHz = *pPllOutKHz;
+ P = 0;
+
+ if (pCinfo->SourceId == NvRmClockSource_PllD0)
+ { // PLLD output is always divided by 2 (after P-divider)
+ VcoKHz = VcoKHz << 1;
+ MaxOutKHz = MaxOutKHz << 1;
+ flags = NvRmPllConfigFlags_DiffClkEnable;
+ }
+ if (pCinfo->SourceId == NvRmClockSource_PllX0)
+ {
+ flags = VcoKHz < NVRM_PLLX_DCC_VCO_MIN ?
+ NvRmPllConfigFlags_DccDisable : NvRmPllConfigFlags_DccEnable;
+ }
+ NV_ASSERT((pCinfo->PllVcoMin <= VcoKHz) && (VcoKHz <= pCinfo->PllVcoMax));
+ NV_ASSERT(VcoKHz <= NVRM_PLL_VCO_RANGE_2);
+ NV_ASSERT(VcoKHz <= MaxOutKHz);
+
+ RefKHz = NvRmPrivGetClockSourceFreq(pCinfo->InputId);
+ NV_ASSERT(RefKHz);
+ if (VcoKHz <= NVRM_PLL_VCO_RANGE_1)
+ M = RefKHz / NVRM_PLL_FCMP_1;
+ else
+ M = (RefKHz + NVRM_PLL_FCMP_2 - 1) / NVRM_PLL_FCMP_2;
+ N = (RefKHz + ((VcoKHz * M) << 1) ) / (RefKHz << 1);
+ if ((RefKHz * N) > (MaxOutKHz * M))
+ N--; // use floor if rounding violates client's max limit
+
+ NvRmPrivAp15PllSet(
+ hRmDevice, pCinfo, M, N, P, (NvU32)-1, 0, 0, NV_TRUE, flags);
+ *pPllOutKHz = NvRmPrivGetClockSourceFreq(pCinfo->SourceId);
+}
+
+/*****************************************************************************/
+
+// Fixed list of PLLP configurations for different reference frequencies
+// arranged according to CLK_RST_CONTROLLER_OSC_CTRL_0_OSC_FREQ_FIELD enum
+static const NvRmPllFixedConfig s_Ap15PllPConfigurations[] =
+{
+ NVRM_PLLP_AT_13MHZ,
+ NVRM_PLLP_AT_19MHZ,
+ NVRM_PLLP_AT_12MHZ,
+ NVRM_PLLP_AT_26MHZ
+};
+
+static void
+Ap15PllPConfigure(NvRmDeviceHandle hRmDevice)
+{
+ NvU32 reg;
+ NvRmFreqKHz PllKHz;
+ NvRmPllFixedConfig PllPConfig = {0};
+
+ const NvRmPllClockInfo* pCinfo =
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllP0)->pInfo.pPll;
+ NV_ASSERT(hRmDevice);
+
+ // Configure and enable PllP at RM fixed frequency,
+ // if it is not already enabled
+ PllKHz = NvRmPrivGetClockSourceFreq(pCinfo->SourceId);
+ if (PllKHz == NVRM_PLLP_FIXED_FREQ_KHZ)
+ return;
+
+ // Get fixed PLLP configuration for current oscillator frequency.
+ reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_OSC_CTRL_0);
+ PllPConfig = s_Ap15PllPConfigurations[NV_DRF_VAL(
+ CLK_RST_CONTROLLER, OSC_CTRL, OSC_FREQ, reg)];
+
+ // Configure and enable PLLP
+ NvRmPrivAp15PllSet(hRmDevice, pCinfo, PllPConfig.M, PllPConfig.N,
+ PllPConfig.P, (NvU32)-1, 0, 0, NV_TRUE,
+ NvRmPllConfigFlags_Override);
+}
+
+/*****************************************************************************/
+
+// Fixed list of PLLU configurations for different reference frequencies
+// arranged according to CLK_RST_CONTROLLER_OSC_CTRL_0_OSC_FREQ_FIELD enum
+static const NvRmPllFixedConfig s_Ap15UsbPllConfigurations[] =
+{
+ NVRM_PLLU_AT_13MHZ,
+ NVRM_PLLU_AT_19MHZ,
+ NVRM_PLLU_AT_12MHZ,
+ NVRM_PLLU_AT_26MHZ
+};
+
+static const NvRmPllFixedConfig s_Ap15UlpiPllConfigurations[] =
+{
+ NVRM_PLLU_ULPI_AT_13MHZ,
+ NVRM_PLLU_ULPI_AT_19MHZ,
+ NVRM_PLLU_ULPI_AT_12MHZ,
+ NVRM_PLLU_ULPI_AT_26MHZ
+};
+
+static const NvRmPllFixedConfig s_Ap15UhsPllConfigurations[] =
+{
+ NVRM_PLLU_HS_AT_13MHZ,
+ NVRM_PLLU_HS_AT_19MHZ,
+ NVRM_PLLU_HS_AT_12MHZ,
+ NVRM_PLLU_HS_AT_26MHZ
+};
+
+static void
+PllUmipiConfigure(NvRmDeviceHandle hRmDevice, NvRmFreqKHz TargetFreq)
+{
+ NvU32 reg;
+ NvRmPllFixedConfig UsbConfig = {0};
+ const NvRmPllClockInfo* pCinfo =
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllU0)->pInfo.pPll;
+ NvRmFreqKHz CurrentFreq = NvRmPrivGetClockSourceFreq(pCinfo->SourceId);
+ NV_ASSERT(hRmDevice);
+
+ if (CurrentFreq == TargetFreq)
+ return; // PLLU is already configured at target frequency - exit
+
+ // Index into fixed PLLU configuration tables based on oscillator frequency
+ reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_OSC_CTRL_0);
+ reg = NV_DRF_VAL(CLK_RST_CONTROLLER, OSC_CTRL, OSC_FREQ, reg);
+
+ if (TargetFreq == NvRmFreqUnspecified)
+ {
+ // By default set standard USB frequency, if PLLU is not configured
+ if ((CurrentFreq == s_Ap15UsbPllConfigurations[reg].OutputKHz) ||
+ (CurrentFreq == s_Ap15UlpiPllConfigurations[reg].OutputKHz))
+ {
+ return; // PLLU is already configured at supported frequency - exit
+ }
+ UsbConfig = s_Ap15UsbPllConfigurations[reg];
+ }
+ else if (TargetFreq == s_Ap15UsbPllConfigurations[reg].OutputKHz)
+ {
+ UsbConfig = s_Ap15UsbPllConfigurations[reg];
+ }
+ else if (TargetFreq == s_Ap15UlpiPllConfigurations[reg].OutputKHz)
+ {
+ UsbConfig = s_Ap15UlpiPllConfigurations[reg];
+ }
+ else
+ {
+ NV_ASSERT(!"Invalid target frequency");
+ return;
+ }
+ // Configure and enable PLLU
+ NvRmPrivAp15PllSet(hRmDevice, pCinfo, UsbConfig.M, UsbConfig.N,
+ UsbConfig.P, (NvU32)-1, 0, 0, NV_TRUE, 0);
+}
+
+static void
+PllUhsConfigure(NvRmDeviceHandle hRmDevice, NvRmFreqKHz TargetFreq)
+{
+ NvU32 reg;
+ NvRmPllFixedConfig UsbConfig = {0};
+ const NvRmPllClockInfo* pCinfo =
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllU0)->pInfo.pPll;
+ NvRmFreqKHz CurrentFreq = NvRmPrivGetClockSourceFreq(pCinfo->SourceId);
+ NV_ASSERT(hRmDevice);
+
+ // Index into fixed PLLU configuration tables based on oscillator frequency
+ reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_OSC_CTRL_0);
+ reg = NV_DRF_VAL(CLK_RST_CONTROLLER, OSC_CTRL, OSC_FREQ, reg);
+
+ // If PLLU is already configured - exit
+ if (CurrentFreq == s_Ap15UhsPllConfigurations[reg].OutputKHz)
+ return;
+
+ /*
+ * Target may be unspecified, or any of the standard USB, ULPI, or UHS
+ * frequencies. In any case, main PLLU HS output is configured at UHS
+ * frequency, with ULPI and USB frequencies are generated on secondary
+ * outputs by fixed post dividers
+ */
+ if (!( (TargetFreq == NvRmFreqUnspecified) ||
+ (TargetFreq == s_Ap15UsbPllConfigurations[reg].OutputKHz) ||
+ (TargetFreq == s_Ap15UlpiPllConfigurations[reg].OutputKHz) ||
+ (TargetFreq == s_Ap15UhsPllConfigurations[reg].OutputKHz) )
+ )
+ {
+ NV_ASSERT(!"Invalid target frequency");
+ return;
+ }
+ // Configure and enable PLLU
+ UsbConfig = s_Ap15UhsPllConfigurations[reg];
+ NvRmPrivAp15PllSet(hRmDevice, pCinfo, UsbConfig.M, UsbConfig.N,
+ UsbConfig.P, (NvU32)-1, 0, 0, NV_TRUE, 0);
+}
+
+static void
+Ap15PllUConfigure(NvRmDeviceHandle hRmDevice, NvRmFreqKHz TargetFreq)
+{
+ const NvRmPllClockInfo* pCinfo =
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllU0)->pInfo.pPll;
+
+ if (pCinfo->PllType == NvRmPllType_MIPI)
+ PllUmipiConfigure(hRmDevice, TargetFreq);
+ else if (pCinfo->PllType == NvRmPllType_UHS)
+ PllUhsConfigure(hRmDevice, TargetFreq);
+}
+
+/*****************************************************************************/
+
+// Fixed list of PLLA configurations for supported audio clocks
+static const NvRmPllFixedConfig s_Ap15AudioPllConfigurations[] =
+{
+ NVRM_PLLA_CONFIGURATIONS
+};
+
+static void
+Ap15PllAConfigure(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz* pAudioTargetKHz)
+{
+// The reminder bits used to check divisibility
+#define REMINDER_BITS (6)
+
+ NvU32 i, rem;
+ NvRmFreqKHz OutputKHz;
+ NvU32 BestRem = (0x1 << REMINDER_BITS);
+ NvU32 BestIndex = NV_ARRAY_SIZE(s_Ap15AudioPllConfigurations) - 1;
+
+ NvRmPllFixedConfig AudioConfig = {0};
+ const NvRmPllClockInfo* pPllCinfo =
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllA1)->pInfo.pPll;
+ const NvRmDividerClockInfo* pDividerCinfo =
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllA0)->pInfo.pDivider;
+ NV_ASSERT(hRmDevice);
+ NV_ASSERT(*pAudioTargetKHz);
+
+ // Fixed PLLA FPGA configuration
+ if (NvRmPrivGetExecPlatform(hRmDevice) == ExecPlatform_Fpga)
+ {
+ *pAudioTargetKHz = NvRmPrivGetClockSourceFreq(pDividerCinfo->SourceId);
+ return;
+ }
+ // Find PLLA configuration with smallest output frequency that can be
+ // divided by fractional divider into the closest one to the target.
+ for (i = 0; i < NV_ARRAY_SIZE(s_Ap15AudioPllConfigurations); i++)
+ {
+ OutputKHz = s_Ap15AudioPllConfigurations[i].OutputKHz;
+ if (*pAudioTargetKHz > OutputKHz)
+ continue;
+ rem = ((OutputKHz << (REMINDER_BITS + 1)) / (*pAudioTargetKHz)) &
+ ((0x1 << REMINDER_BITS) - 1);
+ if (rem < BestRem)
+ {
+ BestRem = rem;
+ BestIndex = i;
+ if (rem == 0)
+ break;
+ }
+ }
+
+ // Configure PLLA and output divider
+ AudioConfig = s_Ap15AudioPllConfigurations[BestIndex];
+ NvRmPrivAp15PllSet(hRmDevice, pPllCinfo, AudioConfig.M, AudioConfig.N,
+ AudioConfig.P, (NvU32)-1, 0, 0, NV_TRUE, 0);
+ NvRmPrivDividerSet(
+ hRmDevice, pDividerCinfo, AudioConfig.D);
+ *pAudioTargetKHz = NvRmPrivGetClockSourceFreq(pDividerCinfo->SourceId);
+}
+
+static void
+Ap15PllAControl(
+ NvRmDeviceHandle hRmDevice,
+ NvBool Enable)
+{
+ const NvRmDividerClockInfo* pCinfo =
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllA0)->pInfo.pDivider;
+ if (NvRmPrivGetExecPlatform(hRmDevice) == ExecPlatform_Fpga)
+ return; // No PLLA control on FPGA
+
+ if (Enable)
+ {
+ Ap15PllControl(hRmDevice, NvRmClockSource_PllA1, NV_TRUE);
+ }
+ else
+ {
+ // Disable provided PLLA is not used as a source for any clock
+ if (NvRmPrivGetDfsFlags(hRmDevice) & NvRmDfsStatusFlags_StopPllA0)
+ Ap15PllControl(hRmDevice, NvRmClockSource_PllA1, NV_FALSE);
+ }
+ NvRmPrivDividerFreqUpdate(hRmDevice, pCinfo);
+}
+
+static void
+Ap15AudioSyncInit(NvRmDeviceHandle hRmDevice, NvRmFreqKHz AudioSyncKHz)
+{
+ NvRmFreqKHz AudioTargetKHz;
+ NvRmClockSource AudioSyncSource;
+ const NvRmSelectorClockInfo* pCinfo;
+ NV_ASSERT(hRmDevice);
+
+ // Configure PLLA. Requested frequency must always exactly match one of the
+ // fixed audio frequencies.
+ AudioTargetKHz = AudioSyncKHz;
+ Ap15PllAConfigure(hRmDevice, &AudioTargetKHz);
+ NV_ASSERT(AudioTargetKHz == AudioSyncKHz);
+
+ // Use PLLA as audio sync source, and disable doublers.
+ // (verify if SoC supports audio sync selectors)
+ AudioSyncSource = NvRmClockSource_PllA0;
+ if (NvRmPrivGetClockSourceHandle(NvRmClockSource_AudioSync))
+ {
+ pCinfo = NvRmPrivGetClockSourceHandle(
+ NvRmClockSource_AudioSync)->pInfo.pSelector;
+ NvRmPrivSelectorClockSet(hRmDevice, pCinfo, AudioSyncSource, NV_FALSE);
+ }
+ if (NvRmPrivGetClockSourceHandle(NvRmClockSource_MpeAudio))
+ {
+ pCinfo = NvRmPrivGetClockSourceHandle(
+ NvRmClockSource_MpeAudio)->pInfo.pSelector;
+ NvRmPrivSelectorClockSet(hRmDevice, pCinfo, AudioSyncSource, NV_FALSE);
+ }
+}
+
+/*****************************************************************************/
+
+static void
+Ap15PllDControl(
+ NvRmDeviceHandle hRmDevice,
+ NvBool Enable)
+{
+ NvU32 reg;
+ NvRmModuleClockInfo* pCinfo = NULL;
+ NvRmModuleClockState* pCstate = NULL;
+ NV_ASSERT_SUCCESS(NvRmPrivGetClockState(
+ hRmDevice, NvRmModuleID_Dsi, &pCinfo, &pCstate));
+
+ if (Enable)
+ {
+ Ap15PllControl(hRmDevice, NvRmClockSource_PllD0, NV_TRUE);
+ pCstate->actual_freq =
+ NvRmPrivGetClockSourceFreq(NvRmClockSource_PllD0);
+ return;
+ }
+
+ // Disable PLLD if it is not used by either display head or DSI
+ reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ pCinfo->ClkEnableOffset);
+ if (NvRmPrivIsSourceSelectedByModule(hRmDevice, NvRmClockSource_PllD0,
+ NVRM_MODULE_ID(NvRmModuleID_Display, 0)) ||
+ NvRmPrivIsSourceSelectedByModule(hRmDevice, NvRmClockSource_PllD0,
+ NVRM_MODULE_ID(NvRmModuleID_Display, 1)) ||
+ ((reg & pCinfo->ClkEnableField) == pCinfo->ClkEnableField))
+ return;
+
+ Ap15PllControl(hRmDevice, NvRmClockSource_PllD0, NV_FALSE);
+ pCstate->actual_freq =
+ NvRmPrivGetClockSourceFreq(NvRmClockSource_PllD0);
+}
+
+// Fixed list of PLL HDMI configurations for different reference frequencies
+// arranged according to CLK_RST_CONTROLLER_OSC_CTRL_0_OSC_FREQ_FIELD enum
+static const NvRmPllFixedConfig s_Ap15HdmiPllConfigurations[] =
+{
+ NVRM_PLLHD_AT_13MHZ,
+ NVRM_PLLHD_AT_19MHZ,
+ NVRM_PLLHD_AT_12MHZ,
+ NVRM_PLLHD_AT_26MHZ
+};
+
+static void
+Ap15PllDConfigure(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz TargetFreq)
+{
+ NvRmFreqKHz MaxFreq = NvRmPrivGetSocClockLimits(NvRmModuleID_Dsi)->MaxKHz;
+ NvRmModuleClockInfo* pCinfo = NULL;
+ NvRmModuleClockState* pCstate = NULL;
+ NV_ASSERT_SUCCESS(NvRmPrivGetClockState(
+ hRmDevice, NvRmModuleID_Dsi, &pCinfo, &pCstate));
+
+ /*
+ * PLLD is adjusted when DDK/ODM is initializing DSI or reconfiguring
+ * display clock (for HDMI, DSI, or in some cases CRT).
+ */
+ if ((TargetFreq == NVRM_HDMI_720p_1080i_FIXED_FREQ_KHZ) ||
+ (TargetFreq == NVRM_HDMI_720p_1080p_FIXED_FREQ_KHZ) ||
+ (TargetFreq == NVRM_HDMI_480_FIXED_FREQ_KHZ))
+ {
+ // 480p or 720p or 1080i/1080p HDMI - use fixed PLLD configuration
+ NvU32 reg;
+ NvRmPllFixedConfig HdmiConfig = {0};
+ const NvRmPllClockInfo* pCinfo =
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllD0)->pInfo.pPll;
+
+ reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_OSC_CTRL_0);
+ HdmiConfig = s_Ap15HdmiPllConfigurations[NV_DRF_VAL(
+ CLK_RST_CONTROLLER, OSC_CTRL, OSC_FREQ, reg)];
+
+ NvRmPrivAp15PllSet(hRmDevice, pCinfo, HdmiConfig.M, HdmiConfig.N,
+ HdmiConfig.P, (NvU32)-1, 0, 0, NV_TRUE, 0);
+ }
+ else
+ {
+ // for other targets use simple variable configuration
+ if (TargetFreq < NVRM_PLLD_DISPLAY_MIN_KHZ)
+ {
+ NV_ASSERT((TargetFreq * NVRM_DISPLAY_DIVIDER_MAX) >=
+ NVRM_PLLD_DISPLAY_MIN_KHZ);
+ TargetFreq =
+ ((NVRM_PLLD_DISPLAY_MIN_KHZ / TargetFreq) + 1) * TargetFreq;
+ }
+ NV_ASSERT(TargetFreq <= MaxFreq);
+ NvRmPrivAp15PllConfigureSimple(
+ hRmDevice, NvRmClockSource_PllD0, MaxFreq, &TargetFreq);
+ }
+
+ // Update DSI clock state (PLLD is a single source, no divider)
+ pCstate->SourceClock = 0;
+ pCstate->Divider = 1;
+ pCstate->actual_freq =
+ NvRmPrivGetClockSourceFreq(NvRmClockSource_PllD0);
+ NvRmPrivModuleVscaleReAttach(hRmDevice,
+ pCinfo, pCstate, pCstate->actual_freq, pCstate->actual_freq);
+}
+
+/*****************************************************************************/
+/*****************************************************************************/
+
+static void
+Ap15DisplayClockConfigure(
+ NvRmDeviceHandle hRmDevice,
+ NvRmModuleClockInfo *pCinfo,
+ NvRmFreqKHz MinFreq,
+ NvRmFreqKHz MaxFreq,
+ NvRmFreqKHz TargetFreq,
+ NvRmModuleClockState* pCstate,
+ NvU32 flags)
+{
+ NvU32 i;
+ NvRmClockSource SourceId;
+ NvRmFreqKHz PixelFreq = TargetFreq;
+ NvRmFreqKHz SourceClockFreq = NvRmPrivGetClockSourceFreq(NvRmClockSource_ClkM);
+
+ /*
+ * Display clock source selection policy:
+ * - if MIPI flag is specified - use PLLD, and reconfigure it as necessary
+ * - else if Oscillator output provides required accuracy - use Oscillator
+ * - else if PLLP fixed output provides required accuracy - use fixed PLLP
+ * - else if PPLC is used by other head - use PLLD, and reconfigure it as
+ * necessary
+ * - else - use use PLLC, and reconfigure it as necessary
+ */
+ if (flags & NvRmClockConfig_MipiSync)
+ {
+ // PLLD requested
+ SourceId = NvRmClockSource_PllD0;
+ Ap15PllDConfigure(hRmDevice, TargetFreq);
+ }
+ else if (NvRmIsFreqRangeReachable(
+ SourceClockFreq, MinFreq, MaxFreq, NVRM_DISPLAY_DIVIDER_MAX))
+ {
+ // Target frequency is reachable from Oscillator - nothing to do
+ SourceId = NvRmClockSource_ClkM;
+ }
+ else if (NvRmIsFreqRangeReachable(NVRM_PLLP_FIXED_FREQ_KHZ,
+ MinFreq, MaxFreq, NVRM_DISPLAY_DIVIDER_MAX))
+ {
+ // Target frequency is reachable from PLLP0 - make sure it is enabled
+ SourceId = NvRmClockSource_PllP0;
+ Ap15PllPConfigure(hRmDevice);
+ }
+ else if (NvRmPrivIsSourceSelectedByModule(hRmDevice, NvRmClockSource_PllC0,
+ NVRM_MODULE_ID(pCinfo->Module, (1 - pCinfo->Instance))))
+ {
+ // PLLC is used by the other head - only PLLD left
+ SourceId = NvRmClockSource_PllD0;
+ Ap15PllDConfigure(hRmDevice, TargetFreq);
+ }
+ else
+ {
+ // PLLC is available - use it
+ SourceId = NvRmClockSource_PllC0;
+ TargetFreq = NvRmPrivGetMaxFreqPllC(hRmDevice); // Target PLLC max
+ if (!NvRmIsFreqRangeReachable(
+ TargetFreq, MinFreq, MaxFreq, NVRM_DISPLAY_DIVIDER_MAX))
+ {
+ TargetFreq = MaxFreq; // Target pixel range max
+ }
+ NvRmPrivReConfigurePllC(hRmDevice, TargetFreq);
+ }
+
+ // Fill in clock state
+ for (i = 0; i < NvRmClockSource_Num; i++)
+ {
+ if (pCinfo->Sources[i] == SourceId)
+ break;
+ }
+ NV_ASSERT(i < NvRmClockSource_Num);
+ pCstate->SourceClock = i; // source index
+ pCstate->Divider = 1; // no divider (display driver has its own)
+ pCstate->actual_freq = NvRmPrivGetClockSourceFreq(SourceId); // source KHz
+ NV_ASSERT(NvRmIsFreqRangeReachable(
+ pCstate->actual_freq, MinFreq, MaxFreq, NVRM_DISPLAY_DIVIDER_MAX));
+
+ if (flags & NvRmClockConfig_SubConfig)
+ {
+ NvRmModuleClockInfo* pTvDacInfo = NULL;
+ NvRmModuleClockState* pTvDacState = NULL;
+ NV_ASSERT_SUCCESS(NvRmPrivGetClockState(
+ hRmDevice, NvRmModuleID_Tvo, &pTvDacInfo, &pTvDacState));
+
+ // TVDAC is the 2nd TVO subclock (CVE is the 1st one)
+ pTvDacInfo += 2;
+ pTvDacState += 2;
+ NV_ASSERT(pTvDacInfo->Module == NvRmModuleID_Tvo);
+ NV_ASSERT(pTvDacInfo->SubClockId == 2);
+
+ // enable the tvdac clock
+ if ((hRmDevice->ChipId.Id == 0x15) || (hRmDevice->ChipId.Id == 0x16))
+ Ap15EnableTvDacClock(hRmDevice, ModuleClockState_Enable);
+ else if (hRmDevice->ChipId.Id == 0x20)
+ Ap20EnableTvDacClock(hRmDevice, ModuleClockState_Enable);
+
+ // Set TVDAC = pixel clock (same source index and calculate divider
+ // exactly as dc_hal.c does)
+ pTvDacState->SourceClock = i;
+ pTvDacState->Divider =
+ (((pCstate->actual_freq * 2 ) + PixelFreq / 2) / PixelFreq) - 2;
+ pTvDacState->actual_freq =
+ (pCstate->actual_freq * 2 ) / (pTvDacState->Divider + 2);
+ NvRmPrivModuleClockSet(hRmDevice, pTvDacInfo, pTvDacState);
+ }
+ if (flags & NvRmClockConfig_DisableTvDAC)
+ {
+ // disable the tvdac clock
+ if ((hRmDevice->ChipId.Id == 0x15) || (hRmDevice->ChipId.Id == 0x16))
+ Ap15EnableTvDacClock(hRmDevice, ModuleClockState_Disable);
+ else if (hRmDevice->ChipId.Id == 0x20)
+ Ap20EnableTvDacClock(hRmDevice, ModuleClockState_Disable);
+ }
+}
+
+NvBool
+NvRmPrivAp15IsModuleClockException(
+ NvRmDeviceHandle hRmDevice,
+ NvRmModuleClockInfo *pCinfo,
+ NvU32 ClockSourceCount,
+ NvRmFreqKHz MinFreq,
+ NvRmFreqKHz MaxFreq,
+ const NvRmFreqKHz* PrefFreqList,
+ NvU32 PrefCount,
+ NvRmModuleClockState* pCstate,
+ NvU32 flags)
+{
+ NV_ASSERT(hRmDevice);
+ NV_ASSERT(pCinfo && PrefFreqList && pCstate);
+
+ switch (pCinfo->Module)
+ {
+ case NvRmModuleID_Display:
+ /*
+ * Special handling for display clocks. Must satisfy requirements
+ * for the 1st requested frequency and complete configuration.
+ * Note that AP15 display divider is within module itself, so the
+ * input request is for pisxel clock, but output *pCstate specifies
+ * source frequency. Display driver will configure divider.
+ */
+ Ap15DisplayClockConfigure(hRmDevice, pCinfo,
+ MinFreq, MaxFreq, PrefFreqList[0], pCstate, flags);
+ return NV_TRUE;
+
+ case NvRmModuleID_Dsi:
+ /*
+ * Reconfigure PLLD to match requested frequency, and update DSI
+ * clock state.
+ */
+ Ap15PllDConfigure(hRmDevice, PrefFreqList[0]);
+ NV_ASSERT((MinFreq <= pCstate->actual_freq) &&
+ (pCstate->actual_freq <= MaxFreq));
+ return NV_TRUE;
+
+ case NvRmModuleID_Hdmi:
+ /*
+ * Complete configuration from PLLD if requested (PLLD should be already
+ * configured properly for display)
+ */
+ if (flags & NvRmClockConfig_MipiSync)
+ {
+ NvRmFreqKHz FreqKHz =
+ NvRmPrivGetClockSourceFreq(NvRmClockSource_PllD0);
+ pCstate->SourceClock = 1; // PLLD source index
+ pCstate->Divider = ((FreqKHz << 2) + PrefFreqList[0]) /
+ (PrefFreqList[0] << 1) - 2;
+ pCstate->actual_freq = (FreqKHz << 1) / (pCstate->Divider + 2);
+ NV_ASSERT(pCinfo->Sources[pCstate->SourceClock] ==
+ NvRmClockSource_PllD0);
+ NV_ASSERT(pCstate->Divider <= pCinfo->DivisorFieldMask);
+ NV_ASSERT((MinFreq <= pCstate->actual_freq) &&
+ (pCstate->actual_freq <= MaxFreq));
+ return NV_TRUE;
+ }
+ return NV_FALSE;
+
+ case NvRmModuleID_Spdif:
+ if (flags & NvRmClockConfig_SubConfig)
+ return NV_FALSE; // Nothing special for SPDIFIN
+ // fall through for SPDIFOUT
+ case NvRmModuleID_I2s:
+ /*
+ * If requested, reconfigure PLLA to match target frequency, and
+ * complete clock configuration with PLLA as a source. Otherwise,
+ * make sure PLLA is enabled (at current configuration), and
+ * continue regular configuration for SPDIFOUT and I2S.
+ */
+ if (flags & NvRmClockConfig_AudioAdjust)
+ {
+ NvRmFreqKHz FreqKHz = PrefFreqList[0];
+ Ap15PllAConfigure(hRmDevice, &FreqKHz);
+
+ pCstate->SourceClock = 0; // PLLA source index
+ pCstate->Divider = ((FreqKHz << 2) + PrefFreqList[0]) /
+ (PrefFreqList[0] << 1) - 2;
+ pCstate->actual_freq = (FreqKHz << 1) / (pCstate->Divider + 2);
+ if (NvRmPrivGetExecPlatform(hRmDevice) == ExecPlatform_Fpga)
+ { // Fake return on FPGA (PLLA is not configurable, anyway)
+ pCstate->actual_freq = PrefFreqList[0];
+ }
+ NV_ASSERT(pCinfo->Sources[pCstate->SourceClock] ==
+ NvRmClockSource_PllA0);
+ NV_ASSERT(pCstate->Divider <= pCinfo->DivisorFieldMask);
+ NV_ASSERT((MinFreq <= pCstate->actual_freq) &&
+ (pCstate->actual_freq <= MaxFreq));
+ return NV_TRUE;
+ }
+ Ap15PllAControl(hRmDevice, NV_TRUE);
+ return NV_FALSE;
+
+ case NvRmModuleID_Usb2Otg:
+ /*
+ * Reconfigure PLLU to match requested frequency, and complete USB
+ * clock configuration (PLLU is a single source, no divider)
+ */
+ Ap15PllUConfigure(hRmDevice, PrefFreqList[0]);
+ pCstate->SourceClock = 0;
+ pCstate->Divider = 1;
+ pCstate->actual_freq = PrefFreqList[0];
+ return NV_TRUE;
+
+ default:
+ // No exception for other modules - continue regular configuration
+ return NV_FALSE;
+ }
+}
+
+/*****************************************************************************/
+
+void
+NvRmPrivAp15DisablePLLs(
+ NvRmDeviceHandle hRmDevice,
+ const NvRmModuleClockInfo* pCinfo,
+ const NvRmModuleClockState* pCstate)
+{
+#if !NV_OAL
+ switch (pCinfo->Module)
+ {
+ case NvRmModuleID_Display:
+ NvRmPrivBoostPllC(hRmDevice);
+ Ap15PllDControl(hRmDevice, NV_FALSE);
+ break;
+
+ case NvRmModuleID_Spdif:
+ case NvRmModuleID_I2s:
+ Ap15PllAControl(hRmDevice, NV_FALSE);
+ break;
+
+ default:
+ break;
+ }
+#endif
+}
+
+void
+NvRmPrivAp15PllDPowerControl(
+ NvRmDeviceHandle hRmDevice,
+ NvBool ConfigEntry,
+ NvBool* pMipiPllVddOn)
+{
+#if !NV_OAL
+ if (ConfigEntry)
+ {
+ // On entry to display clock configuration get PLLD power ready
+ if (!(*pMipiPllVddOn))
+ {
+ NvRmPrivPmuRailControl(hRmDevice, NV_VDD_PLLD_ODM_ID, NV_TRUE);
+ *pMipiPllVddOn = NV_TRUE;
+ }
+ }
+ else
+ {
+ // On exit from display clock configuration turn off PLLD power
+ // if it is disabled
+ if ((*pMipiPllVddOn) &&
+ (NvRmPrivGetClockSourceFreq(NvRmClockSource_PllD0) <=
+ NvRmPrivGetClockSourceFreq(NvRmClockSource_ClkM)))
+ {
+ NvRmPrivPmuRailControl(hRmDevice, NV_VDD_PLLD_ODM_ID, NV_FALSE);
+ *pMipiPllVddOn = NV_FALSE;
+ }
+ }
+#endif
+}
+
+void
+NvRmPrivConfigureClockSource(
+ NvRmDeviceHandle hRmDevice,
+ NvRmModuleID ModuleId,
+ NvBool enable)
+{
+ // Extract module and instance from composite module id.
+ NvU32 Module = NVRM_MODULE_ID_MODULE( ModuleId );
+
+ switch (Module)
+ {
+ case NvRmModuleID_Usb2Otg:
+ // Do not disable the PLLU clock once it is enabled
+ // Set PLLU default configuration if it is not already configured
+ if (enable)
+ Ap15PllUConfigure(hRmDevice, NvRmFreqUnspecified);
+ break;
+#if !NV_OAL
+ case NvRmModuleID_Spdif:
+ case NvRmModuleID_I2s:
+ if (enable)
+ {
+ // Do not enable if PLLA is not used as a source for any clock
+ if (NvRmPrivGetDfsFlags(hRmDevice) & NvRmDfsStatusFlags_StopPllA0)
+ break;
+ }
+ // fall through
+ case NvRmModuleID_Mpe:
+ Ap15PllAControl(hRmDevice, enable);
+ break;
+
+ case NvRmModuleID_Dsi:
+ Ap15PllDControl(hRmDevice, enable);
+ break;
+
+ case NvRmPrivModuleID_Pcie:
+ NvRmPrivAp20PllEControl(hRmDevice, enable);
+ break;
+#endif
+ default:
+ break;
+ }
+ return;
+}
+
+/*****************************************************************************/
+/*****************************************************************************/
+
+/*
+ * Basic DFS clock control policy outline:
+ * - Oscillator ClkM, doubler ClkD, and memory PLLM0 - always available, fixed
+ * frequency sources.
+ * - Peripheral PLLP0 may be dynamically enabled / disabled when DFS is stopped
+ * and CPU is power gated. Hence, when DFS is running it is always enabled
+ * and configured at fixed PLLP0 frequency.
+ * - Cpu PLLC0 may be dynamically enabled / disabled when DFS is stopped and
+ * CPU is power gated. Hence, when DFS is running it is always enabled. PLLC0
+ * is commonly configured at maximum CPU domain frequency. If necessary, it
+ * may be adjusted to provide required display pixel clock frequency.
+ * - System buses, and MC/EMC configuration, clock source multiplexes and
+ * dividers, as well as PLLP2, PLLP4 and PLLM1 dividers are under exclusive
+ * DFS control, and are not accessed by any other code except bootloader
+ * before RM is open.
+ */
+
+// Limit frequencies ratio for Vpipe : System >= 1 : 2^(value - 1)
+#define LIMIT_SYS_TO_VDE_RATIO (2)
+
+// Limit frequencies ratio for AHB : System >= 1:2 and APB : System >= 1 : 4
+#define LIMIT_SYS_TO_AHB_APB_RATIOS (1)
+
+// PLLP2 must be used as a variable source for System clock.
+#define PLLP_POLICY_ENTRY(KHz) \
+ { NvRmClockSource_PllP2,\
+ (NVRM_PLLP_FIXED_FREQ_KHZ * 2)/((NVRM_PLLP_FIXED_FREQ_KHZ * 2)/KHz),\
+ ((NVRM_PLLP_FIXED_FREQ_KHZ * 2)/KHz - 2)\
+ },
+static const NvRmDfsSource s_Ap15PllPSystemClockPolicy[] =
+{
+ NVRM_AP15_PLLP_POLICY_SYSTEM_CLOCK
+};
+static const NvU32 s_Ap15PllPSystemClockPolicyEntries =
+ NV_ARRAY_SIZE(s_Ap15PllPSystemClockPolicy);
+#undef PLLP_POLICY_ENTRY
+
+
+// PLLP4 must be used as a variable source for cpu clock.
+#define PLLP_POLICY_ENTRY(KHz) \
+ { NvRmClockSource_PllP4,\
+ (NVRM_PLLP_FIXED_FREQ_KHZ * 2)/((NVRM_PLLP_FIXED_FREQ_KHZ * 2)/KHz),\
+ ((NVRM_PLLP_FIXED_FREQ_KHZ * 2)/KHz - 2)\
+ },
+static const NvRmDfsSource s_Ap15PllPCpuClockPolicy[] =
+{
+ NVRM_AP15_PLLP_POLICY_CPU_CLOCK
+};
+static const NvU32 s_Ap15PllPCpuClockPolicyEntries =
+ NV_ARRAY_SIZE(s_Ap15PllPCpuClockPolicy);
+#undef PLLP_POLICY_ENTRY
+
+/*
+ * Sorted list of timing parameters for discrete set of EMC frequencies used
+ * by DFS: entry 0 specifies timing parameters for PLLM0 output frequency,
+ * entry n (n = 1, 2, ... number of EMC steps-1) specifies timing parameters
+ * for EMC frequency = PLLM0 frequency / (2 * n); thus only frequencies evenly
+ * divided down from PLLM0 will be used by DFS
+ */
+static NvRmAp15EmcTimingConfig
+s_Ap15EmcConfigSortedTable[NVRM_AP15_DFS_EMC_FREQ_STEPS];
+
+static struct MemClocksRec
+{
+ // Index of selected EMC configuration entry
+ NvU32 Index;
+
+ // Pointers to EMC and MC clock descriptors
+ NvRmModuleClockInfo* pEmcInfo;
+ NvRmModuleClockInfo* pMcInfo;
+
+ // Pointers to EMC and MC clock state records
+ NvRmModuleClockState* pEmcState;
+ NvRmModuleClockState* pMcState;
+
+} s_MemClocks = {0};
+
+static const NvU32 s_Cpu2EmcRatioPolicyTable[] =
+{
+ NVRM_AP15_CPU_EMC_RATIO_POLICY
+};
+
+/*****************************************************************************/
+
+static void
+Ap15Emc2xFreqGet(
+ NvRmDeviceHandle hRmDevice)
+{
+ NvU32 reg;
+ NvRmFreqKHz SourceClockFreq;
+ NvRmModuleClockInfo* pCinfo = s_MemClocks.pEmcInfo;
+ NvRmModuleClockState* pCstate = s_MemClocks.pEmcState;
+
+ NV_ASSERT(pCinfo && pCstate);
+
+ // Determine EMC2x source and divider setting; update EMC2x clock state
+ reg = NV_REGR(hRmDevice,
+ NvRmPrivModuleID_ClockAndReset, 0, pCinfo->ClkSourceOffset);
+ pCstate->Divider =
+ ((reg >> pCinfo->DivisorFieldShift) & pCinfo->DivisorFieldMask);
+ pCstate->SourceClock =
+ ((reg >> pCinfo->SourceFieldShift) & pCinfo->SourceFieldMask);
+ SourceClockFreq =
+ NvRmPrivGetClockSourceFreq(pCinfo->Sources[pCstate->SourceClock]);
+
+ // Fractional divider output = (Source Frequency * 2) / (divider + 2)
+ pCstate->actual_freq = ((SourceClockFreq << 1) / (pCstate->Divider + 2));
+}
+
+// Enable/Disable EMC low-latency return-fifo reservation scheme
+// (enable requires confirmation polling)
+#define NVRM_AP15_EMCLL_RETRSV_ENABLE \
+do\
+{\
+ NvU32 reg; \
+ reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, \
+ 0, EMC_LL_ARB_CONFIG_0); \
+ reg = NV_FLD_SET_DRF_DEF( \
+ EMC, LL_ARB_CONFIG, LL_RETRSV_ENABLE, ENABLED, reg); \
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, \
+ 0, EMC_LL_ARB_CONFIG_0, reg); \
+ while (reg != NV_REGR(hRmDevice, NvRmPrivModuleID_ExternalMemoryController,\
+ 0, EMC_LL_ARB_CONFIG_0)) \
+ ; \
+} while(0)
+
+#define NVRM_AP15_EMCLL_RETRSV_DISABLE \
+do\
+{\
+ NvU32 reg; \
+ reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, \
+ 0, EMC_LL_ARB_CONFIG_0); \
+ reg = NV_FLD_SET_DRF_DEF( \
+ EMC, LL_ARB_CONFIG, LL_RETRSV_ENABLE, DISABLED, reg); \
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, \
+ 0, EMC_LL_ARB_CONFIG_0, reg); \
+} while (0)
+
+void
+NvRmPrivAp15SetEmcForCpuSrcSwitch(NvRmDeviceHandle hRmDevice)
+{
+ NVRM_AP15_EMCLL_RETRSV_ENABLE;
+}
+
+void
+NvRmPrivAp15SetEmcForCpuDivSwitch(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz CpuFreq,
+ NvBool Before)
+{
+ NvRmFreqKHz EmcFreq = (s_MemClocks.pEmcState->actual_freq >> 1);
+ if (Before && (CpuFreq < EmcFreq))
+ {
+ NVRM_AP15_EMCLL_RETRSV_ENABLE;
+ }
+ else if (!Before && (CpuFreq >= EmcFreq))
+ {
+ NVRM_AP15_EMCLL_RETRSV_DISABLE;
+ }
+}
+
+static void
+Ap15EmcTimingSet(
+ NvRmDeviceHandle hRmDevice,
+ NvBool FreqRising,
+ NvBool BeforeDividerChange,
+ const NvRmAp15EmcTimingConfig* pEmcConfig)
+{
+ // Write shadow timing registers
+ if (FreqRising == BeforeDividerChange) // "overlap down" parameters
+ {
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_TIMING0_0, pEmcConfig->Timing0Reg);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_TIMING1_0, pEmcConfig->Timing1Reg);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_TIMING2_0, pEmcConfig->Timing2Reg);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_TIMING3_0, pEmcConfig->Timing3Reg);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_TIMING5_0, pEmcConfig->Timing5Reg);
+ }
+ else // "overlap up" parameters
+ {
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_TIMING4_0, pEmcConfig->Timing4Reg);
+
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_FBIO_CFG6_0, pEmcConfig->FbioCfg6Reg);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_FBIO_DQSIB_DLY_0, pEmcConfig->FbioDqsibDly);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_FBIO_QUSE_DLY_0, pEmcConfig->FbioQuseDly);
+ }
+ // Trigger active register update from shadow
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_TIMING_CONTROL_0, 0x1);
+
+ // Make sure update from shadow is completed
+ if (FreqRising == BeforeDividerChange)
+ {
+ while((NV_REGR(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_TIMING0_0)) != pEmcConfig->Timing0Reg);
+ }
+ else
+ {
+ while((NV_REGR(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_TIMING4_0)) != pEmcConfig->Timing4Reg);
+ // Re-trigger active register update (need it for trimmers only)
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ExternalMemoryController, 0,
+ EMC_TIMING_CONTROL_0, 0x1);
+ }
+}
+
+static void
+Ap15Emc2xClockSet(
+ NvRmDeviceHandle hRmDevice,
+ NvBool FreqRising,
+ const NvRmAp15EmcTimingConfig* pEmcConfig)
+{
+ NvU32 reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_CLK_SOURCE_EMC_0);
+ reg = NV_FLD_SET_DRF_NUM(CLK_RST_CONTROLLER, CLK_SOURCE_EMC,
+ EMC_2X_CLK_DIVISOR, pEmcConfig->Emc2xDivisor, reg);
+ NV_ASSERT(pEmcConfig->Emc2xKHz); // validate table entry
+
+ // Update EMC state
+ s_MemClocks.pEmcState->actual_freq = pEmcConfig->Emc2xKHz;
+ s_MemClocks.pEmcState->Divider = pEmcConfig->Emc2xDivisor;
+
+ // Set EMC parameters and EMC divisor (the EMC clock source is always
+ // PLLM0 starting from BL)
+ Ap15EmcTimingSet(hRmDevice, FreqRising, NV_TRUE, pEmcConfig);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_CLK_SOURCE_EMC_0, reg);
+ Ap15EmcTimingSet(hRmDevice, FreqRising, NV_FALSE, pEmcConfig);
+}
+
+static void
+Ap15McClockSet(
+ NvRmDeviceHandle hRmDevice,
+ const NvRmAp15EmcTimingConfig* pEmcConfig)
+{
+ NvU32 src, div;
+ NvU32 reg = NV_REGR(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_CLK_SOURCE_MEM_0);
+ src = NV_DRF_VAL(CLK_RST_CONTROLLER, CLK_SOURCE_MEM, MEM_CLK_SRC, reg);
+ div = NV_DRF_VAL(CLK_RST_CONTROLLER, CLK_SOURCE_MEM, MEM_CLK_DIVISOR, reg);
+
+ // Update MC state
+ s_MemClocks.pMcState->actual_freq = pEmcConfig->McKHz;
+ s_MemClocks.pMcState->SourceClock = pEmcConfig->McClockSource;
+ s_MemClocks.pMcState->Divider = pEmcConfig->McDivisor;
+
+ // Set MC divisor before source, if new value is bigger than the old one
+ if (pEmcConfig->McDivisor > div)
+ {
+ reg = NV_FLD_SET_DRF_NUM(CLK_RST_CONTROLLER, CLK_SOURCE_MEM,
+ MEM_CLK_DIVISOR, pEmcConfig->McDivisor, reg);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_CLK_SOURCE_MEM_0, reg);
+ NvOsWaitUS(NVRM_CLOCK_CHANGE_DELAY);
+ }
+
+ // Modify MC source if it is to be changed
+ if (pEmcConfig->McClockSource != src)
+ {
+ NvRmPrivMemoryClockReAttach(
+ hRmDevice, s_MemClocks.pMcInfo, s_MemClocks.pMcState);
+ reg = NV_FLD_SET_DRF_NUM(CLK_RST_CONTROLLER, CLK_SOURCE_MEM,
+ MEM_CLK_SRC, pEmcConfig->McClockSource, reg);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_CLK_SOURCE_MEM_0, reg);
+ NvOsWaitUS(NVRM_CLOCK_CHANGE_DELAY);
+ }
+
+ // Set MC divisor after source, if new value is smaller than the old one
+ if (pEmcConfig->McDivisor < div)
+ {
+ reg = NV_FLD_SET_DRF_NUM(CLK_RST_CONTROLLER, CLK_SOURCE_MEM,
+ MEM_CLK_DIVISOR, pEmcConfig->McDivisor, reg);
+ NV_REGW(hRmDevice, NvRmPrivModuleID_ClockAndReset, 0,
+ CLK_RST_CONTROLLER_CLK_SOURCE_MEM_0, reg);
+ NvOsWaitUS(NVRM_CLOCK_CHANGE_DELAY);
+ }
+}
+
+void
+NvRmPrivAp15EmcConfigInit(NvRmDeviceHandle hRmDevice)
+{
+ NvU32 i, j, k, reg=0;
+ NvU32 ConfigurationsCount;
+ NvRmFreqKHz Emc2xKHz, McKHz, McMax;
+ NvRmFreqKHz PllM0KHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_PllM0);
+ const NvOdmSdramControllerConfig* pEmcConfigurations =
+ NvOdmQuerySdramControllerConfigGet(&ConfigurationsCount, &reg);
+
+ // Init memory configuration structure
+ NV_ASSERT_SUCCESS(NvRmPrivGetClockState(
+ hRmDevice, NvRmPrivModuleID_ExternalMemoryController,
+ &s_MemClocks.pEmcInfo, &s_MemClocks.pEmcState));
+ NV_ASSERT_SUCCESS(NvRmPrivGetClockState(
+ hRmDevice, NvRmPrivModuleID_MemoryController,
+ &s_MemClocks.pMcInfo, &s_MemClocks.pMcState));
+ s_MemClocks.Index = NVRM_AP15_DFS_EMC_FREQ_STEPS; // invalid index
+ NvOsMemset(s_Ap15EmcConfigSortedTable, 0, // clean table
+ sizeof(s_Ap15EmcConfigSortedTable));
+
+ // Get EMC2x clock state from h/w
+ Ap15Emc2xFreqGet(hRmDevice);
+
+ // Check if configuration table is provided by ODM
+ if ((ConfigurationsCount == 0) || (pEmcConfigurations == NULL))
+ {
+ s_Ap15EmcConfigSortedTable[0].Emc2xKHz = 0; // invalidate PLLM0 entry
+ return;
+ }
+ if (reg != NV_EMC_BASIC_REV)
+ {
+ s_Ap15EmcConfigSortedTable[0].Emc2xKHz = 0; // invalidate PLLM0 entry
+ NV_ASSERT(!"Invalid configuration table revision");
+ return;
+ }
+
+ // Check PLLM0 range
+ NV_ASSERT(PllM0KHz);
+ if (PllM0KHz > (NvRmPrivGetSocClockLimits(
+ NvRmPrivModuleID_ExternalMemoryController)->MaxKHz))
+ {
+ s_Ap15EmcConfigSortedTable[0].Emc2xKHz = 0; // invalidate PLLM0 entry
+ NV_ASSERT(!"PLLM0 is outside supported EMC range");
+ return;
+ }
+
+ // Check if PLLM0 is configured by boot loader as EMC clock source
+ // (it can not and will not be changed by RM)
+ if (s_MemClocks.pEmcState->SourceClock !=
+ CLK_RST_CONTROLLER_CLK_SOURCE_EMC_0_EMC_2X_CLK_SRC_PLLM_OUT0)
+ {
+ s_Ap15EmcConfigSortedTable[0].Emc2xKHz = 0; // invalidate PLLM0 entry
+ NV_ASSERT(!"Other than PLLM0 clock source is used for EMC");
+ return;
+ }
+
+ // Sort list of EMC timing parameters in descending order of frequencies
+ // evenly divided down from PLLM0; find matching entry for boot divisor
+ for (i = 0, k = 0, Emc2xKHz = PllM0KHz; i < NVRM_AP15_DFS_EMC_FREQ_STEPS; )
+ {
+ s_Ap15EmcConfigSortedTable[i].Emc2xKHz = 0; // mark entry invalid
+ for (j = 0; j < ConfigurationsCount; j++)
+ {
+ // Find match with 1MHz tolerance for allowed configuration
+ if ((Emc2xKHz <= (pEmcConfigurations[j].SdramKHz * 2 + 1000)) &&
+ (Emc2xKHz >= (pEmcConfigurations[j].SdramKHz * 2 - 1000)))
+ {
+ s_Ap15EmcConfigSortedTable[i].Timing0Reg = pEmcConfigurations[j].EmcTiming0;
+ s_Ap15EmcConfigSortedTable[i].Timing1Reg = pEmcConfigurations[j].EmcTiming1;
+ s_Ap15EmcConfigSortedTable[i].Timing2Reg = pEmcConfigurations[j].EmcTiming2;
+ s_Ap15EmcConfigSortedTable[i].Timing3Reg = pEmcConfigurations[j].EmcTiming3;
+ s_Ap15EmcConfigSortedTable[i].Timing4Reg = pEmcConfigurations[j].EmcTiming4;
+ s_Ap15EmcConfigSortedTable[i].Timing5Reg = pEmcConfigurations[j].EmcTiming5;
+
+ s_Ap15EmcConfigSortedTable[i].FbioCfg6Reg =
+ pEmcConfigurations[j].EmcFbioCfg6;
+ s_Ap15EmcConfigSortedTable[i].FbioDqsibDly =
+ pEmcConfigurations[j].EmcFbioDqsibDly +
+ NvRmPrivGetEmcDqsibOffset(hRmDevice);
+ s_Ap15EmcConfigSortedTable[i].FbioQuseDly =
+ pEmcConfigurations[j].EmcFbioQuseDly;
+ s_Ap15EmcConfigSortedTable[i].CoreVoltageMv =
+ pEmcConfigurations[j].EmcCoreVoltageMv;
+
+ // Determine EMC and MC clock divisors, MC clock source
+ // (EMC always uses PLLM0 as a source), and CPU clock limit
+ s_Ap15EmcConfigSortedTable[i].Emc2xKHz = Emc2xKHz; // accurate KHz
+ if (i == 0)
+ {
+ /*
+ * The first table entry specifies parameters for EMC2xFreq
+ * = PLLM0 frequency; the divisor field in EMC fractional
+ * divider register is set to "0". The divisor field in MC
+ * divider is set to "1", so that Emc1xFreq ~ 75% of McFreq
+ * using PLLM0 as MC clock source, if maximum MC frequency
+ * limit is not violated. Otherwise, find the highest MC
+ * frequency below the limit with PLLP0 as a source.
+ */
+ s_Ap15EmcConfigSortedTable[i].Emc2xDivisor = 0;
+ McKHz = (PllM0KHz * 2) / 3;
+ McMax = NvRmPrivGetSocClockLimits(
+ NvRmPrivModuleID_MemoryController)->MaxKHz;
+ NV_ASSERT(McMax);
+ if (McKHz <= McMax)
+ {
+ s_Ap15EmcConfigSortedTable[i].McDivisor = 1;
+ s_Ap15EmcConfigSortedTable[i].McClockSource =
+ CLK_RST_CONTROLLER_CLK_SOURCE_MEM_0_MEM_CLK_SRC_PLLM_OUT0;
+ }
+ else if (NVRM_PLLP_FIXED_FREQ_KHZ <= McMax)
+ {
+ McKHz = NVRM_PLLP_FIXED_FREQ_KHZ;
+ s_Ap15EmcConfigSortedTable[i].McDivisor = 0;
+ s_Ap15EmcConfigSortedTable[i].McClockSource =
+ CLK_RST_CONTROLLER_CLK_SOURCE_MEM_0_MEM_CLK_SRC_PLLP_OUT0;
+ }
+ else
+ {
+ reg = (2 * NVRM_PLLP_FIXED_FREQ_KHZ + McMax - 1) / McMax;
+ McKHz = (2 * NVRM_PLLP_FIXED_FREQ_KHZ) / reg;
+ s_Ap15EmcConfigSortedTable[i].McDivisor = reg - 2;
+ s_Ap15EmcConfigSortedTable[i].McClockSource =
+ CLK_RST_CONTROLLER_CLK_SOURCE_MEM_0_MEM_CLK_SRC_PLLP_OUT0;
+ }
+ }
+ else
+ {
+ /*
+ * If i = 1, 2, ... the table entry specifies parameters
+ * for EMC2xFreq = PLLM0 frequency/(2 * k); the divisor
+ * field in EMC fractional divider register should be set
+ * as 2 * (2 * k) - 2 = 4 * k - 2. The divisor field in MC
+ * divider is determined so that Emc1xFreq ~ 85% of McFreq
+ * using the same PLLM0 as MC clock source
+ */
+ s_Ap15EmcConfigSortedTable[i].Emc2xDivisor = (k << 2) - 2;
+ s_Ap15EmcConfigSortedTable[i].McDivisor = (19 +
+ 17 * s_Ap15EmcConfigSortedTable[i].Emc2xDivisor) / 10;
+ s_Ap15EmcConfigSortedTable[i].McClockSource =
+ CLK_RST_CONTROLLER_CLK_SOURCE_MEM_0_MEM_CLK_SRC_PLLM_OUT0;
+ McKHz = 2 * PllM0KHz /
+ (s_Ap15EmcConfigSortedTable[i].McDivisor + 2);
+ }
+ if (s_Ap15EmcConfigSortedTable[i].Emc2xDivisor ==
+ s_MemClocks.pEmcState->Divider)
+ {
+ s_MemClocks.Index = i; // Boot configuration found
+ }
+ s_Ap15EmcConfigSortedTable[i].McKHz = McKHz;
+ /*
+ * H/w CPU clock limit is determined from inequality:
+ * 1 mcclk period + 12 cpuclk periods >= 2 emcclck periods, or
+ * CpuKHz <= 11.9 * McKHz * Emc2xKHz / (4 * McKHz - Emc2xKHz)
+ * with 0.1/12 ~ 0.8% margin
+ * S/w CPU clock limit is determined per s/w policy:
+ * CpuKHz <= CpuMax * PolicyTabel[PLLM0/(2*EMC2xKHz)] / 256
+ * Final CPU clock limit is minimum of the above limits
+ */
+ s_Ap15EmcConfigSortedTable[i].CpuLimitKHz =
+ (NvU32)NvDiv64(((NvU64)Emc2xKHz * McKHz * 119),
+ (((McKHz << 2) - Emc2xKHz) * 10));
+ reg = NvRmPrivGetSocClockLimits(NvRmModuleID_Cpu)->MaxKHz;
+ if (k != 0)
+ {
+ NV_ASSERT(k < NV_ARRAY_SIZE(s_Cpu2EmcRatioPolicyTable));
+ reg = (reg * s_Cpu2EmcRatioPolicyTable[k]) >> 8;
+ }
+ if (s_Ap15EmcConfigSortedTable[i].CpuLimitKHz > reg)
+ s_Ap15EmcConfigSortedTable[i].CpuLimitKHz = reg;
+
+ break;
+ }
+ }
+ if (s_Ap15EmcConfigSortedTable[i].Emc2xKHz != 0)
+ i++; // Entry found - advance sorting index
+ else if (i == 0)
+ break; // PLLM0 entry not found - abort sorting
+
+ Emc2xKHz = PllM0KHz / ((++k) << 1);
+ if (Emc2xKHz < NvRmPrivGetSocClockLimits(
+ NvRmPrivModuleID_ExternalMemoryController)->MinKHz)
+ break; // Abort sorting at minimum EMC frequency
+ }
+ // Check if match for boot configuration found
+ if (s_MemClocks.Index == NVRM_AP15_DFS_EMC_FREQ_STEPS)
+ s_Ap15EmcConfigSortedTable[0].Emc2xKHz = 0; // invalidate PLLM0 entry
+}
+
+static NvBool
+Ap15Emc2xClockSourceFind(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz MaxKHz,
+ NvRmFreqKHz DomainKHz,
+ NvRmFreqKHz* pCpuTargetKHz,
+ NvRmDfsSource* pDfsSource)
+{
+ NvU32 i;
+ NvBool FinalStep = NV_TRUE;
+ NV_ASSERT(DomainKHz <= MaxKHz);
+ pDfsSource->DividerSetting = 0; // no divider
+
+ // If PLLM0 entry in EMC frequeuncies table is invalid, EMC frequency
+ // will not be scaled; just fill in current EMC frequency
+ if (s_Ap15EmcConfigSortedTable[0].Emc2xKHz == 0)
+ {
+ pDfsSource->SourceId = NvRmClockSource_Invalid;
+ pDfsSource->SourceKHz = s_MemClocks.pEmcState->actual_freq;
+ pDfsSource->MinMv = NvRmVoltsMaximum; // no v-scaling in this case
+ return FinalStep;
+ }
+
+ // Only PLLM0 is used as EMC frequency source by DFS; its frequency is
+ // always within h/w limits
+ pDfsSource->SourceId = NvRmClockSource_PllM0;
+ NV_ASSERT(s_Ap15EmcConfigSortedTable[0].Emc2xKHz <= MaxKHz);
+
+ // Search sorted pre-defind EMC frequencies (divided down from PLLM0) for
+ // the entry above and closest to the traget that also has CPU limit above
+ // the CPU target. Use PLLM0 entry if not found.
+ for (i = NVRM_AP15_DFS_EMC_FREQ_STEPS; i > 0;)
+ {
+ i--;
+ if ((DomainKHz <= s_Ap15EmcConfigSortedTable[i].Emc2xKHz) &&
+ (*pCpuTargetKHz <= s_Ap15EmcConfigSortedTable[i].CpuLimitKHz))
+ break;
+ }
+
+ // Make sure the new entry is adjacent to the current (one step at a time)
+ if (i > (s_MemClocks.Index + 1))
+ {
+ i = s_MemClocks.Index + 1;
+ FinalStep = NV_FALSE; // need more steps to reach target
+ }
+ else if ((i + 1) < s_MemClocks.Index)
+ {
+ i = s_MemClocks.Index - 1;
+ FinalStep = NV_FALSE; // need more steps to reach target
+ }
+
+ // Record found EMC entry, and limit CPU target if necessary
+ pDfsSource->DividerSetting = i;
+ pDfsSource->SourceKHz = s_Ap15EmcConfigSortedTable[i].Emc2xKHz;
+ if (*pCpuTargetKHz > s_Ap15EmcConfigSortedTable[i].CpuLimitKHz)
+ *pCpuTargetKHz = s_Ap15EmcConfigSortedTable[i].CpuLimitKHz;
+ pDfsSource->MinMv = s_Ap15EmcConfigSortedTable[i].CoreVoltageMv;
+ return FinalStep;
+}
+
+static void
+Ap15Emc2xClockConfigure(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz MaxKHz,
+ NvRmFreqKHz* pDomainKHz,
+ const NvRmDfsSource* pDfsSource)
+{
+ NvU32 Index;
+ NvRmFreqKHz CpuFreq = NvRmPrivGetClockSourceFreq(NvRmClockSource_CpuBus);
+
+ // Always return the requested source frequency
+ *pDomainKHz = pDfsSource->SourceKHz;
+ NV_ASSERT(*pDomainKHz);
+
+ // If other than PLLM0 source is selected, EMC frequency is not scaled.
+ if (pDfsSource->SourceId != NvRmClockSource_PllM0)
+ return;
+
+ // Divider settings in EMC source descriptor is an index into the table of
+ // pre-defined EMC configurations in descending frequency order.
+ Index = pDfsSource->DividerSetting;
+ if (Index == s_MemClocks.Index)
+ return; // do nothing new index is the same as current
+
+ // In case of EMC frequency increase: check if EMC LL reservation should
+ // be enabled, reconfigure EMC, then MC (make sure MC never exceeds EMC2x)
+ // In case of EMC frequency decrease: reconfigure MC, then EMC (make sure
+ // MC never exceeds EMC2x) and check if EMC LL reservation can be disabled
+ if (Index < s_MemClocks.Index)
+ {
+ if (CpuFreq < (*pDomainKHz >> 1))
+ {
+ NVRM_AP15_EMCLL_RETRSV_ENABLE;
+ }
+ Ap15Emc2xClockSet(
+ hRmDevice, NV_TRUE, &s_Ap15EmcConfigSortedTable[Index]);
+ Ap15McClockSet(hRmDevice, &s_Ap15EmcConfigSortedTable[Index]);
+ }
+ else
+ {
+ Ap15McClockSet(hRmDevice, &s_Ap15EmcConfigSortedTable[Index]);
+ Ap15Emc2xClockSet(
+ hRmDevice, NV_FALSE, &s_Ap15EmcConfigSortedTable[Index]);
+
+ if (CpuFreq >= (*pDomainKHz >> 1))
+ {
+ NVRM_AP15_EMCLL_RETRSV_DISABLE;
+ }
+ }
+ s_MemClocks.Index = Index;
+}
+
+void
+NvRmPrivAp15FastClockConfig(NvRmDeviceHandle hRmDevice)
+{
+#if !NV_OAL
+ NvU32 divm1, divp2;
+ NvRmFreqKHz SclkKHz, CpuKHz, PllP2KHz, PllM1KHz;
+ NvRmFreqKHz FreqKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_PllM0);
+
+ // Set fastest EMC/MC configuration provided PLLM0 boot frequency matches
+ // one of the pre-defined configurations, i.e, it is the first entry in the
+ // sorted table
+ if (s_Ap15EmcConfigSortedTable[0].Emc2xKHz == FreqKHz)
+ {
+ for (;;)
+ {
+ Ap15Emc2xClockSet(
+ hRmDevice, NV_TRUE, &s_Ap15EmcConfigSortedTable[s_MemClocks.Index]);
+ Ap15McClockSet(hRmDevice, &s_Ap15EmcConfigSortedTable[s_MemClocks.Index]);
+ if (s_MemClocks.Index == 0)
+ break;
+ s_MemClocks.Index--;
+ }
+ }
+
+ // Set AVP/System Bus clock (now, with nominal core voltage it can be up
+ // to SoC maximum). First determine settings for PLLP and PLLM dividers
+ // to get maximum possible frequency on PLLP_OUT2 and PLLM_OUT1 outputs.
+ SclkKHz = NvRmPrivGetSocClockLimits(NvRmPrivModuleID_System)->MaxKHz;
+ NV_ASSERT(SclkKHz);
+
+ FreqKHz = NVRM_PLLP_FIXED_FREQ_KHZ;
+ PllP2KHz = SclkKHz;
+ divp2 = NvRmPrivFindFreqMaxBelow(
+ NvRmClockDivider_Fractional_2, FreqKHz, PllP2KHz, &PllP2KHz);
+
+ FreqKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_PllM0);
+ PllM1KHz = SclkKHz;
+ divm1 = NvRmPrivFindFreqMaxBelow(
+ NvRmClockDivider_Fractional_2, FreqKHz, PllM1KHz, &PllM1KHz);
+
+ // Now configure both dividers and select the output with highest frequency
+ // as a source for the system bus clock; reconfigure MIO as necessary
+ SclkKHz = NV_MAX(PllM1KHz, PllP2KHz);
+ FreqKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_SystemBus);
+ if (FreqKHz < SclkKHz)
+ {
+ Ap15MioReconfigure(hRmDevice, SclkKHz);
+ }
+ NvRmPrivDividerSet(
+ hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllP2)->pInfo.pDivider,
+ divp2);
+ NvRmPrivDividerSet(
+ hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_PllM1)->pInfo.pDivider,
+ divm1);
+ if (SclkKHz == PllP2KHz)
+ {
+ NvRmPrivCoreClockSet(hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_SystemBus)->pInfo.pCore,
+ NvRmClockSource_PllP2, 0, 0);
+ }
+ else
+ {
+ NvRmPrivCoreClockSet(hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_SystemBus)->pInfo.pCore,
+ NvRmClockSource_PllM1, 0, 0);
+ }
+ if (FreqKHz >= SclkKHz)
+ {
+ Ap15MioReconfigure(hRmDevice, SclkKHz);
+ }
+ NvRmPrivBusClockInit(hRmDevice, SclkKHz);
+
+ // Set PLLC and CPU clock to SoC maximum - can be done now, when core
+ // voltage is guaranteed to be nominal, provided none of the display
+ // heads is already using PLLC as pixel clock source.
+ CpuKHz = NvRmPrivGetSocClockLimits(NvRmModuleID_Cpu)->MaxKHz;
+ FreqKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_PllC0);
+ if (CpuKHz != FreqKHz)
+ {
+ NvRmPrivBoostPllC(hRmDevice);
+ }
+ NvRmPrivCoreClockSet(hRmDevice,
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_CpuBus)->pInfo.pCore,
+ NvRmClockSource_PllC0, 0, 0);
+#endif
+}
+
+void
+NvRmPrivAp15ClipCpuEmcHighLimits(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz* pCpuHighKHz,
+ NvRmFreqKHz* pEmcHighKHz)
+{
+#if !NV_OAL
+ NvU32 i;
+ NvRmFreqKHz EmcKHz;
+ NvRmFreqKHz MinKHz = NvRmPrivDfsGetMinKHz(NvRmDfsClockId_Emc);
+ NV_ASSERT(pEmcHighKHz && pCpuHighKHz);
+
+ // Nothing to do if no EMC scaling.
+ if (s_Ap15EmcConfigSortedTable[0].Emc2xKHz == 0)
+ return;
+
+ // Clip strategy: "throttling" - find the floor for EMC high limit
+ // (above domain minimum, of course)
+ if ((*pEmcHighKHz) < MinKHz)
+ *pEmcHighKHz = MinKHz;
+ for (i = 0; i < NVRM_AP15_DFS_EMC_FREQ_STEPS; i++)
+ {
+ EmcKHz = s_Ap15EmcConfigSortedTable[i].Emc2xKHz >> 1;
+ if (EmcKHz <= (*pEmcHighKHz))
+ break;
+ }
+ if ((i == NVRM_AP15_DFS_EMC_FREQ_STEPS) || (EmcKHz < MinKHz))
+ {
+ i--;
+ EmcKHz = s_Ap15EmcConfigSortedTable[i].Emc2xKHz >> 1;
+ }
+ *pEmcHighKHz = EmcKHz;
+
+ // Clip strategy: "throttling" - restrict CPU high limit by EMC
+ // configuration ((above domain minimum, of course)
+ if ((*pCpuHighKHz) > s_Ap15EmcConfigSortedTable[i].CpuLimitKHz)
+ (*pCpuHighKHz) = s_Ap15EmcConfigSortedTable[i].CpuLimitKHz;
+ if ((*pCpuHighKHz) < NvRmPrivDfsGetMinKHz(NvRmDfsClockId_Cpu))
+ *pCpuHighKHz = NvRmPrivDfsGetMinKHz(NvRmDfsClockId_Cpu);
+#endif
+}
+
+NvRmFreqKHz
+NvRmPrivAp15GetEmcSyncFreq(
+ NvRmDeviceHandle hRmDevice,
+ NvRmModuleID Module)
+{
+ NvRmFreqKHz FreqKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_PllM0);
+
+ switch (Module)
+ {
+ case NvRmModuleID_2D:
+ case NvRmModuleID_Epp:
+ // 2D/EPP frequency is dynamically synchronized with current EMC speed
+ // (high if EMC divisor 0, and low otherwise)
+ if (s_MemClocks.pEmcState && (s_MemClocks.pEmcState->Divider != 0))
+ FreqKHz = FreqKHz / NVRM_PLLM_2D_LOW_SPEED_RATIO;
+ else
+ FreqKHz = FreqKHz / NVRM_PLLM_2D_HIGH_SPEED_RATIO;
+ break;
+
+ case NvRmModuleID_GraphicsHost:
+ // Host frequency is static, synchronized with EMC range set by BCT
+ FreqKHz = FreqKHz / NVRM_PLLM_HOST_SPEED_RATIO;
+ break;
+
+ default:
+ NV_ASSERT(!"Invalid module for EMC synchronization");
+ FreqKHz = NvRmPrivGetSocClockLimits(Module)->MaxKHz;
+ break;
+ }
+ return FreqKHz;
+}
+
+/*****************************************************************************/
+
+static void
+Ap15SystemClockSourceFind(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz MaxKHz,
+ NvRmFreqKHz DomainKHz,
+ NvRmDfsSource* pDfsSource)
+{
+ NvU32 i;
+ NvRmFreqKHz SourceKHz;
+ NV_ASSERT(DomainKHz <= MaxKHz);
+ pDfsSource->DividerSetting = 0; // no divider
+
+ // 1st try oscillator
+ SourceKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_ClkM);
+ NV_ASSERT(SourceKHz <= MaxKHz);
+ if (DomainKHz <= SourceKHz)
+ {
+ pDfsSource->SourceId = NvRmClockSource_ClkM;
+ pDfsSource->SourceKHz = SourceKHz;
+ goto get_mv;
+ }
+
+ // 2nd choice - doubler
+ SourceKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_ClkD);
+ NV_ASSERT(SourceKHz <= MaxKHz);
+ if (DomainKHz <= SourceKHz)
+ {
+ pDfsSource->SourceId = NvRmClockSource_ClkD;
+ pDfsSource->SourceKHz = SourceKHz;
+ goto get_mv;
+ }
+
+ /*
+ * 3rd option - PLLP divider per policy specification. Find
+ * the policy entry with source frequency closest and above requested.
+ * If requested frequency exceeds all policy options within domain
+ * maximum limit, select the entry with the highest possible frequency.
+ */
+ for (i = 0; i < s_Ap15PllPSystemClockPolicyEntries; i++)
+ {
+ SourceKHz = s_Ap15PllPSystemClockPolicy[i].SourceKHz;
+ if (SourceKHz > MaxKHz)
+ {
+ NV_ASSERT(i);
+ i--;
+ break;
+ }
+ if (DomainKHz <= SourceKHz)
+ {
+ break;
+ }
+ }
+ if (i == s_Ap15PllPSystemClockPolicyEntries)
+ {
+ i--; // last/highest source is the best we can do
+ }
+ pDfsSource->SourceId = s_Ap15PllPSystemClockPolicy[i].SourceId;
+ pDfsSource->SourceKHz = s_Ap15PllPSystemClockPolicy[i].SourceKHz;
+ pDfsSource->DividerSetting = s_Ap15PllPSystemClockPolicy[i].DividerSetting;
+
+ /*
+ * 4st and final option - PLLM divider fixed at maximum possible frequency
+ * during initialization. Select PLLP/PLLM divider according to the
+ * following rule: select the divider with smaller frequency if it is equal
+ * or above the target frequency, otherwise select the divider with bigger
+ * output frequency.
+ */
+ SourceKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_PllM1);
+ NV_ASSERT(SourceKHz <= MaxKHz);
+ if (SourceKHz > pDfsSource->SourceKHz)
+ {
+ if (pDfsSource->SourceKHz >= DomainKHz)
+ goto get_mv; // keep PLLP divider as a source
+ }
+ else // SourceKHz <= pDfsSource->SourceKHz
+ {
+ if (SourceKHz < DomainKHz)
+ goto get_mv; // keep PLLP divider as a source
+ }
+ // Select PLLM_OUT1 divider as a source (considered as a fixed source -
+ // divider settings are ignored)
+ pDfsSource->SourceId = NvRmClockSource_PllM1;
+ pDfsSource->SourceKHz = SourceKHz;
+
+get_mv:
+ // Finally get operational voltage for found source
+ pDfsSource->MinMv = NvRmPrivModuleVscaleGetMV(
+ hRmDevice, NvRmPrivModuleID_System, pDfsSource->SourceKHz);
+}
+
+static void
+Ap15CpuClockSourceFind(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz MaxKHz,
+ NvRmFreqKHz DomainKHz,
+ NvRmDfsSource* pDfsSource)
+{
+ NvU32 i;
+ NvRmFreqKHz SourceKHz;
+ NV_ASSERT(DomainKHz <= MaxKHz);
+ pDfsSource->DividerSetting = 0; // no divider
+
+ // 1st try oscillator
+ SourceKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_ClkM);
+ NV_ASSERT(SourceKHz <= MaxKHz);
+ if (DomainKHz <= SourceKHz)
+ {
+ pDfsSource->SourceId = NvRmClockSource_ClkM;
+ pDfsSource->SourceKHz = SourceKHz;
+ goto get_mv;
+ }
+
+ // 2nd choice - doubler - no longer supported
+ // 3rd choice - PLLP divider per policy specification
+ SourceKHz =
+ s_Ap15PllPCpuClockPolicy[s_Ap15PllPCpuClockPolicyEntries-1].SourceKHz;
+ NV_ASSERT(SourceKHz <= MaxKHz);
+ if (DomainKHz <= SourceKHz)
+ {
+ // The requested frequency is within PLLP divider policy table, and all
+ // policy entries are within domain maximum limit. Then, find the entry
+ // with source frequency closest and above the requested.
+ for (i = 0; i < s_Ap15PllPCpuClockPolicyEntries; i++)
+ {
+ SourceKHz = s_Ap15PllPCpuClockPolicy[i].SourceKHz;
+ if (DomainKHz <= SourceKHz)
+ break;
+ }
+ if (s_Ap15PllPCpuClockPolicy[i].DividerSetting == 0)
+ pDfsSource->SourceId = NvRmClockSource_PllP0; // Bypass 1:1 divider
+ else
+ pDfsSource->SourceId = s_Ap15PllPCpuClockPolicy[i].SourceId;
+ pDfsSource->SourceKHz = s_Ap15PllPCpuClockPolicy[i].SourceKHz;
+ pDfsSource->DividerSetting = s_Ap15PllPCpuClockPolicy[i].DividerSetting;
+ goto get_mv;
+ }
+
+ // 4th choice PLLM base output
+ SourceKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_PllM0);
+ NV_ASSERT(SourceKHz <= MaxKHz);
+ if (DomainKHz <= SourceKHz)
+ {
+ pDfsSource->SourceId = NvRmClockSource_PllM0;
+ pDfsSource->SourceKHz = SourceKHz;
+ goto get_mv;
+ }
+
+ // 5th choice PLLP base output (not used - covered by 3rd choice, case 1:1)
+ // 6th and final choice - PLLC base output at domain limit
+ pDfsSource->SourceId = NvRmClockSource_PllC0;
+ pDfsSource->SourceKHz = MaxKHz;
+
+get_mv:
+ // Finally get operational voltage for found source
+ pDfsSource->MinMv = NvRmPrivModuleVscaleGetMV(
+ hRmDevice, NvRmModuleID_Cpu, pDfsSource->SourceKHz);
+}
+
+static void
+Ap15SystemBusClockConfigure(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz MaxKHz,
+ NvRmFreqKHz* pDomainKHz,
+ const NvRmDfsSource* pDfsSource)
+{
+ NvRmClockSource SourceId = pDfsSource->SourceId;
+ const NvRmCoreClockInfo* pCinfo =
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_SystemBus)->pInfo.pCore;
+
+ switch(SourceId)
+ {
+ case NvRmClockSource_PllP2:
+ // Reconfigure PLLP variable divider if it is used as a source
+ NvRmPrivDividerSet(hRmDevice,
+ NvRmPrivGetClockSourceHandle(SourceId)->pInfo.pDivider,
+ pDfsSource->DividerSetting);
+ // fall through
+ case NvRmClockSource_PllM1:
+ case NvRmClockSource_ClkD:
+ case NvRmClockSource_ClkM:
+ break; // fixed sources - do nothing
+ default:
+ NV_ASSERT(!"Invalid source (per policy)");
+ }
+ NV_ASSERT_SUCCESS(NvRmPrivCoreClockConfigure(
+ hRmDevice, pCinfo, MaxKHz, pDomainKHz, &SourceId));
+}
+
+static void
+Ap15CpuBusClockConfigure(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz MaxKHz,
+ NvRmFreqKHz* pDomainKHz,
+ const NvRmDfsSource* pDfsSource)
+{
+ NvRmClockSource SourceId = pDfsSource->SourceId;
+ const NvRmCoreClockInfo* pCinfo =
+ NvRmPrivGetClockSourceHandle(NvRmClockSource_CpuBus)->pInfo.pCore;
+
+ switch(SourceId)
+ {
+ case NvRmClockSource_PllC0:
+ // DFS PLLC policy - configure PLLC if disabled; otherwise keep
+ // keep it as is (the latter means either DFS has already set it
+ // to domain limit, or PLLC is used as display pixel clock source)
+ if (NvRmPrivGetClockSourceFreq(NvRmClockSource_PllC0) <=
+ NvRmPrivGetClockSourceFreq(NvRmClockSource_ClkM))
+ {
+ NvRmFreqKHz TargetKHz = pDfsSource->SourceKHz;
+ NvRmPrivAp15PllConfigureSimple(
+ hRmDevice, SourceId, MaxKHz, &TargetKHz);
+ }
+ break;
+ case NvRmClockSource_PllP4:
+ // Reconfigure PLLP variable divider if it is used as a source;
+ // If source frequency is going down, get EMC configuration is ready
+ if (pDfsSource->SourceKHz < NvRmPrivGetClockSourceFreq(SourceId))
+ NvRmPrivAp15SetEmcForCpuSrcSwitch(hRmDevice);
+ NvRmPrivDividerSet(hRmDevice,
+ NvRmPrivGetClockSourceHandle(SourceId)->pInfo.pDivider,
+ pDfsSource->DividerSetting);
+ // fall through
+ case NvRmClockSource_PllP0:
+ case NvRmClockSource_PllM0:
+ case NvRmClockSource_ClkD:
+ case NvRmClockSource_ClkM:
+ break; // fixed sources - do nothing
+ default:
+ NV_ASSERT(!"Invalid source (per policy)");
+ }
+ NV_ASSERT_SUCCESS(NvRmPrivCoreClockConfigure(
+ hRmDevice, pCinfo, MaxKHz, pDomainKHz, &SourceId));
+}
+
+/*****************************************************************************/
+/* If time is specified in ns, and frequency in KHz, then cycles =
+ * (ns * KHz / 10^6) = (ns * (KHz * 2^20 / 10^6) / 2^20) = (ns * KiHz / 2^20),
+ * where KiHz = (KHz * 2^20 / 10^6) ~ (KHz * 4295 / 4096) with error < 0.001%.
+ */
+#define NVRM_TIME_TO_CYCLES(ns, KiHz) (((ns * KiHz) + (0x1 << 20)- 1) >> 20)
+
+#define NV_DRF_MAX_NUM(d,r,f,n) \
+ ((((n) <= NV_FIELD_MASK(d##_##r##_0_##f##_RANGE)) ? \
+ (n) : NV_FIELD_MASK(d##_##r##_0_##f##_RANGE)) << \
+ NV_FIELD_SHIFT(d##_##r##_0_##f##_RANGE))
+
+static void
+Ap15MioReconfigure(
+ NvRmDeviceHandle hRmDevice,
+ NvRmFreqKHz MioKHz)
+{
+ NvU32 reg, mask;
+ NvU32 MioKiHz = ((MioKHz * 4295) >> 12);
+ NvOdmAsynchMemConfig MemConfig;
+ /*
+ * Reconfigure MIO timing when clock frequency changes. Check only Async
+ * Memory devices connected to CS1/MIO_B and CS3/MIO_A (CS0 is dedicated
+ * for NOR, we do not care after boot, and CS2 is dedicated to SDRAM with
+ * its own clock)
+ */
+ if (NvOdmQueryAsynchMemConfig(1, &MemConfig) == NV_TRUE)
+ {
+ reg = NV_REGR(hRmDevice, NvRmModuleID_Misc, 0, APB_MISC_PP_XMB_MIO_CFG_0);
+ mask =
+ NV_DRF_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_B_WR_DEAD_TIME, 0xFFFFFFFFUL) |
+ NV_DRF_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_B_WR_TIME, 0xFFFFFFFFUL) |
+ NV_DRF_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_B_RD_DEAD_TIME, 0xFFFFFFFFUL) |
+ NV_DRF_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_B_RD_TIME, 0xFFFFFFFFUL);
+ reg = (reg & (~mask)) |
+ NV_DRF_MAX_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_B_WR_DEAD_TIME,
+ NVRM_TIME_TO_CYCLES(MemConfig.WriteDeadTime, MioKiHz)) |
+ NV_DRF_MAX_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_B_WR_TIME,
+ NVRM_TIME_TO_CYCLES(MemConfig.WriteAccessTime, MioKiHz)) |
+ NV_DRF_MAX_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_B_RD_DEAD_TIME,
+ NVRM_TIME_TO_CYCLES(MemConfig.ReadDeadTime, MioKiHz)) |
+ NV_DRF_MAX_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_B_RD_TIME,
+ NVRM_TIME_TO_CYCLES(MemConfig.ReadAccessTime, MioKiHz));
+ NV_REGW(hRmDevice, NvRmModuleID_Misc, 0, APB_MISC_PP_XMB_MIO_CFG_0, reg);
+ }
+ if (NvOdmQueryAsynchMemConfig(3, &MemConfig) == NV_TRUE)
+ {
+ reg = NV_REGR(hRmDevice, NvRmModuleID_Misc, 0, APB_MISC_PP_XMB_MIO_CFG_0);
+ mask =
+ NV_DRF_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_A_WR_DEAD_TIME, 0xFFFFFFFFUL) |
+ NV_DRF_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_A_WR_TIME, 0xFFFFFFFFUL) |
+ NV_DRF_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_A_RD_DEAD_TIME, 0xFFFFFFFFUL) |
+ NV_DRF_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_A_RD_TIME, 0xFFFFFFFFUL);
+ reg = (reg & (~mask)) |
+ NV_DRF_MAX_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_A_WR_DEAD_TIME,
+ NVRM_TIME_TO_CYCLES(MemConfig.WriteDeadTime, MioKiHz)) |
+ NV_DRF_MAX_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_A_WR_TIME,
+ NVRM_TIME_TO_CYCLES(MemConfig.WriteAccessTime, MioKiHz)) |
+ NV_DRF_MAX_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_A_RD_DEAD_TIME,
+ NVRM_TIME_TO_CYCLES(MemConfig.ReadDeadTime, MioKiHz)) |
+ NV_DRF_MAX_NUM(APB_MISC_PP, XMB_MIO_CFG, MIO_A_RD_TIME,
+ NVRM_TIME_TO_CYCLES(MemConfig.ReadAccessTime, MioKiHz));
+ NV_REGW(hRmDevice, NvRmModuleID_Misc, 0, APB_MISC_PP_XMB_MIO_CFG_0, reg);
+ }
+}
+
+NvBool NvRmPrivAp15DfsClockConfigure(
+ NvRmDeviceHandle hRmDevice,
+ const NvRmDfsFrequencies* pMaxKHz,
+ NvRmDfsFrequencies* pDfsKHz)
+{
+ NvU32 i;
+ NvBool Status;
+ NvRmFreqKHz FreqKHz;
+ NvRmDfsSource CpuClockSource;
+ NvRmDfsSource SystemClockSource;
+ NvRmDfsSource Emc2xClockSource;
+ NvBool CpuKHzUp = pDfsKHz->Domains[NvRmDfsClockId_Cpu] >
+ NvRmPrivGetClockSourceFreq(NvRmClockSource_CpuBus);
+
+ NV_ASSERT(hRmDevice);
+ NV_ASSERT(pMaxKHz && pDfsKHz);
+
+ /*
+ * Adjust System bus core clock. It should be sufficient to supply AVP,
+ * and all bus clocks. Also make sure that AHB bus frequency is above
+ * the one requested for APB clock.
+ */
+ for (FreqKHz = 0, i = 1; i < NvRmDfsClockId_Num; i++)
+ {
+ if ((i != NvRmDfsClockId_Cpu) &&
+ (i != NvRmDfsClockId_Emc))
+ {
+ FreqKHz = (FreqKHz > pDfsKHz->Domains[i]) ?
+ FreqKHz : pDfsKHz->Domains[i];
+ }
+ }
+ pDfsKHz->Domains[NvRmDfsClockId_System] = FreqKHz;
+
+#if LIMIT_SYS_TO_VDE_RATIO
+ if (pDfsKHz->Domains[NvRmDfsClockId_Vpipe] <
+ (FreqKHz >> (LIMIT_SYS_TO_VDE_RATIO - 1)))
+ {
+ pDfsKHz->Domains[NvRmDfsClockId_Vpipe] =
+ (FreqKHz >> (LIMIT_SYS_TO_VDE_RATIO - 1));
+ }
+#endif
+
+#if LIMIT_SYS_TO_AHB_APB_RATIOS
+ if (pDfsKHz->Domains[NvRmDfsClockId_Apb] < (FreqKHz >> 2))
+ {
+ pDfsKHz->Domains[NvRmDfsClockId_Apb] = (FreqKHz >> 2);
+ }
+ if (pDfsKHz->Domains[NvRmDfsClockId_Ahb] < (FreqKHz >> 1))
+ {
+ pDfsKHz->Domains[NvRmDfsClockId_Ahb] = (FreqKHz >> 1);
+ }
+#endif
+ if (pDfsKHz->Domains[NvRmDfsClockId_Ahb] <
+ pDfsKHz->Domains[NvRmDfsClockId_Apb])
+ {
+ pDfsKHz->Domains[NvRmDfsClockId_Ahb] =
+ pDfsKHz->Domains[NvRmDfsClockId_Apb];
+ }
+
+ // Find clock sources for CPU, System and Memory clocks. H/w requirement
+ // to increase memory clocks in steps, may limit CPU clock as well
+ Ap15SystemClockSourceFind(hRmDevice,
+ pMaxKHz->Domains[NvRmDfsClockId_System],
+ pDfsKHz->Domains[NvRmDfsClockId_System],
+ &SystemClockSource);
+ Status = Ap15Emc2xClockSourceFind(hRmDevice,
+ (pMaxKHz->Domains[NvRmDfsClockId_Emc] << 1),
+ (pDfsKHz->Domains[NvRmDfsClockId_Emc] << 1),
+ &pDfsKHz->Domains[NvRmDfsClockId_Cpu],
+ &Emc2xClockSource);
+ Ap15CpuClockSourceFind(hRmDevice,
+ pMaxKHz->Domains[NvRmDfsClockId_Cpu],
+ pDfsKHz->Domains[NvRmDfsClockId_Cpu],
+ &CpuClockSource);
+
+#if !NV_OAL
+ // Adjust core voltage for the new clock sources before actual change
+ NvRmPrivVoltageScale(NV_TRUE, CpuClockSource.MinMv,
+ SystemClockSource.MinMv, Emc2xClockSource.MinMv);
+#endif
+
+ // Configure System bus and derived clocks. Note that APB is the only
+ // clock in system complex that may have different (lower) maximum
+ // limit - pass it explicitly to set function.
+ if (FreqKHz < NvRmPrivGetClockSourceFreq(NvRmClockSource_SystemBus))
+ FreqKHz = NvRmPrivGetClockSourceFreq(NvRmClockSource_SystemBus);
+ Ap15MioReconfigure(hRmDevice, FreqKHz); // MIO timing for max frequency
+ Ap15SystemBusClockConfigure(hRmDevice,
+ pMaxKHz->Domains[NvRmDfsClockId_System],
+ &pDfsKHz->Domains[NvRmDfsClockId_System],
+ &SystemClockSource);
+ pDfsKHz->Domains[NvRmDfsClockId_Avp] =
+ pDfsKHz->Domains[NvRmDfsClockId_System]; // no AVP clock skipping
+ NvRmPrivBusClockFreqSet(hRmDevice,
+ pDfsKHz->Domains[NvRmDfsClockId_System],
+ &pDfsKHz->Domains[NvRmDfsClockId_Vpipe],
+ &pDfsKHz->Domains[NvRmDfsClockId_Ahb],
+ &pDfsKHz->Domains[NvRmDfsClockId_Apb],
+ pMaxKHz->Domains[NvRmDfsClockId_Apb]);
+ Ap15MioReconfigure(hRmDevice, pDfsKHz->Domains[NvRmDfsClockId_Ahb]);
+
+ // Configure CPU core clock before Memory if CPU frequency goes down
+ if (!CpuKHzUp)
+ {
+ Ap15CpuBusClockConfigure(hRmDevice,
+ pMaxKHz->Domains[NvRmDfsClockId_Cpu],
+ &pDfsKHz->Domains[NvRmDfsClockId_Cpu],
+ &CpuClockSource);
+ }
+ // Configure Memory clocks and convert frequency to DFS EMC 1x domain
+ FreqKHz = pDfsKHz->Domains[NvRmDfsClockId_Emc] << 1;
+ Ap15Emc2xClockConfigure(hRmDevice,
+ (pMaxKHz->Domains[NvRmDfsClockId_Emc] << 1),
+ &FreqKHz, &Emc2xClockSource);
+ pDfsKHz->Domains[NvRmDfsClockId_Emc] = FreqKHz >> 1;
+ // Configure CPU core clock after Memory if CPU frequency goes up
+ if (CpuKHzUp)
+ {
+ Ap15CpuBusClockConfigure(hRmDevice,
+ pMaxKHz->Domains[NvRmDfsClockId_Cpu],
+ &pDfsKHz->Domains[NvRmDfsClockId_Cpu],
+ &CpuClockSource);
+ }
+
+#if !NV_OAL
+ // Adjust core voltage for the new clock sources after actual change
+ NvRmPrivVoltageScale(NV_FALSE, CpuClockSource.MinMv,
+ SystemClockSource.MinMv, Emc2xClockSource.MinMv);
+#endif
+ return Status;
+}
+
+void
+NvRmPrivAp15DfsClockFreqGet(
+ NvRmDeviceHandle hRmDevice,
+ NvRmDfsFrequencies* pDfsKHz)
+{
+ NvRmFreqKHz SystemFreq;
+ const NvRmCoreClockInfo* pCinfo;
+ NV_ASSERT(hRmDevice && pDfsKHz);
+
+ // Get frequencies of the System core clock, AVP clock (the same as System
+ // - no clock skipping), AHB, APB, and V-pipe bus clock frequencies
+ pCinfo = NvRmPrivGetClockSourceHandle(NvRmClockSource_SystemBus)->pInfo.pCore;
+ SystemFreq = NvRmPrivCoreClockFreqGet(hRmDevice, pCinfo);
+ pDfsKHz->Domains[NvRmDfsClockId_System] = SystemFreq;
+ pDfsKHz->Domains[NvRmDfsClockId_Avp] = SystemFreq;
+
+ NvRmPrivBusClockFreqGet(
+ hRmDevice, SystemFreq,
+ &pDfsKHz->Domains[NvRmDfsClockId_Vpipe],
+ &pDfsKHz->Domains[NvRmDfsClockId_Ahb],
+ &pDfsKHz->Domains[NvRmDfsClockId_Apb]);
+
+ // Get CPU core clock frequencies
+ pCinfo = NvRmPrivGetClockSourceHandle(NvRmClockSource_CpuBus)->pInfo.pCore;
+ pDfsKHz->Domains[NvRmDfsClockId_Cpu] =
+ NvRmPrivCoreClockFreqGet(hRmDevice, pCinfo);
+
+ // Get EMC clock frequency (DFS monitors EMC 1x domain)
+ Ap15Emc2xFreqGet(hRmDevice); // Get EMC2x clock state from h/w
+ pDfsKHz->Domains[NvRmDfsClockId_Emc] =
+ (s_MemClocks.pEmcState->actual_freq >> 1);
+}
+
+void
+NvRmPrivAp15DfsVscaleFreqGet(
+ NvRmDeviceHandle hRmDevice,
+ NvRmMilliVolts TargetMv,
+ NvRmDfsFrequencies* pDfsKHz)
+{
+ NvU32 i;
+ NvRmMilliVolts v;
+ NvRmFreqKHz Fa, Fb, f;
+ NvRmDfsSource DfsClockSource;
+ NvRmFreqKHz CpuMaxKHz = NvRmPrivGetSocClockLimits(NvRmModuleID_Cpu)->MaxKHz;
+ NvRmFreqKHz SysMaxKHz =
+ NvRmPrivGetSocClockLimits(NvRmPrivModuleID_System)->MaxKHz;
+ NV_ASSERT(hRmDevice && pDfsKHz);
+
+ // If PLLM0 entry in EMC scaling table is valid, search the table for
+ // the entry below and closest to the traget voltage. Otherwise, there
+ // is no EMC scaling - just return current EMC frequency.
+ pDfsKHz->Domains[NvRmDfsClockId_Emc] =
+ (s_MemClocks.pEmcState->actual_freq >> 1);
+ f = NvRmFreqMaximum; // assume CPU is not throttled by EMC
+ if (s_Ap15EmcConfigSortedTable[0].Emc2xKHz != 0)
+ {
+ for (i = 0; i < (NVRM_AP15_DFS_EMC_FREQ_STEPS - 1); i++)
+ {
+ if ((s_Ap15EmcConfigSortedTable[i+1].Emc2xKHz == 0) ||
+ (s_Ap15EmcConfigSortedTable[i].CoreVoltageMv <= TargetMv))
+ break; // exit if found entry or next entry is invalid
+ }
+ pDfsKHz->Domains[NvRmDfsClockId_Emc] =
+ (s_Ap15EmcConfigSortedTable[i].Emc2xKHz >> 1);
+ f = s_Ap15EmcConfigSortedTable[i].CpuLimitKHz; // throttle CPU
+ }
+
+ // Binary search for maximum CPU frequency, with source that can be used
+ // at target voltage or below
+ Fb = NV_MIN(CpuMaxKHz, f);
+ Fa = NvRmPrivGetClockSourceFreq(NvRmClockSource_ClkM);
+ NV_ASSERT(Fa <= Fb);
+ while ((Fb - Fa) > 1000) // 1MHz resolution
+ {
+ f = (Fa + Fb) >> 1;
+ Ap15CpuClockSourceFind(hRmDevice, CpuMaxKHz, f, &DfsClockSource);
+ v = DfsClockSource.MinMv;
+ if (v <= TargetMv)
+ Fa = f;
+ else
+ Fb = f;
+ }
+ pDfsKHz->Domains[NvRmDfsClockId_Cpu] = Fa;
+
+ // Binary search for maximum System/Avp frequency, with source that can be used
+ // at target voltage or below
+ Fb = SysMaxKHz;
+ Fa = NvRmPrivGetClockSourceFreq(NvRmClockSource_ClkM);
+ NV_ASSERT(Fa <= Fb);
+ while ((Fb - Fa) > 1000) // 1MHz resolution
+ {
+ f = (Fa + Fb) >> 1;
+ Ap15SystemClockSourceFind(hRmDevice, SysMaxKHz, f, &DfsClockSource);
+ v = DfsClockSource.MinMv;
+ if (v <= TargetMv)
+ Fa = f;
+ else
+ Fb = f;
+ }
+ pDfsKHz->Domains[NvRmDfsClockId_System] = Fa;
+ pDfsKHz->Domains[NvRmDfsClockId_Avp] = Fa;
+ pDfsKHz->Domains[NvRmDfsClockId_Ahb] = Fa;
+ pDfsKHz->Domains[NvRmDfsClockId_Apb] = Fa;
+ pDfsKHz->Domains[NvRmDfsClockId_Vpipe] = Fa;
+}
+
+/*****************************************************************************/
+