linux-clk.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/8] Add EMC scaling support for Tegra210
@ 2019-03-25  7:45 Joseph Lo
  2019-03-25  7:45 ` [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings Joseph Lo
                   ` (6 more replies)
  0 siblings, 7 replies; 28+ messages in thread
From: Joseph Lo @ 2019-03-25  7:45 UTC (permalink / raw)
  To: Thierry Reding, Peter De Schrijver, Jonathan Hunter, Rob Herring,
	Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree, Joseph Lo

This series introduces the EMC clock scaling support for Tegra210. There
are three parts of this series.

i. The DT bindings of the EMC table and how to deal with them.
To support LPDDR4 DRAM devices, the EMC table with timing data must be
trained before it can be used. The bootloader firmware will help to do
that at the early boot stage. And help to merge the trained timing data
into the DTB that EMC driver in the kernel will use later for EMC clock
scaling.

And for backward compatibility to support the upstreaming devices, like
Jetson TX1 and Shield, the bindings should remain the same. Which means
we may not able to change the bindings currently.

ii. The EMC driver will use the trained EMC table with the ram code that
the platform used to update the EMC registers for clock scaling. And to
support higher rates (above 800MHz), periodic training is needed. This
will be done by a timer. And two EMC sources will be used (PLL_M and
		PLL_MB) for the rate switching above or equal to 665MHz.
It will check the current source and switch to another one dynamically.

iii. The detail sequence for EMC scaling support, this includes how we
update the table to EMC registers and the periodic training support.

Here are the details and dependency of these patches.
Patch 1 is the binding document of the EMC node and table.
Patch 2 is the clock driver update for EMC scaling support. Need to note
that with the update of the MC clock, the patch 3 (EMC clock driver) and
patch 6 (EMC node in DT) will be needed to make MC clock to work
normally.
Patch 3 is the EMC clock driver. It can get the trained table from the
firmware and know the current EMC status, like the rate and source
clock.
Patch 4 adds more APIs and support code. Tha will be needed for the EMC
scaling sequence.
Patch 5 adds the EMC scaling sequence code. With this, we can support
EMC clock scaling now.
Patch 6 introduces the EMC node in DT.
Patch 7 adds EMC table of ram code 0.
Patch 8 adds EMC table of ram code 1.

Joseph Lo (8):
  dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  clk: tegra: clock changes for emc scaling support on Tegra210
  memory: tegra: Add Tegra210 EMC clock driver
  memory: tegra: add EMC scaling support code for Tegra210
  memory: tegra: Add EMC scaling sequence code for Tegra210
  arm64: tegra: Add external memory controller node for Tegra210
  arm64: tegra: Add EMC table of ram code 0 for Tegra210 Shield platform
  arm64: tegra: Add EMC table of ram code 1 for Tegra210 Shield platform

 .../nvidia,tegra210-emc.txt                   |   605 +
 .../boot/dts/nvidia/tegra210-p2894-emc.dtsi   | 24851 ++++++++++++++++
 .../arm64/boot/dts/nvidia/tegra210-p2894.dtsi |     1 +
 arch/arm64/boot/dts/nvidia/tegra210.dtsi      |    23 +
 drivers/clk/tegra/clk-tegra210.c              |   112 +-
 drivers/memory/tegra/Kconfig                  |    10 +
 drivers/memory/tegra/Makefile                 |     1 +
 drivers/memory/tegra/tegra210-dt-parse.c      |   340 +
 drivers/memory/tegra/tegra210-emc-cc-r21021.c |  1962 ++
 drivers/memory/tegra/tegra210-emc-reg.h       |  1482 +
 drivers/memory/tegra/tegra210-emc.c           |  1814 ++
 include/dt-bindings/clock/tegra210-car.h      |     4 +-
 include/linux/clk/tegra.h                     |     5 +
 13 files changed, 31192 insertions(+), 18 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
 create mode 100644 arch/arm64/boot/dts/nvidia/tegra210-p2894-emc.dtsi
 create mode 100644 drivers/memory/tegra/tegra210-dt-parse.c
 create mode 100644 drivers/memory/tegra/tegra210-emc-cc-r21021.c
 create mode 100644 drivers/memory/tegra/tegra210-emc-reg.h
 create mode 100644 drivers/memory/tegra/tegra210-emc.c

-- 
2.21.0


^ permalink raw reply	[flat|nested] 28+ messages in thread

* [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-03-25  7:45 [PATCH 0/8] Add EMC scaling support for Tegra210 Joseph Lo
@ 2019-03-25  7:45 ` Joseph Lo
  2019-03-31  6:41   ` Rob Herring
                     ` (2 more replies)
  2019-03-25  7:45 ` [PATCH 2/8] clk: tegra: clock changes for emc scaling support on Tegra210 Joseph Lo
                   ` (5 subsequent siblings)
  6 siblings, 3 replies; 28+ messages in thread
From: Joseph Lo @ 2019-03-25  7:45 UTC (permalink / raw)
  To: Thierry Reding, Peter De Schrijver, Jonathan Hunter, Rob Herring,
	Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree, Joseph Lo

Add the binding document for the external memory controller (EMC) which
communicates with external LPDDR4 devices. It includes the bindings of
the EMC node and the EMC table of different rates.

To support high rates for LPDDR4, the EMC table must be trained before
it can be used for runtime clock switching. It has been done by firmware
and merged to the table that Linux kernel uses. For backward
compatibility with the devices that had been launched on the market, like
Shield and Jetson platforms, the bindings in the EMC table should remain
the same. So the firmware can recognize them and merge the trained EMC
table for the kernel.

Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.

Signed-off-by: Joseph Lo <josephl@nvidia.com>
---
 .../nvidia,tegra210-emc.txt                   | 605 ++++++++++++++++++
 1 file changed, 605 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt

diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
new file mode 100644
index 000000000000..1f6b6df6d37b
--- /dev/null
+++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
@@ -0,0 +1,605 @@
+NVIDIA Tegra210 SoC EMC (external memory controller)
+====================================================
+
+Required properties :
+- compatible : should be "nvidia,tegra21-emc", "nvidia,tegra210-emc".
+- reg : physical base address and length of the controller's registers.
+- clocks : phandles of the possible source clocks
+- clock-names : names of the possible source clocks
+- #address-cells : should be 1
+- #size-cells : should be 0
+- nvidia,memory-controller : phandle of the memory controller.
+- nvidia,use-ram-code : boolean, indicates whether we should use RAM_CODE in
+		        the register to find matching emc-table nodes
+
+The node should contain a "emc-table" subnode for each supported RAM type
+(see field RAM_CODE in register APB_MISC_PP_STRAPPING_OPT_A), with its unit
+address being its RAM_CODE.
+
+Required properties for "emc-table" nodes :
+- nvidia,ram-code : Should contain the value of RAM_CODE this timing set is
+		    used for.
+
+Each "emc-table" node should contain a "emc-table" subnode for every supported
+EMC clock rate. The "emc-table" subnodes should have the clock rate in
+kilohertz  as their unit address.
+
+Required properties for "emc-table" nodes :
+- compatible :  "nvidia,tegra21-emc-table", "nvidia,tegra210-emc-table"
+- nvidia,revision : revision of the parameter set used for this node. All
+                    nodes in the same "emc-table" should have the same revision
+- nvidia,dvfs-version : string for the DVFS version of this table
+- clock-frequency : frequency in kilohertz
+- nvidia,emc-min-mv : minimum voltage in millivolt for this rate
+- nvidia,gk20a-min-mv : minimum GPU voltage in millivolt for this rate
+- nvidia,source : name of clock source to be used for this rate
+- nvidia,src-sel-reg : value of EMC CAR register to be used for this rate
+- nvidia,needs-training : 1 if needs training at boot, 0 otherwise
+- nvidia,trained : 1 if initial training has been done by firmware, 0 otherwise
+- nvidia,periodic_training : 1 if needs periodic training, 0 otherwise
+- nvidia,trained_dram_clktree_c0d0u0 : training data word
+- nvidia,trained_dram_clktree_c0d0u1 : training data word
+- nvidia,trained_dram_clktree_c0d1u0 : training data word
+- nvidia,trained_dram_clktree_c0d1u1 : training data word
+- nvidia,trained_dram_clktree_c1d0u0 : training data word
+- nvidia,trained_dram_clktree_c1d0u1 : training data word
+- nvidia,trained_dram_clktree_c1d1u0 : training data word
+- nvidia,trained_dram_clktree_c1d1u1 : training data word
+- nvidia,current_dram_clktree_c0d0u0 : training data word
+- nvidia,current_dram_clktree_c0d0u1 : training data word
+- nvidia,current_dram_clktree_c0d1u0 : training data word
+- nvidia,current_dram_clktree_c0d1u1 : training data word
+- nvidia,current_dram_clktree_c1d0u0 : training data word
+- nvidia,current_dram_clktree_c1d0u1 : training data word
+- nvidia,current_dram_clktree_c1d1u0 : training data word
+- nvidia,current_dram_clktree_c1d1u1 : training data word
+- nvidia,run_clocks : training data
+- nvidia,tree_margin : training data
+- nvidia,burst-regs-num : number of values in nvidia,emc-registers
+- nvidia,burst-regs-per-ch-num : number of values in
+				 nvidia,emc-burst-regs-per-ch
+- nvidia,trim-regs-num : number of values in nvidia,emc-trim-regs
+- nvidia,trim-regs-per-ch-num : number of values in nvidia,emc-trim-regs-per-ch
+- nvidia,burst-mc-regs-num : number of values in nvidia,emc-burst-mc-regs
+- nvidia,la-scale-regs-num : number of values in nvidia,emc-la-scale-regs
+- nvidia,vref-regs-num : number of values in nvidia,emc-vref-regs
+- nvidia,dram-timing-regs-num : number of values in nvidia,emc-dram-timing-regs
+- nvidia,min-mrs-wait : value of the EMC_MRS register
+- nvidia,emc-mrw : value of the EMC_MRW register
+- nvidia,emc-mrw2 : value of the EMC_MRW2 register
+- nvidia,emc-mrw3 : value of the EMC_MRW3 register
+- nvidia,emc-mrw4 : value of the EMC_MRW4 register
+- nvidia,emc-mrw9 : value of the EMC_MRW4 register
+- nvidia,emc-mrs : value of the EMC_MRS register
+- nvidia,emc-emrs : value of the EMC_EMRS register
+- nvidia,emc-emrs2 : value of the EMC_EMRS2 register
+- nvidia,emc-auto-cal-config : value of the EMC_AUTO_CAL_CONFIG register
+- nvidia,emc-auto-cal-config2 : value of the EMC_AUTO_CAL_CONFIG2 register
+- nvidia,emc-auto-cal-config3 : value of the EMC_AUTO_CAL_CONFIG3 register
+- nvidia,emc-auto-cal-config4 : value of the EMC_AUTO_CAL_CONFIG4 register
+- nvidia,emc-auto-cal-config5 : value of the EMC_AUTO_CAL_CONFIG5 register
+- nvidia,emc-auto-cal-config6 : value of the EMC_AUTO_CAL_CONFIG6 register
+- nvidia,emc-auto-cal-config7 : value of the EMC_AUTO_CAL_CONFIG7 register
+- nvidia,emc-auto-cal-config8 : value of the EMC_AUTO_CAL_CONFIG8 register
+- nvidia,emc-cfg-2 : value of the EMC_CFG_2 register
+- nvidia,emc-sel-dpd-ctrl : value of the EMC_SEL_DPD_CTRL register
+- nvidia,emc-fdpd-ctrl-cmd-no-ramp : value of the EMC_FDPD_CTRL_CMD_NO_RAMP
+				     register
+- nvidia,dll-clk-src : value of the CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL
+		       register
+- nvidia,clk-out-enb-x-0-clk-enb-emc-dll : boolean, enable EMC_DLL in the
+					   CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET,
+					   or clear in the
+					   CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR
+- nvidia,emc-clock-latency-change : clock latency value in micro seconds
+- nvidia,ptfv : control data for periodic training
+- nvidia,emc-registers :
+- nvidia,emc-shadow-regs-ca-train :
+- nvidia,emc-shadow-regs-quse-train :
+- nvidia,emc-shadow-regs-rdwr-train :
+  values for the following registers (See TRM 18.10.2 for register descriptions)
+	EMC_RC
+	EMC_RFC
+	EMC_RFCPB
+	EMC_REFCTRL2
+	EMC_RFC_SLR
+	EMC_RAS
+	EMC_RP
+	EMC_R2W
+	EMC_W2R
+	EMC_R2P
+	EMC_W2P
+	EMC_R2R
+	EMC_TPPD
+	EMC_CCDMW
+	EMC_RD_RCD
+	EMC_WR_RCD
+	EMC_RRD
+	EMC_REXT
+	EMC_WEXT
+	EMC_WDV_CHK
+	EMC_WDV
+	EMC_WSV
+	EMC_WEV
+	EMC_WDV_MASK
+	EMC_WS_DURATION
+	EMC_WE_DURATION
+	EMC_QUSE
+	EMC_QUSE_WIDTH
+	EMC_IBDLY
+	EMC_OBDLY
+	EMC_EINPUT
+	EMC_MRW6
+	EMC_EINPUT_DURATION
+	EMC_PUTERM_EXTRA
+	EMC_PUTERM_WIDTH
+	EMC_QRST
+	EMC_QSAFE
+	EMC_RDV
+	EMC_RDV_MASK
+	EMC_RDV_EARLY
+	EMC_RDV_EARLY_MASK
+	EMC_REFRESH
+	EMC_BURST_REFRESH_NUM
+	EMC_PRE_REFRESH_REQ_CNT
+	EMC_PDEX2WR
+	EMC_PDEX2RD
+	EMC_PCHG2PDEN
+	EMC_ACT2PDEN
+	EMC_AR2PDEN
+	EMC_RW2PDEN
+	EMC_CKE2PDEN
+	EMC_PDEX2CKE
+	EMC_PDEX2MRR
+	EMC_TXSR
+	EMC_TXSRDLL
+	EMC_TCKE
+	EMC_TCKESR
+	EMC_TPD
+	EMC_TFAW
+	EMC_TRPAB
+	EMC_TCLKSTABLE
+	EMC_TCLKSTOP
+	EMC_MRW7
+	EMC_TREFBW
+	EMC_ODT_WRITE
+	EMC_FBIO_CFG5
+	EMC_FBIO_CFG7
+	EMC_CFG_DIG_DLL
+	EMC_CFG_DIG_DLL_PERIOD
+	EMC_PMACRO_IB_RXRT
+	EMC_CFG_PIPE_1
+	EMC_CFG_PIPE_2
+	EMC_PMACRO_QUSE_DDLL_RANK0_4
+	EMC_PMACRO_QUSE_DDLL_RANK0_5
+	EMC_PMACRO_QUSE_DDLL_RANK1_4
+	EMC_PMACRO_QUSE_DDLL_RANK1_5
+	EMC_MRW8
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4
+	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5
+	EMC_PMACRO_DDLL_LONG_CMD_0
+	EMC_PMACRO_DDLL_LONG_CMD_1
+	EMC_PMACRO_DDLL_LONG_CMD_2
+	EMC_PMACRO_DDLL_LONG_CMD_3
+	EMC_PMACRO_DDLL_LONG_CMD_4
+	EMC_PMACRO_DDLL_SHORT_CMD_0
+	EMC_PMACRO_DDLL_SHORT_CMD_1
+	EMC_PMACRO_DDLL_SHORT_CMD_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_3
+	EMC_TXDSRVTTGEN
+	EMC_FDPD_CTRL_DQ
+	EMC_FDPD_CTRL_CMD
+	EMC_FBIO_SPARE
+	EMC_ZCAL_INTERVAL
+	EMC_ZCAL_WAIT_CNT
+	EMC_MRS_WAIT_CNT
+	EMC_MRS_WAIT_CNT2
+	EMC_AUTO_CAL_CHANNEL
+	EMC_DLL_CFG_0
+	EMC_DLL_CFG_1
+	EMC_PMACRO_AUTOCAL_CFG_COMMON
+	EMC_PMACRO_ZCTRL
+	EMC_CFG
+	EMC_CFG_PIPE
+	EMC_DYN_SELF_REF_CONTROL
+	EMC_QPOP
+	EMC_DQS_BRLSHFT_0
+	EMC_DQS_BRLSHFT_1
+	EMC_CMD_BRLSHFT_2
+	EMC_CMD_BRLSHFT_3
+	EMC_PMACRO_PAD_CFG_CTRL
+	EMC_PMACRO_DATA_PAD_RX_CTRL
+	EMC_PMACRO_CMD_PAD_RX_CTRL
+	EMC_PMACRO_DATA_RX_TERM_MODE
+	EMC_PMACRO_CMD_RX_TERM_MODE
+	EMC_PMACRO_CMD_PAD_TX_CTRL
+	EMC_PMACRO_DATA_PAD_TX_CTRL
+	EMC_PMACRO_COMMON_PAD_TX_CTRL
+	EMC_PMACRO_VTTGEN_CTRL_0
+	EMC_PMACRO_VTTGEN_CTRL_1
+	EMC_PMACRO_VTTGEN_CTRL_2
+	EMC_PMACRO_BRICK_CTRL_RFU1
+	EMC_PMACRO_CMD_BRICK_CTRL_FDPD
+	EMC_PMACRO_BRICK_CTRL_RFU2
+	EMC_PMACRO_DATA_BRICK_CTRL_FDPD
+	EMC_PMACRO_BG_BIAS_CTRL_0
+	EMC_CFG_3
+	EMC_PMACRO_TX_PWRD_0
+	EMC_PMACRO_TX_PWRD_1
+	EMC_PMACRO_TX_PWRD_2
+	EMC_PMACRO_TX_PWRD_3
+	EMC_PMACRO_TX_PWRD_4
+	EMC_PMACRO_TX_PWRD_5
+	EMC_CONFIG_SAMPLE_DELAY
+	EMC_PMACRO_TX_SEL_CLK_SRC_0
+	EMC_PMACRO_TX_SEL_CLK_SRC_1
+	EMC_PMACRO_TX_SEL_CLK_SRC_2
+	EMC_PMACRO_TX_SEL_CLK_SRC_3
+	EMC_PMACRO_TX_SEL_CLK_SRC_4
+	EMC_PMACRO_TX_SEL_CLK_SRC_5
+	EMC_PMACRO_DDLL_BYPASS
+	EMC_PMACRO_DDLL_PWRD_0
+	EMC_PMACRO_DDLL_PWRD_1
+	EMC_PMACRO_DDLL_PWRD_2
+	EMC_PMACRO_CMD_CTRL_0
+	EMC_PMACRO_CMD_CTRL_1
+	EMC_PMACRO_CMD_CTRL_2
+	EMC_TR_TIMING_0
+	EMC_TR_DVFS
+	EMC_TR_CTRL_1
+	EMC_TR_RDV
+	EMC_TR_QPOP
+	EMC_TR_RDV_MASK
+	EMC_MRW14
+	EMC_TR_QSAFE
+	EMC_TR_QRST
+	EMC_TRAINING_CTRL
+	EMC_TRAINING_SETTLE
+	EMC_TRAINING_VREF_SETTLE
+	EMC_TRAINING_CA_FINE_CTRL
+	EMC_TRAINING_CA_CTRL_MISC
+	EMC_TRAINING_CA_CTRL_MISC1
+	EMC_TRAINING_CA_VREF_CTRL
+	EMC_TRAINING_QUSE_CORS_CTRL
+	EMC_TRAINING_QUSE_FINE_CTRL
+	EMC_TRAINING_QUSE_CTRL_MISC
+	EMC_TRAINING_QUSE_VREF_CTRL
+	EMC_TRAINING_READ_FINE_CTRL
+	EMC_TRAINING_READ_CTRL_MISC
+	EMC_TRAINING_READ_VREF_CTRL
+	EMC_TRAINING_WRITE_FINE_CTRL
+	EMC_TRAINING_WRITE_CTRL_MISC
+	EMC_TRAINING_WRITE_VREF_CTRL
+	EMC_TRAINING_MPC
+	EMC_MRW15
+- nvidia,emc-burst-regs-per-ch : values for the following registers (See TRM
+				 18.10.2 for register descriptions) the array
+				 containts 2 values for each register, one per
+				 channel.
+	EMC_MRW10
+	EMC_MRW11
+	EMC_MRW12
+	EMC_MRW13
+- nvidia,emc-trim-regs : values for the following registers (See TRM 18.10.2
+			 for register descriptions)
+	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0
+	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1
+	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2
+	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3
+	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0
+	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1
+	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2
+	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1
+	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2
+	EMC_PMACRO_IB_VREF_DQS_0
+	EMC_PMACRO_IB_VREF_DQS_1
+	EMC_PMACRO_IB_VREF_DQ_0
+	EMC_PMACRO_IB_VREF_DQ_1
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1
+	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2
+	EMC_PMACRO_QUSE_DDLL_RANK0_0
+	EMC_PMACRO_QUSE_DDLL_RANK0_1
+	EMC_PMACRO_QUSE_DDLL_RANK0_2
+	EMC_PMACRO_QUSE_DDLL_RANK0_3
+	EMC_PMACRO_QUSE_DDLL_RANK1_0
+	EMC_PMACRO_QUSE_DDLL_RANK1_1
+	EMC_PMACRO_QUSE_DDLL_RANK1_2
+	EMC_PMACRO_QUSE_DDLL_RANK1_3
+- nvidia,emc-trim-regs-per-ch : values for the following registers (See TRM
+				18.10.2 for register descriptions)
+	EMC_CMD_BRLSHFT_0
+	EMC_CMD_BRLSHFT_1
+	EMC_DATA_BRLSHFT_0 (channel 0)
+	EMC_DATA_BRLSHFT_0 (channel 1)
+	EMC_DATA_BRLSHFT_1 (channel 0)
+	EMC_DATA_BRLSHFT_1 (channel 1)
+	EMC_QUSE_BRLSHFT_0
+	EMC_QUSE_BRLSHFT_1
+	EMC_QUSE_BRLSHFT_2
+	EMC_QUSE_BRLSHFT_3
+- nvidia,emc-vref-regs : values for the following registers (See TRM 18.10.2
+			 for register descriptions) the array containts 2
+			 values for each register, one per channel.
+	 EMC_TRAINING_OPT_DQS_IB_VREF_RANK0
+	 EMC_TRAINING_OPT_DQS_IB_VREF_RANK1
+- nvidia,emc-dram-timing-regs : DRAM timing values. These are not written to
+				registers but used during the sequence.
+	T_RP : row pre-charge delay
+	T_FC_LPDDR4 : frequency change time
+	T_RFC : refresh cycle time
+	T_PDEX : power down exit delay
+	RL : mode register read latency
+- nvidia,emc-training-mod-regs :
+- nvidia,emc-save-restore-mod-regs :
+	deprecated, keep for for backward compatibility
+- nvidia,emc-burst-mc-regs : values for the following registers
+			     (See TRM 18.10.1 for register descriptions)
+	MC_EMEM_ARB_CFG
+	MC_EMEM_ARB_OUTSTANDING_REQ
+	MC_EMEM_ARB_REFPB_HP_CTRL
+	MC_EMEM_ARB_REFPB_BANK_CTRL
+	MC_EMEM_ARB_TIMING_RCD
+	MC_EMEM_ARB_TIMING_RP
+	MC_EMEM_ARB_TIMING_RC
+	MC_EMEM_ARB_TIMING_RAS
+	MC_EMEM_ARB_TIMING_FAW
+	MC_EMEM_ARB_TIMING_RRD
+	MC_EMEM_ARB_TIMING_RAP2PRE
+	MC_EMEM_ARB_TIMING_WAP2PRE
+	MC_EMEM_ARB_TIMING_R2R
+	MC_EMEM_ARB_TIMING_W2W
+	MC_EMEM_ARB_TIMING_R2W
+	MC_EMEM_ARB_TIMING_CCDMW
+	MC_EMEM_ARB_TIMING_W2R
+	MC_EMEM_ARB_TIMING_RFCPB
+	MC_EMEM_ARB_DA_TURNS
+	MC_EMEM_ARB_DA_COVERS
+	MC_EMEM_ARB_MISC0
+	MC_EMEM_ARB_MISC1
+	MC_EMEM_ARB_MISC2
+	MC_EMEM_ARB_RING1_THROTTLE
+	MC_EMEM_ARB_DHYST_CTRL
+	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0
+	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1
+	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2
+	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3
+	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4
+	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5
+	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6
+	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7
+- nvidia,emc-la-scale-regs : values for the following registers
+			     (See TRM 18.10.1 for register descriptions)
+	MC_MLL_MPCORER_PTSA_RATE
+	MC_FTOP_PTSA_RATE
+	MC_PTSA_GRANT_DECREMENT
+	MC_LATENCY_ALLOWANCE_XUSB_0
+	MC_LATENCY_ALLOWANCE_XUSB_1
+	MC_LATENCY_ALLOWANCE_TSEC_0
+	MC_LATENCY_ALLOWANCE_SDMMCA_0
+	MC_LATENCY_ALLOWANCE_SDMMCAA_0
+	MC_LATENCY_ALLOWANCE_SDMMC_0
+	MC_LATENCY_ALLOWANCE_SDMMCAB_0
+	MC_LATENCY_ALLOWANCE_PPCS_0
+	MC_LATENCY_ALLOWANCE_PPCS_1
+	MC_LATENCY_ALLOWANCE_MPCORE_0
+	MC_LATENCY_ALLOWANCE_HC_0
+	MC_LATENCY_ALLOWANCE_HC_1
+	MC_LATENCY_ALLOWANCE_AVPC_0
+	MC_LATENCY_ALLOWANCE_GPU_0
+	MC_LATENCY_ALLOWANCE_GPU2_0
+	MC_LATENCY_ALLOWANCE_NVENC_0
+	MC_LATENCY_ALLOWANCE_NVDEC_0
+	MC_LATENCY_ALLOWANCE_VIC_0
+	MC_LATENCY_ALLOWANCE_VI2_0
+	MC_LATENCY_ALLOWANCE_ISP2_0
+	MC_LATENCY_ALLOWANCE_ISP2_1
+
+Example:
+	external-memory-controller@7001b000 {
+		compatible = "nvidia,tegra21-emc", "nvidia,tegra210-emc";
+		reg = <0x0 0x7001b000 0x0 0x1000>,
+		      <0x0 0x7001e000 0x0 0x1000>,
+		      <0x0 0x7001f000 0x0 0x1000>;
+		clocks = <&tegra_car TEGRA210_CLK_EMC>,
+		         <&tegra_car TEGRA210_CLK_PLL_M>,
+			 <&tegra_car TEGRA210_CLK_PLL_C>,
+			 <&tegra_car TEGRA210_CLK_PLL_P>,
+			 <&tegra_car TEGRA210_CLK_CLK_M>,
+			 <&tegra_car TEGRA210_CLK_PLL_M_UD>,
+			 <&tegra_car TEGRA210_CLK_PLL_MB_UD>,
+			 <&tegra_car TEGRA210_CLK_PLL_MB>,
+			 <&tegra_car TEGRA210_CLK_PLL_P_UD>;
+		clock-names = "emc", "pll_m", "pll_c", "pll_p", "clk_m",
+			      "pll_m_ud", "pll_mb_ud", "pll_mb", "pll_p_ud";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		nvidia,memory-controller = <&mc>;
+		nvidia,use-ram-code;
+
+		emc-table@0 {
+			nvidia,ram-code = <0>;
+			emc-table@40800 {
+				compatible = "nvidia,tegra21-emc-table";
+				...
+			};
+			emc-table@204000 {
+				...
+			};
+			...
+		};
+
+		emc-table@1 {
+			nvidia,ram-code = <1>;
+			emc-table@40800 {
+				compatible = "nvidia,tegra21-emc-table";
+				...
+			};
+			emc-table@204000 {
+				...
+			};
+			...
+		};
+	};
-- 
2.21.0


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 2/8] clk: tegra: clock changes for emc scaling support on Tegra210
  2019-03-25  7:45 [PATCH 0/8] Add EMC scaling support for Tegra210 Joseph Lo
  2019-03-25  7:45 ` [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings Joseph Lo
@ 2019-03-25  7:45 ` Joseph Lo
  2019-04-03  9:22   ` Thierry Reding
  2019-03-25  7:45 ` [PATCH 3/8] memory: tegra: Add Tegra210 EMC clock driver Joseph Lo
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 28+ messages in thread
From: Joseph Lo @ 2019-03-25  7:45 UTC (permalink / raw)
  To: Thierry Reding, Peter De Schrijver, Jonathan Hunter, Rob Herring,
	Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree, Joseph Lo

1) Introduce low jitter paths for pllp and pll_mb used by the EMC driver.
2) Remove the old emc_mux clock and don't use the common EMC clock
   definition. This will be replaced by a new clock defined in the EMC
   driver.
3) Export functions to allow accessing the CAR register required for EMC
   clock scaling. These functions will be used to access the CAR register
   as part of the scaling sequence.

Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.

Signed-off-by: Joseph Lo <josephl@nvidia.com>
---
 drivers/clk/tegra/clk-tegra210.c         | 112 +++++++++++++++++++----
 include/dt-bindings/clock/tegra210-car.h |   4 +-
 include/linux/clk/tegra.h                |   5 +
 3 files changed, 103 insertions(+), 18 deletions(-)

diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c
index 7545af763d7a..e17b5279ea69 100644
--- a/drivers/clk/tegra/clk-tegra210.c
+++ b/drivers/clk/tegra/clk-tegra210.c
@@ -47,6 +47,7 @@
 #define CLK_SOURCE_LA 0x1f8
 #define CLK_SOURCE_SDMMC2 0x154
 #define CLK_SOURCE_SDMMC4 0x164
+#define CLK_SOURCE_EMC_DLL 0x664
 
 #define PLLC_BASE 0x80
 #define PLLC_OUT 0x84
@@ -234,6 +235,10 @@
 #define RST_DFLL_DVCO 0x2f4
 #define DVFS_DFLL_RESET_SHIFT 0
 
+#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET	0x284
+#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR	0x288
+#define CLK_OUT_ENB_X_CLK_ENB_EMC_DLL		BIT(14)
+
 #define CLK_RST_CONTROLLER_RST_DEV_Y_SET 0x2a8
 #define CLK_RST_CONTROLLER_RST_DEV_Y_CLR 0x2ac
 
@@ -319,12 +324,6 @@ static unsigned long tegra210_input_freq[] = {
 	[8] = 12000000,
 };
 
-static const char *mux_pllmcp_clkm[] = {
-	"pll_m", "pll_c", "pll_p", "clk_m", "pll_m_ud", "pll_mb", "pll_mb",
-	"pll_p",
-};
-#define mux_pllmcp_clkm_idx NULL
-
 #define PLL_ENABLE			(1 << 30)
 
 #define PLLCX_MISC1_IDDQ		(1 << 27)
@@ -2310,7 +2309,7 @@ static struct tegra_clk tegra210_clks[tegra_clk_max] __initdata = {
 	[tegra_clk_i2c2] = { .dt_id = TEGRA210_CLK_I2C2, .present = true },
 	[tegra_clk_uartc_8] = { .dt_id = TEGRA210_CLK_UARTC, .present = true },
 	[tegra_clk_mipi_cal] = { .dt_id = TEGRA210_CLK_MIPI_CAL, .present = true },
-	[tegra_clk_emc] = { .dt_id = TEGRA210_CLK_EMC, .present = true },
+	[tegra_clk_emc] = { .dt_id = TEGRA210_CLK_EMC, .present = false },
 	[tegra_clk_usb2] = { .dt_id = TEGRA210_CLK_USB2, .present = true },
 	[tegra_clk_bsev] = { .dt_id = TEGRA210_CLK_BSEV, .present = true },
 	[tegra_clk_uartd_8] = { .dt_id = TEGRA210_CLK_UARTD, .present = true },
@@ -2921,6 +2920,82 @@ static int tegra210_init_pllu(void)
 	return 0;
 }
 
+void tegra210_clk_emc_dll_enable(bool flag)
+{
+	unsigned long flags = 0;
+	u32 offset = flag ? CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET :
+		     CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR;
+
+	spin_lock_irqsave(&emc_lock, flags);
+
+	writel_relaxed(CLK_OUT_ENB_X_CLK_ENB_EMC_DLL, clk_base + offset);
+	readl(clk_base + offset);
+
+	spin_unlock_irqrestore(&emc_lock, flags);
+}
+EXPORT_SYMBOL_GPL(tegra210_clk_emc_dll_enable);
+
+void tegra210_clk_emc_dll_update_setting(u32 emc_dll_src_value)
+{
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&emc_lock, flags);
+
+	writel_relaxed(emc_dll_src_value, clk_base + CLK_SOURCE_EMC_DLL);
+	readl(clk_base + CLK_SOURCE_EMC_DLL);
+
+	spin_unlock_irqrestore(&emc_lock, flags);
+}
+EXPORT_SYMBOL_GPL(tegra210_clk_emc_dll_update_setting);
+
+void tegra210_clk_emc_update_setting(u32 emc_src_value)
+{
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&emc_lock, flags);
+
+	writel_relaxed(emc_src_value, clk_base + CLK_SOURCE_EMC);
+	readl(clk_base + CLK_SOURCE_EMC);
+
+	spin_unlock_irqrestore(&emc_lock, flags);
+}
+EXPORT_SYMBOL_GPL(tegra210_clk_emc_update_setting);
+
+u32 tegra210_clk_emc_get_setting(void)
+{
+	unsigned long flags = 0;
+	u32 val;
+
+	spin_lock_irqsave(&emc_lock, flags);
+
+	val = readl_relaxed(clk_base + CLK_SOURCE_EMC);
+
+	spin_unlock_irqrestore(&emc_lock, flags);
+
+	return val;
+}
+EXPORT_SYMBOL_GPL(tegra210_clk_emc_get_setting);
+
+static const struct clk_div_table mc_div_table_tegra210[] = {
+	{ .val = 0, .div = 2 },
+	{ .val = 1, .div = 4 },
+	{ .val = 2, .div = 1 },
+	{ .val = 3, .div = 2 },
+	{ .val = 0, .div = 0 },
+};
+
+static void tegra_clk_register_mc_tegra210(const char *name,
+					   const char *parent_name)
+{
+	struct clk *clk;
+
+	clk = clk_register_divider_table(NULL, name, parent_name, 0,
+					 clk_base + CLK_SOURCE_EMC,
+					 15, 2, CLK_DIVIDER_READ_ONLY,
+					 mc_div_table_tegra210, &emc_lock);
+	clks[TEGRA210_CLK_MC] = clk;
+}
+
 static const char * const sor1_out_parents[] = {
 	/*
 	 * Bit 0 of the mux selects sor1_pad_clkout, irrespective of bit 1, so
@@ -3004,15 +3079,8 @@ static __init void tegra210_periph_clk_init(void __iomem *clk_base,
 			CLK_SOURCE_LA, 0);
 	clks[TEGRA210_CLK_LA] = clk;
 
-	/* emc mux */
-	clk = clk_register_mux(NULL, "emc_mux", mux_pllmcp_clkm,
-			       ARRAY_SIZE(mux_pllmcp_clkm), 0,
-			       clk_base + CLK_SOURCE_EMC,
-			       29, 3, 0, &emc_lock);
-
-	clk = tegra_clk_register_mc("mc", "emc_mux", clk_base + CLK_SOURCE_EMC,
-				    &emc_lock);
-	clks[TEGRA210_CLK_MC] = clk;
+	/* mc */
+	tegra_clk_register_mc_tegra210("mc", "emc");
 
 	/* cml0 */
 	clk = clk_register_gate(NULL, "cml0", "pll_e", 0, clk_base + PLLE_AUX,
@@ -3116,6 +3184,18 @@ static void __init tegra210_pll_init(void __iomem *clk_base,
 	clk_register_clkdev(clk, "pll_m_ud", NULL);
 	clks[TEGRA210_CLK_PLL_M_UD] = clk;
 
+	/* PLLMB_UD */
+	clk = clk_register_fixed_factor(NULL, "pll_mb_ud", "pll_mb",
+					CLK_SET_RATE_PARENT, 1, 1);
+	clk_register_clkdev(clk, "pll_mb_ud", NULL);
+	clks[TEGRA210_CLK_PLL_MB_UD] = clk;
+
+	/* PLLP_UD */
+	clk = clk_register_fixed_factor(NULL, "pll_p_ud", "pll_p",
+					0, 1, 1);
+	clks[TEGRA210_CLK_PLL_P_UD] = clk;
+
+
 	/* PLLU_VCO */
 	if (!tegra210_init_pllu()) {
 		clk = clk_register_fixed_rate(NULL, "pll_u_vco", "pll_ref", 0,
diff --git a/include/dt-bindings/clock/tegra210-car.h b/include/dt-bindings/clock/tegra210-car.h
index 6b77e721f6b1..832a89788525 100644
--- a/include/dt-bindings/clock/tegra210-car.h
+++ b/include/dt-bindings/clock/tegra210-car.h
@@ -349,8 +349,8 @@
 #define TEGRA210_CLK_PLL_P_OUT_XUSB 317
 #define TEGRA210_CLK_XUSB_SSP_SRC 318
 #define TEGRA210_CLK_PLL_RE_OUT1 319
-/* 320 */
-/* 321 */
+#define TEGRA210_CLK_PLL_MB_UD 320
+#define TEGRA210_CLK_PLL_P_UD 321
 #define TEGRA210_CLK_ISP 322
 #define TEGRA210_CLK_PLL_A_OUT_ADSP 323
 #define TEGRA210_CLK_PLL_A_OUT0_OUT_ADSP 324
diff --git a/include/linux/clk/tegra.h b/include/linux/clk/tegra.h
index afb9edfa5d58..7018afb1990e 100644
--- a/include/linux/clk/tegra.h
+++ b/include/linux/clk/tegra.h
@@ -130,4 +130,9 @@ extern void tegra210_put_utmipll_in_iddq(void);
 extern void tegra210_put_utmipll_out_iddq(void);
 extern int tegra210_clk_handle_mbist_war(unsigned int id);
 
+void tegra210_clk_emc_dll_enable(bool flag);
+void tegra210_clk_emc_dll_update_setting(u32 emc_dll_src_value);
+void tegra210_clk_emc_update_setting(u32 emc_src_value);
+u32 tegra210_clk_emc_get_setting(void);
+
 #endif /* __LINUX_CLK_TEGRA_H_ */
-- 
2.21.0


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 3/8] memory: tegra: Add Tegra210 EMC clock driver
  2019-03-25  7:45 [PATCH 0/8] Add EMC scaling support for Tegra210 Joseph Lo
  2019-03-25  7:45 ` [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings Joseph Lo
  2019-03-25  7:45 ` [PATCH 2/8] clk: tegra: clock changes for emc scaling support on Tegra210 Joseph Lo
@ 2019-03-25  7:45 ` Joseph Lo
  2019-04-03 11:34   ` Thierry Reding
  2019-04-03 11:55   ` Dmitry Osipenko
  2019-03-25  7:45 ` [PATCH 4/8] memory: tegra: add EMC scaling support code for Tegra210 Joseph Lo
                   ` (3 subsequent siblings)
  6 siblings, 2 replies; 28+ messages in thread
From: Joseph Lo @ 2019-03-25  7:45 UTC (permalink / raw)
  To: Thierry Reding, Peter De Schrijver, Jonathan Hunter, Rob Herring,
	Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree, Joseph Lo

This is the initial patch for Tegra210 EMC clock driver, which doesn't
include the support code and detail sequence for clock scaling yet.

The driver is designed to support LPDDR4 SDRAMs. Because of the LPDDR4
devices need to do initial time training before it can be used, the
firmware will help to do that at early boot stage. The trained table for
the rates that we will support in the kernel will be merged to the
kernel DTB. So the driver can get the trained table for clock scaling
support.

For the higher rate support (above 800MHz), the periodic training is
needed for the timing compensation. So basically, two methodologies for
clock scaling support, one is following the clock changing sequence to
update the EMC table to EMC registers and another is if the rate needs
periodic training, then we will start a timer to do that periodically
until it leaves the rate that doesn't need that.

Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.

Signed-off-by: Joseph Lo <josephl@nvidia.com>
---
 drivers/memory/tegra/Kconfig             |   10 +
 drivers/memory/tegra/Makefile            |    1 +
 drivers/memory/tegra/tegra210-dt-parse.c |  340 +++++++
 drivers/memory/tegra/tegra210-emc-reg.h  | 1083 ++++++++++++++++++++++
 drivers/memory/tegra/tegra210-emc.c      |  886 ++++++++++++++++++
 5 files changed, 2320 insertions(+)
 create mode 100644 drivers/memory/tegra/tegra210-dt-parse.c
 create mode 100644 drivers/memory/tegra/tegra210-emc-reg.h
 create mode 100644 drivers/memory/tegra/tegra210-emc.c

diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig
index 34e0b70f5c5f..614e9b370183 100644
--- a/drivers/memory/tegra/Kconfig
+++ b/drivers/memory/tegra/Kconfig
@@ -25,3 +25,13 @@ config TEGRA124_EMC
 	  Tegra124 chips. The EMC controls the external DRAM on the board.
 	  This driver is required to change memory timings / clock rate for
 	  external memory.
+
+config TEGRA210_EMC
+	bool "NVIDIA Tegra210 External Memory Controller driver"
+	default y
+	depends on TEGRA_MC && ARCH_TEGRA_210_SOC
+	help
+	  This driver is for the External Memory Controller (EMC) found on
+	  Tegra210 chips. The EMC controls the external DRAM on the board.
+	  This driver is required to change memory timings / clock rate for
+	  external memory.
diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile
index 3971a6b7c487..36a835620bbd 100644
--- a/drivers/memory/tegra/Makefile
+++ b/drivers/memory/tegra/Makefile
@@ -12,4 +12,5 @@ obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
 
 obj-$(CONFIG_TEGRA20_EMC)  += tegra20-emc.o
 obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o
+obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o tegra210-dt-parse.o
 obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o
diff --git a/drivers/memory/tegra/tegra210-dt-parse.c b/drivers/memory/tegra/tegra210-dt-parse.c
new file mode 100644
index 000000000000..6a3a3a28ac64
--- /dev/null
+++ b/drivers/memory/tegra/tegra210-dt-parse.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2013-2019, NVIDIA CORPORATION.  All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <soc/tegra/fuse.h>
+
+#include "tegra210-emc-reg.h"
+
+static struct device_node *tegra_emc_ramcode_devnode(
+	struct device_node *np)
+{
+	struct device_node *iter;
+	u32 reg;
+
+	for_each_child_of_node(np, iter) {
+		if (of_property_read_u32(iter, "nvidia,ram-code", &reg))
+			continue;
+		if (reg == tegra_read_ram_code())
+			return of_node_get(iter);
+	}
+
+	return NULL;
+}
+
+static void *tegra_emc_dt_parse_pdata_comp(const char *emc_mode,
+					   const char *comp,
+					   void *pdata,
+					   struct device_node *tnp,
+					   struct platform_device *pdev,
+					   int num_tables, int *table_count)
+{
+#define PNE_U32(node, entry, tbl_entry)					\
+	do {								\
+		int __ret__;						\
+		u32 __tmp__;						\
+									\
+		__ret__ = of_property_read_u32((node), (entry), &__tmp__); \
+		if (__ret__) {						\
+			dev_err(&pdev->dev, "Failed to parse %s in %s: %d\n", \
+				(entry), (node)->full_name, __ret__);	\
+			continue;					\
+		}							\
+									\
+		tables[i].tbl_entry = __tmp__;				\
+	} while (0)
+
+#define PNE_U32_ARRAY(node, entry, tbl_entry, length)			\
+	do {								\
+		int __ret__;						\
+									\
+		__ret__ = of_property_read_u32_array((node), (entry),	\
+						     (tbl_entry), (length)); \
+		if (__ret__) {						\
+			dev_err(&pdev->dev, "Failed to parse %s in %s: %d\n", \
+				(entry), (node)->full_name, __ret__);	\
+			continue;					\
+		}							\
+	} while (0)
+
+	int i = 0, ret = 0;
+	struct device_node *iter;
+	struct emc_table *tables;
+
+	tables = devm_kzalloc(&pdev->dev, sizeof(*tables) * num_tables,
+			      GFP_KERNEL);
+
+	if (!tables) {
+		of_node_put(tnp);
+		return tables;
+	}
+
+	for_each_child_of_node(tnp, iter) {
+		if (of_device_is_compatible(iter, comp)) {
+			const char *source_name;
+			const char *dvfs_ver;
+
+			ret = of_property_read_string(iter, "nvidia,source",
+						      &source_name);
+			if (ret) {
+				dev_err(&pdev->dev, "no source name in %s\n",
+					iter->full_name);
+				continue;
+			}
+			strlcpy(tables[i].clock_src, source_name,
+				sizeof(tables[i].clock_src));
+
+			ret = of_property_read_string(iter,
+						      "nvidia,dvfs-version",
+						      &dvfs_ver);
+			if (ret) {
+				dev_err(&pdev->dev, "no dvfs version in %s\n",
+					iter->full_name);
+				continue;
+			}
+			strlcpy(tables[i].dvfs_ver, dvfs_ver,
+				sizeof(tables[i].dvfs_ver));
+
+			PNE_U32(iter, "nvidia,revision", rev);
+			PNE_U32(iter, "clock-frequency", rate);
+			PNE_U32(iter, "nvidia,emc-min-mv", min_volt);
+			PNE_U32(iter, "nvidia,gk20a-min-mv", gpu_min_volt);
+			PNE_U32(iter, "nvidia,src-sel-reg", clk_src_emc);
+			PNE_U32(iter, "nvidia,burst-regs-num", num_burst);
+			PNE_U32(iter, "nvidia,emc-cfg-2", emc_cfg_2);
+			PNE_U32(iter, "nvidia,emc-sel-dpd-ctrl",
+				emc_sel_dpd_ctrl);
+			PNE_U32(iter, "nvidia,emc-auto-cal-config",
+				emc_auto_cal_config);
+			PNE_U32(iter, "nvidia,emc-auto-cal-config2",
+				emc_auto_cal_config2);
+			PNE_U32(iter, "nvidia,emc-auto-cal-config3",
+				emc_auto_cal_config3);
+			PNE_U32(iter, "nvidia,emc-clock-latency-change",
+				latency);
+			PNE_U32_ARRAY(iter, "nvidia,emc-registers",
+				      tables[i].burst_regs,
+				      tables[i].num_burst);
+
+			PNE_U32(iter, "nvidia,needs-training", needs_training);
+			PNE_U32(iter, "nvidia,trained", trained);
+			if (tables[i].rev < 0x6)
+				goto skip_periodic_training_params;
+			PNE_U32(iter, "nvidia,periodic_training",
+				periodic_training);
+			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d0u0",
+				trained_dram_clktree_c0d0u0);
+			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d0u1",
+				trained_dram_clktree_c0d0u1);
+			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d1u0",
+				trained_dram_clktree_c0d1u0);
+			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d1u1",
+				trained_dram_clktree_c0d1u1);
+			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d0u0",
+				trained_dram_clktree_c1d0u0);
+			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d0u1",
+				trained_dram_clktree_c1d0u1);
+			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d1u0",
+				trained_dram_clktree_c1d1u0);
+			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d1u1",
+				trained_dram_clktree_c1d1u1);
+			PNE_U32(iter, "nvidia,current_dram_clktree_c0d0u0",
+				current_dram_clktree_c0d0u0);
+			PNE_U32(iter, "nvidia,current_dram_clktree_c0d0u1",
+				current_dram_clktree_c0d0u1);
+			PNE_U32(iter, "nvidia,current_dram_clktree_c0d1u0",
+				current_dram_clktree_c0d1u0);
+			PNE_U32(iter, "nvidia,current_dram_clktree_c0d1u1",
+				current_dram_clktree_c0d1u1);
+			PNE_U32(iter, "nvidia,current_dram_clktree_c1d0u0",
+				current_dram_clktree_c1d0u0);
+			PNE_U32(iter, "nvidia,current_dram_clktree_c1d0u1",
+				current_dram_clktree_c1d0u1);
+			PNE_U32(iter, "nvidia,current_dram_clktree_c1d1u0",
+				current_dram_clktree_c1d1u0);
+			PNE_U32(iter, "nvidia,current_dram_clktree_c1d1u1",
+				current_dram_clktree_c1d1u1);
+			PNE_U32(iter, "nvidia,run_clocks", run_clocks);
+			PNE_U32(iter, "nvidia,tree_margin", tree_margin);
+
+skip_periodic_training_params:
+			PNE_U32(iter, "nvidia,burst-regs-per-ch-num",
+				num_burst_per_ch);
+			PNE_U32(iter, "nvidia,trim-regs-num", num_trim);
+			PNE_U32(iter, "nvidia,trim-regs-per-ch-num",
+				num_trim_per_ch);
+			PNE_U32(iter, "nvidia,burst-mc-regs-num",
+				num_mc_regs);
+			PNE_U32(iter, "nvidia,la-scale-regs-num",
+				num_up_down);
+			PNE_U32(iter, "nvidia,vref-regs-num", vref_num);
+			PNE_U32(iter, "nvidia,dram-timing-regs-num",
+				dram_timing_num);
+			PNE_U32(iter, "nvidia,min-mrs-wait", min_mrs_wait);
+			PNE_U32(iter, "nvidia,emc-mrw", emc_mrw);
+			PNE_U32(iter, "nvidia,emc-mrw2", emc_mrw2);
+			PNE_U32(iter, "nvidia,emc-mrw3", emc_mrw3);
+			PNE_U32(iter, "nvidia,emc-mrw4", emc_mrw4);
+			PNE_U32(iter, "nvidia,emc-mrw9", emc_mrw9);
+			PNE_U32(iter, "nvidia,emc-mrs", emc_mrs);
+			PNE_U32(iter, "nvidia,emc-emrs", emc_emrs);
+			PNE_U32(iter, "nvidia,emc-emrs2", emc_emrs2);
+			PNE_U32(iter, "nvidia,emc-auto-cal-config4",
+				emc_auto_cal_config4);
+			PNE_U32(iter, "nvidia,emc-auto-cal-config5",
+				emc_auto_cal_config5);
+			PNE_U32(iter, "nvidia,emc-auto-cal-config6",
+				emc_auto_cal_config6);
+			PNE_U32(iter, "nvidia,emc-auto-cal-config7",
+				emc_auto_cal_config7);
+			PNE_U32(iter, "nvidia,emc-auto-cal-config8",
+				emc_auto_cal_config8);
+			PNE_U32(iter, "nvidia,emc-fdpd-ctrl-cmd-no-ramp",
+				emc_fdpd_ctrl_cmd_no_ramp);
+			PNE_U32(iter, "nvidia,dll-clk-src", dll_clk_src);
+			PNE_U32(iter, "nvidia,clk-out-enb-x-0-clk-enb-emc-dll",
+				clk_out_enb_x_0_clk_enb_emc_dll);
+
+			if (tables[i].rev >= 0x7)
+				PNE_U32_ARRAY(iter, "nvidia,ptfv",
+					      tables[i].ptfv_list,
+					      sizeof(tables[i].ptfv_list)
+						     / sizeof(u32));
+
+			PNE_U32_ARRAY(iter, "nvidia,emc-burst-regs-per-ch",
+				      tables[i].burst_reg_per_ch,
+				      tables[i].num_burst_per_ch);
+			PNE_U32_ARRAY(iter, "nvidia,emc-shadow-regs-ca-train",
+				      tables[i].shadow_regs_ca_train,
+				      tables[i].num_burst);
+			PNE_U32_ARRAY(iter, "nvidia,emc-shadow-regs-quse-train",
+				      tables[i].shadow_regs_quse_train,
+				      tables[i].num_burst);
+			PNE_U32_ARRAY(iter, "nvidia,emc-shadow-regs-rdwr-train",
+				      tables[i].shadow_regs_rdwr_train,
+				      tables[i].num_burst);
+			PNE_U32_ARRAY(iter, "nvidia,emc-trim-regs",
+				      tables[i].trim_regs,
+				      tables[i].num_trim);
+			PNE_U32_ARRAY(iter, "nvidia,emc-trim-regs-per-ch",
+				      tables[i].trim_perch_regs,
+				      tables[i].num_trim_per_ch);
+			PNE_U32_ARRAY(iter, "nvidia,emc-vref-regs",
+				      tables[i].vref_perch_regs,
+				      tables[i].vref_num);
+			PNE_U32_ARRAY(iter, "nvidia,emc-dram-timing-regs",
+				      tables[i].dram_timings,
+				      tables[i].dram_timing_num);
+			PNE_U32_ARRAY(iter, "nvidia,emc-burst-mc-regs",
+				      tables[i].burst_mc_regs,
+				      tables[i].num_mc_regs);
+			PNE_U32_ARRAY(iter, "nvidia,emc-la-scale-regs",
+				      tables[i].la_scale_regs,
+				      tables[i].num_up_down);
+			i++;
+		}
+	}
+
+	*table_count = i;
+
+	return tables;
+}
+
+static const struct of_device_id emc_table_match[] = {
+	{
+		.compatible = "nvidia,tegra210-emc-table",
+		.data = "nvidia,tegra210-emc-table-derated",
+	},
+	{
+		.compatible = "nvidia,tegra21-emc-table",
+		.data = "nvidia,tegra21-emc-table-derated",
+	},
+	{ },
+};
+
+int tegra_emc_dt_parse_pdata(struct platform_device *pdev,
+			     struct emc_table **tables,
+			     struct emc_table **derated_tables,
+			     int *num_entries)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *tnp, *iter;
+	int num_tables, table_count;
+	u32 tegra_bct_strapping;
+	const char *emc_mode = "nvidia,emc-mode-0";
+	struct tegra21_emc_pdata *pdata = NULL;
+	const char *comp = NULL;
+	const char *comp_derated = NULL;
+
+	if (!np) {
+		dev_err(&pdev->dev,
+			"Unable to find external-memory-controller node\n");
+		return -ENODEV;
+	}
+
+	tegra_bct_strapping = tegra_read_ram_code();
+
+	if (of_find_property(np, "nvidia,use-ram-code", NULL)) {
+		tnp = tegra_emc_ramcode_devnode(np);
+
+		if (!tnp) {
+			dev_warn(&pdev->dev,
+				 "can't find emc table for ram-code 0x%02x\n",
+				 tegra_bct_strapping);
+			return -ENODEV;
+		}
+	} else
+		tnp = of_node_get(np);
+
+	num_tables = 0;
+	for_each_child_of_node(tnp, iter) {
+		if (!comp) {
+			const struct of_device_id *m =
+				of_match_node(emc_table_match, iter);
+			if (m) {
+				comp = m->compatible;
+				comp_derated = m->data;
+				num_tables++;
+			}
+			continue;
+		}
+		if (of_device_is_compatible(iter, comp))
+			num_tables++;
+	}
+
+	if (!num_tables) {
+		*tables = NULL;
+		goto out;
+	}
+
+	*tables = tegra_emc_dt_parse_pdata_comp(emc_mode, comp, pdata, tnp,
+						pdev, num_tables, &table_count);
+	*num_entries = table_count;
+
+	/* populate the derated tables */
+	num_tables = 0;
+	for_each_child_of_node(tnp, iter) {
+		if (of_device_is_compatible(iter, comp_derated))
+			num_tables++;
+	}
+
+	if (!num_tables) {
+		*derated_tables = NULL;
+		goto out;
+	}
+
+	*derated_tables = tegra_emc_dt_parse_pdata_comp(emc_mode,
+							comp_derated,
+							pdata, tnp, pdev,
+							num_tables,
+							&table_count);
+
+out:
+	of_node_put(tnp);
+	return 0;
+}
diff --git a/drivers/memory/tegra/tegra210-emc-reg.h b/drivers/memory/tegra/tegra210-emc-reg.h
new file mode 100644
index 000000000000..84fcc85f3b6d
--- /dev/null
+++ b/drivers/memory/tegra/tegra210-emc-reg.h
@@ -0,0 +1,1083 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2015-2019, NVIDIA CORPORATION.  All rights reserved.
+ */
+
+#ifndef _TEGRA210_EMC_REG_H
+#define _TEGRA210_EMC_REG_H
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+
+#include "mc.h"
+
+#define MC_EMEM_ARB_CFG						0x90
+#define MC_EMEM_ARB_OUTSTANDING_REQ				0x94
+#define MC_EMEM_ARB_TIMING_RCD					0x98
+#define MC_EMEM_ARB_TIMING_RP					0x9c
+#define MC_EMEM_ARB_TIMING_RC					0xa0
+#define MC_EMEM_ARB_TIMING_RAS					0xa4
+#define MC_EMEM_ARB_TIMING_FAW					0xa8
+#define MC_EMEM_ARB_TIMING_RRD					0xac
+#define MC_EMEM_ARB_TIMING_RAP2PRE				0xb0
+#define MC_EMEM_ARB_TIMING_WAP2PRE				0xb4
+#define MC_EMEM_ARB_TIMING_R2R					0xb8
+#define MC_EMEM_ARB_TIMING_W2W					0xbc
+#define MC_EMEM_ARB_TIMING_R2W					0xc0
+#define MC_EMEM_ARB_TIMING_W2R					0xc4
+#define MC_EMEM_ARB_MISC2					0xc8
+#define MC_EMEM_ARB_DA_TURNS					0xd0
+#define MC_EMEM_ARB_DA_COVERS					0xd4
+#define MC_EMEM_ARB_MISC0					0xd8
+#define MC_EMEM_ARB_MISC0_EMC_SAME_FREQ				BIT(27)
+#define MC_EMEM_ARB_MISC1					0xdc
+#define MC_EMEM_ARB_RING1_THROTTLE				0xe0
+#define MC_LATENCY_ALLOWANCE_AVPC_0				0x2e4
+#define MC_LATENCY_ALLOWANCE_HC_0				0x310
+#define MC_LATENCY_ALLOWANCE_HC_1				0x314
+#define MC_LATENCY_ALLOWANCE_MPCORE_0				0x320
+#define MC_LATENCY_ALLOWANCE_NVENC_0				0x328
+#define MC_LATENCY_ALLOWANCE_PPCS_0				0x344
+#define MC_LATENCY_ALLOWANCE_PPCS_1				0x348
+#define MC_LATENCY_ALLOWANCE_ISP2_0				0x370
+#define MC_LATENCY_ALLOWANCE_ISP2_1				0x374
+#define MC_LATENCY_ALLOWANCE_XUSB_0				0x37c
+#define MC_LATENCY_ALLOWANCE_XUSB_1				0x380
+#define MC_LATENCY_ALLOWANCE_TSEC_0				0x390
+#define MC_LATENCY_ALLOWANCE_VIC_0				0x394
+#define MC_LATENCY_ALLOWANCE_VI2_0				0x398
+#define MC_LATENCY_ALLOWANCE_GPU_0				0x3ac
+#define MC_LATENCY_ALLOWANCE_SDMMCA_0				0x3b8
+#define MC_LATENCY_ALLOWANCE_SDMMCAA_0				0x3bc
+#define MC_LATENCY_ALLOWANCE_SDMMC_0				0x3c0
+#define MC_LATENCY_ALLOWANCE_SDMMCAB_0				0x3c4
+#define MC_LATENCY_ALLOWANCE_GPU2_0				0x3e8
+#define MC_LATENCY_ALLOWANCE_NVDEC_0				0x3d8
+#define MC_MLL_MPCORER_PTSA_RATE				0x44c
+#define MC_FTOP_PTSA_RATE					0x50c
+#define MC_EMEM_ARB_TIMING_RFCPB				0x6c0
+#define MC_EMEM_ARB_TIMING_CCDMW				0x6c4
+#define MC_EMEM_ARB_REFPB_HP_CTRL				0x6f0
+#define MC_EMEM_ARB_REFPB_BANK_CTRL				0x6f4
+#define MC_PTSA_GRANT_DECREMENT					0x960
+#define MC_EMEM_ARB_DHYST_CTRL					0xbcc
+#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0			0xbd0
+#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1			0xbd4
+#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2			0xbd8
+#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3			0xbdc
+#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4			0xbe0
+#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5			0xbe4
+#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6			0xbe8
+#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7			0xbec
+
+#define CLK_RST_CONTROLLER_CLK_SOURCE_EMC			0x19c
+#define EMC_CLK_EMC_2X_CLK_SRC_SHIFT				29
+#define EMC_CLK_EMC_2X_CLK_SRC_MASK				\
+	(0x7 << EMC_CLK_EMC_2X_CLK_SRC_SHIFT)
+#define	EMC_CLK_MC_EMC_SAME_FREQ				BIT(16)
+#define EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT			0
+#define EMC_CLK_EMC_2X_CLK_DIVISOR_MASK				\
+	(0xff << EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT)
+
+#define EMC_CFG							0xc
+#define EMC_RC							0x2c
+#define EMC_RFC							0x30
+#define EMC_RAS							0x34
+#define EMC_RP							0x38
+#define EMC_R2W							0x3c
+#define EMC_W2R							0x40
+#define EMC_R2P							0x44
+#define EMC_W2P							0x48
+#define EMC_RD_RCD						0x4c
+#define EMC_WR_RCD						0x50
+#define EMC_RRD							0x54
+#define EMC_REXT						0x58
+#define EMC_WDV							0x5c
+#define EMC_QUSE						0x60
+#define EMC_QRST						0x64
+#define EMC_QSAFE						0x68
+#define EMC_RDV							0x6c
+#define EMC_REFRESH						0x70
+#define EMC_BURST_REFRESH_NUM					0x74
+#define EMC_PDEX2WR						0x78
+#define EMC_PDEX2RD						0x7c
+#define EMC_PCHG2PDEN						0x80
+#define EMC_ACT2PDEN						0x84
+#define EMC_AR2PDEN						0x88
+#define EMC_RW2PDEN						0x8c
+#define EMC_TXSR						0x90
+#define EMC_TCKE						0x94
+#define EMC_TFAW						0x98
+#define EMC_TRPAB						0x9c
+#define EMC_TCLKSTABLE						0xa0
+#define EMC_TCLKSTOP						0xa4
+#define EMC_TREFBW						0xa8
+#define EMC_TPPD						0xac
+#define EMC_ODT_WRITE						0xb0
+#define EMC_PDEX2MRR						0xb4
+#define EMC_WEXT						0xb8
+#define EMC_RFC_SLR						0xc0
+#define EMC_MRS_WAIT_CNT2					0xc4
+#define EMC_MRS_WAIT_CNT					0xc8
+#define EMC_FBIO_SPARE						0x100
+#define EMC_FBIO_CFG5						0x104
+#define EMC_FBIO_CFG5_DRAM_TYPE_SHIFT				0
+#define EMC_FBIO_CFG5_DRAM_TYPE_MASK				\
+	(0x3 <<	EMC_FBIO_CFG5_DRAM_TYPE_SHIFT)
+#define EMC_PDEX2CKE						0x118
+#define EMC_CKE2PDEN						0x11c
+#define EMC_R2R							0x144
+#define EMC_EINPUT						0x14c
+#define EMC_EINPUT_DURATION					0x150
+#define EMC_PUTERM_EXTRA					0x154
+#define EMC_TCKESR						0x158
+#define EMC_TPD							0x15c
+#define EMC_CFG_DIG_DLL						0x2bc
+#define EMC_CFG_DIG_DLL_PERIOD					0x2c0
+#define EMC_RDV_MASK						0x2cc
+#define EMC_WDV_MASK						0x2d0
+#define EMC_RDV_EARLY_MASK					0x2d4
+#define EMC_RDV_EARLY						0x2d8
+#define EMC_ZCAL_INTERVAL					0x2e0
+#define EMC_ZCAL_WAIT_CNT					0x2e4
+#define EMC_FDPD_CTRL_DQ					0x310
+#define EMC_FDPD_CTRL_CMD					0x314
+#define EMC_PMACRO_CMD_BRICK_CTRL_FDPD				0x318
+#define EMC_PMACRO_DATA_BRICK_CTRL_FDPD				0x31c
+#define EMC_PMACRO_BRICK_CTRL_RFU1				0x330
+#define EMC_PMACRO_BRICK_CTRL_RFU2				0x334
+#define EMC_TR_TIMING_0						0x3b4
+#define EMC_TR_CTRL_1						0x3bc
+#define EMC_TR_RDV						0x3c4
+#define EMC_PRE_REFRESH_REQ_CNT					0x3dc
+#define EMC_DYN_SELF_REF_CONTROL				0x3e0
+#define EMC_TXSRDLL						0x3e4
+#define EMC_TR_QPOP						0x3f4
+#define EMC_TR_RDV_MASK						0x3f8
+#define EMC_TR_QSAFE						0x3fc
+#define EMC_TR_QRST						0x400
+#define EMC_TR_DVFS						0x460
+#define EMC_AUTO_CAL_CHANNEL					0x464
+#define EMC_IBDLY						0x468
+#define EMC_OBDLY						0x46c
+#define EMC_TXDSRVTTGEN						0x480
+#define EMC_WE_DURATION						0x48c
+#define EMC_WS_DURATION						0x490
+#define EMC_WEV							0x494
+#define EMC_WSV							0x498
+#define EMC_CFG_3						0x49c
+#define EMC_MRW6						0x4a4
+#define EMC_MRW7						0x4a8
+#define EMC_MRW8						0x4ac
+#define EMC_MRW10						0x4b4
+#define EMC_MRW11						0x4b8
+#define EMC_MRW12						0x4bc
+#define EMC_MRW13						0x4c0
+#define EMC_MRW14						0x4c4
+#define EMC_MRW15						0x4d0
+#define EMC_WDV_CHK						0x4e0
+#define EMC_CFG_PIPE_2						0x554
+#define EMC_CFG_PIPE_1						0x55c
+#define EMC_CFG_PIPE						0x560
+#define EMC_QPOP						0x564
+#define EMC_QUSE_WIDTH						0x568
+#define EMC_PUTERM_WIDTH					0x56c
+#define EMC_REFCTRL2						0x580
+#define EMC_FBIO_CFG7						0x584
+#define EMC_DATA_BRLSHFT_0					0x588
+#define EMC_DATA_BRLSHFT_1					0x58c
+#define EMC_RFCPB						0x590
+#define EMC_DQS_BRLSHFT_0					0x594
+#define EMC_DQS_BRLSHFT_1					0x598
+#define EMC_CMD_BRLSHFT_0					0x59c
+#define EMC_CMD_BRLSHFT_1					0x5a0
+#define EMC_CMD_BRLSHFT_2					0x5a4
+#define EMC_CMD_BRLSHFT_3					0x5a8
+#define EMC_QUSE_BRLSHFT_0					0x5ac
+#define EMC_QUSE_BRLSHFT_1					0x5b8
+#define EMC_QUSE_BRLSHFT_2					0x5bc
+#define EMC_CCDMW						0x5c0
+#define EMC_QUSE_BRLSHFT_3					0x5c4
+#define EMC_DLL_CFG_0						0x5e4
+#define EMC_DLL_CFG_1						0x5e8
+#define EMC_CONFIG_SAMPLE_DELAY					0x5f0
+#define EMC_PMACRO_QUSE_DDLL_RANK0_0				0x600
+#define EMC_PMACRO_QUSE_DDLL_RANK0_1				0x604
+#define EMC_PMACRO_QUSE_DDLL_RANK0_2				0x608
+#define EMC_PMACRO_QUSE_DDLL_RANK0_3				0x60c
+#define EMC_PMACRO_QUSE_DDLL_RANK0_4				0x610
+#define EMC_PMACRO_QUSE_DDLL_RANK0_5				0x614
+#define EMC_PMACRO_QUSE_DDLL_RANK1_0				0x620
+#define EMC_PMACRO_QUSE_DDLL_RANK1_1				0x624
+#define EMC_PMACRO_QUSE_DDLL_RANK1_2				0x628
+#define EMC_PMACRO_QUSE_DDLL_RANK1_3				0x62c
+#define EMC_PMACRO_QUSE_DDLL_RANK1_4				0x630
+#define EMC_PMACRO_QUSE_DDLL_RANK1_5				0x634
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0			0x640
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1			0x644
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2			0x648
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3			0x64c
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4			0x650
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5			0x654
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0			0x660
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1			0x664
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2			0x668
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3			0x66c
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4			0x670
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5			0x674
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0			0x680
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1			0x684
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2			0x688
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3			0x68c
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4			0x690
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5			0x694
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0			0x6a0
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1			0x6a4
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2			0x6a8
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3			0x6ac
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4			0x6b0
+#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5			0x6b4
+#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0			0x6c0
+#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1			0x6c4
+#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2			0x6c8
+#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3			0x6cc
+#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0			0x6e0
+#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1			0x6e4
+#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2			0x6e8
+#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3			0x6ec
+#define EMC_PMACRO_TX_PWRD_0					0x720
+#define EMC_PMACRO_TX_PWRD_1					0x724
+#define EMC_PMACRO_TX_PWRD_2					0x728
+#define EMC_PMACRO_TX_PWRD_3					0x72c
+#define EMC_PMACRO_TX_PWRD_4					0x730
+#define EMC_PMACRO_TX_PWRD_5					0x734
+#define EMC_PMACRO_TX_SEL_CLK_SRC_0				0x740
+#define EMC_PMACRO_TX_SEL_CLK_SRC_1				0x744
+#define EMC_PMACRO_TX_SEL_CLK_SRC_3				0x74c
+#define EMC_PMACRO_TX_SEL_CLK_SRC_2				0x748
+#define EMC_PMACRO_TX_SEL_CLK_SRC_4				0x750
+#define EMC_PMACRO_TX_SEL_CLK_SRC_5				0x754
+#define EMC_PMACRO_DDLL_BYPASS					0x760
+#define EMC_PMACRO_DDLL_PWRD_0					0x770
+#define EMC_PMACRO_DDLL_PWRD_1					0x774
+#define EMC_PMACRO_DDLL_PWRD_2					0x778
+#define EMC_PMACRO_CMD_CTRL_0					0x780
+#define EMC_PMACRO_CMD_CTRL_1					0x784
+#define EMC_PMACRO_CMD_CTRL_2					0x788
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0		0x800
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1		0x804
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2		0x808
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_3		0x80c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0		0x810
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1		0x814
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2		0x818
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_3		0x81c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0		0x820
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1		0x824
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2		0x828
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_3		0x82c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0		0x830
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1		0x834
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2		0x838
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_3		0x83c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0		0x840
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1		0x844
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2		0x848
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_3		0x84c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0		0x850
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1		0x854
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2		0x858
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_3		0x85c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0		0x860
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1		0x864
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2		0x868
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_3		0x86c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0		0x870
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1		0x874
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2		0x878
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_3		0x87c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0		0x880
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1		0x884
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2		0x888
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_3		0x88c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0		0x890
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1		0x894
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2		0x898
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_3		0x89c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0		0x8a0
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1		0x8a4
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2		0x8a8
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_3		0x8ac
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0		0x8b0
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1		0x8b4
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2		0x8b8
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_3		0x8bc
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0		0x900
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1		0x904
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2		0x908
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_3		0x90c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0		0x910
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1		0x914
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2		0x918
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_3		0x91c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0		0x920
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1		0x924
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2		0x928
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_3		0x92c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0		0x930
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1		0x934
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2		0x938
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_3		0x93c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0		0x940
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1		0x944
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2		0x948
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_3		0x94c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0		0x950
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1		0x954
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2		0x958
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_3		0x95c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0		0x960
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1		0x964
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2		0x968
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_3		0x96c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0		0x970
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1		0x974
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2		0x978
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_3		0x97c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_0		0x980
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_1		0x984
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_2		0x988
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_3		0x98c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_0		0x990
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_1		0x994
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_2		0x998
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_3		0x99c
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_0		0x9a0
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_1		0x9a4
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_2		0x9a8
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_3		0x9ac
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_0		0x9b0
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_1		0x9b4
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_2		0x9b8
+#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_3		0x9bc
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0		0xa00
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1		0xa04
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2		0xa08
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0		0xa10
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1		0xa14
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2		0xa18
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0		0xa20
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1		0xa24
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2		0xa28
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0		0xa30
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1		0xa34
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2		0xa38
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0		0xa40
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1		0xa44
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2		0xa48
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0		0xa50
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1		0xa54
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2		0xa58
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0		0xa60
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1		0xa64
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2		0xa68
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0		0xa70
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1		0xa74
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2		0xa78
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0		0xb00
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1		0xb04
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2		0xb08
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0		0xb10
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1		0xb14
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2		0xb18
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0		0xb20
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1		0xb24
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2		0xb28
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0		0xb30
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1		0xb34
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2		0xb38
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0		0xb40
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1		0xb44
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2		0xb48
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0		0xb50
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1		0xb54
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2		0xb58
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0		0xb60
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1		0xb64
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2		0xb68
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0		0xb70
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1		0xb74
+#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2		0xb78
+#define EMC_PMACRO_IB_VREF_DQ_0					0xbe0
+#define EMC_PMACRO_IB_VREF_DQ_1					0xbe4
+#define EMC_PMACRO_IB_VREF_DQS_0				0xbf0
+#define EMC_PMACRO_IB_VREF_DQS_1				0xbf4
+#define EMC_PMACRO_DDLL_LONG_CMD_0				0xc00
+#define EMC_PMACRO_DDLL_LONG_CMD_1				0xc04
+#define EMC_PMACRO_DDLL_LONG_CMD_2				0xc08
+#define EMC_PMACRO_DDLL_LONG_CMD_3				0xc0c
+#define EMC_PMACRO_DDLL_LONG_CMD_4				0xc10
+#define EMC_PMACRO_DDLL_LONG_CMD_5				0xc14
+#define EMC_PMACRO_DDLL_SHORT_CMD_0				0xc20
+#define EMC_PMACRO_DDLL_SHORT_CMD_1				0xc24
+#define EMC_PMACRO_DDLL_SHORT_CMD_2				0xc28
+#define EMC_PMACRO_VTTGEN_CTRL_0				0xc34
+#define EMC_PMACRO_VTTGEN_CTRL_1				0xc38
+#define EMC_PMACRO_BG_BIAS_CTRL_0				0xc3c
+#define EMC_PMACRO_PAD_CFG_CTRL					0xc40
+#define EMC_PMACRO_ZCTRL					0xc44
+#define EMC_PMACRO_CMD_PAD_RX_CTRL				0xc50
+#define EMC_PMACRO_DATA_PAD_RX_CTRL				0xc54
+#define EMC_PMACRO_CMD_RX_TERM_MODE				0xc58
+#define EMC_PMACRO_DATA_RX_TERM_MODE				0xc5c
+#define EMC_PMACRO_CMD_PAD_TX_CTRL				0xc60
+#define EMC_PMACRO_DATA_PAD_TX_CTRL				0xc64
+#define EMC_PMACRO_COMMON_PAD_TX_CTRL				0xc68
+#define EMC_PMACRO_AUTOCAL_CFG_COMMON				0xc78
+#define EMC_PMACRO_VTTGEN_CTRL_2				0xcf0
+#define EMC_PMACRO_IB_RXRT					0xcf4
+#define EMC_TRAINING_CTRL					0xe04
+#define EMC_TRAINING_QUSE_CORS_CTRL				0xe0c
+#define EMC_TRAINING_QUSE_FINE_CTRL				0xe10
+#define EMC_TRAINING_QUSE_CTRL_MISC				0xe14
+#define EMC_TRAINING_WRITE_FINE_CTRL				0xe18
+#define EMC_TRAINING_WRITE_CTRL_MISC				0xe1c
+#define EMC_TRAINING_WRITE_VREF_CTRL				0xe20
+#define EMC_TRAINING_READ_FINE_CTRL				0xe24
+#define EMC_TRAINING_READ_CTRL_MISC				0xe28
+#define EMC_TRAINING_READ_VREF_CTRL				0xe2c
+#define EMC_TRAINING_CA_FINE_CTRL				0xe30
+#define EMC_TRAINING_CA_CTRL_MISC				0xe34
+#define EMC_TRAINING_CA_CTRL_MISC1				0xe38
+#define EMC_TRAINING_CA_VREF_CTRL				0xe3c
+#define EMC_TRAINING_SETTLE					0xe44
+#define EMC_TRAINING_MPC					0xe5c
+#define EMC_TRAINING_VREF_SETTLE				0xe6c
+#define EMC_TRAINING_QUSE_VREF_CTRL				0xed0
+#define EMC_TRAINING_OPT_DQS_IB_VREF_RANK0			0xed4
+#define EMC_TRAINING_OPT_DQS_IB_VREF_RANK1			0xed8
+
+#define EMC_COPY_TABLE_PARAM_PERIODIC_FIELDS			BIT(0)
+#define EMC_COPY_TABLE_PARAM_TRIM_REGS				BIT(1)
+
+enum {
+	REG_MC,
+	REG_EMC,
+	REG_EMC0,
+	REG_EMC1,
+};
+
+#define BURST_REGS_PER_CH_LIST						\
+{									\
+	DEFINE_REG(REG_EMC0, EMC_MRW10),				\
+	DEFINE_REG(REG_EMC1, EMC_MRW10),				\
+	DEFINE_REG(REG_EMC0, EMC_MRW11),				\
+	DEFINE_REG(REG_EMC1, EMC_MRW11),				\
+	DEFINE_REG(REG_EMC0, EMC_MRW12),				\
+	DEFINE_REG(REG_EMC1, EMC_MRW12),				\
+	DEFINE_REG(REG_EMC0, EMC_MRW13),				\
+	DEFINE_REG(REG_EMC1, EMC_MRW13),				\
+}
+
+#define BURST_REGS_LIST							\
+{									\
+	DEFINE_REG(REG_EMC, EMC_RC),					\
+	DEFINE_REG(REG_EMC, EMC_RFC),					\
+	DEFINE_REG(REG_EMC, EMC_RFCPB),					\
+	DEFINE_REG(REG_EMC, EMC_REFCTRL2),				\
+	DEFINE_REG(REG_EMC, EMC_RFC_SLR),				\
+	DEFINE_REG(REG_EMC, EMC_RAS),					\
+	DEFINE_REG(REG_EMC, EMC_RP),					\
+	DEFINE_REG(REG_EMC, EMC_R2W),					\
+	DEFINE_REG(REG_EMC, EMC_W2R),					\
+	DEFINE_REG(REG_EMC, EMC_R2P),					\
+	DEFINE_REG(REG_EMC, EMC_W2P),					\
+	DEFINE_REG(REG_EMC, EMC_R2R),					\
+	DEFINE_REG(REG_EMC, EMC_TPPD),					\
+	DEFINE_REG(REG_EMC, EMC_CCDMW),					\
+	DEFINE_REG(REG_EMC, EMC_RD_RCD),				\
+	DEFINE_REG(REG_EMC, EMC_WR_RCD),				\
+	DEFINE_REG(REG_EMC, EMC_RRD),					\
+	DEFINE_REG(REG_EMC, EMC_REXT),					\
+	DEFINE_REG(REG_EMC, EMC_WEXT),					\
+	DEFINE_REG(REG_EMC, EMC_WDV_CHK),				\
+	DEFINE_REG(REG_EMC, EMC_WDV),					\
+	DEFINE_REG(REG_EMC, EMC_WSV),					\
+	DEFINE_REG(REG_EMC, EMC_WEV),					\
+	DEFINE_REG(REG_EMC, EMC_WDV_MASK),				\
+	DEFINE_REG(REG_EMC, EMC_WS_DURATION),				\
+	DEFINE_REG(REG_EMC, EMC_WE_DURATION),				\
+	DEFINE_REG(REG_EMC, EMC_QUSE),					\
+	DEFINE_REG(REG_EMC, EMC_QUSE_WIDTH),				\
+	DEFINE_REG(REG_EMC, EMC_IBDLY),					\
+	DEFINE_REG(REG_EMC, EMC_OBDLY),					\
+	DEFINE_REG(REG_EMC, EMC_EINPUT),				\
+	DEFINE_REG(REG_EMC, EMC_MRW6),					\
+	DEFINE_REG(REG_EMC, EMC_EINPUT_DURATION),			\
+	DEFINE_REG(REG_EMC, EMC_PUTERM_EXTRA),				\
+	DEFINE_REG(REG_EMC, EMC_PUTERM_WIDTH),				\
+	DEFINE_REG(REG_EMC, EMC_QRST),					\
+	DEFINE_REG(REG_EMC, EMC_QSAFE),					\
+	DEFINE_REG(REG_EMC, EMC_RDV),					\
+	DEFINE_REG(REG_EMC, EMC_RDV_MASK),				\
+	DEFINE_REG(REG_EMC, EMC_RDV_EARLY),				\
+	DEFINE_REG(REG_EMC, EMC_RDV_EARLY_MASK),			\
+	DEFINE_REG(REG_EMC, EMC_REFRESH),				\
+	DEFINE_REG(REG_EMC, EMC_BURST_REFRESH_NUM),			\
+	DEFINE_REG(REG_EMC, EMC_PRE_REFRESH_REQ_CNT),			\
+	DEFINE_REG(REG_EMC, EMC_PDEX2WR),				\
+	DEFINE_REG(REG_EMC, EMC_PDEX2RD),				\
+	DEFINE_REG(REG_EMC, EMC_PCHG2PDEN),				\
+	DEFINE_REG(REG_EMC, EMC_ACT2PDEN),				\
+	DEFINE_REG(REG_EMC, EMC_AR2PDEN),				\
+	DEFINE_REG(REG_EMC, EMC_RW2PDEN),				\
+	DEFINE_REG(REG_EMC, EMC_CKE2PDEN),				\
+	DEFINE_REG(REG_EMC, EMC_PDEX2CKE),				\
+	DEFINE_REG(REG_EMC, EMC_PDEX2MRR),				\
+	DEFINE_REG(REG_EMC, EMC_TXSR),					\
+	DEFINE_REG(REG_EMC, EMC_TXSRDLL),				\
+	DEFINE_REG(REG_EMC, EMC_TCKE),					\
+	DEFINE_REG(REG_EMC, EMC_TCKESR),				\
+	DEFINE_REG(REG_EMC, EMC_TPD),					\
+	DEFINE_REG(REG_EMC, EMC_TFAW),					\
+	DEFINE_REG(REG_EMC, EMC_TRPAB),					\
+	DEFINE_REG(REG_EMC, EMC_TCLKSTABLE),				\
+	DEFINE_REG(REG_EMC, EMC_TCLKSTOP),				\
+	DEFINE_REG(REG_EMC, EMC_MRW7),					\
+	DEFINE_REG(REG_EMC, EMC_TREFBW),				\
+	DEFINE_REG(REG_EMC, EMC_ODT_WRITE),				\
+	DEFINE_REG(REG_EMC, EMC_FBIO_CFG5),				\
+	DEFINE_REG(REG_EMC, EMC_FBIO_CFG7),				\
+	DEFINE_REG(REG_EMC, EMC_CFG_DIG_DLL),				\
+	DEFINE_REG(REG_EMC, EMC_CFG_DIG_DLL_PERIOD),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_RXRT),			\
+	DEFINE_REG(REG_EMC, EMC_CFG_PIPE_1),				\
+	DEFINE_REG(REG_EMC, EMC_CFG_PIPE_2),				\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_4),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_5),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_4),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_5),		\
+	DEFINE_REG(REG_EMC, EMC_MRW8),					\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_LONG_CMD_0),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_LONG_CMD_1),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_LONG_CMD_2),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_LONG_CMD_3),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_LONG_CMD_4),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_SHORT_CMD_0),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_SHORT_CMD_1),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_SHORT_CMD_2),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_3),	\
+	DEFINE_REG(REG_EMC, EMC_TXDSRVTTGEN),				\
+	DEFINE_REG(REG_EMC, EMC_FDPD_CTRL_DQ),				\
+	DEFINE_REG(REG_EMC, EMC_FDPD_CTRL_CMD),				\
+	DEFINE_REG(REG_EMC, EMC_FBIO_SPARE),				\
+	DEFINE_REG(REG_EMC, EMC_ZCAL_INTERVAL),				\
+	DEFINE_REG(REG_EMC, EMC_ZCAL_WAIT_CNT),				\
+	DEFINE_REG(REG_EMC, EMC_MRS_WAIT_CNT),				\
+	DEFINE_REG(REG_EMC, EMC_MRS_WAIT_CNT2),				\
+	DEFINE_REG(REG_EMC, EMC_AUTO_CAL_CHANNEL),			\
+	DEFINE_REG(REG_EMC, EMC_DLL_CFG_0),				\
+	DEFINE_REG(REG_EMC, EMC_DLL_CFG_1),				\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_AUTOCAL_CFG_COMMON),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_ZCTRL),				\
+	DEFINE_REG(REG_EMC, EMC_CFG),					\
+	DEFINE_REG(REG_EMC, EMC_CFG_PIPE),				\
+	DEFINE_REG(REG_EMC, EMC_DYN_SELF_REF_CONTROL),			\
+	DEFINE_REG(REG_EMC, EMC_QPOP),					\
+	DEFINE_REG(REG_EMC, EMC_DQS_BRLSHFT_0),				\
+	DEFINE_REG(REG_EMC, EMC_DQS_BRLSHFT_1),				\
+	DEFINE_REG(REG_EMC, EMC_CMD_BRLSHFT_2),				\
+	DEFINE_REG(REG_EMC, EMC_CMD_BRLSHFT_3),				\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_PAD_CFG_CTRL),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DATA_PAD_RX_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_PAD_RX_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DATA_RX_TERM_MODE),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_RX_TERM_MODE),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_PAD_TX_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DATA_PAD_TX_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_COMMON_PAD_TX_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_VTTGEN_CTRL_0),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_VTTGEN_CTRL_1),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_VTTGEN_CTRL_2),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_BRICK_CTRL_RFU1),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_BRICK_CTRL_FDPD),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_BRICK_CTRL_RFU2),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DATA_BRICK_CTRL_FDPD),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_BG_BIAS_CTRL_0),			\
+	DEFINE_REG(REG_EMC, EMC_CFG_3),					\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_0),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_1),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_2),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_3),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_4),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_5),			\
+	DEFINE_REG(REG_EMC, EMC_CONFIG_SAMPLE_DELAY),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_0),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_1),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_2),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_3),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_4),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_5),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_BYPASS),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_PWRD_0),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_PWRD_1),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_PWRD_2),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_CTRL_0),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_CTRL_1),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_CTRL_2),			\
+	DEFINE_REG(REG_EMC, EMC_TR_TIMING_0),				\
+	DEFINE_REG(REG_EMC, EMC_TR_DVFS),				\
+	DEFINE_REG(REG_EMC, EMC_TR_CTRL_1),				\
+	DEFINE_REG(REG_EMC, EMC_TR_RDV),				\
+	DEFINE_REG(REG_EMC, EMC_TR_QPOP),				\
+	DEFINE_REG(REG_EMC, EMC_TR_RDV_MASK),				\
+	DEFINE_REG(REG_EMC, EMC_MRW14),					\
+	DEFINE_REG(REG_EMC, EMC_TR_QSAFE),				\
+	DEFINE_REG(REG_EMC, EMC_TR_QRST),				\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_CTRL),				\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_SETTLE),			\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_VREF_SETTLE),			\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_CA_FINE_CTRL),			\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_CA_CTRL_MISC),			\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_CA_CTRL_MISC1),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_CA_VREF_CTRL),			\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_QUSE_CORS_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_QUSE_FINE_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_QUSE_CTRL_MISC),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_QUSE_VREF_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_READ_FINE_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_READ_CTRL_MISC),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_READ_VREF_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_WRITE_FINE_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_WRITE_CTRL_MISC),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_WRITE_VREF_CTRL),		\
+	DEFINE_REG(REG_EMC, EMC_TRAINING_MPC),				\
+	DEFINE_REG(REG_EMC, EMC_MRW15),					\
+}
+
+#define TRIM_REGS_PER_CH_LIST						\
+{									\
+	DEFINE_REG(REG_EMC0, EMC_CMD_BRLSHFT_0),			\
+	DEFINE_REG(REG_EMC1, EMC_CMD_BRLSHFT_1),			\
+	DEFINE_REG(REG_EMC0, EMC_DATA_BRLSHFT_0),			\
+	DEFINE_REG(REG_EMC1, EMC_DATA_BRLSHFT_0),			\
+	DEFINE_REG(REG_EMC0, EMC_DATA_BRLSHFT_1),			\
+	DEFINE_REG(REG_EMC1, EMC_DATA_BRLSHFT_1),			\
+	DEFINE_REG(REG_EMC0, EMC_QUSE_BRLSHFT_0),			\
+	DEFINE_REG(REG_EMC1, EMC_QUSE_BRLSHFT_1),			\
+	DEFINE_REG(REG_EMC0, EMC_QUSE_BRLSHFT_2),			\
+	DEFINE_REG(REG_EMC1, EMC_QUSE_BRLSHFT_3),			\
+}
+
+#define TRIM_REGS_LIST							\
+{									\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_VREF_DQS_0),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_VREF_DQS_1),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_VREF_DQ_0),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_VREF_DQ_1),			\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2),	\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_0),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_1),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_2),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_3),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_0),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_1),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_2),		\
+	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_3),		\
+}
+
+#define BURST_MC_REGS_LIST						\
+{									\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_CFG),				\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_OUTSTANDING_REQ),		\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_REFPB_HP_CTRL),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_REFPB_BANK_CTRL),		\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RCD),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RP),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RC),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RAS),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_FAW),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RRD),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RAP2PRE),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_WAP2PRE),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_R2R),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_W2W),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_R2W),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_CCDMW),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_W2R),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RFCPB),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DA_TURNS),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DA_COVERS),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_MISC0),				\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_MISC1),				\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_MISC2),				\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_RING1_THROTTLE),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_CTRL),			\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0),		\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1),		\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2),		\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3),		\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4),		\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5),		\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6),		\
+	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7),		\
+}
+
+#define BURST_UP_DOWN_REGS_LIST						\
+{									\
+	DEFINE_REG(REG_MC, MC_MLL_MPCORER_PTSA_RATE),			\
+	DEFINE_REG(REG_MC, MC_FTOP_PTSA_RATE),				\
+	DEFINE_REG(REG_MC, MC_PTSA_GRANT_DECREMENT),			\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_XUSB_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_XUSB_1),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_TSEC_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_SDMMCA_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_SDMMCAA_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_SDMMC_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_SDMMCAB_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_PPCS_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_PPCS_1),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_MPCORE_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_HC_0),			\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_HC_1),			\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_AVPC_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_GPU_0),			\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_GPU2_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_NVENC_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_NVDEC_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_VIC_0),			\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_VI2_0),			\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_ISP2_0),		\
+	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_ISP2_1),		\
+}
+
+#define VREF_REGS_PER_CH_LIST						\
+{									\
+	DEFINE_REG(REG_EMC0, EMC_TRAINING_OPT_DQS_IB_VREF_RANK0),	\
+	DEFINE_REG(REG_EMC1, EMC_TRAINING_OPT_DQS_IB_VREF_RANK0),	\
+	DEFINE_REG(REG_EMC0, EMC_TRAINING_OPT_DQS_IB_VREF_RANK1),	\
+	DEFINE_REG(REG_EMC1, EMC_TRAINING_OPT_DQS_IB_VREF_RANK1),	\
+}
+
+#define DEFINE_REG(type, reg)	reg##_INDEX
+enum BURST_REGS_LIST;
+enum TRIM_REGS_LIST;
+enum BURST_MC_REGS_LIST;
+enum BURST_UP_DOWN_REGS_LIST;
+#undef DEFINE_REG
+
+#define DEFINE_REG(type, reg)	type##_##reg##_INDEX
+enum BURST_REGS_PER_CH_LIST;
+enum TRIM_REGS_PER_CH_LIST;
+enum VREF_REGS_PER_CH_LIST;
+#undef DEFINE_REG
+
+enum {
+	DRAM_TYPE_DDR3   = 0,
+	DRAM_TYPE_LPDDR4 = 1,
+	DRAM_TYPE_LPDDR2 = 2,
+	DRAM_TYPE_DDR2 = 3,
+};
+
+struct emc_table {
+	u32 rev;
+	char dvfs_ver[60];
+	u32 rate;
+	u32 min_volt;
+	u32 gpu_min_volt;
+	char clock_src[32];
+	u32 clk_src_emc;
+	u32 needs_training;
+	u32 training_parttern;
+	u32 trained;
+
+	u32 periodic_training;
+	u32 trained_dram_clktree_c0d0u0;
+	u32 trained_dram_clktree_c0d0u1;
+	u32 trained_dram_clktree_c0d1u0;
+	u32 trained_dram_clktree_c0d1u1;
+	u32 trained_dram_clktree_c1d0u0;
+	u32 trained_dram_clktree_c1d0u1;
+	u32 trained_dram_clktree_c1d1u0;
+	u32 trained_dram_clktree_c1d1u1;
+	u32 current_dram_clktree_c0d0u0;
+	u32 current_dram_clktree_c0d0u1;
+	u32 current_dram_clktree_c0d1u0;
+	u32 current_dram_clktree_c0d1u1;
+	u32 current_dram_clktree_c1d0u0;
+	u32 current_dram_clktree_c1d0u1;
+	u32 current_dram_clktree_c1d1u0;
+	u32 current_dram_clktree_c1d1u1;
+	u32 run_clocks;
+	u32 tree_margin;
+
+	u32 num_burst;
+	u32 num_burst_per_ch;
+	u32 num_trim;
+	u32 num_trim_per_ch;
+	u32 num_mc_regs;
+	u32 num_up_down;
+	u32 vref_num;
+	u32 training_mod_num;
+	u32 dram_timing_num;
+
+	u32  ptfv_list[12];
+
+	u32 burst_regs[221];
+	u32 burst_reg_per_ch[8];
+	u32 shadow_regs_ca_train[221];
+	u32 shadow_regs_quse_train[221];
+	u32 shadow_regs_rdwr_train[221];
+
+	u32 trim_regs[138];
+	u32 trim_perch_regs[10];
+
+	u32 vref_perch_regs[4];
+
+	u32 dram_timings[5];
+	u32 training_mod_regs[20];
+	u32 save_restore_mod_regs[12];
+	u32 burst_mc_regs[33];
+	u32 la_scale_regs[24];
+
+	u32 min_mrs_wait;
+	u32 emc_mrw;
+	u32 emc_mrw2;
+	u32 emc_mrw3;
+	u32 emc_mrw4;
+	u32 emc_mrw9;
+	u32 emc_mrs;
+	u32 emc_emrs;
+	u32 emc_emrs2;
+	u32 emc_auto_cal_config;
+	u32 emc_auto_cal_config2;
+	u32 emc_auto_cal_config3;
+	u32 emc_auto_cal_config4;
+	u32 emc_auto_cal_config5;
+	u32 emc_auto_cal_config6;
+	u32 emc_auto_cal_config7;
+	u32 emc_auto_cal_config8;
+	u32 emc_cfg_2;
+	u32 emc_sel_dpd_ctrl;
+	u32 emc_fdpd_ctrl_cmd_no_ramp;
+	u32 dll_clk_src;
+	u32 clk_out_enb_x_0_clk_enb_emc_dll;
+	u32 latency;
+};
+
+struct tegra_emc {
+	struct clk_hw hw;
+	struct clk *emc_clk;
+	struct device *dev;
+
+	struct tegra_mc *mc;
+
+	void __iomem *emc_base;
+	void __iomem *emc0_base;
+	void __iomem *emc1_base;
+
+	struct emc_table *current_timing;
+	struct emc_table *next_timing;
+	struct emc_table start_timing;
+
+	struct emc_table *emc_table;
+	struct emc_table *emc_table_normal;
+	struct emc_table *emc_table_derated;
+
+	unsigned int emc_table_size;
+
+	int dram_dev_num;
+	u32 dram_type;
+	u32 ram_code;
+	u32 clk_setting;
+};
+#define to_emc(_hw) container_of(_hw, struct tegra_emc, hw)
+
+struct supported_sequence {
+	u8     table_rev;
+	void (*set_clock)(struct tegra_emc *emc, u32 clksrc);
+	u32  (*periodic_compensation)(struct tegra_emc *emc);
+	char  *seq_rev;
+};
+
+int tegra_emc_dt_parse_pdata(struct platform_device *pdev,
+			     struct emc_table **tables,
+			     struct emc_table **derated_tables,
+			     int *num_entries);
+
+#endif
diff --git a/drivers/memory/tegra/tegra210-emc.c b/drivers/memory/tegra/tegra210-emc.c
new file mode 100644
index 000000000000..0c20bcd0e6de
--- /dev/null
+++ b/drivers/memory/tegra/tegra210-emc.c
@@ -0,0 +1,886 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015-2019, NVIDIA CORPORATION.  All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk/tegra.h>
+#include <linux/clk-provider.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <soc/tegra/fuse.h>
+#include <soc/tegra/mc.h>
+
+#include "mc.h"
+#include "tegra210-emc-reg.h"
+
+#define TEGRA_EMC_TABLE_MAX_SIZE		16
+#define TEGRA210_EMC_SUSPEND_RATE		204000000
+
+enum TEGRA_EMC_SOURCE {
+	TEGRA_EMC_SRC_PLLM,
+	TEGRA_EMC_SRC_PLLC,
+	TEGRA_EMC_SRC_PLLP,
+	TEGRA_EMC_SRC_CLKM,
+	TEGRA_EMC_SRC_PLLM_UD,
+	TEGRA_EMC_SRC_PLLMB_UD,
+	TEGRA_EMC_SRC_PLLMB,
+	TEGRA_EMC_SRC_PLLP_UD,
+	TEGRA_EMC_SRC_COUNT,
+};
+
+struct emc_sel {
+	struct clk *input;
+	u32 value;
+	unsigned long input_rate;
+
+	struct clk *input_b;
+	u32 value_b;
+	unsigned long input_rate_b;
+};
+
+struct emc_stats {
+	u64 time_at_clock[TEGRA_EMC_TABLE_MAX_SIZE];
+	int last_sel;
+	u64 last_update;
+	u64 clkchange_count;
+	spinlock_t spinlock;
+};
+
+static struct emc_sel *emc_clk_sel;
+static struct clk *emc_src[TEGRA_EMC_SRC_COUNT];
+static const char *emc_src_names[TEGRA_EMC_SRC_COUNT] = {
+	[TEGRA_EMC_SRC_PLLM] = "pll_m",
+	[TEGRA_EMC_SRC_PLLC] = "pll_c",
+	[TEGRA_EMC_SRC_PLLP] = "pll_p",
+	[TEGRA_EMC_SRC_CLKM] = "clk_m",
+	[TEGRA_EMC_SRC_PLLM_UD] = "pll_m_ud",
+	[TEGRA_EMC_SRC_PLLMB_UD] = "pll_mb_ud",
+	[TEGRA_EMC_SRC_PLLMB] = "pll_mb",
+	[TEGRA_EMC_SRC_PLLP_UD] = "pll_p_ud",
+};
+static struct emc_stats emc_stats;
+static struct supported_sequence supported_seqs[] = {
+	{
+		0,
+		NULL,
+		NULL,
+		NULL
+	}
+};
+static struct supported_sequence *seq;
+static struct tegra_emc *tegra_emc;
+static DEFINE_SPINLOCK(emc_access_lock);
+static ktime_t clkchange_time;
+static int clkchange_delay = 100;
+
+static void emc_train(struct timer_list *tmr);
+DEFINE_TIMER(emc_training_timer, emc_train);
+static u32 timer_period_training = 100;
+
+#define DEFINE_REG(type, reg) (reg)
+u32 burst_regs_per_ch_off[] = BURST_REGS_PER_CH_LIST;
+u32 burst_regs_off[] = BURST_REGS_LIST;
+u32 burst_mc_regs_off[] = BURST_MC_REGS_LIST;
+u32 la_scale_regs_off[] = BURST_UP_DOWN_REGS_LIST;
+u32 trim_regs_per_ch_off[] = TRIM_REGS_PER_CH_LIST;
+u32 trim_regs_off[] = TRIM_REGS_LIST;
+u32 vref_regs_per_ch_off[] = VREF_REGS_PER_CH_LIST;
+#undef DEFINE_REG
+
+#define DEFINE_REG(type, reg) (type)
+u32 burst_regs_per_ch_type[] = BURST_REGS_PER_CH_LIST;
+u32 trim_regs_per_ch_type[] = TRIM_REGS_PER_CH_LIST;
+u32 vref_regs_per_ch_type[] = VREF_REGS_PER_CH_LIST;
+#undef DEFINE_REG
+
+#ifdef CONFIG_PM_SLEEP
+static bool emc_suspend;
+static unsigned long emc_resume_rate;
+#endif
+
+inline u32 emc_readl(struct tegra_emc *emc, unsigned long offset)
+{
+	return readl(emc->emc_base + offset);
+}
+
+inline u32 emc_readl_per_ch(struct tegra_emc *emc, int type,
+			    unsigned long offset)
+{
+	u32 val = 0;
+
+	switch (type) {
+	case REG_EMC:
+	case REG_EMC0:
+		val = readl(emc->emc_base + offset);
+		break;
+	case REG_EMC1:
+		val = readl(emc->emc1_base + offset);
+		break;
+	}
+
+	return val;
+}
+
+static inline u32 emc_src_val(u32 val)
+{
+	return (val & EMC_CLK_EMC_2X_CLK_SRC_MASK) >>
+		EMC_CLK_EMC_2X_CLK_SRC_SHIFT;
+}
+
+static inline u32 emc_div_val(u32 val)
+{
+	return (val & EMC_CLK_EMC_2X_CLK_DIVISOR_MASK) >>
+		EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT;
+}
+
+static void emc_train(struct timer_list *tmr)
+{
+	unsigned long flags;
+	struct tegra_emc *emc = tegra_emc;
+
+	if (!emc->current_timing)
+		return;
+
+	spin_lock_irqsave(&emc_access_lock, flags);
+	if (seq->periodic_compensation)
+		seq->periodic_compensation(emc);
+	spin_unlock_irqrestore(&emc_access_lock, flags);
+
+	mod_timer(&emc_training_timer,
+		  jiffies + msecs_to_jiffies(timer_period_training));
+}
+
+static void emc_training_timer_start(void)
+{
+	mod_timer(&emc_training_timer,
+		  jiffies + msecs_to_jiffies(timer_period_training));
+}
+
+static void emc_training_timer_stop(void)
+{
+	del_timer(&emc_training_timer);
+}
+
+static void emc_set_clock(struct tegra_emc *emc, u32 clksrc)
+{
+	seq->set_clock(emc, clksrc);
+
+	if (emc->next_timing->periodic_training)
+		emc_training_timer_start();
+	else
+		emc_training_timer_stop();
+}
+
+static inline void emc_get_timing(struct tegra_emc *emc,
+				  struct emc_table *timing)
+{
+	int i, div;
+	u32 val;
+	unsigned long rate;
+
+	for (i = 0; i < timing->num_burst; i++) {
+		if (burst_regs_off[i])
+			timing->burst_regs[i] = emc_readl(emc,
+							  burst_regs_off[i]);
+		else
+			timing->burst_regs[i] = 0;
+	}
+
+	for (i = 0; i < timing->num_burst_per_ch; i++)
+		timing->burst_reg_per_ch[i] = emc_readl_per_ch(emc,
+			burst_regs_per_ch_type[i], burst_regs_per_ch_off[i]);
+
+	for (i = 0; i < timing->num_trim; i++)
+		timing->trim_regs[i] = emc_readl(emc, trim_regs_off[i]);
+
+	for (i = 0; i < timing->num_trim_per_ch; i++)
+		timing->trim_perch_regs[i] = emc_readl_per_ch(emc,
+			trim_regs_per_ch_type[i], trim_regs_per_ch_off[i]);
+
+	for (i = 0; i < timing->vref_num; i++)
+		timing->vref_perch_regs[i] = emc_readl_per_ch(emc,
+			vref_regs_per_ch_type[i], vref_regs_per_ch_off[i]);
+
+	for (i = 0; i < timing->num_mc_regs; i++)
+		timing->burst_mc_regs[i] = mc_readl(emc->mc,
+						    burst_mc_regs_off[i]);
+
+	for (i = 0; i < timing->num_up_down; i++)
+		timing->la_scale_regs[i] = mc_readl(emc->mc,
+						    la_scale_regs_off[i]);
+
+	val = tegra210_clk_emc_get_setting();
+	rate = clk_get_rate(emc_src[emc_src_val(val)]);
+	div = emc_div_val(val);
+	div += 2;
+	rate *= 2;
+	rate += div - 1;
+	do_div(rate, div);
+	timing->rate = rate / 1000;
+}
+
+static void __emc_copy_table_params(struct emc_table *src,
+				    struct emc_table *dst, int flags)
+{
+	int i;
+
+	if (flags & EMC_COPY_TABLE_PARAM_PERIODIC_FIELDS) {
+		dst->trained_dram_clktree_c0d0u0 =
+			src->trained_dram_clktree_c0d0u0;
+		dst->trained_dram_clktree_c0d0u1 =
+			src->trained_dram_clktree_c0d0u1;
+		dst->trained_dram_clktree_c0d1u0 =
+			src->trained_dram_clktree_c0d1u0;
+		dst->trained_dram_clktree_c0d1u1 =
+			src->trained_dram_clktree_c0d1u1;
+		dst->trained_dram_clktree_c1d0u0 =
+			src->trained_dram_clktree_c1d0u0;
+		dst->trained_dram_clktree_c1d0u1 =
+			src->trained_dram_clktree_c1d0u1;
+		dst->trained_dram_clktree_c1d1u0 =
+			src->trained_dram_clktree_c1d1u0;
+		dst->trained_dram_clktree_c1d1u1 =
+			src->trained_dram_clktree_c1d1u1;
+		dst->current_dram_clktree_c0d0u0 =
+			src->current_dram_clktree_c0d0u0;
+		dst->current_dram_clktree_c0d0u1 =
+			src->current_dram_clktree_c0d0u1;
+		dst->current_dram_clktree_c0d1u0 =
+			src->current_dram_clktree_c0d1u0;
+		dst->current_dram_clktree_c0d1u1 =
+			src->current_dram_clktree_c0d1u1;
+		dst->current_dram_clktree_c1d0u0 =
+			src->current_dram_clktree_c1d0u0;
+		dst->current_dram_clktree_c1d0u1 =
+			src->current_dram_clktree_c1d0u1;
+		dst->current_dram_clktree_c1d1u0 =
+			src->current_dram_clktree_c1d1u0;
+		dst->current_dram_clktree_c1d1u1 =
+			src->current_dram_clktree_c1d1u1;
+	}
+
+	if (flags & EMC_COPY_TABLE_PARAM_TRIM_REGS) {
+		for (i = 0; i < src->num_trim_per_ch; i++)
+			dst->trim_perch_regs[i] = src->trim_perch_regs[i];
+
+		for (i = 0; i < src->num_trim; i++)
+			dst->trim_regs[i] = src->trim_regs[i];
+
+		for (i = 0; i < src->num_burst_per_ch; i++)
+			dst->burst_reg_per_ch[i] = src->burst_reg_per_ch[i];
+
+		dst->trained = src->trained;
+	}
+}
+
+static void emc_copy_table_params(struct emc_table *src,
+				  struct emc_table *dst,
+				  int table_size,
+				  int flags)
+{
+	int i;
+
+	for (i = 0; i < table_size; i++)
+		__emc_copy_table_params(&src[i], &dst[i], flags);
+}
+
+static void emc_last_stats_update(int last_sel)
+{
+	unsigned long flags;
+	u64 cur_jiffies = get_jiffies_64();
+
+	spin_lock_irqsave(&emc_stats.spinlock, flags);
+
+	if (emc_stats.last_sel < TEGRA_EMC_TABLE_MAX_SIZE)
+		emc_stats.time_at_clock[emc_stats.last_sel] =
+			emc_stats.time_at_clock[emc_stats.last_sel]
+			+ (cur_jiffies - emc_stats.last_update);
+
+	emc_stats.last_update = cur_jiffies;
+
+	if (last_sel < TEGRA_EMC_TABLE_MAX_SIZE) {
+		emc_stats.clkchange_count++;
+		emc_stats.last_sel = last_sel;
+	}
+
+	spin_unlock_irqrestore(&emc_stats.spinlock, flags);
+}
+
+static int emc_table_lookup(struct tegra_emc *emc, unsigned long rate)
+{
+	int i;
+
+	for (i = 0; i < emc->emc_table_size; i++) {
+		if (emc_clk_sel[i].input == NULL)
+			continue;
+
+		if (emc->emc_table[i].rate == rate)
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static struct clk *emc_predict_parent(struct tegra_emc *emc,
+				      unsigned long rate)
+{
+	struct clk *old_parent, *new_parent;
+	unsigned long parent_rate;
+	int idx;
+
+	idx = emc_table_lookup(emc, rate / 1000);
+	if (idx < 0)
+		return ERR_PTR(-EINVAL);
+
+	parent_rate = emc_clk_sel[idx].input_rate * 1000;
+	new_parent = emc_clk_sel[idx].input;
+	old_parent = clk_get_parent(emc->emc_clk);
+
+	if (parent_rate == clk_get_rate(old_parent))
+		return old_parent;
+
+	if (clk_is_match(new_parent, old_parent))
+		new_parent = emc_clk_sel[idx].input_b;
+
+	if (parent_rate != clk_get_rate(new_parent))
+		clk_set_rate(new_parent, parent_rate);
+
+	return new_parent;
+}
+
+static int emc_set_rate(struct tegra_emc *emc, unsigned long rate)
+{
+	int i;
+	unsigned long flags;
+	s64 last_change_delay;
+	struct clk *parent;
+
+	if (emc_suspend)
+		rate = TEGRA210_EMC_SUSPEND_RATE;
+
+	if (rate == emc->current_timing->rate)
+		return 0;
+
+	i = emc_table_lookup(emc, rate / 1000);
+
+	if (i < 0)
+		return i;
+
+	if (rate > 204000000 && !emc->emc_table[i].trained)
+		return -EINVAL;
+
+	parent = emc_predict_parent(emc, rate);
+	if (clk_is_match(parent, emc_clk_sel[i].input))
+		emc->clk_setting = emc_clk_sel[i].value;
+	else
+		emc->clk_setting = emc_clk_sel[i].value_b;
+
+	emc->next_timing = &emc->emc_table[i];
+	last_change_delay = ktime_us_delta(ktime_get(), clkchange_time);
+	if ((last_change_delay >= 0) && (last_change_delay < clkchange_delay))
+		udelay(clkchange_delay - (int)last_change_delay);
+
+	spin_lock_irqsave(&emc_access_lock, flags);
+	emc_set_clock(emc, emc->clk_setting);
+	clkchange_time = ktime_get();
+	emc->current_timing = &emc->emc_table[i];
+	spin_unlock_irqrestore(&emc_access_lock, flags);
+
+	emc_last_stats_update(i);
+
+	return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int emc_stats_show(struct seq_file *s, void *data)
+{
+	int i;
+	struct tegra_emc *emc = (struct tegra_emc *)s->private;
+
+	if (!emc->emc_table_size || !seq)
+		return 0;
+
+	emc_last_stats_update(TEGRA_EMC_TABLE_MAX_SIZE);
+
+	seq_printf(s, "%-10s %-10s\n", "rate kHz", "time");
+	for (i = 0; i < emc->emc_table_size; i++) {
+		if (emc_clk_sel[i].input == NULL)
+			continue;
+
+		seq_printf(s, "%-10u %-10llu\n",
+			   emc->emc_table[i].rate,
+			   jiffies_64_to_clock_t(
+			   emc_stats.time_at_clock[i]));
+	}
+	seq_printf(s, "%-15s %llu\n", "transitions:",
+		   emc_stats.clkchange_count);
+	seq_printf(s, "%-15s %llu\n", "time-stamp:",
+		   jiffies_64_to_clock_t(emc_stats.last_update));
+
+	return 0;
+}
+
+static int emc_stats_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, emc_stats_show, inode->i_private);
+}
+
+static const struct file_operations emc_stats_fops = {
+	.open		= emc_stats_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int debug_emc_get_rate(void *data, u64 *val)
+{
+	struct clk *c = data;
+
+	*val = clk_get_rate(c);
+
+	return 0;
+}
+
+static int debug_emc_set_rate(void *data, u64 val)
+{
+	struct clk *c = data;
+
+	return clk_set_rate(c, val);
+}
+DEFINE_SIMPLE_ATTRIBUTE(emc_rate_fops, debug_emc_get_rate,
+			debug_emc_set_rate, "%llu\n");
+
+static int tegra_emc_debug_init(struct tegra_emc *emc)
+{
+	struct dentry *emc_debugfs_root;
+
+	emc_debugfs_root = debugfs_create_dir("tegra_emc", NULL);
+	if (!emc_debugfs_root)
+		return -ENOMEM;
+
+	if (!debugfs_create_file("stats", 0444, emc_debugfs_root, emc,
+				 &emc_stats_fops))
+		goto err_out;
+
+	if (!debugfs_create_file("rate", 0644, emc_debugfs_root, emc->emc_clk,
+				 &emc_rate_fops))
+		goto err_out;
+
+	return 0;
+
+err_out:
+	debugfs_remove_recursive(emc_debugfs_root);
+	return -ENOMEM;
+}
+#endif /* CONFIG_DEBUG_FS */
+
+static u8 clk_emc_get_parent(struct clk_hw *hw)
+{
+	struct tegra_emc *emc = to_emc(hw);
+
+	if (!emc->clk_setting)
+		emc->clk_setting = tegra210_clk_emc_get_setting();
+
+	return emc_src_val(emc->clk_setting);
+}
+
+static unsigned long clk_emc_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct tegra_emc *emc = to_emc(hw);
+
+	if (!emc->emc_table_size || !seq) {
+		u32 emc_setting = tegra210_clk_emc_get_setting();
+
+		return clk_get_rate(emc_src[emc_src_val(emc_setting)]);
+	}
+
+	return emc->current_timing->rate * 1000;
+}
+
+static long clk_emc_round_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long *prate)
+{
+	struct tegra_emc *emc = to_emc(hw);
+	int i;
+
+	if (!emc->emc_table_size || !seq) {
+		u32 emc_setting = tegra210_clk_emc_get_setting();
+
+		return clk_get_rate(emc_src[emc_src_val(emc_setting)]);
+	}
+
+	if (emc_suspend)
+		return TEGRA210_EMC_SUSPEND_RATE;
+
+	rate /= 1000;
+
+	for (i = 0; i < emc->emc_table_size; i++) {
+		if (emc->emc_table[i].rate >= rate)
+			return emc->emc_table[i].rate * 1000;
+	}
+
+	return emc->emc_table[i - 1].rate * 1000;
+}
+
+static int clk_emc_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	struct tegra_emc *emc = to_emc(hw);
+	struct clk *old_parent, *new_parent;
+	int ret = -EINVAL;
+
+	if (!emc->emc_table_size || !seq)
+		return ret;
+
+	if (emc_suspend)
+		rate = TEGRA210_EMC_SUSPEND_RATE;
+
+	old_parent = clk_get_parent(hw->clk);
+	new_parent = emc_predict_parent(emc, rate);
+	if (IS_ERR(new_parent))
+		goto out;
+
+	if (!clk_is_match(new_parent, old_parent))
+		clk_prepare_enable(new_parent);
+
+	ret = emc_set_rate(emc, rate);
+	if (ret) {
+		if (new_parent != old_parent)
+			clk_disable_unprepare(new_parent);
+		goto out;
+	}
+
+	if (!clk_is_match(new_parent, old_parent)) {
+		clk_hw_reparent(hw, __clk_get_hw(new_parent));
+		clk_disable_unprepare(old_parent);
+	}
+
+out:
+	return ret;
+}
+
+static const struct clk_ops tegra_clk_emc_ops = {
+	.get_parent = clk_emc_get_parent,
+	.recalc_rate = clk_emc_recalc_rate,
+	.round_rate = clk_emc_round_rate,
+	.set_rate = clk_emc_set_rate,
+};
+
+static int find_matching_input(struct emc_table *table, struct emc_sel *sel)
+{
+	u32 div_value;
+	u32 src_value;
+	unsigned long input_rate = 0;
+	struct clk *input_clk;
+
+	div_value = emc_div_val(table->clk_src_emc);
+	src_value = emc_src_val(table->clk_src_emc);
+
+	if (div_value & 0x1) {
+		pr_warn("Tegra EMC: invalid odd divider for EMC rate %u\n",
+			table->rate);
+		return -EINVAL;
+	}
+
+	if (!(table->clk_src_emc & EMC_CLK_MC_EMC_SAME_FREQ) !=
+	    !(MC_EMEM_ARB_MISC0_EMC_SAME_FREQ &
+	    table->burst_regs[MC_EMEM_ARB_MISC0_INDEX])) {
+		pr_warn("Tegra EMC: ambiguous EMC to MC ratio for rate %u\n",
+			table->rate);
+		return -EINVAL;
+	}
+
+	input_clk = emc_src[src_value];
+	if (input_clk == emc_src[TEGRA_EMC_SRC_PLLM]
+		|| input_clk == emc_src[TEGRA_EMC_SRC_PLLM_UD]) {
+		input_rate = table->rate * (1 + div_value / 2);
+	} else {
+		input_rate = clk_get_rate(input_clk) / 1000;
+		if (input_rate != (table->rate * (1 + div_value / 2))) {
+			pr_warn("Tegra EMC: rate %u doesn't match input\n",
+				table->rate);
+			return -EINVAL;
+		}
+	}
+
+	sel->input = input_clk;
+	sel->input_rate = input_rate;
+	sel->value = table->clk_src_emc;
+	sel->input_b = input_clk;
+	sel->input_rate_b = input_rate;
+	sel->value_b = table->clk_src_emc;
+
+	if (input_clk == emc_src[TEGRA_EMC_SRC_PLLM]) {
+		sel->input_b = emc_src[TEGRA_EMC_SRC_PLLMB];
+		sel->value_b = table->clk_src_emc &
+			       ~EMC_CLK_EMC_2X_CLK_SRC_MASK;
+		sel->value_b |= TEGRA_EMC_SRC_PLLMB <<
+				EMC_CLK_EMC_2X_CLK_SRC_SHIFT;
+	}
+
+	if (input_clk == emc_src[TEGRA_EMC_SRC_PLLM_UD]) {
+		sel->input_b = emc_src[TEGRA_EMC_SRC_PLLMB_UD];
+		sel->value_b = table->clk_src_emc &
+			       ~EMC_CLK_EMC_2X_CLK_SRC_MASK;
+		sel->value_b |= TEGRA_EMC_SRC_PLLMB_UD <<
+				EMC_CLK_EMC_2X_CLK_SRC_SHIFT;
+	}
+
+	return 0;
+}
+
+static int tegra210_emc_probe(struct platform_device *pdev)
+{
+	int i, div;
+	unsigned long table_rate;
+	unsigned long current_rate;
+	struct device_node *np;
+	struct platform_device *mc;
+	struct tegra_emc *emc;
+	struct clk_init_data init;
+	struct clk *clk;
+	struct resource *r;
+	u32 emc_setting;
+
+	emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL);
+	if (!emc)
+		return -ENOMEM;
+
+	np = of_parse_phandle(pdev->dev.of_node, "nvidia,memory-controller", 0);
+	if (!np) {
+		dev_err(&pdev->dev, "could not get memory controller\n");
+		return -ENOENT;
+	}
+
+	mc = of_find_device_by_node(np);
+	of_node_put(np);
+	if (!mc)
+		return -ENOENT;
+
+	emc->mc = platform_get_drvdata(mc);
+	if (!emc->mc)
+		return -EPROBE_DEFER;
+
+	emc->ram_code = tegra_read_ram_code();
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	emc->emc_base = devm_ioremap_resource(&pdev->dev, r);
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	emc->emc0_base = devm_ioremap_resource(&pdev->dev, r);
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+	emc->emc1_base = devm_ioremap_resource(&pdev->dev, r);
+
+	for (i = 0; i < TEGRA_EMC_SRC_COUNT; i++) {
+		emc_src[i] = devm_clk_get(&pdev->dev,
+						emc_src_names[i]);
+		if (IS_ERR(emc_src[i])) {
+			dev_err(&pdev->dev, "Can not find EMC source clock\n");
+			return -ENODATA;
+		}
+	}
+
+	/* Init EMC rate statistic data */
+	emc_stats.clkchange_count = 0;
+	spin_lock_init(&emc_stats.spinlock);
+	emc_stats.last_update = get_jiffies_64();
+	emc_stats.last_sel = TEGRA_EMC_TABLE_MAX_SIZE;
+
+	emc->dram_type = (emc_readl(emc, EMC_FBIO_CFG5) &
+			  EMC_FBIO_CFG5_DRAM_TYPE_MASK) >>
+			  EMC_FBIO_CFG5_DRAM_TYPE_SHIFT;
+	if (emc->dram_type != DRAM_TYPE_DDR3 &&
+	    emc->dram_type != DRAM_TYPE_LPDDR2 &&
+	    emc->dram_type != DRAM_TYPE_LPDDR4) {
+		dev_err(&pdev->dev, "DRAM not supported\n");
+		return -ENODATA;
+	}
+
+	emc->dram_dev_num = tegra_mc_get_emem_device_count(emc->mc);
+
+	tegra_emc_dt_parse_pdata(pdev, &emc->emc_table_normal,
+				 &emc->emc_table_derated,
+				 &emc->emc_table_size);
+	if (!emc->emc_table_size ||
+	    emc->emc_table_size > TEGRA_EMC_TABLE_MAX_SIZE) {
+		dev_err(&pdev->dev, "Invalid table size %d\n",
+			emc->emc_table_size);
+		goto emc_clk_register;
+	}
+	emc->emc_table = emc->emc_table_normal;
+
+	/*
+	 * Copy trained trimmers from the normal table to the derated
+	 * table for LP4. Bootloader trains only the normal table.
+	 * Trimmers are the same for derated and normal tables.
+	 */
+	if (emc->emc_table_derated && emc->dram_type == DRAM_TYPE_LPDDR4)
+		emc_copy_table_params(emc->emc_table_normal,
+				      emc->emc_table_derated,
+				      emc->emc_table_size,
+				      EMC_COPY_TABLE_PARAM_PERIODIC_FIELDS |
+				      EMC_COPY_TABLE_PARAM_TRIM_REGS);
+
+	seq = supported_seqs;
+	while (seq->table_rev) {
+		if (seq->table_rev == emc->emc_table[0].rev)
+			break;
+		seq++;
+	}
+	if (!seq->set_clock) {
+		seq = NULL;
+		dev_err(&pdev->dev, "Invalid EMC sequence for table Rev. %d\n",
+			emc->emc_table[0].rev);
+		goto emc_clk_register;
+	}
+
+	emc_clk_sel = devm_kcalloc(&pdev->dev,
+				   emc->emc_table_size,
+				   sizeof(struct emc_sel),
+				   GFP_KERNEL);
+	if (!emc_clk_sel) {
+		dev_err(&pdev->dev, "Memory allocation failed\n");
+		return -ENOMEM;
+	}
+
+	/* calculate the rate from source clock */
+	emc_setting = tegra210_clk_emc_get_setting();
+	current_rate = clk_get_rate(emc_src[emc_src_val(emc_setting)]);
+	div = emc_div_val(emc_setting);
+	div += 2;
+	current_rate *= 2;
+	current_rate += div - 1;
+	do_div(current_rate, div);
+	current_rate /=  1000;
+
+	for (i = 0; i < emc->emc_table_size; i++) {
+		table_rate = emc->emc_table[i].rate;
+		if (!table_rate)
+			continue;
+
+		if (i && ((table_rate <= emc->emc_table[i-1].rate) ||
+		   (emc->emc_table[i].min_volt <
+		    emc->emc_table[i-1].min_volt)))
+			continue;
+
+		if (emc->emc_table[i].rev != emc->emc_table[0].rev)
+			continue;
+
+		if (find_matching_input(&emc->emc_table[i], &emc_clk_sel[i]))
+			continue;
+
+		if (table_rate == current_rate)
+			emc_stats.last_sel = i;
+	}
+
+	dev_info(&pdev->dev, "validated EMC DFS table\n");
+
+	/* Update the start_timing base on the settings from firmware */
+	emc->start_timing.num_burst = emc->emc_table[0].num_burst;
+	emc->start_timing.num_burst_per_ch =
+		emc->emc_table[0].num_burst_per_ch;
+	emc->start_timing.num_trim = emc->emc_table[0].num_trim;
+	emc->start_timing.num_trim_per_ch =
+		emc->emc_table[0].num_trim_per_ch;
+	emc->start_timing.num_mc_regs = emc->emc_table[0].num_mc_regs;
+	emc->start_timing.num_up_down = emc->emc_table[0].num_up_down;
+	emc->start_timing.vref_num = emc->emc_table[0].vref_num;
+
+	emc_get_timing(emc, &emc->start_timing);
+	emc->current_timing = &emc->start_timing;
+	emc->clk_setting = emc_setting;
+
+emc_clk_register:
+	init.name = "emc";
+	init.ops = &tegra_clk_emc_ops;
+	init.flags = CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE;
+	init.parent_names = emc_src_names;
+	init.num_parents = ARRAY_SIZE(emc_src_names);
+	emc->hw.init = &init;
+
+	clk = clk_register(&pdev->dev, &emc->hw);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+	emc->emc_clk = clk;
+	emc->dev = &pdev->dev;
+	tegra_emc = emc;
+	dev_set_drvdata(emc->dev, emc);
+
+	if (emc->emc_table_size && seq) {
+		for (i = 0; i < emc->emc_table_size; i++) {
+			table_rate = emc->emc_table[i].rate * 1000;
+			if (clk_set_rate(clk, table_rate))
+				dev_info(&pdev->dev,
+					 "rate: %lu validation fail\n",
+					 table_rate);
+
+			dev_info(&pdev->dev, "rate: %lu validation success\n",
+				 table_rate);
+		}
+	}
+
+	if (IS_ENABLED(CONFIG_DEBUG_FS))
+		tegra_emc_debug_init(emc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tegra210_emc_suspend(struct device *dev)
+{
+	struct tegra_emc *emc = dev_get_drvdata(dev);
+
+	if (!IS_ERR(emc->emc_clk)) {
+		emc_suspend = true;
+		emc_resume_rate = clk_get_rate(emc->emc_clk);
+		clk_set_rate(emc->emc_clk, TEGRA210_EMC_SUSPEND_RATE);
+
+		pr_debug("%s at rate %lu\n", __func__,
+			 clk_get_rate(emc->emc_clk));
+	}
+
+	return 0;
+}
+
+static int tegra210_emc_resume(struct device *dev)
+{
+	struct tegra_emc *emc = dev_get_drvdata(dev);
+
+	if (!IS_ERR(emc->emc_clk)) {
+		emc_suspend = false;
+		clk_set_rate(emc->emc_clk, emc_resume_rate);
+
+		pr_debug("%s at rate %lu\n", __func__,
+			 clk_get_rate(emc->emc_clk));
+	}
+
+	return 0;
+}
+
+static const struct dev_pm_ops tegra210_emc_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(tegra210_emc_suspend, tegra210_emc_resume)
+};
+#endif
+
+static const struct of_device_id tegra210_emc_of_match[] = {
+	{ .compatible = "nvidia,tegra210-emc", },
+	{ },
+};
+
+static struct platform_driver tegra210_emc_driver = {
+	.driver	= {
+		.name = "tegra210-emc",
+		.of_match_table = tegra210_emc_of_match,
+		.pm = &tegra210_emc_pm_ops,
+	},
+	.probe = tegra210_emc_probe,
+};
+
+static int __init tegra210_emc_init(void)
+{
+	return platform_driver_register(&tegra210_emc_driver);
+}
+subsys_initcall(tegra210_emc_init);
-- 
2.21.0


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 4/8] memory: tegra: add EMC scaling support code for Tegra210
  2019-03-25  7:45 [PATCH 0/8] Add EMC scaling support for Tegra210 Joseph Lo
                   ` (2 preceding siblings ...)
  2019-03-25  7:45 ` [PATCH 3/8] memory: tegra: Add Tegra210 EMC clock driver Joseph Lo
@ 2019-03-25  7:45 ` Joseph Lo
  2019-04-02 11:39   ` Dmitry Osipenko
  2019-03-25  7:45 ` [PATCH 5/8] memory: tegra: Add EMC scaling sequence " Joseph Lo
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 28+ messages in thread
From: Joseph Lo @ 2019-03-25  7:45 UTC (permalink / raw)
  To: Thierry Reding, Peter De Schrijver, Jonathan Hunter, Rob Herring,
	Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree, Joseph Lo

This patch adds the required APIs and variables for the EMC scaling
sequence code on Tegra210.

Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.

Signed-off-by: Joseph Lo <josephl@nvidia.com>
---
 drivers/memory/tegra/tegra210-emc-reg.h | 265 +++++++
 drivers/memory/tegra/tegra210-emc.c     | 923 ++++++++++++++++++++++++
 2 files changed, 1188 insertions(+)

diff --git a/drivers/memory/tegra/tegra210-emc-reg.h b/drivers/memory/tegra/tegra210-emc-reg.h
index 84fcc85f3b6d..31a69e718dbc 100644
--- a/drivers/memory/tegra/tegra210-emc-reg.h
+++ b/drivers/memory/tegra/tegra210-emc-reg.h
@@ -12,6 +12,13 @@
 
 #include "mc.h"
 
+#define DVFS_FGCG_HIGH_SPEED_THRESHOLD				1000
+#define IOBRICK_DCC_THRESHOLD					2400
+#define DVFS_FGCG_MID_SPEED_THRESHOLD				600
+
+#define EMC_STATUS_UPDATE_TIMEOUT				1000
+
+#define MC_EMEM_ADR_CFG						0x54
 #define MC_EMEM_ARB_CFG						0x90
 #define MC_EMEM_ARB_OUTSTANDING_REQ				0x94
 #define MC_EMEM_ARB_TIMING_RCD					0x98
@@ -75,12 +82,33 @@
 #define EMC_CLK_EMC_2X_CLK_SRC_SHIFT				29
 #define EMC_CLK_EMC_2X_CLK_SRC_MASK				\
 	(0x7 << EMC_CLK_EMC_2X_CLK_SRC_SHIFT)
+#define EMC_CLK_SOURCE_PLLM_LJ					0x4
+#define EMC_CLK_SOURCE_PLLMB_LJ					0x5
 #define	EMC_CLK_MC_EMC_SAME_FREQ				BIT(16)
 #define EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT			0
 #define EMC_CLK_EMC_2X_CLK_DIVISOR_MASK				\
 	(0xff << EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT)
 
+#define CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL			0x664
+#define DLL_CLK_EMC_DLL_CLK_SRC_SHIFT				29
+#define DLL_CLK_EMC_DLL_CLK_SRC_MASK				\
+	(0x7 << DLL_CLK_EMC_DLL_CLK_SRC_SHIFT)
+#define DLL_CLK_EMC_DLL_DDLL_CLK_SEL_SHIFT			10
+#define DLL_CLK_EMC_DLL_DDLL_CLK_SEL_MASK			\
+	(0x3 << DLL_CLK_EMC_DLL_DDLL_CLK_SEL_SHIFT)
+#define PLLM_VCOA						0
+#define PLLM_VCOB						1
+#define EMC_DLL_SWITCH_OUT					2
+#define DLL_CLK_EMC_DLL_CLK_DIVISOR_SHIFT			0
+#define DLL_CLK_EMC_DLL_CLK_DIVISOR_MASK			\
+	(0xff << DLL_CLK_EMC_DLL_CLK_DIVISOR_SHIFT)
+
+#define EMC_INTSTATUS						0x0
+#define EMC_INTSTATUS_CLKCHANGE_COMPLETE			BIT(4)
+#define EMC_DBG							0x8
+#define EMC_DBG_WRITE_MUX_ACTIVE				BIT(1)
 #define EMC_CFG							0xc
+#define EMC_TIMING_CONTROL					0x28
 #define EMC_RC							0x2c
 #define EMC_RFC							0x30
 #define EMC_RAS							0x34
@@ -125,16 +153,40 @@
 #define EMC_FBIO_CFG5_DRAM_TYPE_SHIFT				0
 #define EMC_FBIO_CFG5_DRAM_TYPE_MASK				\
 	(0x3 <<	EMC_FBIO_CFG5_DRAM_TYPE_SHIFT)
+#define EMC_FBIO_CFG5_CMD_TX_DIS				BIT(8)
+
 #define EMC_PDEX2CKE						0x118
 #define EMC_CKE2PDEN						0x11c
+#define EMC_MPC							0x128
 #define EMC_R2R							0x144
 #define EMC_EINPUT						0x14c
 #define EMC_EINPUT_DURATION					0x150
 #define EMC_PUTERM_EXTRA					0x154
 #define EMC_TCKESR						0x158
 #define EMC_TPD							0x15c
+#define EMC_EMC_STATUS						0x2b4
+#define EMC_EMC_STATUS_TIMING_UPDATE_STALLED			BIT(23)
 #define EMC_CFG_DIG_DLL						0x2bc
+#define EMC_CFG_DIG_DLL_CFG_DLL_EN				BIT(0)
+#define EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_UNTIL_LOCK		BIT(1)
+#define EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_TRAFFIC		BIT(3)
+#define EMC_CFG_DIG_DLL_CFG_DLL_STALL_RW_UNTIL_LOCK		BIT(4)
+#define EMC_CFG_DIG_DLL_CFG_DLL_MODE_SHIFT			6
+#define EMC_CFG_DIG_DLL_CFG_DLL_MODE_MASK			\
+	(0x3 << EMC_CFG_DIG_DLL_CFG_DLL_MODE_SHIFT)
+#define EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_SHIFT		8
+#define EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_MASK			\
+	(0x7 << EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_SHIFT)
+
 #define EMC_CFG_DIG_DLL_PERIOD					0x2c0
+#define EMC_DIG_DLL_STATUS					0x2c4
+#define EMC_DIG_DLL_STATUS_DLL_LOCK				BIT(15)
+#define EMC_DIG_DLL_STATUS_DLL_PRIV_UPDATED			BIT(17)
+#define EMC_DIG_DLL_STATUS_DLL_OUT_SHIFT			0
+#define EMC_DIG_DLL_STATUS_DLL_OUT_MASK				\
+	(0x7ff << EMC_DIG_DLL_STATUS_DLL_OUT_SHIFT)
+
+#define EMC_CFG_DIG_DLL_1					0x2c8
 #define EMC_RDV_MASK						0x2cc
 #define EMC_WDV_MASK						0x2d0
 #define EMC_RDV_EARLY_MASK					0x2d4
@@ -153,6 +205,8 @@
 #define EMC_PRE_REFRESH_REQ_CNT					0x3dc
 #define EMC_DYN_SELF_REF_CONTROL				0x3e0
 #define EMC_TXSRDLL						0x3e4
+#define EMC_CCFIFO_ADDR						0x3e8
+#define EMC_CCFIFO_DATA						0x3ec
 #define EMC_TR_QPOP						0x3f4
 #define EMC_TR_RDV_MASK						0x3f8
 #define EMC_TR_QSAFE						0x3fc
@@ -185,8 +239,60 @@
 #define EMC_PUTERM_WIDTH					0x56c
 #define EMC_REFCTRL2						0x580
 #define EMC_FBIO_CFG7						0x584
+#define EMC_FBIO_CFG7_CH0_ENABLE				BIT(1)
+#define EMC_FBIO_CFG7_CH1_ENABLE				BIT(2)
 #define EMC_DATA_BRLSHFT_0					0x588
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_SHIFT	21
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_SHIFT	18
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_SHIFT	15
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_SHIFT	12
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_SHIFT	9
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_SHIFT	6
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_SHIFT	3
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_SHIFT	0
+#define EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_SHIFT)
+
 #define EMC_DATA_BRLSHFT_1					0x58c
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_SHIFT	21
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_SHIFT	18
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_SHIFT	15
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_SHIFT	12
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_SHIFT	9
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_SHIFT	6
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_SHIFT	3
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_SHIFT)
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_SHIFT	0
+#define EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_MASK	\
+	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_SHIFT)
+
 #define EMC_RFCPB						0x590
 #define EMC_DQS_BRLSHFT_0					0x594
 #define EMC_DQS_BRLSHFT_1					0x598
@@ -201,6 +307,10 @@
 #define EMC_QUSE_BRLSHFT_3					0x5c4
 #define EMC_DLL_CFG_0						0x5e4
 #define EMC_DLL_CFG_1						0x5e8
+#define EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_SHIFT		10
+#define EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_MASK		\
+	(0x7ff << EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_SHIFT)
+
 #define EMC_CONFIG_SAMPLE_DELAY					0x5f0
 #define EMC_PMACRO_QUSE_DDLL_RANK0_0				0x600
 #define EMC_PMACRO_QUSE_DDLL_RANK0_1				0x604
@@ -215,15 +325,103 @@
 #define EMC_PMACRO_QUSE_DDLL_RANK1_4				0x630
 #define EMC_PMACRO_QUSE_DDLL_RANK1_5				0x634
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0			0x640
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_SHIFT \
+	16
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_SHIFT)
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_SHIFT \
+	0
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_MASK \
+	(0x3ff <<							    \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_SHIFT)
+
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1			0x644
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_SHIFT \
+	16
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_SHIFT)
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_SHIFT \
+	0
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_SHIFT)
+
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2			0x648
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_SHIFT  \
+	16
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_SHIFT)
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_SHIFT \
+	0
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_SHIFT)
+
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3			0x64c
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_SHIFT \
+	16
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_SHIFT)
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_SHIFT \
+	0
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_SHIFT)
+
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4			0x650
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5			0x654
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0			0x660
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_SHIFT \
+	16
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_SHIFT)
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_SHIFT \
+	0
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_SHIFT)
+
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1			0x664
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_SHIFT \
+	16
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_SHIFT)
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_SHIFT \
+	0
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_SHIFT)
+
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2			0x668
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_SHIFT \
+	16
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_SHIFT)
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_SHIFT \
+	0
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_SHIFT)
+
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3			0x66c
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_SHIFT \
+	16
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_SHIFT)
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_SHIFT \
+	0
+#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_MASK  \
+	(0x3ff <<							     \
+	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_SHIFT)
+
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4			0x670
 #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5			0x674
 #define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0			0x680
@@ -432,7 +630,18 @@
 #define EMC_PMACRO_CMD_RX_TERM_MODE				0xc58
 #define EMC_PMACRO_DATA_RX_TERM_MODE				0xc5c
 #define EMC_PMACRO_CMD_PAD_TX_CTRL				0xc60
+#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_E_DCC		BIT(1)
+#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSP_TX_E_DCC		BIT(9)
+#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSN_TX_E_DCC		BIT(16)
+#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_CMD_TX_E_DCC		BIT(24)
+#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_DRVFORCEON		BIT(26)
+
 #define EMC_PMACRO_DATA_PAD_TX_CTRL				0xc64
+#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC		BIT(1)
+#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC		BIT(9)
+#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC		BIT(16)
+#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC		BIT(24)
+
 #define EMC_PMACRO_COMMON_PAD_TX_CTRL				0xc68
 #define EMC_PMACRO_AUTOCAL_CFG_COMMON				0xc78
 #define EMC_PMACRO_VTTGEN_CTRL_2				0xcf0
@@ -954,6 +1163,16 @@ enum {
 	DRAM_TYPE_DDR2 = 3,
 };
 
+enum {
+	SINGLE_CHANNEL = 0,
+	DUAL_CHANNEL
+};
+
+enum {
+	DLL_OFF,
+	DLL_ON
+};
+
 struct emc_table {
 	u32 rev;
 	char dvfs_ver[60];
@@ -1075,9 +1294,55 @@ struct supported_sequence {
 	char  *seq_rev;
 };
 
+extern u32 burst_mc_regs_off[];
+extern u32 burst_regs_off[];
+extern u32 burst_regs_per_ch_off[];
+extern u32 burst_regs_per_ch_type[];
+extern unsigned long dram_over_temp_state;
+extern u32 la_scale_regs_off[];
+extern u32 trim_regs_off[];
+extern u32 trim_regs_per_ch_off[];
+extern u32 trim_regs_per_ch_type[];
+extern u32 vref_regs_per_ch_off[];
+extern u32 vref_regs_per_ch_type[];
+
+void ccfifo_writel(struct tegra_emc *emc, u32 val, unsigned long addr,
+		   u32 delay);
+u32 div_o3(u32 a, u32 b);
+void emc_writel(struct tegra_emc *emc, u32 val, unsigned long offset);
+u32  emc_readl(struct tegra_emc *emc, unsigned long offset);
+void emc_writel_per_ch(struct tegra_emc *emc, u32 val, int type,
+		       unsigned long offset);
+u32  emc1_readl(struct tegra_emc *emc, unsigned long offset);
+
+void do_clock_change(struct tegra_emc *emc, u32 clksrc);
+void emc_set_shadow_bypass(struct tegra_emc *emc, int set);
+void emc_timing_update(struct tegra_emc *emc, int dual_chan);
+u32 get_dll_state(struct emc_table *next_timing);
+struct emc_table *get_timing_from_freq(struct tegra_emc *emc,
+				       unsigned long rate);
+void set_over_temp_timing(struct tegra_emc *emc, struct emc_table *timing,
+			  unsigned long state);
 int tegra_emc_dt_parse_pdata(struct platform_device *pdev,
 			     struct emc_table **tables,
 			     struct emc_table **derated_tables,
 			     int *num_entries);
+u32 tegra210_actual_osc_clocks(u32 in);
+u32 tegra210_apply_periodic_compensation_trimmer(struct emc_table *next_timing,
+						 u32 offset);
+void tegra210_dll_disable(struct tegra_emc *emc, int channel_mode);
+void tegra210_dll_enable(struct tegra_emc *emc, int channel_mode);
+u32 tegra210_dll_prelock(struct tegra_emc *emc, int dvfs_with_training,
+			 u32 clksrc);
+u32 tegra210_dvfs_power_ramp_down(struct tegra_emc *emc, u32 clk,
+				  int flip_backward);
+u32 tegra210_dvfs_power_ramp_up(struct tegra_emc *emc, u32 clk,
+				int flip_backward);
+void tegra210_update_emc_alt_timing(struct tegra_emc *emc,
+				    struct emc_table *current_timing);
+void tegra210_reset_dram_clktree_values(struct emc_table *table);
+void tegra210_start_periodic_compensation(struct tegra_emc *emc);
+int wait_for_update(struct tegra_emc *emc, u32 status_reg, u32 bit_mask,
+		    bool updated_state, int chan);
 
 #endif
diff --git a/drivers/memory/tegra/tegra210-emc.c b/drivers/memory/tegra/tegra210-emc.c
index 0c20bcd0e6de..26d0de0ab319 100644
--- a/drivers/memory/tegra/tegra210-emc.c
+++ b/drivers/memory/tegra/tegra210-emc.c
@@ -20,6 +20,37 @@
 #define TEGRA_EMC_TABLE_MAX_SIZE		16
 #define TEGRA210_EMC_SUSPEND_RATE		204000000
 
+#define EMC0_EMC_DATA_BRLSHFT_0_INDEX	2
+#define EMC1_EMC_DATA_BRLSHFT_0_INDEX	3
+#define EMC0_EMC_DATA_BRLSHFT_1_INDEX	4
+#define EMC1_EMC_DATA_BRLSHFT_1_INDEX	5
+
+#define TRIM_REG(chan, rank, reg, byte)					\
+	(((EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
+	   _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte ## _MASK &	\
+	   next_timing->trim_regs[EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ##	\
+				 rank ## _ ## reg ## _INDEX]) >>	\
+	  EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
+	  _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte ## _SHIFT)	\
+	 +								\
+	 (((EMC_DATA_BRLSHFT_ ## rank ## _RANK ## rank ## _BYTE ##	\
+	    byte ## _DATA_BRLSHFT_MASK &				\
+	    next_timing->trim_perch_regs[EMC ## chan ##			\
+			      _EMC_DATA_BRLSHFT_ ## rank ## _INDEX]) >>	\
+	   EMC_DATA_BRLSHFT_ ## rank ## _RANK ## rank ## _BYTE ##	\
+	   byte ## _DATA_BRLSHFT_SHIFT) * 64))
+
+#define CALC_TEMP(rank, reg, byte1, byte2, n)				\
+	(((new[n] << EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ##	\
+	   reg ## _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte1 ## _SHIFT) & \
+	  EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
+	  _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte1 ## _MASK)	\
+	 |								\
+	 ((new[n + 1] << EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ##\
+	   reg ## _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte2 ## _SHIFT) & \
+	  EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
+	  _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte2 ## _MASK))	\
+
 enum TEGRA_EMC_SOURCE {
 	TEGRA_EMC_SRC_PLLM,
 	TEGRA_EMC_SRC_PLLC,
@@ -32,6 +63,14 @@ enum TEGRA_EMC_SOURCE {
 	TEGRA_EMC_SRC_COUNT,
 };
 
+enum {
+	TEGRA_DRAM_OVER_TEMP_NONE = 0,
+	TEGRA_DRAM_OVER_TEMP_REFRESH_X2,
+	TEGRA_DRAM_OVER_TEMP_REFRESH_X4,
+	TEGRA_DRAM_OVER_TEMP_THROTTLE, /* 4x Refresh + derating. */
+	TEGRA_DRAM_OVER_TEMP_MAX,
+};
+
 struct emc_sel {
 	struct clk *input;
 	u32 value;
@@ -76,6 +115,7 @@ static struct tegra_emc *tegra_emc;
 static DEFINE_SPINLOCK(emc_access_lock);
 static ktime_t clkchange_time;
 static int clkchange_delay = 100;
+unsigned long dram_over_temp_state = TEGRA_DRAM_OVER_TEMP_NONE;
 
 static void emc_train(struct timer_list *tmr);
 DEFINE_TIMER(emc_training_timer, emc_train);
@@ -102,11 +142,33 @@ static bool emc_suspend;
 static unsigned long emc_resume_rate;
 #endif
 
+inline void emc_writel(struct tegra_emc *emc, u32 val, unsigned long offset)
+{
+	writel(val, emc->emc_base + offset);
+}
+
 inline u32 emc_readl(struct tegra_emc *emc, unsigned long offset)
 {
 	return readl(emc->emc_base + offset);
 }
 
+inline u32 emc1_readl(struct tegra_emc *emc, unsigned long offset)
+{
+	return readl(emc->emc1_base + offset);
+}
+
+inline void emc_writel_per_ch(struct tegra_emc *emc, u32 val, int type,
+			      unsigned long offset)
+{
+	switch (type) {
+	case REG_EMC:
+	case REG_EMC0:
+		return writel(val, emc->emc_base + offset);
+	case REG_EMC1:
+		return writel(val, emc->emc1_base + offset);
+	}
+}
+
 inline u32 emc_readl_per_ch(struct tegra_emc *emc, int type,
 			    unsigned long offset)
 {
@@ -125,6 +187,14 @@ inline u32 emc_readl_per_ch(struct tegra_emc *emc, int type,
 	return val;
 }
 
+inline void ccfifo_writel(struct tegra_emc *emc, u32 val, unsigned long addr,
+			  u32 delay)
+{
+	writel(val, emc->emc_base + EMC_CCFIFO_DATA);
+	writel((addr & 0xffff) | ((delay & 0x7fff) << 16) | (1 << 31),
+		emc->emc_base + EMC_CCFIFO_ADDR);
+}
+
 static inline u32 emc_src_val(u32 val)
 {
 	return (val & EMC_CLK_EMC_2X_CLK_SRC_MASK) >>
@@ -175,6 +245,16 @@ static void emc_set_clock(struct tegra_emc *emc, u32 clksrc)
 		emc_training_timer_stop();
 }
 
+static struct emc_table *emc_get_table(struct tegra_emc *emc,
+				unsigned long over_temp_state)
+{
+	if ((over_temp_state == TEGRA_DRAM_OVER_TEMP_THROTTLE) &&
+	    (emc->emc_table_derated != NULL))
+		return emc->emc_table_derated;
+	else
+		return emc->emc_table_normal;
+}
+
 static inline void emc_get_timing(struct tegra_emc *emc,
 				  struct emc_table *timing)
 {
@@ -223,6 +303,142 @@ static inline void emc_get_timing(struct tegra_emc *emc,
 	timing->rate = rate / 1000;
 }
 
+static void tegra210_change_dll_src(struct tegra_emc *emc,
+				    u32 clksrc)
+{
+
+	u32 out_enb_x;
+	u32 dll_setting = emc->next_timing->dll_clk_src;
+	u32 emc_clk_src;
+	u32 emc_clk_div;
+
+	out_enb_x = 0;
+	emc_clk_src = (clksrc & EMC_CLK_EMC_2X_CLK_SRC_MASK) >>
+		       EMC_CLK_EMC_2X_CLK_SRC_SHIFT;
+	emc_clk_div = (clksrc & EMC_CLK_EMC_2X_CLK_DIVISOR_MASK) >>
+		       EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT;
+
+	dll_setting &= ~(DLL_CLK_EMC_DLL_CLK_SRC_MASK |
+			 DLL_CLK_EMC_DLL_CLK_DIVISOR_MASK);
+	dll_setting |= emc_clk_src << DLL_CLK_EMC_DLL_CLK_SRC_SHIFT;
+	dll_setting |= emc_clk_div << DLL_CLK_EMC_DLL_CLK_DIVISOR_SHIFT;
+
+	dll_setting &= ~DLL_CLK_EMC_DLL_DDLL_CLK_SEL_MASK;
+	if (emc_clk_src == EMC_CLK_SOURCE_PLLMB_LJ)
+		dll_setting |= (PLLM_VCOB <<
+				DLL_CLK_EMC_DLL_DDLL_CLK_SEL_SHIFT);
+	else if (emc_clk_src == EMC_CLK_SOURCE_PLLM_LJ)
+		dll_setting |= (PLLM_VCOA <<
+				DLL_CLK_EMC_DLL_DDLL_CLK_SEL_SHIFT);
+	else
+		dll_setting |= (EMC_DLL_SWITCH_OUT <<
+				DLL_CLK_EMC_DLL_DDLL_CLK_SEL_SHIFT);
+
+	tegra210_clk_emc_dll_update_setting(dll_setting);
+
+	if (emc->next_timing->clk_out_enb_x_0_clk_enb_emc_dll)
+		tegra210_clk_emc_dll_enable(true);
+	else
+		tegra210_clk_emc_dll_enable(false);
+}
+
+void do_clock_change(struct tegra_emc *emc, u32 clksrc)
+{
+	int err;
+
+	mc_readl(emc->mc, MC_EMEM_ADR_CFG);
+	emc_readl(emc, EMC_INTSTATUS);
+
+	tegra210_clk_emc_update_setting(clksrc);
+
+	err = wait_for_update(emc, EMC_INTSTATUS,
+			      EMC_INTSTATUS_CLKCHANGE_COMPLETE, true, REG_EMC);
+	if (err) {
+		pr_err("%s: clock change completion error: %d", __func__, err);
+		WARN_ON(1);
+	}
+}
+
+struct emc_table *get_timing_from_freq(struct tegra_emc *emc,
+				       unsigned long rate)
+{
+	int i;
+
+	for (i = 0; i < emc->emc_table_size; i++)
+		if (emc->emc_table[i].rate == rate)
+			return &emc->emc_table[i];
+
+	return NULL;
+}
+
+int wait_for_update(struct tegra_emc *emc, u32 status_reg, u32 bit_mask,
+		    bool updated_state, int chan)
+{
+	int i, err = -ETIMEDOUT;
+	u32 reg;
+
+	for (i = 0; i < EMC_STATUS_UPDATE_TIMEOUT; i++) {
+		reg = emc_readl_per_ch(emc, chan, status_reg);
+		if (!!(reg & bit_mask) == updated_state) {
+			err = 0;
+			goto done;
+		}
+		udelay(1);
+	}
+
+done:
+	return err;
+}
+
+void emc_set_shadow_bypass(struct tegra_emc *emc, int set)
+{
+	u32 emc_dbg = emc_readl(emc, EMC_DBG);
+
+	if (set)
+		emc_writel(emc, emc_dbg | EMC_DBG_WRITE_MUX_ACTIVE, EMC_DBG);
+	else
+		emc_writel(emc, emc_dbg & ~EMC_DBG_WRITE_MUX_ACTIVE, EMC_DBG);
+}
+
+u32 get_dll_state(struct emc_table *next_timing)
+{
+	bool next_dll_enabled;
+
+	next_dll_enabled = !(next_timing->emc_emrs & 0x1);
+	if (next_dll_enabled)
+		return DLL_ON;
+	else
+		return DLL_OFF;
+}
+
+u32 div_o3(u32 a, u32 b)
+{
+	u32 result = a / b;
+
+	if ((b * result) < a)
+		return result + 1;
+	else
+		return result;
+}
+
+void emc_timing_update(struct tegra_emc *emc, int dual_chan)
+{
+	int err = 0;
+
+	emc_writel(emc, 0x1, EMC_TIMING_CONTROL);
+	err |= wait_for_update(emc, EMC_EMC_STATUS,
+			       EMC_EMC_STATUS_TIMING_UPDATE_STALLED, false,
+			       REG_EMC);
+	if (dual_chan)
+		err |= wait_for_update(emc, EMC_EMC_STATUS,
+				       EMC_EMC_STATUS_TIMING_UPDATE_STALLED,
+				       false, REG_EMC1);
+	if (err) {
+		pr_err("%s: timing update error: %d", __func__, err);
+		WARN_ON(1);
+	}
+}
+
 static void __emc_copy_table_params(struct emc_table *src,
 				    struct emc_table *dst, int flags)
 {
@@ -277,6 +493,29 @@ static void __emc_copy_table_params(struct emc_table *src,
 	}
 }
 
+void tegra210_update_emc_alt_timing(struct tegra_emc *emc,
+				    struct emc_table *current_timing)
+{
+	struct emc_table *current_table, *alt_timing;
+	int i;
+
+	if (!emc->emc_table_derated)
+		return;
+
+	current_table = emc_get_table(emc, dram_over_temp_state);
+	i = current_timing - current_table;
+
+	WARN_ON(i < 0);
+
+	if (dram_over_temp_state == TEGRA_DRAM_OVER_TEMP_THROTTLE)
+		alt_timing = &emc->emc_table_normal[i];
+	else
+		alt_timing = &emc->emc_table_derated[i];
+
+	__emc_copy_table_params(current_timing, alt_timing,
+				EMC_COPY_TABLE_PARAM_PERIODIC_FIELDS);
+}
+
 static void emc_copy_table_params(struct emc_table *src,
 				  struct emc_table *dst,
 				  int table_size,
@@ -288,6 +527,690 @@ static void emc_copy_table_params(struct emc_table *src,
 		__emc_copy_table_params(&src[i], &dst[i], flags);
 }
 
+u32 tegra210_actual_osc_clocks(u32 in)
+{
+	if (in < 0x40)
+		return in * 16;
+	else if (in < 0x80)
+		return 2048;
+	else if (in < 0xc0)
+		return 4096;
+	else
+		return 8192;
+}
+
+void tegra210_start_periodic_compensation(struct tegra_emc *emc)
+{
+	u32 mpc_req = 0x4b;
+
+	emc_writel(emc, mpc_req, EMC_MPC);
+	mpc_req = emc_readl(emc, EMC_MPC);
+}
+
+u32 tegra210_apply_periodic_compensation_trimmer(struct emc_table *next_timing,
+						 u32 offset)
+{
+	u32 i, temp = 0;
+	u32 next_timing_rate_mhz = next_timing->rate / 1000;
+	s32 tree_delta[4];
+	s32 tree_delta_taps[4];
+	s32 new[] = {
+		TRIM_REG(0, 0, 0, 0),
+		TRIM_REG(0, 0, 0, 1),
+		TRIM_REG(0, 0, 1, 2),
+		TRIM_REG(0, 0, 1, 3),
+
+		TRIM_REG(1, 0, 2, 4),
+		TRIM_REG(1, 0, 2, 5),
+		TRIM_REG(1, 0, 3, 6),
+		TRIM_REG(1, 0, 3, 7),
+
+		TRIM_REG(0, 1, 0, 0),
+		TRIM_REG(0, 1, 0, 1),
+		TRIM_REG(0, 1, 1, 2),
+		TRIM_REG(0, 1, 1, 3),
+
+		TRIM_REG(1, 1, 2, 4),
+		TRIM_REG(1, 1, 2, 5),
+		TRIM_REG(1, 1, 3, 6),
+		TRIM_REG(1, 1, 3, 7)
+	};
+
+	switch (offset) {
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0:
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1:
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2:
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3:
+	case EMC_DATA_BRLSHFT_0:
+		tree_delta[0] = 128 *
+				(next_timing->current_dram_clktree_c0d0u0 -
+				 next_timing->trained_dram_clktree_c0d0u0);
+		tree_delta[1] = 128 *
+				(next_timing->current_dram_clktree_c0d0u1 -
+				 next_timing->trained_dram_clktree_c0d0u1);
+		tree_delta[2] = 128 *
+				(next_timing->current_dram_clktree_c1d0u0 -
+				 next_timing->trained_dram_clktree_c1d0u0);
+		tree_delta[3] = 128 *
+				(next_timing->current_dram_clktree_c1d0u1 -
+				 next_timing->trained_dram_clktree_c1d0u1);
+
+		tree_delta_taps[0] = (tree_delta[0] *
+				     (s32)next_timing_rate_mhz) / 1000000;
+		tree_delta_taps[1] = (tree_delta[1] *
+				     (s32)next_timing_rate_mhz) / 1000000;
+		tree_delta_taps[2] = (tree_delta[2] *
+				     (s32)next_timing_rate_mhz) / 1000000;
+		tree_delta_taps[3] = (tree_delta[3] *
+				     (s32)next_timing_rate_mhz) / 1000000;
+
+		for (i = 0; i < 4; i++) {
+			if ((tree_delta_taps[i] > next_timing->tree_margin) ||
+			    (tree_delta_taps[i] <
+			    (-1 * next_timing->tree_margin))) {
+				new[i * 2] = new[i * 2] + tree_delta_taps[i];
+				new[i * 2 + 1] = new[i * 2 + 1] +
+						 tree_delta_taps[i];
+			}
+		}
+
+		if (offset == EMC_DATA_BRLSHFT_0) {
+			for (i = 0; i < 8; i++)
+				new[i] = new[i] / 64;
+		} else {
+			for (i = 0; i < 8; i++)
+				new[i] = new[i] % 64;
+		}
+		break;
+
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0:
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1:
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2:
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3:
+	case EMC_DATA_BRLSHFT_1:
+		tree_delta[0] = 128 *
+				(next_timing->current_dram_clktree_c0d1u0 -
+				 next_timing->trained_dram_clktree_c0d1u0);
+		tree_delta[1] = 128 *
+				(next_timing->current_dram_clktree_c0d1u1 -
+				 next_timing->trained_dram_clktree_c0d1u1);
+		tree_delta[2] = 128 *
+				(next_timing->current_dram_clktree_c1d1u0 -
+				 next_timing->trained_dram_clktree_c1d1u0);
+		tree_delta[3] = 128 *
+				(next_timing->current_dram_clktree_c1d1u1 -
+				 next_timing->trained_dram_clktree_c1d1u1);
+
+		tree_delta_taps[0] = (tree_delta[0] *
+				     (s32)next_timing_rate_mhz) / 1000000;
+		tree_delta_taps[1] = (tree_delta[1] *
+				     (s32)next_timing_rate_mhz) / 1000000;
+		tree_delta_taps[2] = (tree_delta[2] *
+				     (s32)next_timing_rate_mhz) / 1000000;
+		tree_delta_taps[3] = (tree_delta[3] *
+				     (s32)next_timing_rate_mhz) / 1000000;
+
+		for (i = 0; i < 4; i++) {
+			if ((tree_delta_taps[i] > next_timing->tree_margin) ||
+			    (tree_delta_taps[i] <
+			     (-1 * next_timing->tree_margin))) {
+				new[8 + i * 2] = new[8 + i * 2] +
+						 tree_delta_taps[i];
+				new[8 + i * 2 + 1] = new[8 + i * 2 + 1] +
+						     tree_delta_taps[i];
+			}
+		}
+
+		if (offset == EMC_DATA_BRLSHFT_1) {
+			for (i = 0; i < 8; i++)
+				new[i + 8] = new[i + 8] / 64;
+		} else {
+			for (i = 0; i < 8; i++)
+				new[i + 8] = new[i + 8] % 64;
+		}
+		break;
+	}
+
+	switch (offset) {
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0:
+		temp = CALC_TEMP(0, 0, 0, 1, 0);
+		break;
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1:
+		temp = CALC_TEMP(0, 1, 2, 3, 2);
+		break;
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2:
+		temp = CALC_TEMP(0, 2, 4, 5, 4);
+		break;
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3:
+		temp = CALC_TEMP(0, 3, 6, 7, 6);
+		break;
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0:
+		temp = CALC_TEMP(1, 0, 0, 1, 8);
+		break;
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1:
+		temp = CALC_TEMP(1, 1, 2, 3, 10);
+		break;
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2:
+		temp = CALC_TEMP(1, 2, 4, 5, 12);
+		break;
+	case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3:
+		temp = CALC_TEMP(1, 3, 6, 7, 14);
+		break;
+	case EMC_DATA_BRLSHFT_0:
+		temp = ((new[0] <<
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_MASK) |
+		       ((new[1] <<
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_MASK) |
+		       ((new[2] <<
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_MASK) |
+		       ((new[3] <<
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_MASK) |
+		       ((new[4] <<
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_MASK) |
+		       ((new[5] <<
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_MASK) |
+		       ((new[6] <<
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_MASK) |
+		       ((new[7] <<
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_MASK);
+		break;
+	case EMC_DATA_BRLSHFT_1:
+		temp = ((new[8] <<
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_MASK) |
+		       ((new[9] <<
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_MASK) |
+		       ((new[10] <<
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_MASK) |
+		       ((new[11] <<
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_MASK) |
+		       ((new[12] <<
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_MASK) |
+		       ((new[13] <<
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_MASK) |
+		       ((new[14] <<
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_MASK) |
+		       ((new[15] <<
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_SHIFT) &
+			 EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_MASK);
+		break;
+	default:
+		break;
+	}
+
+	return temp;
+}
+
+u32 tegra210_dll_prelock(struct tegra_emc *emc, int dvfs_with_training,
+			 u32 clksrc)
+{
+	u32 emc_dig_dll_status;
+	u32 dll_locked;
+	u32 dll_out;
+	u32 emc_cfg_dig_dll;
+	u32 emc_dll_cfg_0;
+	u32 emc_dll_cfg_1;
+	u32 ddllcal_ctrl_start_trim_val;
+	u32 dll_en;
+	u32 dual_channel_lpddr4_case;
+	u32 dll_priv_updated;
+
+	dual_channel_lpddr4_case =
+	      !!(emc_readl(emc, EMC_FBIO_CFG7) & EMC_FBIO_CFG7_CH1_ENABLE) &
+	      !!(emc_readl(emc, EMC_FBIO_CFG7) & EMC_FBIO_CFG7_CH0_ENABLE);
+
+	emc_dig_dll_status = 0;
+	dll_locked = 0;
+	dll_out = 0;
+	emc_cfg_dig_dll = 0;
+	emc_dll_cfg_0 = 0;
+	emc_dll_cfg_1 = 0;
+	ddllcal_ctrl_start_trim_val = 0;
+	dll_en = 0;
+
+	emc_cfg_dig_dll = emc_readl(emc, EMC_CFG_DIG_DLL) &
+			  ~EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_MASK;
+	emc_cfg_dig_dll |= (3 << EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_SHIFT);
+	emc_cfg_dig_dll &= ~EMC_CFG_DIG_DLL_CFG_DLL_EN;
+	emc_cfg_dig_dll &= ~EMC_CFG_DIG_DLL_CFG_DLL_MODE_MASK;
+	emc_cfg_dig_dll |= (3 << EMC_CFG_DIG_DLL_CFG_DLL_MODE_SHIFT);
+	emc_cfg_dig_dll |= EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_TRAFFIC;
+	emc_cfg_dig_dll &= ~EMC_CFG_DIG_DLL_CFG_DLL_STALL_RW_UNTIL_LOCK;
+	emc_cfg_dig_dll &= ~EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_UNTIL_LOCK;
+
+	emc_writel(emc, emc_cfg_dig_dll, EMC_CFG_DIG_DLL);
+	emc_writel(emc, 1, EMC_TIMING_CONTROL);
+
+	wait_for_update(emc, EMC_EMC_STATUS,
+			EMC_EMC_STATUS_TIMING_UPDATE_STALLED, 0, REG_EMC);
+	if (dual_channel_lpddr4_case)
+		wait_for_update(emc, EMC_EMC_STATUS,
+				EMC_EMC_STATUS_TIMING_UPDATE_STALLED,
+				0, REG_EMC1);
+
+	do {
+		emc_cfg_dig_dll = emc_readl(emc, EMC_CFG_DIG_DLL);
+		dll_en = emc_cfg_dig_dll & EMC_CFG_DIG_DLL_CFG_DLL_EN;
+	} while (dll_en == 1);
+
+	if (dual_channel_lpddr4_case) {
+		do {
+			emc_cfg_dig_dll = emc1_readl(emc, EMC_CFG_DIG_DLL);
+			dll_en = emc_cfg_dig_dll & EMC_CFG_DIG_DLL_CFG_DLL_EN;
+		} while (dll_en == 1);
+	}
+
+	emc_dll_cfg_0 = emc->next_timing->burst_regs[EMC_DLL_CFG_0_INDEX];
+
+	emc_writel(emc, emc_dll_cfg_0, EMC_DLL_CFG_0);
+
+	if (emc->next_timing->rate >= 400000
+		&& emc->next_timing->rate < 600000)
+		ddllcal_ctrl_start_trim_val = 150;
+	else if (emc->next_timing->rate >= 600000
+		&& emc->next_timing->rate < 800000)
+		ddllcal_ctrl_start_trim_val = 100;
+	else if (emc->next_timing->rate >= 800000
+		&& emc->next_timing->rate < 1000000)
+		ddllcal_ctrl_start_trim_val = 70;
+	else if (emc->next_timing->rate >= 1000000
+		&& emc->next_timing->rate < 1200000)
+		ddllcal_ctrl_start_trim_val = 30;
+	else
+		ddllcal_ctrl_start_trim_val = 20;
+
+	emc_dll_cfg_1 = emc_readl(emc, EMC_DLL_CFG_1);
+	emc_dll_cfg_1 &= EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_MASK;
+	emc_dll_cfg_1 |= ddllcal_ctrl_start_trim_val;
+	emc_writel(emc, emc_dll_cfg_1, EMC_DLL_CFG_1);
+
+	tegra210_change_dll_src(emc, clksrc);
+
+	emc_cfg_dig_dll = emc_readl(emc, EMC_CFG_DIG_DLL);
+	emc_cfg_dig_dll |= EMC_CFG_DIG_DLL_CFG_DLL_EN;
+	emc_writel(emc, emc_cfg_dig_dll, EMC_CFG_DIG_DLL);
+
+	emc_timing_update(emc, dual_channel_lpddr4_case ?
+			  DUAL_CHANNEL : SINGLE_CHANNEL);
+
+	do {
+		emc_cfg_dig_dll = emc_readl(emc, EMC_CFG_DIG_DLL);
+		dll_en = emc_cfg_dig_dll & EMC_CFG_DIG_DLL_CFG_DLL_EN;
+	} while (dll_en == 0);
+
+	if (dual_channel_lpddr4_case) {
+		do {
+			emc_cfg_dig_dll = emc1_readl(emc, EMC_CFG_DIG_DLL);
+			dll_en = emc_cfg_dig_dll & EMC_CFG_DIG_DLL_CFG_DLL_EN;
+		} while (dll_en == 0);
+	}
+
+	do {
+		emc_dig_dll_status = emc_readl(emc, EMC_DIG_DLL_STATUS);
+		dll_locked = emc_dig_dll_status & EMC_DIG_DLL_STATUS_DLL_LOCK;
+		dll_priv_updated = emc_dig_dll_status &
+				   EMC_DIG_DLL_STATUS_DLL_PRIV_UPDATED;
+	} while (!dll_locked || !dll_priv_updated);
+
+	emc_dig_dll_status = emc_readl(emc, EMC_DIG_DLL_STATUS);
+	return emc_dig_dll_status & EMC_DIG_DLL_STATUS_DLL_OUT_MASK;
+}
+
+u32 tegra210_dvfs_power_ramp_up(struct tegra_emc *emc, u32 clk,
+				int flip_backward)
+{
+	u32 pmacro_cmd_pad;
+	u32 pmacro_dq_pad;
+	u32 pmacro_rfu1;
+	u32 pmacro_cfg5;
+	u32 pmacro_common_tx;
+	u32 ramp_up_wait = 0;
+
+	if (flip_backward) {
+		pmacro_cmd_pad   = emc->current_timing->burst_regs[
+					EMC_PMACRO_CMD_PAD_TX_CTRL_INDEX];
+		pmacro_dq_pad    = emc->current_timing->burst_regs[
+					EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX];
+		pmacro_rfu1      = emc->current_timing->burst_regs[
+					EMC_PMACRO_BRICK_CTRL_RFU1_INDEX];
+		pmacro_cfg5      = emc->current_timing->burst_regs[
+					EMC_FBIO_CFG5_INDEX];
+		pmacro_common_tx = emc->current_timing->burst_regs[
+					EMC_PMACRO_COMMON_PAD_TX_CTRL_INDEX];
+	} else {
+		pmacro_cmd_pad   = emc->next_timing->burst_regs[
+					EMC_PMACRO_CMD_PAD_TX_CTRL_INDEX];
+		pmacro_dq_pad    = emc->next_timing->burst_regs[
+					EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX];
+		pmacro_rfu1      = emc->next_timing->burst_regs[
+					EMC_PMACRO_BRICK_CTRL_RFU1_INDEX];
+		pmacro_cfg5      = emc->next_timing->burst_regs[
+					EMC_FBIO_CFG5_INDEX];
+		pmacro_common_tx = emc->next_timing->burst_regs[
+					EMC_PMACRO_COMMON_PAD_TX_CTRL_INDEX];
+	}
+	pmacro_cmd_pad |= EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_DRVFORCEON;
+
+	if (clk < 1000000 / DVFS_FGCG_MID_SPEED_THRESHOLD) {
+		ccfifo_writel(emc, pmacro_common_tx & 0xa,
+			      EMC_PMACRO_COMMON_PAD_TX_CTRL, 0);
+		ccfifo_writel(emc, pmacro_common_tx & 0xf,
+			      EMC_PMACRO_COMMON_PAD_TX_CTRL,
+			      (100000 / clk) + 1);
+		ramp_up_wait += 100000;
+	} else {
+		ccfifo_writel(emc, pmacro_common_tx | 0x8,
+			      EMC_PMACRO_COMMON_PAD_TX_CTRL, 0);
+	}
+
+	if (clk < 1000000 / DVFS_FGCG_HIGH_SPEED_THRESHOLD) {
+		if (clk < 1000000 / IOBRICK_DCC_THRESHOLD) {
+			pmacro_cmd_pad |=
+				EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSP_TX_E_DCC |
+				EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSN_TX_E_DCC;
+			pmacro_cmd_pad &=
+				~(EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_E_DCC |
+				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_CMD_TX_E_DCC);
+			ccfifo_writel(emc, pmacro_cmd_pad,
+				      EMC_PMACRO_CMD_PAD_TX_CTRL,
+				      (100000 / clk) + 1);
+			ramp_up_wait += 100000;
+
+			pmacro_dq_pad |=
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC |
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC;
+			pmacro_dq_pad &=
+			       ~(EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC |
+				 EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC);
+			ccfifo_writel(emc, pmacro_dq_pad,
+				      EMC_PMACRO_DATA_PAD_TX_CTRL, 0);
+			ccfifo_writel(emc, pmacro_rfu1 & 0xfe40fe40,
+				      EMC_PMACRO_BRICK_CTRL_RFU1, 0);
+		} else {
+			ccfifo_writel(emc, pmacro_rfu1 & 0xfe40fe40,
+				      EMC_PMACRO_BRICK_CTRL_RFU1,
+				      (100000 / clk) + 1);
+			ramp_up_wait += 100000;
+		}
+
+		ccfifo_writel(emc, pmacro_rfu1 & 0xfeedfeed,
+			      EMC_PMACRO_BRICK_CTRL_RFU1, (100000 / clk) + 1);
+		ramp_up_wait += 100000;
+
+		if (clk < 1000000 / IOBRICK_DCC_THRESHOLD) {
+			pmacro_cmd_pad |=
+				EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSP_TX_E_DCC |
+				EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSN_TX_E_DCC |
+				EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_E_DCC |
+				EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_CMD_TX_E_DCC;
+			ccfifo_writel(emc, pmacro_cmd_pad,
+				      EMC_PMACRO_CMD_PAD_TX_CTRL,
+				      (100000 / clk) + 1);
+			ramp_up_wait += 100000;
+
+			pmacro_dq_pad |=
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC |
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC |
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC |
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC;
+			ccfifo_writel(emc, pmacro_dq_pad,
+				      EMC_PMACRO_DATA_PAD_TX_CTRL, 0);
+			ccfifo_writel(emc, pmacro_rfu1,
+				      EMC_PMACRO_BRICK_CTRL_RFU1, 0);
+		} else {
+			ccfifo_writel(emc, pmacro_rfu1,
+				      EMC_PMACRO_BRICK_CTRL_RFU1,
+				      (100000 / clk) + 1);
+			ramp_up_wait += 100000;
+		}
+
+		ccfifo_writel(emc, pmacro_cfg5 & ~EMC_FBIO_CFG5_CMD_TX_DIS,
+			      EMC_FBIO_CFG5, (100000 / clk) + 10);
+		ramp_up_wait += 100000 + (10 * clk);
+	} else if (clk < 1000000 / DVFS_FGCG_MID_SPEED_THRESHOLD) {
+		ccfifo_writel(emc, pmacro_rfu1 | 0x06000600,
+			      EMC_PMACRO_BRICK_CTRL_RFU1, (100000 / clk) + 1);
+		ccfifo_writel(emc, pmacro_cfg5 & ~EMC_FBIO_CFG5_CMD_TX_DIS,
+			      EMC_FBIO_CFG5, (100000 / clk) + 10);
+		ramp_up_wait += 100000 + 10 * clk;
+	} else {
+		ccfifo_writel(emc, pmacro_rfu1 | 0x00000600,
+			      EMC_PMACRO_BRICK_CTRL_RFU1, 0);
+		ccfifo_writel(emc, pmacro_cfg5 & ~EMC_FBIO_CFG5_CMD_TX_DIS,
+			      EMC_FBIO_CFG5, 12);
+		ramp_up_wait += 12 * clk;
+	}
+
+	pmacro_cmd_pad &= ~EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_DRVFORCEON;
+	ccfifo_writel(emc, pmacro_cmd_pad, EMC_PMACRO_CMD_PAD_TX_CTRL, 5);
+
+	return ramp_up_wait;
+}
+
+u32 tegra210_dvfs_power_ramp_down(struct tegra_emc *emc, u32 clk,
+				  int flip_backward)
+{
+	u32 ramp_down_wait = 0;
+	u32 pmacro_cmd_pad;
+	u32 pmacro_dq_pad;
+	u32 pmacro_rfu1;
+	u32 pmacro_cfg5;
+	u32 pmacro_common_tx;
+	u32 seq_wait;
+
+	if (flip_backward) {
+		pmacro_cmd_pad   = emc->next_timing->burst_regs[
+					EMC_PMACRO_CMD_PAD_TX_CTRL_INDEX];
+		pmacro_dq_pad    = emc->next_timing->burst_regs[
+					EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX];
+		pmacro_rfu1      = emc->next_timing->burst_regs[
+					EMC_PMACRO_BRICK_CTRL_RFU1_INDEX];
+		pmacro_cfg5      = emc->next_timing->burst_regs[
+					EMC_FBIO_CFG5_INDEX];
+		pmacro_common_tx = emc->next_timing->burst_regs[
+					EMC_PMACRO_COMMON_PAD_TX_CTRL_INDEX];
+	} else {
+		pmacro_cmd_pad   = emc->current_timing->burst_regs[
+					EMC_PMACRO_CMD_PAD_TX_CTRL_INDEX];
+		pmacro_dq_pad    = emc->current_timing->burst_regs[
+					EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX];
+		pmacro_rfu1      = emc->current_timing->burst_regs[
+					EMC_PMACRO_BRICK_CTRL_RFU1_INDEX];
+		pmacro_cfg5      = emc->current_timing->burst_regs[
+					EMC_FBIO_CFG5_INDEX];
+		pmacro_common_tx = emc->current_timing->burst_regs[
+					EMC_PMACRO_COMMON_PAD_TX_CTRL_INDEX];
+	}
+
+	pmacro_cmd_pad |= EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_DRVFORCEON;
+
+	ccfifo_writel(emc, pmacro_cmd_pad, EMC_PMACRO_CMD_PAD_TX_CTRL, 0);
+	ccfifo_writel(emc, pmacro_cfg5 | EMC_FBIO_CFG5_CMD_TX_DIS,
+		      EMC_FBIO_CFG5, 12);
+	ramp_down_wait = 12 * clk;
+
+	seq_wait = (100000 / clk) + 1;
+
+	if (clk < (1000000 / DVFS_FGCG_HIGH_SPEED_THRESHOLD)) {
+		if (clk < (1000000 / IOBRICK_DCC_THRESHOLD)) {
+			pmacro_cmd_pad &=
+				~(EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_E_DCC |
+				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_CMD_TX_E_DCC);
+			pmacro_cmd_pad |=
+				EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSP_TX_E_DCC |
+				EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSN_TX_E_DCC;
+			ccfifo_writel(emc, pmacro_cmd_pad,
+				      EMC_PMACRO_CMD_PAD_TX_CTRL, seq_wait);
+			ramp_down_wait += 100000;
+
+			pmacro_dq_pad &=
+			      ~(EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC |
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC);
+			pmacro_dq_pad |=
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC |
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC;
+			ccfifo_writel(emc, pmacro_dq_pad,
+				      EMC_PMACRO_DATA_PAD_TX_CTRL, 0);
+			ccfifo_writel(emc, pmacro_rfu1 & ~0x01120112,
+				      EMC_PMACRO_BRICK_CTRL_RFU1, 0);
+		} else {
+			ccfifo_writel(emc, pmacro_rfu1 & ~0x01120112,
+				      EMC_PMACRO_BRICK_CTRL_RFU1, seq_wait);
+			ramp_down_wait += 100000;
+		}
+
+		ccfifo_writel(emc, pmacro_rfu1 & ~0x01bf01bf,
+			      EMC_PMACRO_BRICK_CTRL_RFU1, seq_wait);
+		ramp_down_wait += 100000;
+
+		if (clk < (1000000 / IOBRICK_DCC_THRESHOLD)) {
+			pmacro_cmd_pad &=
+				~(EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_E_DCC |
+				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_CMD_TX_E_DCC |
+				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSP_TX_E_DCC |
+				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSN_TX_E_DCC);
+			ccfifo_writel(emc, pmacro_cmd_pad,
+				      EMC_PMACRO_CMD_PAD_TX_CTRL, seq_wait);
+			ramp_down_wait += 100000;
+
+			pmacro_dq_pad &=
+			      ~(EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC |
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC |
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC |
+				EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC);
+			ccfifo_writel(emc, pmacro_dq_pad,
+				      EMC_PMACRO_DATA_PAD_TX_CTRL, 0);
+			ccfifo_writel(emc, pmacro_rfu1 & ~0x07ff07ff,
+				      EMC_PMACRO_BRICK_CTRL_RFU1, 0);
+		} else {
+			ccfifo_writel(emc, pmacro_rfu1 & ~0x07ff07ff,
+				      EMC_PMACRO_BRICK_CTRL_RFU1, seq_wait);
+			ramp_down_wait += 100000;
+		}
+	} else {
+		ccfifo_writel(emc, pmacro_rfu1 & ~0xffff07ff,
+			      EMC_PMACRO_BRICK_CTRL_RFU1, seq_wait + 19);
+		ramp_down_wait += 100000 + (20 * clk);
+	}
+
+	if (clk < (1000000 / DVFS_FGCG_MID_SPEED_THRESHOLD)) {
+		ramp_down_wait += 100000;
+		ccfifo_writel(emc, pmacro_common_tx & ~0x5,
+			      EMC_PMACRO_COMMON_PAD_TX_CTRL, seq_wait);
+		ramp_down_wait += 100000;
+		ccfifo_writel(emc, pmacro_common_tx & ~0xf,
+			      EMC_PMACRO_COMMON_PAD_TX_CTRL, seq_wait);
+		ramp_down_wait += 100000;
+		ccfifo_writel(emc, 0, 0, seq_wait);
+		ramp_down_wait += 100000;
+	} else {
+		ccfifo_writel(emc, pmacro_common_tx & ~0xf,
+			      EMC_PMACRO_COMMON_PAD_TX_CTRL, seq_wait);
+	}
+
+	return ramp_down_wait;
+}
+
+void tegra210_reset_dram_clktree_values(struct emc_table *table)
+{
+	#define __RESET_CLKTREE(TBL, C, D, U)				\
+	(TBL->current_dram_clktree_c ## C ## d ## D ## u ## U =		\
+	TBL->trained_dram_clktree_c ## C ## d ## D ## u ## U)
+
+	__RESET_CLKTREE(table, 0, 0, 0);
+	__RESET_CLKTREE(table, 0, 0, 1);
+	__RESET_CLKTREE(table, 1, 0, 0);
+	__RESET_CLKTREE(table, 1, 0, 1);
+	__RESET_CLKTREE(table, 1, 1, 0);
+	__RESET_CLKTREE(table, 1, 1, 1);
+}
+
+static void update_dll_control(struct tegra_emc *emc, u32 emc_cfg_dig_dll,
+			       int channel_mode, bool updated_state)
+{
+	emc_writel(emc, emc_cfg_dig_dll, EMC_CFG_DIG_DLL);
+	emc_timing_update(emc, channel_mode);
+
+	wait_for_update(emc, EMC_CFG_DIG_DLL, EMC_CFG_DIG_DLL_CFG_DLL_EN,
+			updated_state, REG_EMC);
+	if (channel_mode == DUAL_CHANNEL)
+		wait_for_update(emc, EMC_CFG_DIG_DLL,
+				EMC_CFG_DIG_DLL_CFG_DLL_EN,
+				updated_state, REG_EMC1);
+}
+
+void tegra210_dll_disable(struct tegra_emc *emc, int channel_mode)
+{
+	u32 emc_cfg_dig_dll;
+
+	emc_cfg_dig_dll = emc_readl(emc, EMC_CFG_DIG_DLL);
+	emc_cfg_dig_dll &= ~EMC_CFG_DIG_DLL_CFG_DLL_EN;
+
+	update_dll_control(emc, emc_cfg_dig_dll, channel_mode, false);
+}
+
+void tegra210_dll_enable(struct tegra_emc *emc, int channel_mode)
+{
+	u32 emc_cfg_dig_dll;
+
+	emc_cfg_dig_dll = emc_readl(emc, EMC_CFG_DIG_DLL);
+	emc_cfg_dig_dll |= EMC_CFG_DIG_DLL_CFG_DLL_EN;
+
+	update_dll_control(emc, emc_cfg_dig_dll, channel_mode, true);
+}
+
+void set_over_temp_timing(struct tegra_emc *emc, struct emc_table *timing,
+			  unsigned long state)
+{
+#define REFRESH_X2      1
+#define REFRESH_X4      2
+#define REFRESH_SPEEDUP(val, speedup)					\
+		(val = ((val) & 0xFFFF0000) | (((val) & 0xFFFF) >> (speedup)))
+
+	u32 ref = timing->burst_regs[EMC_REFRESH_INDEX];
+	u32 pre_ref = timing->burst_regs[EMC_PRE_REFRESH_REQ_CNT_INDEX];
+	u32 dsr_cntrl = timing->burst_regs[EMC_DYN_SELF_REF_CONTROL_INDEX];
+
+	switch (state) {
+	case TEGRA_DRAM_OVER_TEMP_NONE:
+	case TEGRA_DRAM_OVER_TEMP_THROTTLE:
+		break;
+	case TEGRA_DRAM_OVER_TEMP_REFRESH_X2:
+		REFRESH_SPEEDUP(ref, REFRESH_X2);
+		REFRESH_SPEEDUP(pre_ref, REFRESH_X2);
+		REFRESH_SPEEDUP(dsr_cntrl, REFRESH_X2);
+		break;
+	case TEGRA_DRAM_OVER_TEMP_REFRESH_X4:
+		REFRESH_SPEEDUP(ref, REFRESH_X4);
+		REFRESH_SPEEDUP(pre_ref, REFRESH_X4);
+		REFRESH_SPEEDUP(dsr_cntrl, REFRESH_X4);
+		break;
+	default:
+	WARN(1, "%s: Failed to set dram over temp state %lu\n",
+		__func__, state);
+	return;
+	}
+
+	emc_writel(emc, ref, burst_regs_off[EMC_REFRESH_INDEX]);
+	emc_writel(emc, pre_ref, burst_regs_off[EMC_PRE_REFRESH_REQ_CNT_INDEX]);
+	emc_writel(emc, dsr_cntrl,
+		   burst_regs_off[EMC_DYN_SELF_REF_CONTROL_INDEX]);
+}
+
 static void emc_last_stats_update(int last_sel)
 {
 	unsigned long flags;
-- 
2.21.0


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 5/8] memory: tegra: Add EMC scaling sequence code for Tegra210
  2019-03-25  7:45 [PATCH 0/8] Add EMC scaling support for Tegra210 Joseph Lo
                   ` (3 preceding siblings ...)
  2019-03-25  7:45 ` [PATCH 4/8] memory: tegra: add EMC scaling support code for Tegra210 Joseph Lo
@ 2019-03-25  7:45 ` Joseph Lo
  2019-04-02 11:36   ` Dmitry Osipenko
  2019-03-25  7:45 ` [PATCH 6/8] arm64: tegra: Add external memory controller node " Joseph Lo
  2019-03-29 14:41 ` [PATCH 0/8] Add EMC scaling support " Peter De Schrijver
  6 siblings, 1 reply; 28+ messages in thread
From: Joseph Lo @ 2019-03-25  7:45 UTC (permalink / raw)
  To: Thierry Reding, Peter De Schrijver, Jonathan Hunter, Rob Herring,
	Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree, Joseph Lo

This patch includes the sequence for controlling the rate changing for
EMC frequency and the dynamic training mechanism when the rate reaches
the higher rates of EMC rate.

And historically there have been different sequences to change the EMC
clock. The sequence to be used is specified in the scaling data.
However, for the currently supported upstreaming platform, only the most
recent sequence is used. So only support that in this patch.

Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.

Signed-off-by: Joseph Lo <josephl@nvidia.com>
---
 drivers/memory/tegra/Makefile                 |    2 +-
 drivers/memory/tegra/tegra210-emc-cc-r21021.c | 1962 +++++++++++++++++
 drivers/memory/tegra/tegra210-emc-reg.h       |  134 ++
 drivers/memory/tegra/tegra210-emc.c           |    5 +
 4 files changed, 2102 insertions(+), 1 deletion(-)
 create mode 100644 drivers/memory/tegra/tegra210-emc-cc-r21021.c

diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile
index 36a835620bbd..dcc245b2ef45 100644
--- a/drivers/memory/tegra/Makefile
+++ b/drivers/memory/tegra/Makefile
@@ -12,5 +12,5 @@ obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
 
 obj-$(CONFIG_TEGRA20_EMC)  += tegra20-emc.o
 obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o
-obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o tegra210-dt-parse.o
+obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o tegra210-dt-parse.o tegra210-emc-cc-r21021.o
 obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o
diff --git a/drivers/memory/tegra/tegra210-emc-cc-r21021.c b/drivers/memory/tegra/tegra210-emc-cc-r21021.c
new file mode 100644
index 000000000000..f577a8c3aa95
--- /dev/null
+++ b/drivers/memory/tegra/tegra210-emc-cc-r21021.c
@@ -0,0 +1,1962 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014-2019, NVIDIA CORPORATION.  All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <soc/tegra/mc.h>
+
+#include "mc.h"
+#include "tegra210-emc-reg.h"
+
+#define DVFS_CLOCK_CHANGE_VERSION	21021
+#define EMC_PRELOCK_VERSION		2101
+
+#define emc_cc_dbg(t, ...) pr_debug(__VA_ARGS__)
+
+/*
+ * Enable flags for specifying verbosity.
+ */
+#define INFO            (1 << 0)
+#define STEPS           (1 << 1)
+#define SUB_STEPS       (1 << 2)
+#define PRELOCK         (1 << 3)
+#define PRELOCK_STEPS   (1 << 4)
+#define ACTIVE_EN       (1 << 5)
+#define PRAMP_UP        (1 << 6)
+#define PRAMP_DN        (1 << 7)
+#define EMA_WRITES      (1 << 10)
+#define EMA_UPDATES     (1 << 11)
+#define PER_TRAIN       (1 << 16)
+#define CC_PRINT        (1 << 17)
+#define CCFIFO          (1 << 29)
+#define REGS            (1 << 30)
+#define REG_LISTS       (1 << 31)
+
+enum {
+	DVFS_SEQUENCE = 1,
+	WRITE_TRAINING_SEQUENCE = 2,
+	PERIODIC_TRAINING_SEQUENCE = 3,
+	DVFS_PT1 = 10,
+	DVFS_UPDATE = 11,
+	TRAINING_PT1 = 12,
+	TRAINING_UPDATE = 13,
+	PERIODIC_TRAINING_UPDATE = 14
+};
+
+/*
+ * PTFV defines - basically just indexes into the per table PTFV array.
+ */
+#define PTFV_DQSOSC_MOVAVG_C0D0U0_INDEX		0
+#define PTFV_DQSOSC_MOVAVG_C0D0U1_INDEX		1
+#define PTFV_DQSOSC_MOVAVG_C0D1U0_INDEX		2
+#define PTFV_DQSOSC_MOVAVG_C0D1U1_INDEX		3
+#define PTFV_DQSOSC_MOVAVG_C1D0U0_INDEX		4
+#define PTFV_DQSOSC_MOVAVG_C1D0U1_INDEX		5
+#define PTFV_DQSOSC_MOVAVG_C1D1U0_INDEX		6
+#define PTFV_DQSOSC_MOVAVG_C1D1U1_INDEX		7
+#define PTFV_DVFS_SAMPLES_INDEX			9
+#define PTFV_MOVAVG_WEIGHT_INDEX		10
+#define PTFV_CONFIG_CTRL_INDEX			11
+
+#define PTFV_CONFIG_CTRL_USE_PREVIOUS_EMA	(1 << 0)
+
+/*
+ * Do arithmetic in fixed point.
+ */
+#define MOVAVG_PRECISION_FACTOR		100
+
+/*
+ * The division portion of the average operation.
+ */
+#define __AVERAGE_PTFV(dev)						\
+	({ next_timing->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] = \
+	   next_timing->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] / \
+	   next_timing->ptfv_list[PTFV_DVFS_SAMPLES_INDEX]; })
+
+/*
+ * Convert val to fixed point and add it to the temporary average.
+ */
+#define __INCREMENT_PTFV(dev, val)					\
+	({ next_timing->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] += \
+	   ((val) * MOVAVG_PRECISION_FACTOR); })
+
+/*
+ * Convert a moving average back to integral form and return the value.
+ */
+#define __MOVAVG_AC(timing, dev)					\
+	((timing)->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] /	\
+	 MOVAVG_PRECISION_FACTOR)
+
+/* Weighted update. */
+#define __WEIGHTED_UPDATE_PTFV(dev, nval)				\
+	do {								\
+		int w = PTFV_MOVAVG_WEIGHT_INDEX;			\
+		int dqs = PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX;		\
+									\
+		next_timing->ptfv_list[dqs] =				\
+			((nval * MOVAVG_PRECISION_FACTOR) +		\
+			 (next_timing->ptfv_list[dqs] *			\
+			  next_timing->ptfv_list[w])) /			\
+			(next_timing->ptfv_list[w] + 1);		\
+									\
+		emc_cc_dbg(EMA_UPDATES, "%s: (s=%u) EMA: %u\n",		\
+			   __stringify(dev), nval,			\
+			   next_timing->ptfv_list[dqs]);		\
+	} while (0)
+
+/* Access a particular average. */
+#define __MOVAVG(timing, dev)                      \
+	((timing)->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX])
+
+static u32 update_clock_tree_delay(struct tegra_emc *emc,
+				   u32 dram_dev_num, u32 channel_mode, int type)
+{
+	u32 mrr_req = 0, mrr_data = 0;
+	u32 temp0_0 = 0, temp0_1 = 0, temp1_0 = 0, temp1_1 = 0;
+	s32 tdel = 0, tmdel = 0, adel = 0;
+	u32 cval = 0;
+	struct emc_table *last_timing = emc->current_timing;
+	struct emc_table *next_timing = emc->next_timing;
+	u32 last_timing_rate_mhz = last_timing->rate / 1000;
+	u32 next_timing_rate_mhz = next_timing->rate / 1000;
+	int dvfs_pt1 = type == DVFS_PT1;
+	int dvfs_update = type == DVFS_UPDATE;
+	int periodic_training_update = type == PERIODIC_TRAINING_UPDATE;
+
+	/*
+	 * Dev0 MSB.
+	 */
+	if (dvfs_pt1 || periodic_training_update) {
+		mrr_req = (2 << EMC_MRR_DEV_SEL_SHIFT) |
+			(19 << EMC_MRR_MA_SHIFT);
+		emc_writel(emc, mrr_req, EMC_MRR);
+
+		WARN(wait_for_update(emc, EMC_EMC_STATUS,
+				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
+		     "Timed out waiting for MRR 19 (ch=0)\n");
+		if (channel_mode == DUAL_CHANNEL)
+			WARN(wait_for_update(emc, EMC_EMC_STATUS,
+					     EMC_EMC_STATUS_MRR_DIVLD, 1,
+					     REG_EMC1),
+			     "Timed out waiting for MRR 19 (ch=1)\n");
+
+		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
+			   EMC_MRR_DATA_SHIFT;
+
+		temp0_0 = (mrr_data & 0xff) << 8;
+		temp0_1 = mrr_data & 0xff00;
+
+		if (channel_mode == DUAL_CHANNEL) {
+			mrr_data = (emc1_readl(emc, EMC_MRR) &
+				    EMC_MRR_DATA_MASK) <<
+				   EMC_MRR_DATA_SHIFT;
+			temp1_0 = (mrr_data & 0xff) << 8;
+			temp1_1 = mrr_data & 0xff00;
+		}
+
+		/*
+		 * Dev0 LSB.
+		 */
+		mrr_req = (mrr_req & ~EMC_MRR_MA_MASK) |
+			  (18 << EMC_MRR_MA_SHIFT);
+		emc_writel(emc, mrr_req, EMC_MRR);
+
+		WARN(wait_for_update(emc, EMC_EMC_STATUS,
+				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
+		     "Timed out waiting for MRR 18 (ch=0)\n");
+		if (channel_mode == DUAL_CHANNEL)
+			WARN(wait_for_update(emc, EMC_EMC_STATUS,
+					     EMC_EMC_STATUS_MRR_DIVLD, 1,
+					     REG_EMC1),
+			     "Timed out waiting for MRR 18 (ch=1)\n");
+
+		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
+			   EMC_MRR_DATA_SHIFT;
+
+		temp0_0 |= mrr_data & 0xff;
+		temp0_1 |= (mrr_data & 0xff00) >> 8;
+
+		if (channel_mode == DUAL_CHANNEL) {
+			mrr_data = (emc1_readl(emc, EMC_MRR) &
+				    EMC_MRR_DATA_MASK) <<
+				   EMC_MRR_DATA_SHIFT;
+			temp1_0 |= (mrr_data & 0xff);
+			temp1_1 |= (mrr_data & 0xff00) >> 8;
+		}
+	}
+
+	if (dvfs_pt1 || periodic_training_update)
+		cval = (1000000 * tegra210_actual_osc_clocks(
+					last_timing->run_clocks)) /
+		       (last_timing_rate_mhz * 2 * temp0_0);
+
+	if (dvfs_pt1)
+		__INCREMENT_PTFV(C0D0U0, cval);
+	else if (dvfs_update)
+		__AVERAGE_PTFV(C0D0U0);
+	else if (periodic_training_update)
+		__WEIGHTED_UPDATE_PTFV(C0D0U0, cval);
+
+	if (dvfs_update || periodic_training_update) {
+		tdel = next_timing->current_dram_clktree_c0d0u0 -
+			__MOVAVG_AC(next_timing, C0D0U0);
+		tmdel = (tdel < 0) ? -1 * tdel : tdel;
+		adel = tmdel;
+		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
+		    next_timing->tree_margin)
+			next_timing->current_dram_clktree_c0d0u0 =
+				__MOVAVG_AC(next_timing, C0D0U0);
+	}
+
+	if (dvfs_pt1 || periodic_training_update)
+		cval = (1000000 * tegra210_actual_osc_clocks(
+					last_timing->run_clocks)) /
+		       (last_timing_rate_mhz * 2 * temp0_1);
+
+	if (dvfs_pt1)
+		__INCREMENT_PTFV(C0D0U1, cval);
+	else if (dvfs_update)
+		__AVERAGE_PTFV(C0D0U1);
+	else if (periodic_training_update)
+		__WEIGHTED_UPDATE_PTFV(C0D0U1, cval);
+
+	if (dvfs_update || periodic_training_update) {
+		tdel = next_timing->current_dram_clktree_c0d0u1 -
+			__MOVAVG_AC(next_timing, C0D0U1);
+		tmdel = (tdel < 0) ? -1 * tdel : tdel;
+
+		if (tmdel > adel)
+			adel = tmdel;
+
+		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
+		    next_timing->tree_margin)
+			next_timing->current_dram_clktree_c0d0u1 =
+				__MOVAVG_AC(next_timing, C0D0U1);
+	}
+
+	if (channel_mode == DUAL_CHANNEL) {
+		if (dvfs_pt1 || periodic_training_update)
+			cval = (1000000 * tegra210_actual_osc_clocks(
+						last_timing->run_clocks)) /
+			       (last_timing_rate_mhz * 2 * temp1_0);
+		if (dvfs_pt1)
+			__INCREMENT_PTFV(C1D0U0, cval);
+		else if (dvfs_update)
+			__AVERAGE_PTFV(C1D0U0);
+		else if (periodic_training_update)
+			__WEIGHTED_UPDATE_PTFV(C1D0U0, cval);
+
+		if (dvfs_update || periodic_training_update) {
+			tdel = next_timing->current_dram_clktree_c1d0u0 -
+				__MOVAVG_AC(next_timing, C1D0U0);
+			tmdel = (tdel < 0) ? -1 * tdel : tdel;
+
+			if (tmdel > adel)
+				adel = tmdel;
+
+			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
+			    next_timing->tree_margin)
+				next_timing->current_dram_clktree_c1d0u0 =
+					__MOVAVG_AC(next_timing, C1D0U0);
+		}
+
+		if (dvfs_pt1 || periodic_training_update)
+			cval = (1000000 * tegra210_actual_osc_clocks(
+						last_timing->run_clocks)) /
+			       (last_timing_rate_mhz * 2 * temp1_1);
+		if (dvfs_pt1)
+			__INCREMENT_PTFV(C1D0U1, cval);
+		else if (dvfs_update)
+			__AVERAGE_PTFV(C1D0U1);
+		else if (periodic_training_update)
+			__WEIGHTED_UPDATE_PTFV(C1D0U1, cval);
+
+		if (dvfs_update || periodic_training_update) {
+			tdel = next_timing->current_dram_clktree_c1d0u1 -
+				__MOVAVG_AC(next_timing, C1D0U1);
+			tmdel = (tdel < 0) ? -1 * tdel : tdel;
+
+			if (tmdel > adel)
+				adel = tmdel;
+
+			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
+			    next_timing->tree_margin)
+				next_timing->current_dram_clktree_c1d0u1 =
+					__MOVAVG_AC(next_timing, C1D0U1);
+		}
+	}
+
+	if (emc->dram_dev_num != TWO_RANK)
+		goto done;
+
+	/*
+	 * Dev1 MSB.
+	 */
+	if (dvfs_pt1 || periodic_training_update) {
+		mrr_req = (1 << EMC_MRR_DEV_SEL_SHIFT) |
+			  (19 << EMC_MRR_MA_SHIFT);
+		emc_writel(emc, mrr_req, EMC_MRR);
+
+		WARN(wait_for_update(emc, EMC_EMC_STATUS,
+				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
+		     "Timed out waiting for MRR 19 (ch=0)\n");
+		if (channel_mode == DUAL_CHANNEL)
+			WARN(wait_for_update(emc, EMC_EMC_STATUS,
+					     EMC_EMC_STATUS_MRR_DIVLD, 1,
+					     REG_EMC1),
+			     "Timed out waiting for MRR 19 (ch=1)\n");
+
+		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
+			   EMC_MRR_DATA_SHIFT;
+
+		temp0_0 = (mrr_data & 0xff) << 8;
+		temp0_1 = mrr_data & 0xff00;
+
+		if (channel_mode == DUAL_CHANNEL) {
+			mrr_data = (emc1_readl(emc, EMC_MRR) &
+				    EMC_MRR_DATA_MASK) <<
+				   EMC_MRR_DATA_SHIFT;
+			temp1_0 = (mrr_data & 0xff) << 8;
+			temp1_1 = mrr_data & 0xff00;
+		}
+
+		/*
+		 * Dev1 LSB.
+		 */
+		mrr_req = (mrr_req & ~EMC_MRR_MA_MASK) |
+			  (18 << EMC_MRR_MA_SHIFT);
+		emc_writel(emc, mrr_req, EMC_MRR);
+
+		WARN(wait_for_update(emc, EMC_EMC_STATUS,
+				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
+		     "Timed out waiting for MRR 18 (ch=0)\n");
+		if (channel_mode == DUAL_CHANNEL)
+			WARN(wait_for_update(emc, EMC_EMC_STATUS,
+					     EMC_EMC_STATUS_MRR_DIVLD, 1,
+					     REG_EMC1),
+			     "Timed out waiting for MRR 18 (ch=1)\n");
+
+		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
+			   EMC_MRR_DATA_SHIFT;
+
+		temp0_0 |= mrr_data & 0xff;
+		temp0_1 |= (mrr_data & 0xff00) >> 8;
+
+		if (channel_mode == DUAL_CHANNEL) {
+			mrr_data = (emc1_readl(emc, EMC_MRR) &
+				    EMC_MRR_DATA_MASK) <<
+				   EMC_MRR_DATA_SHIFT;
+			temp1_0 |= (mrr_data & 0xff);
+			temp1_1 |= (mrr_data & 0xff00) >> 8;
+		}
+	}
+
+	if (dvfs_pt1 || periodic_training_update)
+		cval = (1000000 * tegra210_actual_osc_clocks(
+					last_timing->run_clocks)) /
+		       (last_timing_rate_mhz * 2 * temp0_0);
+
+	if (dvfs_pt1)
+		__INCREMENT_PTFV(C0D1U0, cval);
+	else if (dvfs_update)
+		__AVERAGE_PTFV(C0D1U0);
+	else if (periodic_training_update)
+		__WEIGHTED_UPDATE_PTFV(C0D1U0, cval);
+
+	if (dvfs_update || periodic_training_update) {
+		tdel = next_timing->current_dram_clktree_c0d1u0 -
+		       __MOVAVG_AC(next_timing, C0D1U0);
+		tmdel = (tdel < 0) ? -1 * tdel : tdel;
+		if (tmdel > adel)
+			adel = tmdel;
+
+		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
+		    next_timing->tree_margin)
+			next_timing->current_dram_clktree_c0d1u0 =
+				__MOVAVG_AC(next_timing, C0D1U0);
+	}
+
+	if (dvfs_pt1 || periodic_training_update)
+		cval = (1000000 * tegra210_actual_osc_clocks(
+					last_timing->run_clocks)) /
+		       (last_timing_rate_mhz * 2 * temp0_1);
+
+	if (dvfs_pt1)
+		__INCREMENT_PTFV(C0D1U1, cval);
+	else if (dvfs_update)
+		__AVERAGE_PTFV(C0D1U1);
+	else if (periodic_training_update)
+		__WEIGHTED_UPDATE_PTFV(C0D1U1, cval);
+
+	if (dvfs_update || periodic_training_update) {
+		tdel = next_timing->current_dram_clktree_c0d1u1 -
+		       __MOVAVG_AC(next_timing, C0D1U1);
+		tmdel = (tdel < 0) ? -1 * tdel : tdel;
+		if (tmdel > adel)
+			adel = tmdel;
+
+		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
+		    next_timing->tree_margin)
+			next_timing->current_dram_clktree_c0d1u1 =
+				__MOVAVG_AC(next_timing, C0D1U1);
+	}
+
+	if (channel_mode == DUAL_CHANNEL) {
+		if (dvfs_pt1 || periodic_training_update)
+			cval = (1000000 * tegra210_actual_osc_clocks(
+						last_timing->run_clocks)) /
+			       (last_timing_rate_mhz * 2 * temp1_0);
+
+		if (dvfs_pt1)
+			__INCREMENT_PTFV(C1D1U0, cval);
+		else if (dvfs_update)
+			__AVERAGE_PTFV(C1D1U0);
+		else if (periodic_training_update)
+			__WEIGHTED_UPDATE_PTFV(C1D1U0, cval);
+
+		if (dvfs_update || periodic_training_update) {
+			tdel = next_timing->current_dram_clktree_c1d1u0 -
+			       __MOVAVG_AC(next_timing, C1D1U0);
+			tmdel = (tdel < 0) ? -1 * tdel : tdel;
+			if (tmdel > adel)
+				adel = tmdel;
+
+			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
+			    next_timing->tree_margin)
+				next_timing->current_dram_clktree_c1d1u0 =
+					__MOVAVG_AC(next_timing, C1D1U0);
+		}
+
+		if (dvfs_pt1 || periodic_training_update)
+			cval = (1000000 * tegra210_actual_osc_clocks(
+						last_timing->run_clocks)) /
+			       (last_timing_rate_mhz * 2 * temp1_1);
+
+		if (dvfs_pt1)
+			__INCREMENT_PTFV(C1D1U1, cval);
+		else if (dvfs_update)
+			__AVERAGE_PTFV(C1D1U1);
+		else if (periodic_training_update)
+			__WEIGHTED_UPDATE_PTFV(C1D1U1, cval);
+
+		if (dvfs_update || periodic_training_update) {
+			tdel = next_timing->current_dram_clktree_c1d1u1 -
+			       __MOVAVG_AC(next_timing, C1D1U1);
+			tmdel = (tdel < 0) ? -1 * tdel : tdel;
+			if (tmdel > adel)
+				adel = tmdel;
+
+			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
+			    next_timing->tree_margin)
+				next_timing->current_dram_clktree_c1d1u1 =
+					__MOVAVG_AC(next_timing, C1D1U1);
+		}
+	}
+
+done:
+	return adel;
+}
+
+static u32 periodic_compensation_handler(struct tegra_emc *emc, u32 type,
+					 u32 dram_dev_num,
+					 u32 channel_mode,
+					 struct emc_table *last_timing,
+					 struct emc_table *next_timing)
+{
+#define __COPY_EMA(nt, lt, dev)						\
+	({ __MOVAVG(nt, dev) = __MOVAVG(lt, dev) *			\
+	   (nt)->ptfv_list[PTFV_DVFS_SAMPLES_INDEX]; })
+
+	u32 i;
+	u32 adel = 0;
+	u32 samples = next_timing->ptfv_list[PTFV_DVFS_SAMPLES_INDEX];
+	u32 delay = 2 +
+		(1000 * tegra210_actual_osc_clocks(last_timing->run_clocks) /
+		last_timing->rate);
+
+	if (!next_timing->periodic_training)
+		return 0;
+
+	if (type == DVFS_SEQUENCE) {
+		if (last_timing->periodic_training &&
+		    (next_timing->ptfv_list[PTFV_CONFIG_CTRL_INDEX] &
+		     PTFV_CONFIG_CTRL_USE_PREVIOUS_EMA)) {
+			/*
+			 * If the previous frequency was using periodic
+			 * calibration then we can reuse the previous
+			 * frequencies EMA data.
+			 */
+			__COPY_EMA(next_timing, last_timing, C0D0U0);
+			__COPY_EMA(next_timing, last_timing, C0D0U1);
+			__COPY_EMA(next_timing, last_timing, C1D0U0);
+			__COPY_EMA(next_timing, last_timing, C1D0U1);
+			__COPY_EMA(next_timing, last_timing, C0D1U0);
+			__COPY_EMA(next_timing, last_timing, C0D1U1);
+			__COPY_EMA(next_timing, last_timing, C1D1U0);
+			__COPY_EMA(next_timing, last_timing, C1D1U1);
+		} else {
+			/* Reset the EMA.*/
+			__MOVAVG(next_timing, C0D0U0) = 0;
+			__MOVAVG(next_timing, C0D0U1) = 0;
+			__MOVAVG(next_timing, C1D0U0) = 0;
+			__MOVAVG(next_timing, C1D0U1) = 0;
+			__MOVAVG(next_timing, C0D1U0) = 0;
+			__MOVAVG(next_timing, C0D1U1) = 0;
+			__MOVAVG(next_timing, C1D1U0) = 0;
+			__MOVAVG(next_timing, C1D1U1) = 0;
+
+			for (i = 0; i < samples; i++) {
+				tegra210_start_periodic_compensation(emc);
+				udelay(delay);
+
+				/*
+				 * Generate next sample of data.
+				 */
+				adel = update_clock_tree_delay(emc,
+							emc->dram_dev_num,
+							channel_mode,
+							DVFS_PT1);
+			}
+		}
+
+		/*
+		 * Seems like it should be part of the
+		 * 'if (last_timing->periodic_training)' conditional
+		 * since is already done for the else clause.
+		 */
+		adel = update_clock_tree_delay(emc,
+					       emc->dram_dev_num,
+					       channel_mode,
+					       DVFS_UPDATE);
+	}
+
+	if (type == PERIODIC_TRAINING_SEQUENCE) {
+		tegra210_start_periodic_compensation(emc);
+		udelay(delay);
+
+		adel = update_clock_tree_delay(emc,
+					       emc->dram_dev_num,
+					       channel_mode,
+					       PERIODIC_TRAINING_UPDATE);
+	}
+
+	return adel;
+}
+
+u32 __do_periodic_emc_compensation_r21021(struct tegra_emc *emc)
+{
+	u32 dram_dev_num;
+	u32 channel_mode;
+	u32 emc_cfg, emc_cfg_o;
+	u32 emc_dbg_o;
+	u32 del, i;
+	u32 list[] = {
+		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0,
+		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1,
+		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2,
+		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3,
+		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0,
+		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1,
+		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2,
+		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3,
+		EMC_DATA_BRLSHFT_0,
+		EMC_DATA_BRLSHFT_1
+	};
+	u32 items = ARRAY_SIZE(list);
+	u32 emc_cfg_update;
+	struct emc_table *current_timing = emc->current_timing;
+
+	if (current_timing->periodic_training) {
+		channel_mode =
+			!!(current_timing->burst_regs[EMC_FBIO_CFG7_INDEX] &
+			(1 << 2));
+		dram_dev_num = 1 + (mc_readl(emc->mc, MC_EMEM_ADR_CFG) & 0x1);
+
+		emc_cc_dbg(PER_TRAIN, "Periodic training starting\n");
+
+		emc_dbg_o = emc_readl(emc, EMC_DBG);
+		emc_cfg_o = emc_readl(emc, EMC_CFG);
+		emc_cfg = emc_cfg_o & ~(EMC_CFG_DYN_SELF_REF |
+					EMC_CFG_DRAM_ACPD |
+					EMC_CFG_DRAM_CLKSTOP_PD |
+					EMC_CFG_DRAM_CLKSTOP_PD);
+
+
+		/*
+		 * 1. Power optimizations should be off.
+		 */
+		emc_writel(emc, emc_cfg, EMC_CFG);
+
+		/* Does emc_timing_update() for above changes. */
+		tegra210_dll_disable(emc, channel_mode);
+
+		wait_for_update(emc, EMC_EMC_STATUS,
+				EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK, 0,
+				REG_EMC);
+		if (channel_mode)
+			wait_for_update(emc, EMC_EMC_STATUS,
+					EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK,
+					0, REG_EMC1);
+
+		wait_for_update(emc, EMC_EMC_STATUS,
+				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
+				REG_EMC);
+		if (channel_mode)
+			wait_for_update(emc, EMC_EMC_STATUS,
+				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
+				REG_EMC1);
+
+		emc_cfg_update = emc_readl(emc, EMC_CFG_UPDATE);
+		emc_writel(emc, (emc_cfg_update &
+			    ~EMC_CFG_UPDATE_UPDATE_DLL_IN_UPDATE_MASK) |
+			   (2 << EMC_CFG_UPDATE_UPDATE_DLL_IN_UPDATE_SHIFT),
+			   EMC_CFG_UPDATE);
+
+		/*
+		 * 2. osc kick off - this assumes training and dvfs have set
+		 *    correct MR23.
+		 */
+		tegra210_start_periodic_compensation(emc);
+
+		/*
+		 * 3. Let dram capture its clock tree delays.
+		 */
+		udelay((tegra210_actual_osc_clocks(current_timing->run_clocks) *
+			1000) /
+		       current_timing->rate + 1);
+
+		/*
+		 * 4. Check delta wrt previous values (save value if margin
+		 *    exceeds what is set in table).
+		 */
+		del = periodic_compensation_handler(emc,
+						    PERIODIC_TRAINING_SEQUENCE,
+						    dram_dev_num,
+						    channel_mode,
+						    current_timing,
+						    current_timing);
+
+		/*
+		 * 5. Apply compensation w.r.t. trained values (if clock tree
+		 *    has drifted more than the set margin).
+		 */
+		if (current_timing->tree_margin <
+		    ((del * 128 * (current_timing->rate / 1000)) / 1000000)) {
+			for (i = 0; i < items; i++) {
+				u32 tmp =
+				tegra210_apply_periodic_compensation_trimmer(
+				current_timing, list[i]);
+
+				emc_cc_dbg(EMA_WRITES, "0x%08x <= 0x%08x\n",
+					   list[i], tmp);
+				emc_writel(emc, tmp, list[i]);
+			}
+		}
+
+		emc_writel(emc, emc_cfg_o, EMC_CFG);
+
+		/*
+		 * 6. Timing update actally applies the new trimmers.
+		 */
+		emc_timing_update(emc, channel_mode);
+
+		/* 6.1. Restore the UPDATE_DLL_IN_UPDATE field. */
+		emc_writel(emc, emc_cfg_update, EMC_CFG_UPDATE);
+
+		/* 6.2. Restore the DLL. */
+		tegra210_dll_enable(emc, channel_mode);
+
+		/*
+		 * 7. Copy over the periodic training registers that we updated
+		 *    here to the corresponding derated/non-derated table.
+		 */
+		tegra210_update_emc_alt_timing(emc, current_timing);
+	}
+
+	return 0;
+}
+
+/*
+ * Do the clock change sequence.
+ */
+void emc_set_clock_r21021(struct tegra_emc *emc, u32 clksrc)
+{
+	/*
+	 * This is the timing table for the source frequency. It does _not_
+	 * necessarily correspond to the actual timing values in the EMC at the
+	 * moment. If the boot BCT differs from the table then this can happen.
+	 * However, we need it for accessing the dram_timings (which are not
+	 * really registers) array for the current frequency.
+	 */
+	struct emc_table *fake_timing;
+	struct emc_table *last_timing = emc->current_timing;
+	struct emc_table *next_timing = emc->next_timing;
+
+	u32 i, tmp;
+
+	u32 cya_allow_ref_cc = 0, ref_b4_sref_en = 0, cya_issue_pc_ref = 0;
+
+	u32 zqcal_before_cc_cutoff = 2400; /* In picoseconds */
+	u32 ref_delay_mult;
+	u32 ref_delay;
+	s32 zq_latch_dvfs_wait_time;
+	s32 tZQCAL_lpddr4_fc_adj;
+	/* Scaled by x1000 */
+	u32 tFC_lpddr4 = 1000 * next_timing->dram_timings[T_FC_LPDDR4];
+	u32 tZQCAL_lpddr4 = 1000000;
+
+	u32 dram_type, dram_dev_num, shared_zq_resistor;
+	u32 channel_mode;
+	u32 is_lpddr3;
+
+	u32 emc_cfg, emc_sel_dpd_ctrl, emc_cfg_reg;
+
+	u32 emc_dbg;
+	u32 emc_zcal_interval;
+	u32 emc_zcal_wait_cnt_old;
+	u32 emc_zcal_wait_cnt_new;
+	u32 emc_dbg_active;
+	u32 zq_op;
+	u32 zcal_wait_time_clocks;
+	u32 zcal_wait_time_ps;
+
+	u32 emc_auto_cal_config;
+	u32 auto_cal_en;
+
+	u32 mr13_catr_enable;
+
+	u32 ramp_up_wait = 0, ramp_down_wait = 0;
+
+	/* In picoseconds. */
+	u32 source_clock_period;
+	u32 destination_clock_period;
+
+	u32 emc_dbg_o;
+	u32 emc_cfg_pipe_clk_o;
+	u32 emc_pin_o;
+
+	u32 mr13_flip_fspwr;
+	u32 mr13_flip_fspop;
+
+	u32 opt_zcal_en_cc;
+	u32 opt_do_sw_qrst = 1;
+	u32 opt_dvfs_mode;
+	u32 opt_dll_mode;
+	u32 opt_cc_short_zcal = 1;
+	u32 opt_short_zcal = 1;
+	u32 save_restore_clkstop_pd = 1;
+
+	u32 prelock_dll_en = 0, dll_out;
+
+	int next_push, next_dq_e_ivref, next_dqs_e_ivref;
+
+	u32 opt_war_200024907;
+	u32 zq_wait_long;
+	u32 zq_wait_short;
+
+	u32 bg_regulator_switch_complete_wait_clks;
+	u32 bg_regulator_mode_change;
+	u32 enable_bglp_regulator;
+	u32 enable_bg_regulator;
+
+	u32 tRTM;
+	u32 RP_war;
+	u32 R2P_war;
+	u32 TRPab_war;
+	s32 nRTP;
+	u32 deltaTWATM;
+	u32 W2P_war;
+	u32 tRPST;
+
+	u32 mrw_req;
+	u32 adel = 0, compensate_trimmer_applicable = 0;
+	u32 next_timing_rate_mhz = next_timing->rate / 1000;
+
+	static u32 fsp_for_next_freq;
+
+	emc_cc_dbg(INFO, "Running clock change.\n");
+
+	fake_timing = get_timing_from_freq(emc, last_timing->rate);
+
+	fsp_for_next_freq = !fsp_for_next_freq;
+
+	dram_type = emc_readl(emc, EMC_FBIO_CFG5) &
+		    EMC_FBIO_CFG5_DRAM_TYPE_MASK >>
+		    EMC_FBIO_CFG5_DRAM_TYPE_SHIFT;
+	shared_zq_resistor = last_timing->burst_regs[EMC_ZCAL_WAIT_CNT_INDEX] &
+			     1 << 31;
+	channel_mode = !!(last_timing->burst_regs[EMC_FBIO_CFG7_INDEX] &
+			  1 << 2);
+	opt_zcal_en_cc = (next_timing->burst_regs[EMC_ZCAL_INTERVAL_INDEX] &&
+			  !last_timing->burst_regs[EMC_ZCAL_INTERVAL_INDEX]) ||
+			  dram_type == DRAM_TYPE_LPDDR4;
+	opt_dll_mode = (dram_type == DRAM_TYPE_DDR3) ?
+		       get_dll_state(next_timing) : DLL_OFF;
+	is_lpddr3 = (dram_type == DRAM_TYPE_LPDDR2) &&
+		    next_timing->burst_regs[EMC_FBIO_CFG5_INDEX] &
+		    1 << 25;
+	opt_war_200024907 = (dram_type == DRAM_TYPE_LPDDR4);
+	opt_dvfs_mode = MAN_SR;
+	dram_dev_num = (mc_readl(emc->mc, MC_EMEM_ADR_CFG) & 0x1) + 1;
+
+	emc_cfg_reg = emc_readl(emc, EMC_CFG);
+	emc_auto_cal_config = emc_readl(emc, EMC_AUTO_CAL_CONFIG);
+
+	source_clock_period = 1000000000 / last_timing->rate;
+	destination_clock_period = 1000000000 / next_timing->rate;
+
+	tZQCAL_lpddr4_fc_adj = (destination_clock_period >
+				zqcal_before_cc_cutoff) ?
+		tZQCAL_lpddr4 / destination_clock_period :
+		(tZQCAL_lpddr4 - tFC_lpddr4) / destination_clock_period;
+	emc_dbg_o = emc_readl(emc, EMC_DBG);
+	emc_pin_o = emc_readl(emc, EMC_PIN);
+	emc_cfg_pipe_clk_o = emc_readl(emc, EMC_CFG_PIPE_CLK);
+	emc_dbg = emc_dbg_o;
+
+	emc_cfg = next_timing->burst_regs[EMC_CFG_INDEX];
+	emc_cfg &= ~(EMC_CFG_DYN_SELF_REF | EMC_CFG_DRAM_ACPD |
+		     EMC_CFG_DRAM_CLKSTOP_SR | EMC_CFG_DRAM_CLKSTOP_PD);
+	emc_sel_dpd_ctrl = next_timing->emc_sel_dpd_ctrl;
+	emc_sel_dpd_ctrl &= ~(EMC_SEL_DPD_CTRL_CLK_SEL_DPD_EN |
+			      EMC_SEL_DPD_CTRL_CA_SEL_DPD_EN |
+			      EMC_SEL_DPD_CTRL_RESET_SEL_DPD_EN |
+			      EMC_SEL_DPD_CTRL_ODT_SEL_DPD_EN |
+			      EMC_SEL_DPD_CTRL_DATA_SEL_DPD_EN);
+
+	emc_cc_dbg(INFO, "Clock change version: %d\n",
+		   DVFS_CLOCK_CHANGE_VERSION);
+	emc_cc_dbg(INFO, "DRAM type = %d\n", emc->dram_type);
+	emc_cc_dbg(INFO, "DRAM dev #: %d\n", dram_dev_num);
+	emc_cc_dbg(INFO, "Next EMC clksrc: 0x%08x\n", clksrc);
+	emc_cc_dbg(INFO, "DLL clksrc:      0x%08x\n", next_timing->dll_clk_src);
+	emc_cc_dbg(INFO, "last rate: %u, next rate %u\n", last_timing->rate,
+		   next_timing->rate);
+	emc_cc_dbg(INFO, "last period: %u, next period: %u\n",
+		   source_clock_period, destination_clock_period);
+	emc_cc_dbg(INFO, "  shared_zq_resistor: %d\n", !!shared_zq_resistor);
+	emc_cc_dbg(INFO, "  channel_mode: %d\n", channel_mode);
+	emc_cc_dbg(INFO, "  opt_dll_mode: %d\n", opt_dll_mode);
+
+	/*
+	 * Step 1:
+	 *   Pre DVFS SW sequence.
+	 */
+	emc_cc_dbg(STEPS, "Step 1\n");
+	emc_cc_dbg(STEPS, "Step 1.1: Disable DLL temporarily.\n");
+	tmp = emc_readl(emc, EMC_CFG_DIG_DLL);
+	tmp &= ~EMC_CFG_DIG_DLL_CFG_DLL_EN;
+	emc_writel(emc, tmp, EMC_CFG_DIG_DLL);
+
+	emc_timing_update(emc, channel_mode);
+	wait_for_update(emc, EMC_CFG_DIG_DLL,
+			EMC_CFG_DIG_DLL_CFG_DLL_EN, 0, REG_EMC);
+	if (channel_mode)
+		wait_for_update(emc, EMC_CFG_DIG_DLL,
+				EMC_CFG_DIG_DLL_CFG_DLL_EN, 0, REG_EMC1);
+
+	emc_cc_dbg(STEPS, "Step 1.2: Disable AUTOCAL temporarily.\n");
+	emc_auto_cal_config = next_timing->emc_auto_cal_config;
+	auto_cal_en = emc_auto_cal_config & EMC_AUTO_CAL_CONFIG_AUTO_CAL_ENABLE;
+	emc_auto_cal_config &= ~EMC_AUTO_CAL_CONFIG_AUTO_CAL_START;
+	emc_auto_cal_config |=  EMC_AUTO_CAL_CONFIG_AUTO_CAL_MEASURE_STALL;
+	emc_auto_cal_config |=  EMC_AUTO_CAL_CONFIG_AUTO_CAL_UPDATE_STALL;
+	emc_auto_cal_config |=  auto_cal_en;
+	emc_writel(emc, emc_auto_cal_config, EMC_AUTO_CAL_CONFIG);
+	emc_readl(emc, EMC_AUTO_CAL_CONFIG); /* Flush write. */
+
+	emc_cc_dbg(STEPS, "Step 1.3: Disable other power features.\n");
+	emc_set_shadow_bypass(emc, ACTIVE);
+	emc_writel(emc, emc_cfg, EMC_CFG);
+	emc_writel(emc, emc_sel_dpd_ctrl, EMC_SEL_DPD_CTRL);
+	emc_set_shadow_bypass(emc, ASSEMBLY);
+
+	if (next_timing->periodic_training) {
+		tegra210_reset_dram_clktree_values(next_timing);
+
+		wait_for_update(emc, EMC_EMC_STATUS,
+				EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK, 0,
+				REG_EMC);
+		if (channel_mode)
+			wait_for_update(emc, EMC_EMC_STATUS,
+					EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK,
+					0, REG_EMC1);
+
+		wait_for_update(emc, EMC_EMC_STATUS,
+				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
+				REG_EMC);
+		if (channel_mode)
+			wait_for_update(emc, EMC_EMC_STATUS,
+				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
+				REG_EMC1);
+
+		tegra210_start_periodic_compensation(emc);
+
+		udelay(((1000 *
+			 tegra210_actual_osc_clocks(last_timing->run_clocks)) /
+			last_timing->rate) + 2);
+		adel = periodic_compensation_handler(emc, DVFS_SEQUENCE,
+						     dram_dev_num,
+						     channel_mode,
+						     fake_timing, next_timing);
+		compensate_trimmer_applicable =
+			next_timing->periodic_training &&
+			((adel * 128 * next_timing_rate_mhz) / 1000000) >
+			next_timing->tree_margin;
+	}
+
+	emc_writel(emc, EMC_INTSTATUS_CLKCHANGE_COMPLETE, EMC_INTSTATUS);
+	emc_set_shadow_bypass(emc, ACTIVE);
+	emc_writel(emc, emc_cfg, EMC_CFG);
+	emc_writel(emc, emc_sel_dpd_ctrl, EMC_SEL_DPD_CTRL);
+	emc_writel(emc, emc_cfg_pipe_clk_o | EMC_CFG_PIPE_CLK_CLK_ALWAYS_ON,
+		   EMC_CFG_PIPE_CLK);
+	emc_writel(emc, next_timing->emc_fdpd_ctrl_cmd_no_ramp &
+		   ~EMC_FDPD_CTRL_CMD_NO_RAMP_CMD_DPD_NO_RAMP_ENABLE,
+		   EMC_FDPD_CTRL_CMD_NO_RAMP);
+
+	bg_regulator_mode_change =
+		((next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
+		  EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD) ^
+		 (last_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
+		  EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD)) ||
+		((next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
+		  EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD) ^
+		 (last_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
+		  EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD));
+	enable_bglp_regulator =
+		(next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
+		 EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD) == 0;
+	enable_bg_regulator =
+		(next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
+		 EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD) == 0;
+
+	if (bg_regulator_mode_change) {
+		if (enable_bg_regulator)
+			emc_writel(emc, last_timing->burst_regs
+				   [EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
+				   ~EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD,
+				   EMC_PMACRO_BG_BIAS_CTRL_0);
+		else
+			emc_writel(emc, last_timing->burst_regs
+				   [EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
+				   ~EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD,
+				   EMC_PMACRO_BG_BIAS_CTRL_0);
+	}
+
+	/* Check if we need to turn on VREF generator. */
+	if ((((last_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
+	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF) == 0) &&
+	     ((next_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
+	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF) == 1)) ||
+	    (((last_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
+	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF) == 0) &&
+	     ((next_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
+	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF) == 1))) {
+		u32 pad_tx_ctrl =
+		    next_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX];
+		u32 last_pad_tx_ctrl =
+		    last_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX];
+
+		next_dqs_e_ivref = pad_tx_ctrl &
+				   EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF;
+		next_dq_e_ivref = pad_tx_ctrl &
+				  EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF;
+		next_push = (last_pad_tx_ctrl &
+			     ~EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF &
+			     ~EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF) |
+			    next_dq_e_ivref | next_dqs_e_ivref;
+		emc_writel(emc, next_push, EMC_PMACRO_DATA_PAD_TX_CTRL);
+		udelay(1);
+	} else if (bg_regulator_mode_change) {
+		udelay(1);
+	}
+
+	emc_set_shadow_bypass(emc, ASSEMBLY);
+
+	/*
+	 * Step 2:
+	 *   Prelock the DLL.
+	 */
+	emc_cc_dbg(STEPS, "Step 2\n");
+	if (next_timing->burst_regs[EMC_CFG_DIG_DLL_INDEX] &
+	    EMC_CFG_DIG_DLL_CFG_DLL_EN) {
+		emc_cc_dbg(INFO, "Prelock enabled for target frequency.\n");
+		dll_out = tegra210_dll_prelock(emc, 0, clksrc);
+		emc_cc_dbg(INFO, "DLL out: 0x%03x\n", dll_out);
+		prelock_dll_en = 1;
+	} else {
+		emc_cc_dbg(INFO, "Disabling DLL for target frequency.\n");
+		tegra210_dll_disable(emc, channel_mode);
+	}
+
+	/*
+	 * Step 3:
+	 *   Prepare autocal for the clock change.
+	 */
+	emc_cc_dbg(STEPS, "Step 3\n");
+	emc_set_shadow_bypass(emc, ACTIVE);
+	emc_writel(emc, next_timing->emc_auto_cal_config2,
+		   EMC_AUTO_CAL_CONFIG2);
+	emc_writel(emc, next_timing->emc_auto_cal_config3,
+		   EMC_AUTO_CAL_CONFIG3);
+	emc_writel(emc, next_timing->emc_auto_cal_config4,
+		   EMC_AUTO_CAL_CONFIG4);
+	emc_writel(emc, next_timing->emc_auto_cal_config5,
+		   EMC_AUTO_CAL_CONFIG5);
+	emc_writel(emc, next_timing->emc_auto_cal_config6,
+		   EMC_AUTO_CAL_CONFIG6);
+	emc_writel(emc, next_timing->emc_auto_cal_config7,
+		   EMC_AUTO_CAL_CONFIG7);
+	emc_writel(emc, next_timing->emc_auto_cal_config8,
+		   EMC_AUTO_CAL_CONFIG8);
+	emc_set_shadow_bypass(emc, ASSEMBLY);
+
+	emc_auto_cal_config |= (EMC_AUTO_CAL_CONFIG_AUTO_CAL_COMPUTE_START |
+				auto_cal_en);
+	emc_writel(emc, emc_auto_cal_config, EMC_AUTO_CAL_CONFIG);
+
+	/*
+	 * Step 4:
+	 *   Update EMC_CFG. (??)
+	 */
+	emc_cc_dbg(STEPS, "Step 4\n");
+	if (source_clock_period > 50000 && dram_type == DRAM_TYPE_LPDDR4)
+		ccfifo_writel(emc, 1, EMC_SELF_REF, 0);
+	else
+		emc_writel(emc, next_timing->emc_cfg_2, EMC_CFG_2);
+
+	/*
+	 * Step 5:
+	 *   Prepare reference variables for ZQCAL regs.
+	 */
+	emc_cc_dbg(STEPS, "Step 5\n");
+	emc_zcal_interval = 0;
+	emc_zcal_wait_cnt_old =
+		last_timing->burst_regs[EMC_ZCAL_WAIT_CNT_INDEX];
+	emc_zcal_wait_cnt_new =
+		next_timing->burst_regs[EMC_ZCAL_WAIT_CNT_INDEX];
+	emc_zcal_wait_cnt_old &= ~EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK;
+	emc_zcal_wait_cnt_new &= ~EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK;
+
+	if (dram_type == DRAM_TYPE_LPDDR4)
+		zq_wait_long = max((u32)1,
+				   div_o3(1000000, destination_clock_period));
+	else if (dram_type == DRAM_TYPE_LPDDR2 || is_lpddr3)
+		zq_wait_long = max(next_timing->min_mrs_wait,
+				   div_o3(360000, destination_clock_period)) +
+			       4;
+	else if (dram_type == DRAM_TYPE_DDR3)
+		zq_wait_long = max((u32)256,
+				   div_o3(320000, destination_clock_period) +
+				   2);
+	else
+		zq_wait_long = 0;
+
+	if (dram_type == DRAM_TYPE_LPDDR2 || is_lpddr3)
+		zq_wait_short = max(max(next_timing->min_mrs_wait, (u32)6),
+				    div_o3(90000, destination_clock_period)) +
+				4;
+	else if (dram_type == DRAM_TYPE_DDR3)
+		zq_wait_short = max((u32)64,
+				    div_o3(80000, destination_clock_period)) +
+				2;
+	else
+		zq_wait_short = 0;
+
+	/*
+	 * Step 6:
+	 *   Training code - removed.
+	 */
+	emc_cc_dbg(STEPS, "Step 6\n");
+
+	/*
+	 * Step 7:
+	 *   Program FSP reference registers and send MRWs to new FSPWR.
+	 */
+	emc_cc_dbg(STEPS, "Step 7\n");
+	emc_cc_dbg(SUB_STEPS, "Step 7.1: Bug 200024907 - Patch RP R2P");
+	if (opt_war_200024907) {
+		nRTP = 16;
+		if (source_clock_period >= 1000000/1866) /* 535.91 ps */
+			nRTP = 14;
+		if (source_clock_period >= 1000000/1600) /* 625.00 ps */
+			nRTP = 12;
+		if (source_clock_period >= 1000000/1333) /* 750.19 ps */
+			nRTP = 10;
+		if (source_clock_period >= 1000000/1066) /* 938.09 ps */
+			nRTP = 8;
+
+		deltaTWATM = max_t(u32, div_o3(7500, source_clock_period), 8);
+
+		/*
+		 * Originally there was a + .5 in the tRPST calculation.
+		 * However since we can't do FP in the kernel and the tRTM
+		 * computation was in a floating point ceiling function, adding
+		 * one to tRTP should be ok. There is no other source of non
+		 * integer values, so the result was always going to be
+		 * something for the form: f_ceil(N + .5) = N + 1;
+		 */
+		tRPST = ((last_timing->emc_mrw & 0x80) >> 7);
+		tRTM = fake_timing->dram_timings[RL] +
+		       div_o3(3600, source_clock_period) +
+		       max_t(u32, div_o3(7500, source_clock_period), 8) +
+		       tRPST + 1 + nRTP;
+
+		emc_cc_dbg(INFO, "tRTM = %u, EMC_RP = %u\n", tRTM,
+			   next_timing->burst_regs[EMC_RP_INDEX]);
+
+		if (last_timing->burst_regs[EMC_RP_INDEX] < tRTM) {
+			if (tRTM > (last_timing->burst_regs[EMC_R2P_INDEX] +
+				    last_timing->burst_regs[EMC_RP_INDEX])) {
+				R2P_war = tRTM -
+					  last_timing->burst_regs[EMC_RP_INDEX];
+				RP_war = last_timing->burst_regs[EMC_RP_INDEX];
+				TRPab_war = last_timing->burst_regs[
+					    EMC_TRPAB_INDEX];
+				if (R2P_war > 63) {
+					RP_war = R2P_war +
+						 last_timing->burst_regs[
+						 EMC_RP_INDEX] - 63;
+					if (TRPab_war < RP_war)
+						TRPab_war = RP_war;
+					R2P_war = 63;
+				}
+			} else {
+				R2P_war = last_timing->burst_regs[
+					  EMC_R2P_INDEX];
+				RP_war = last_timing->burst_regs[EMC_RP_INDEX];
+				TRPab_war = last_timing->burst_regs[
+					    EMC_TRPAB_INDEX];
+			}
+
+			if (RP_war < deltaTWATM) {
+				W2P_war = last_timing->burst_regs[EMC_W2P_INDEX]
+					  + deltaTWATM - RP_war;
+				if (W2P_war > 63) {
+					RP_war = RP_war + W2P_war - 63;
+					if (TRPab_war < RP_war)
+						TRPab_war = RP_war;
+					W2P_war = 63;
+				}
+			} else {
+				W2P_war = last_timing->burst_regs[
+					  EMC_W2P_INDEX];
+			}
+
+			if ((last_timing->burst_regs[EMC_W2P_INDEX] ^
+			     W2P_war) ||
+			    (last_timing->burst_regs[EMC_R2P_INDEX] ^
+			     R2P_war) ||
+			    (last_timing->burst_regs[EMC_RP_INDEX] ^
+			     RP_war) ||
+			    (last_timing->burst_regs[EMC_TRPAB_INDEX] ^
+			     TRPab_war)) {
+				emc_writel(emc, RP_war, EMC_RP);
+				emc_writel(emc, R2P_war, EMC_R2P);
+				emc_writel(emc, W2P_war, EMC_W2P);
+				emc_writel(emc, TRPab_war, EMC_TRPAB);
+			}
+			emc_timing_update(emc, DUAL_CHANNEL);
+		} else {
+			emc_cc_dbg(INFO, "Skipped WAR\n");
+		}
+	}
+
+	if (!fsp_for_next_freq) {
+		mr13_flip_fspwr = (next_timing->emc_mrw3 & 0xffffff3f) | 0x80;
+		mr13_flip_fspop = (next_timing->emc_mrw3 & 0xffffff3f) | 0x00;
+	} else {
+		mr13_flip_fspwr = (next_timing->emc_mrw3 & 0xffffff3f) | 0x40;
+		mr13_flip_fspop = (next_timing->emc_mrw3 & 0xffffff3f) | 0xc0;
+	}
+
+	mr13_catr_enable = (mr13_flip_fspwr & 0xFFFFFFFE) | 0x01;
+	if (dram_dev_num == TWO_RANK)
+		mr13_catr_enable = (mr13_catr_enable & 0x3fffffff) | 0x80000000;
+
+	if (dram_type == DRAM_TYPE_LPDDR4) {
+		emc_writel(emc, mr13_flip_fspwr, EMC_MRW3);
+		emc_writel(emc, next_timing->emc_mrw, EMC_MRW);
+		emc_writel(emc, next_timing->emc_mrw2, EMC_MRW2);
+	}
+
+	/*
+	 * Step 8:
+	 *   Program the shadow registers.
+	 */
+	emc_cc_dbg(STEPS, "Step 8\n");
+	emc_cc_dbg(SUB_STEPS, "Writing burst_regs\n");
+	for (i = 0; i < next_timing->num_burst; i++) {
+		u32 var;
+		u32 wval;
+
+		if (!burst_regs_off[i])
+			continue;
+
+		var = burst_regs_off[i];
+		wval = next_timing->burst_regs[i];
+
+		if (dram_type != DRAM_TYPE_LPDDR4 &&
+		    (var == EMC_MRW6      || var == EMC_MRW7 ||
+		     var == EMC_MRW8      || var == EMC_MRW9 ||
+		     var == EMC_MRW10     || var == EMC_MRW11 ||
+		     var == EMC_MRW12     || var == EMC_MRW13 ||
+		     var == EMC_MRW14     || var == EMC_MRW15 ||
+		     var == EMC_TRAINING_CTRL))
+			continue;
+
+		/* Pain... And suffering. */
+		if (var == EMC_CFG) {
+			wval &= ~EMC_CFG_DRAM_ACPD;
+			wval &= ~EMC_CFG_DYN_SELF_REF;
+			if (dram_type == DRAM_TYPE_LPDDR4) {
+				wval &= ~EMC_CFG_DRAM_CLKSTOP_SR;
+				wval &= ~EMC_CFG_DRAM_CLKSTOP_PD;
+			}
+		} else if (var == EMC_MRS_WAIT_CNT &&
+			   dram_type == DRAM_TYPE_LPDDR2 &&
+			   opt_zcal_en_cc && !opt_cc_short_zcal &&
+			   opt_short_zcal) {
+			wval = (wval & ~(EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK <<
+					 EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT)) |
+			   ((zq_wait_long & EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK) <<
+			    EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT);
+		} else if (var == EMC_ZCAL_WAIT_CNT &&
+			   dram_type == DRAM_TYPE_DDR3 && opt_zcal_en_cc &&
+			   !opt_cc_short_zcal && opt_short_zcal) {
+			wval = (wval & ~(EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK <<
+					 EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_SHIFT))
+			       | ((zq_wait_long &
+				   EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK) <<
+				  EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT);
+		} else if (var == EMC_ZCAL_INTERVAL && opt_zcal_en_cc) {
+			wval = 0; /* EMC_ZCAL_INTERVAL reset value. */
+		} else if (var == EMC_PMACRO_AUTOCAL_CFG_COMMON) {
+			wval |= EMC_PMACRO_AUTOCAL_CFG_COMMON_E_CAL_BYPASS_DVFS;
+		} else if (var == EMC_PMACRO_DATA_PAD_TX_CTRL) {
+			wval &=
+			     ~(EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC |
+			       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC |
+			       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC |
+			       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC);
+		} else if (var == EMC_PMACRO_CMD_PAD_TX_CTRL) {
+			wval |= EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_DRVFORCEON;
+			wval &= ~(EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSP_TX_E_DCC |
+				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSN_TX_E_DCC |
+				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_E_DCC |
+				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_CMD_TX_E_DCC);
+		} else if (var == EMC_PMACRO_BRICK_CTRL_RFU1) {
+			wval &= 0xf800f800;
+		} else if (var == EMC_PMACRO_COMMON_PAD_TX_CTRL) {
+			wval &= 0xfffffff0;
+		}
+
+		emc_writel(emc, wval, var);
+	}
+
+	/* SW addition: do EMC refresh adjustment here. */
+	set_over_temp_timing(emc, next_timing, dram_over_temp_state);
+
+	if (dram_type == DRAM_TYPE_LPDDR4) {
+		mrw_req = (23 << EMC_MRW_MRW_MA_SHIFT) |
+			  (next_timing->run_clocks & EMC_MRW_MRW_OP_MASK);
+		emc_writel(emc, mrw_req, EMC_MRW);
+	}
+
+	/* Per channel burst registers. */
+	emc_cc_dbg(SUB_STEPS, "Writing burst_regs_per_ch\n");
+	for (i = 0; i < next_timing->num_burst_per_ch; i++) {
+		if (!burst_regs_per_ch_off[i])
+			continue;
+
+		if (dram_type != DRAM_TYPE_LPDDR4 &&
+		    (burst_regs_per_ch_off[i] == EMC_MRW6 ||
+		     burst_regs_per_ch_off[i] == EMC_MRW7 ||
+		     burst_regs_per_ch_off[i] == EMC_MRW8 ||
+		     burst_regs_per_ch_off[i] == EMC_MRW9 ||
+		     burst_regs_per_ch_off[i] == EMC_MRW10 ||
+		     burst_regs_per_ch_off[i] == EMC_MRW11 ||
+		     burst_regs_per_ch_off[i] == EMC_MRW12 ||
+		     burst_regs_per_ch_off[i] == EMC_MRW13 ||
+		     burst_regs_per_ch_off[i] == EMC_MRW14 ||
+		     burst_regs_per_ch_off[i] == EMC_MRW15))
+			continue;
+
+		/* Filter out second channel if not in DUAL_CHANNEL mode. */
+		if (channel_mode != DUAL_CHANNEL &&
+		    burst_regs_per_ch_type[i] >= REG_EMC1)
+			continue;
+
+		emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
+			   i, next_timing->burst_reg_per_ch[i],
+			   burst_regs_per_ch_off[i]);
+		emc_writel_per_ch(emc, next_timing->burst_reg_per_ch[i],
+				  burst_regs_per_ch_type[i],
+				  burst_regs_per_ch_off[i]);
+	}
+
+	/* Vref regs. */
+	emc_cc_dbg(SUB_STEPS, "Writing vref_regs\n");
+	for (i = 0; i < next_timing->vref_num; i++) {
+		if (!vref_regs_per_ch_off[i])
+			continue;
+
+		if (channel_mode != DUAL_CHANNEL &&
+			vref_regs_per_ch_type[i] >= REG_EMC1)
+			continue;
+
+		emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
+			   i, next_timing->vref_perch_regs[i],
+			   vref_regs_per_ch_off[i]);
+		emc_writel_per_ch(emc, next_timing->vref_perch_regs[i],
+				  vref_regs_per_ch_type[i],
+				  vref_regs_per_ch_off[i]);
+	}
+
+	/* Trimmers. */
+	emc_cc_dbg(SUB_STEPS, "Writing trim_regs\n");
+	for (i = 0; i < next_timing->num_trim; i++) {
+		u64 trim_reg;
+
+		if (!trim_regs_off[i])
+			continue;
+
+		trim_reg = trim_regs_off[i];
+		if (compensate_trimmer_applicable &&
+		    (trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3 ||
+		     trim_reg == EMC_DATA_BRLSHFT_0 ||
+		     trim_reg == EMC_DATA_BRLSHFT_1)) {
+			u32 reg = tegra210_apply_periodic_compensation_trimmer(
+				  next_timing, trim_reg);
+			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n", i, reg,
+				   trim_regs_off[i]);
+			emc_cc_dbg(EMA_WRITES, "0x%08x <= 0x%08x\n",
+				   (u32)(u64)trim_regs_off[i], reg);
+			emc_writel(emc, reg, trim_regs_off[i]);
+		} else {
+			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
+				   i, next_timing->trim_regs[i],
+				   trim_regs_off[i]);
+			emc_writel(emc, next_timing->trim_regs[i],
+				   trim_regs_off[i]);
+		}
+	}
+
+	/* Per channel trimmers. */
+	emc_cc_dbg(SUB_STEPS, "Writing trim_regs_per_ch\n");
+	for (i = 0; i < next_timing->num_trim_per_ch; i++) {
+		u32 trim_reg;
+
+		if (!trim_regs_per_ch_off[i])
+			continue;
+
+		if (channel_mode != DUAL_CHANNEL &&
+			trim_regs_per_ch_type[i] >= REG_EMC1)
+			continue;
+
+		trim_reg = trim_regs_per_ch_off[i];
+		if (compensate_trimmer_applicable &&
+		    (trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2 ||
+		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3 ||
+		     trim_reg == EMC_DATA_BRLSHFT_0 ||
+		     trim_reg == EMC_DATA_BRLSHFT_1)) {
+			u32 reg =
+				tegra210_apply_periodic_compensation_trimmer(
+						next_timing, trim_reg);
+			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
+				   i, reg, trim_regs_per_ch_off[i]);
+			emc_cc_dbg(EMA_WRITES, "0x%08x <= 0x%08x\n",
+				   trim_regs_per_ch_off[i], reg);
+			emc_writel_per_ch(emc, reg, trim_regs_per_ch_type[i],
+					  trim_regs_per_ch_off[i]);
+		} else {
+			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
+				   i, next_timing->trim_perch_regs[i],
+				   trim_regs_per_ch_off[i]);
+			emc_writel_per_ch(emc, next_timing->trim_perch_regs[i],
+					  trim_regs_per_ch_type[i],
+					  trim_regs_per_ch_off[i]);
+		}
+	}
+
+	emc_cc_dbg(SUB_STEPS, "Writing burst_mc_regs\n");
+	for (i = 0; i < next_timing->num_mc_regs; i++) {
+		emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
+			   i, next_timing->burst_mc_regs[i],
+			   burst_mc_regs_off[i]);
+		mc_writel(emc->mc, next_timing->burst_mc_regs[i],
+			  burst_mc_regs_off[i]);
+	}
+
+	/* Registers to be programmed on the faster clock. */
+	if (next_timing->rate < last_timing->rate) {
+		emc_cc_dbg(SUB_STEPS, "Writing la_scale_regs\n");
+		for (i = 0; i < next_timing->num_up_down; i++) {
+			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
+				   i, next_timing->la_scale_regs[i],
+				   la_scale_regs_off[i]);
+			mc_writel(emc->mc, next_timing->la_scale_regs[i],
+				  la_scale_regs_off[i]);
+		}
+	}
+
+	/* Flush all the burst register writes. */
+	wmb();
+
+	/*
+	 * Step 9:
+	 *   LPDDR4 section A.
+	 */
+	emc_cc_dbg(STEPS, "Step 9\n");
+	if (dram_type == DRAM_TYPE_LPDDR4) {
+		emc_writel(emc, emc_zcal_interval, EMC_ZCAL_INTERVAL);
+		emc_writel(emc, emc_zcal_wait_cnt_new, EMC_ZCAL_WAIT_CNT);
+
+		emc_dbg |= (EMC_DBG_WRITE_MUX_ACTIVE |
+			    EMC_DBG_WRITE_ACTIVE_ONLY);
+
+		emc_writel(emc, emc_dbg, EMC_DBG);
+		emc_writel(emc, emc_zcal_interval, EMC_ZCAL_INTERVAL);
+		emc_writel(emc, emc_dbg_o, EMC_DBG);
+	}
+
+	/*
+	 * Step 10:
+	 *   LPDDR4 and DDR3 common section.
+	 */
+	emc_cc_dbg(STEPS, "Step 10\n");
+	if (opt_dvfs_mode == MAN_SR || dram_type == DRAM_TYPE_LPDDR4) {
+		if (dram_type == DRAM_TYPE_LPDDR4)
+			ccfifo_writel(emc, 0x101, EMC_SELF_REF, 0);
+		else
+			ccfifo_writel(emc, 0x1, EMC_SELF_REF, 0);
+
+		if (dram_type == DRAM_TYPE_LPDDR4 &&
+		    destination_clock_period <= zqcal_before_cc_cutoff) {
+			ccfifo_writel(emc, mr13_flip_fspwr ^ 0x40, EMC_MRW3, 0);
+			ccfifo_writel(emc,
+				      (next_timing->burst_regs[EMC_MRW6_INDEX] &
+				       0xFFFF3F3F) |
+				      (last_timing->burst_regs[EMC_MRW6_INDEX] &
+				       0x0000C0C0), EMC_MRW6, 0);
+			ccfifo_writel(emc,
+				      (next_timing->burst_regs[EMC_MRW14_INDEX]
+				       & 0xFFFF0707) |
+				      (last_timing->burst_regs[EMC_MRW14_INDEX]
+				       & 0x00003838), EMC_MRW14, 0);
+
+			if (dram_dev_num == TWO_RANK) {
+				ccfifo_writel(emc,
+				      (next_timing->burst_regs[EMC_MRW7_INDEX] &
+				       0xFFFF3F3F) |
+				      (last_timing->burst_regs[EMC_MRW7_INDEX] &
+				       0x0000C0C0), EMC_MRW7, 0);
+				ccfifo_writel(emc,
+				     (next_timing->burst_regs[EMC_MRW15_INDEX] &
+				      0xFFFF0707) |
+				     (last_timing->burst_regs[EMC_MRW15_INDEX] &
+				      0x00003838), EMC_MRW15, 0);
+			}
+
+			if (opt_zcal_en_cc) {
+				if (dram_dev_num == ONE_RANK)
+					ccfifo_writel(emc,
+						2UL << EMC_ZQ_CAL_DEV_SEL_SHIFT
+						| EMC_ZQ_CAL_ZQ_CAL_CMD,
+						EMC_ZQ_CAL, 0);
+				else if (shared_zq_resistor)
+					ccfifo_writel(emc,
+						2UL << EMC_ZQ_CAL_DEV_SEL_SHIFT
+						| EMC_ZQ_CAL_ZQ_CAL_CMD,
+						EMC_ZQ_CAL, 0);
+				else
+					ccfifo_writel(emc,
+						      EMC_ZQ_CAL_ZQ_CAL_CMD,
+						      EMC_ZQ_CAL, 0);
+			}
+		}
+	}
+
+	emc_dbg = emc_dbg_o;
+	if (dram_type == DRAM_TYPE_LPDDR4) {
+		ccfifo_writel(emc, mr13_flip_fspop | 0x8, EMC_MRW3,
+			      (1000 * fake_timing->dram_timings[T_RP]) /
+			      source_clock_period);
+		ccfifo_writel(emc, 0, 0, tFC_lpddr4 / source_clock_period);
+	}
+
+	if (dram_type == DRAM_TYPE_LPDDR4 || opt_dvfs_mode != MAN_SR) {
+		u32 t = 30 + (cya_allow_ref_cc ?
+			(4000 * fake_timing->dram_timings[T_RFC]) +
+			((1000 * fake_timing->dram_timings[T_RP]) /
+			 source_clock_period) : 0);
+
+		ccfifo_writel(emc, emc_pin_o & ~(EMC_PIN_PIN_CKE_PER_DEV |
+			      EMC_PIN_PIN_CKEB | EMC_PIN_PIN_CKE),
+			      EMC_PIN, t);
+	}
+
+	ref_delay_mult = 1;
+	ref_b4_sref_en = 0;
+	cya_issue_pc_ref = 0;
+
+	ref_delay_mult += ref_b4_sref_en   ? 1 : 0;
+	ref_delay_mult += cya_allow_ref_cc ? 1 : 0;
+	ref_delay_mult += cya_issue_pc_ref ? 1 : 0;
+	ref_delay = ref_delay_mult *
+		    ((1000 * fake_timing->dram_timings[T_RP] /
+		      source_clock_period) +
+		     (1000 * fake_timing->dram_timings[T_RFC] /
+		      source_clock_period)) + 20;
+
+	/*
+	 * Step 11:
+	 *   Ramp down.
+	 */
+	emc_cc_dbg(STEPS, "Step 11\n");
+	ccfifo_writel(emc, 0x0, EMC_CFG_SYNC,
+		      dram_type == DRAM_TYPE_LPDDR4 ? 0 : ref_delay);
+
+	emc_dbg_active = emc_dbg | (EMC_DBG_WRITE_MUX_ACTIVE |
+			 EMC_DBG_WRITE_ACTIVE_ONLY);
+	ccfifo_writel(emc, emc_dbg_active, EMC_DBG, 0);
+
+	ramp_down_wait = tegra210_dvfs_power_ramp_down(emc,
+						       source_clock_period, 0);
+
+	/*
+	 * Step 12:
+	 *   And finally - trigger the clock change.
+	 */
+	emc_cc_dbg(STEPS, "Step 12\n");
+	ccfifo_writel(emc, 1, EMC_STALL_THEN_EXE_AFTER_CLKCHANGE, 0);
+	emc_dbg_active &= ~EMC_DBG_WRITE_ACTIVE_ONLY;
+	ccfifo_writel(emc, emc_dbg_active, EMC_DBG, 0);
+
+	/*
+	 * Step 13:
+	 *   Ramp up.
+	 */
+	emc_cc_dbg(STEPS, "Step 13\n");
+	ramp_up_wait = tegra210_dvfs_power_ramp_up(emc,
+						   destination_clock_period, 0);
+	ccfifo_writel(emc, emc_dbg, EMC_DBG, 0);
+
+	/*
+	 * Step 14:
+	 *   Bringup CKE pins.
+	 */
+	emc_cc_dbg(STEPS, "Step 14\n");
+	if (dram_type == DRAM_TYPE_LPDDR4) {
+		u32 r = emc_pin_o | EMC_PIN_PIN_CKE;
+
+		if (dram_dev_num == TWO_RANK)
+			ccfifo_writel(emc, r | EMC_PIN_PIN_CKEB |
+				      EMC_PIN_PIN_CKE_PER_DEV, EMC_PIN, 0);
+		else
+			ccfifo_writel(emc, r & ~(EMC_PIN_PIN_CKEB |
+				      EMC_PIN_PIN_CKE_PER_DEV), EMC_PIN, 0);
+	}
+
+	/*
+	 * Step 15: (two step 15s ??)
+	 *   Calculate zqlatch wait time; has dependency on ramping times.
+	 */
+	emc_cc_dbg(STEPS, "Step 15\n");
+
+	if (destination_clock_period <= zqcal_before_cc_cutoff) {
+		s32 t = (s32)(ramp_up_wait + ramp_down_wait) /
+			(s32)destination_clock_period;
+		zq_latch_dvfs_wait_time = (s32)tZQCAL_lpddr4_fc_adj - t;
+	} else {
+		zq_latch_dvfs_wait_time = tZQCAL_lpddr4_fc_adj -
+			div_o3(1000 * next_timing->dram_timings[T_PDEX],
+			       destination_clock_period);
+	}
+
+	emc_cc_dbg(INFO, "tZQCAL_lpddr4_fc_adj = %u\n", tZQCAL_lpddr4_fc_adj);
+	emc_cc_dbg(INFO, "destination_clock_period = %u\n",
+		   destination_clock_period);
+	emc_cc_dbg(INFO, "next_timing->dram_timings[T_PDEX] = %u\n",
+		   next_timing->dram_timings[T_PDEX]);
+	emc_cc_dbg(INFO, "zq_latch_dvfs_wait_time = %d\n",
+		   max_t(s32, 0, zq_latch_dvfs_wait_time));
+
+	if (dram_type == DRAM_TYPE_LPDDR4 && opt_zcal_en_cc) {
+		if (dram_dev_num == ONE_RANK) {
+			if (destination_clock_period > zqcal_before_cc_cutoff)
+				ccfifo_writel(emc,
+					      2UL << EMC_ZQ_CAL_DEV_SEL_SHIFT |
+					      EMC_ZQ_CAL_ZQ_CAL_CMD, EMC_ZQ_CAL,
+					      div_o3(1000 *
+						     next_timing->dram_timings
+						     [T_PDEX],
+						     destination_clock_period));
+
+			ccfifo_writel(emc, (mr13_flip_fspop & 0xFFFFFFF7) |
+				      0x0C000000, EMC_MRW3,
+				      div_o3(1000 *
+					     next_timing->dram_timings[T_PDEX],
+					     destination_clock_period));
+			ccfifo_writel(emc, 0, EMC_SELF_REF, 0);
+			ccfifo_writel(emc, 0, EMC_REF, 0);
+			ccfifo_writel(emc, 2UL << EMC_ZQ_CAL_DEV_SEL_SHIFT |
+				      EMC_ZQ_CAL_ZQ_LATCH_CMD,
+				      EMC_ZQ_CAL,
+				      max_t(s32, 0, zq_latch_dvfs_wait_time));
+		} else if (shared_zq_resistor) {
+			if (destination_clock_period > zqcal_before_cc_cutoff)
+				ccfifo_writel(emc,
+					      2UL << EMC_ZQ_CAL_DEV_SEL_SHIFT |
+					      EMC_ZQ_CAL_ZQ_CAL_CMD, EMC_ZQ_CAL,
+					      div_o3(1000 *
+						     next_timing->dram_timings
+						     [T_PDEX],
+						     destination_clock_period));
+
+			ccfifo_writel(emc, 2UL << EMC_ZQ_CAL_DEV_SEL_SHIFT |
+				      EMC_ZQ_CAL_ZQ_LATCH_CMD, EMC_ZQ_CAL,
+				      max_t(s32, 0, zq_latch_dvfs_wait_time) +
+				      div_o3(1000 *
+					     next_timing->dram_timings[T_PDEX],
+					     destination_clock_period));
+			ccfifo_writel(emc, 1UL << EMC_ZQ_CAL_DEV_SEL_SHIFT |
+				      EMC_ZQ_CAL_ZQ_LATCH_CMD,
+				      EMC_ZQ_CAL, 0);
+
+			ccfifo_writel(emc, (mr13_flip_fspop & 0xfffffff7) |
+				      0x0c000000, EMC_MRW3, 0);
+			ccfifo_writel(emc, 0, EMC_SELF_REF, 0);
+			ccfifo_writel(emc, 0, EMC_REF, 0);
+
+			ccfifo_writel(emc, 1UL << EMC_ZQ_CAL_DEV_SEL_SHIFT |
+				      EMC_ZQ_CAL_ZQ_LATCH_CMD, EMC_ZQ_CAL,
+				      tZQCAL_lpddr4 / destination_clock_period);
+		} else {
+			if (destination_clock_period > zqcal_before_cc_cutoff)
+				ccfifo_writel(emc, EMC_ZQ_CAL_ZQ_CAL_CMD,
+					      EMC_ZQ_CAL,
+					      div_o3(1000 *
+						     next_timing->dram_timings
+						     [T_PDEX],
+						     destination_clock_period));
+
+			ccfifo_writel(emc, (mr13_flip_fspop & 0xfffffff7) |
+				      0x0c000000, EMC_MRW3,
+				      div_o3(1000 *
+					     next_timing->dram_timings[T_PDEX],
+					     destination_clock_period));
+			ccfifo_writel(emc, 0, EMC_SELF_REF, 0);
+			ccfifo_writel(emc, 0, EMC_REF, 0);
+
+			ccfifo_writel(emc, EMC_ZQ_CAL_ZQ_LATCH_CMD, EMC_ZQ_CAL,
+				      max_t(s32, 0, zq_latch_dvfs_wait_time));
+		}
+	}
+
+	/* WAR: delay for zqlatch */
+	ccfifo_writel(emc, 0, 0, 10);
+
+	/*
+	 * Step 16:
+	 *   LPDDR4 Conditional Training Kickoff. Removed.
+	 */
+
+	/*
+	 * Step 17:
+	 *   MANSR exit self refresh.
+	 */
+	emc_cc_dbg(STEPS, "Step 17\n");
+	if (opt_dvfs_mode == MAN_SR && dram_type != DRAM_TYPE_LPDDR4)
+		ccfifo_writel(emc, 0, EMC_SELF_REF, 0);
+
+	/*
+	 * Step 18:
+	 *   Send MRWs to LPDDR3/DDR3.
+	 */
+	emc_cc_dbg(STEPS, "Step 18\n");
+	if (dram_type == DRAM_TYPE_LPDDR2) {
+		ccfifo_writel(emc, next_timing->emc_mrw2, EMC_MRW2, 0);
+		ccfifo_writel(emc, next_timing->emc_mrw,  EMC_MRW,  0);
+		if (is_lpddr3)
+			ccfifo_writel(emc, next_timing->emc_mrw4, EMC_MRW4, 0);
+	} else if (dram_type == DRAM_TYPE_DDR3) {
+		if (opt_dll_mode == DLL_ON)
+			ccfifo_writel(emc, next_timing->emc_emrs &
+				      ~EMC_EMRS_USE_EMRS_LONG_CNT, EMC_EMRS, 0);
+		ccfifo_writel(emc, next_timing->emc_emrs2 &
+			      ~EMC_EMRS2_USE_EMRS2_LONG_CNT, EMC_EMRS2, 0);
+		ccfifo_writel(emc, next_timing->emc_mrs |
+			      EMC_EMRS_USE_EMRS_LONG_CNT, EMC_MRS, 0);
+	}
+
+	/*
+	 * Step 19:
+	 *   ZQCAL for LPDDR3/DDR3
+	 */
+	emc_cc_dbg(STEPS, "Step 19\n");
+	if (opt_zcal_en_cc) {
+		if (dram_type == DRAM_TYPE_LPDDR2) {
+			u32 r;
+
+			zq_op = opt_cc_short_zcal  ? 0x56 : 0xAB;
+			zcal_wait_time_ps = opt_cc_short_zcal  ? 90000 : 360000;
+			zcal_wait_time_clocks = div_o3(zcal_wait_time_ps,
+						destination_clock_period);
+			r = zcal_wait_time_clocks <<
+			    EMC_MRS_WAIT_CNT2_MRS_EXT2_WAIT_CNT_SHIFT |
+			    zcal_wait_time_clocks <<
+			    EMC_MRS_WAIT_CNT2_MRS_EXT1_WAIT_CNT_SHIFT;
+			ccfifo_writel(emc, r, EMC_MRS_WAIT_CNT2, 0);
+			ccfifo_writel(emc, 2 << EMC_MRW_MRW_DEV_SELECTN_SHIFT |
+				      EMC_MRW_USE_MRW_EXT_CNT |
+				      10 << EMC_MRW_MRW_MA_SHIFT |
+				      zq_op << EMC_MRW_MRW_OP_SHIFT,
+				      EMC_MRW, 0);
+			if (dram_dev_num == TWO_RANK) {
+				r = 1 << EMC_MRW_MRW_DEV_SELECTN_SHIFT |
+				    EMC_MRW_USE_MRW_EXT_CNT |
+				    10 << EMC_MRW_MRW_MA_SHIFT |
+				    zq_op << EMC_MRW_MRW_OP_SHIFT;
+				ccfifo_writel(emc, r, EMC_MRW, 0);
+			}
+		} else if (dram_type == DRAM_TYPE_DDR3) {
+			zq_op = opt_cc_short_zcal ? 0 : EMC_ZQ_CAL_LONG;
+			ccfifo_writel(emc, zq_op | 2 <<
+				      EMC_ZQ_CAL_DEV_SEL_SHIFT |
+				      EMC_ZQ_CAL_ZQ_CAL_CMD, EMC_ZQ_CAL, 0);
+			if (dram_dev_num == TWO_RANK)
+				ccfifo_writel(emc, zq_op |
+					      1 << EMC_ZQ_CAL_DEV_SEL_SHIFT |
+					      EMC_ZQ_CAL_ZQ_CAL_CMD,
+					      EMC_ZQ_CAL, 0);
+		}
+	}
+
+	if (bg_regulator_mode_change) {
+		emc_set_shadow_bypass(emc, ACTIVE);
+		bg_regulator_switch_complete_wait_clks =
+			ramp_up_wait > 1250000 ? 0 :
+			(1250000 - ramp_up_wait) / destination_clock_period;
+		ccfifo_writel(emc, next_timing->burst_regs
+			      [EMC_PMACRO_BG_BIAS_CTRL_0_INDEX],
+			      EMC_PMACRO_BG_BIAS_CTRL_0,
+			      bg_regulator_switch_complete_wait_clks);
+		emc_set_shadow_bypass(emc, ASSEMBLY);
+	}
+
+	/*
+	 * Step 20:
+	 *   Issue ref and optional QRST.
+	 */
+	emc_cc_dbg(STEPS, "Step 20\n");
+	if (dram_type != DRAM_TYPE_LPDDR4)
+		ccfifo_writel(emc, 0, EMC_REF, 0);
+
+	if (opt_do_sw_qrst) {
+		ccfifo_writel(emc, 1, EMC_ISSUE_QRST, 0);
+		ccfifo_writel(emc, 0, EMC_ISSUE_QRST, 2);
+	}
+
+	/*
+	 * Step 21:
+	 *   Restore ZCAL and ZCAL interval.
+	 */
+	emc_cc_dbg(STEPS, "Step 21\n");
+	if (save_restore_clkstop_pd || opt_zcal_en_cc) {
+		ccfifo_writel(emc, emc_dbg_o | EMC_DBG_WRITE_MUX_ACTIVE,
+			      EMC_DBG, 0);
+		if (opt_zcal_en_cc && dram_type != DRAM_TYPE_LPDDR4)
+			ccfifo_writel(emc, next_timing->burst_regs[
+				      EMC_ZCAL_INTERVAL_INDEX],
+				      EMC_ZCAL_INTERVAL, 0);
+
+		if (save_restore_clkstop_pd)
+			ccfifo_writel(emc,
+				      next_timing->burst_regs[EMC_CFG_INDEX] &
+				      ~EMC_CFG_DYN_SELF_REF, EMC_CFG, 0);
+		ccfifo_writel(emc, emc_dbg_o, EMC_DBG, 0);
+	}
+
+	/*
+	 * Step 22:
+	 *   Restore EMC_CFG_PIPE_CLK.
+	 */
+	emc_cc_dbg(STEPS, "Step 22\n");
+	ccfifo_writel(emc, emc_cfg_pipe_clk_o, EMC_CFG_PIPE_CLK, 0);
+
+	if (bg_regulator_mode_change) {
+		if (enable_bg_regulator)
+			emc_writel(emc, next_timing->burst_regs[
+				   EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
+				   ~EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD,
+				   EMC_PMACRO_BG_BIAS_CTRL_0);
+		else
+			emc_writel(emc, next_timing->burst_regs[
+				   EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
+				   ~EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD,
+				   EMC_PMACRO_BG_BIAS_CTRL_0);
+	}
+
+	/*
+	 * Step 23:
+	 */
+	emc_cc_dbg(STEPS, "Step 23\n");
+
+	tmp = emc_readl(emc, EMC_CFG_DIG_DLL);
+	tmp |= EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_TRAFFIC;
+	tmp &= ~EMC_CFG_DIG_DLL_CFG_DLL_STALL_RW_UNTIL_LOCK;
+	tmp &= ~EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_UNTIL_LOCK;
+	tmp &= ~EMC_CFG_DIG_DLL_CFG_DLL_EN;
+	tmp = (tmp & ~EMC_CFG_DIG_DLL_CFG_DLL_MODE_MASK) |
+		(2 << EMC_CFG_DIG_DLL_CFG_DLL_MODE_SHIFT);
+	emc_writel(emc, tmp, EMC_CFG_DIG_DLL);
+
+	do_clock_change(emc, clksrc);
+
+	/*
+	 * Step 24:
+	 *   Save training results. Removed.
+	 */
+
+	/*
+	 * Step 25:
+	 *   Program MC updown registers.
+	 */
+	emc_cc_dbg(STEPS, "Step 25\n");
+
+	if (next_timing->rate > last_timing->rate) {
+		for (i = 0; i < next_timing->num_up_down; i++)
+			mc_writel(emc->mc, next_timing->la_scale_regs[i],
+				  la_scale_regs_off[i]);
+		emc_timing_update(emc, channel_mode);
+	}
+
+	/*
+	 * Step 26:
+	 *   Restore ZCAL registers.
+	 */
+	emc_cc_dbg(STEPS, "Step 26\n");
+	if (dram_type == DRAM_TYPE_LPDDR4) {
+		emc_set_shadow_bypass(emc, ACTIVE);
+		emc_writel(emc, next_timing->burst_regs[
+			   EMC_ZCAL_WAIT_CNT_INDEX], EMC_ZCAL_WAIT_CNT);
+		emc_writel(emc, next_timing->burst_regs[
+			   EMC_ZCAL_INTERVAL_INDEX], EMC_ZCAL_INTERVAL);
+		emc_set_shadow_bypass(emc, ASSEMBLY);
+	}
+
+	if (dram_type != DRAM_TYPE_LPDDR4 &&
+	    opt_zcal_en_cc && !opt_short_zcal && opt_cc_short_zcal) {
+		udelay(2);
+
+		emc_set_shadow_bypass(emc, ACTIVE);
+		if (dram_type == DRAM_TYPE_LPDDR2)
+			emc_writel(emc, next_timing->burst_regs[
+				   EMC_MRS_WAIT_CNT_INDEX], EMC_MRS_WAIT_CNT);
+		else if (dram_type == DRAM_TYPE_DDR3)
+			emc_writel(emc, next_timing->burst_regs[
+				   EMC_ZCAL_WAIT_CNT_INDEX], EMC_ZCAL_WAIT_CNT);
+		emc_set_shadow_bypass(emc, ASSEMBLY);
+	}
+
+	/*
+	 * Step 27:
+	 *   Restore EMC_CFG, FDPD registers.
+	 */
+	emc_cc_dbg(STEPS, "Step 27\n");
+	emc_set_shadow_bypass(emc, ACTIVE);
+	emc_writel(emc, next_timing->burst_regs[EMC_CFG_INDEX], EMC_CFG);
+	emc_set_shadow_bypass(emc, ASSEMBLY);
+	emc_writel(emc, next_timing->emc_fdpd_ctrl_cmd_no_ramp,
+		   EMC_FDPD_CTRL_CMD_NO_RAMP);
+	emc_writel(emc, next_timing->emc_sel_dpd_ctrl, EMC_SEL_DPD_CTRL);
+
+	/*
+	 * Step 28:
+	 *   Training recover. Removed.
+	 */
+	emc_cc_dbg(STEPS, "Step 28\n");
+
+	emc_set_shadow_bypass(emc, ACTIVE);
+	emc_writel(emc,
+		   next_timing->burst_regs[EMC_PMACRO_AUTOCAL_CFG_COMMON_INDEX],
+		   EMC_PMACRO_AUTOCAL_CFG_COMMON);
+	emc_set_shadow_bypass(emc, ASSEMBLY);
+
+	/*
+	 * Step 29:
+	 *   Power fix WAR.
+	 */
+	emc_cc_dbg(STEPS, "Step 29\n");
+	emc_writel(emc, EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE0 |
+		   EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE1 |
+		   EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE2 |
+		   EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE3 |
+		   EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE4 |
+		   EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE5 |
+		   EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE6 |
+		   EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE7,
+		   EMC_PMACRO_CFG_PM_GLOBAL_0);
+	emc_writel(emc, EMC_PMACRO_TRAINING_CTRL_0_CH0_TRAINING_E_WRPTR,
+		   EMC_PMACRO_TRAINING_CTRL_0);
+	emc_writel(emc, EMC_PMACRO_TRAINING_CTRL_1_CH1_TRAINING_E_WRPTR,
+		   EMC_PMACRO_TRAINING_CTRL_1);
+	emc_writel(emc, 0, EMC_PMACRO_CFG_PM_GLOBAL_0);
+
+	/*
+	 * Step 30:
+	 *   Re-enable autocal.
+	 */
+	emc_cc_dbg(STEPS, "Step 30: Re-enable DLL and AUTOCAL\n");
+	if (next_timing->burst_regs[EMC_CFG_DIG_DLL_INDEX] &
+	    EMC_CFG_DIG_DLL_CFG_DLL_EN) {
+		tmp = emc_readl(emc, EMC_CFG_DIG_DLL);
+		tmp |=  EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_TRAFFIC;
+		tmp |=  EMC_CFG_DIG_DLL_CFG_DLL_EN;
+		tmp &= ~EMC_CFG_DIG_DLL_CFG_DLL_STALL_RW_UNTIL_LOCK;
+		tmp &= ~EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_UNTIL_LOCK;
+		tmp =  (tmp & ~EMC_CFG_DIG_DLL_CFG_DLL_MODE_MASK) |
+		       (2 << EMC_CFG_DIG_DLL_CFG_DLL_MODE_SHIFT);
+		emc_writel(emc, tmp, EMC_CFG_DIG_DLL);
+		emc_timing_update(emc, channel_mode);
+	}
+
+	emc_auto_cal_config = next_timing->emc_auto_cal_config;
+	emc_writel(emc, emc_auto_cal_config, EMC_AUTO_CAL_CONFIG);
+
+	/*
+	 * Step 31:
+	 *   Restore FSP to account for switch back. Only needed in training.
+	 */
+	emc_cc_dbg(STEPS, "Step 31\n");
+
+	/* Step 32:
+	 *   [SW] Update the alternative timing (derated vs normal) table with
+	 * the periodic training values computed during the clock change
+	 * pre-amble.
+	 */
+	emc_cc_dbg(STEPS, "Step 32: Update alt timing\n");
+	tegra210_update_emc_alt_timing(emc, next_timing);
+
+	/* Done! Yay. */
+}
diff --git a/drivers/memory/tegra/tegra210-emc-reg.h b/drivers/memory/tegra/tegra210-emc-reg.h
index 31a69e718dbc..b474b0810e52 100644
--- a/drivers/memory/tegra/tegra210-emc-reg.h
+++ b/drivers/memory/tegra/tegra210-emc-reg.h
@@ -107,7 +107,16 @@
 #define EMC_INTSTATUS_CLKCHANGE_COMPLETE			BIT(4)
 #define EMC_DBG							0x8
 #define EMC_DBG_WRITE_MUX_ACTIVE				BIT(1)
+#define EMC_DBG_WRITE_ACTIVE_ONLY				BIT(30)
 #define EMC_CFG							0xc
+#define EMC_CFG_DRAM_CLKSTOP_PD					BIT(31)
+#define EMC_CFG_DRAM_CLKSTOP_SR					BIT(30)
+#define EMC_CFG_DRAM_ACPD					BIT(29)
+#define EMC_CFG_DYN_SELF_REF					BIT(28)
+#define EMC_PIN							0x24
+#define EMC_PIN_PIN_CKE						BIT(0)
+#define EMC_PIN_PIN_CKEB					BIT(1)
+#define EMC_PIN_PIN_CKE_PER_DEV					BIT(2)
 #define EMC_TIMING_CONTROL					0x28
 #define EMC_RC							0x2c
 #define EMC_RFC							0x30
@@ -147,7 +156,35 @@
 #define EMC_WEXT						0xb8
 #define EMC_RFC_SLR						0xc0
 #define EMC_MRS_WAIT_CNT2					0xc4
+#define EMC_MRS_WAIT_CNT2_MRS_EXT2_WAIT_CNT_SHIFT		16
+#define EMC_MRS_WAIT_CNT2_MRS_EXT1_WAIT_CNT_SHIFT		0
 #define EMC_MRS_WAIT_CNT					0xc8
+#define EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT			0
+#define EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK			\
+	(0x3FF << EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT)
+
+#define EMC_MRS							0xcc
+#define EMC_EMRS						0xd0
+#define EMC_EMRS_USE_EMRS_LONG_CNT				BIT(26)
+#define EMC_REF							0xd4
+#define EMC_SELF_REF						0xe0
+#define EMC_MRW							0xe8
+#define EMC_MRW_MRW_OP_SHIFT					0
+#define EMC_MRW_MRW_OP_MASK					\
+	(0xff << EMC_MRW_MRW_OP_SHIFT)
+#define EMC_MRW_MRW_MA_SHIFT					16
+#define EMC_MRW_USE_MRW_EXT_CNT					27
+#define EMC_MRW_MRW_DEV_SELECTN_SHIFT				30
+
+#define EMC_MRR							0xec
+#define EMC_MRR_DEV_SEL_SHIFT					30
+#define EMC_MRR_MA_SHIFT					16
+#define EMC_MRR_MA_MASK						\
+	(0xff << EMC_MRR_MA_SHIFT)
+#define EMC_MRR_DATA_SHIFT					0
+#define EMC_MRR_DATA_MASK					\
+	(0xffff << EMC_MRR_DATA_SHIFT)
+
 #define EMC_FBIO_SPARE						0x100
 #define EMC_FBIO_CFG5						0x104
 #define EMC_FBIO_CFG5_DRAM_TYPE_SHIFT				0
@@ -158,14 +195,34 @@
 #define EMC_PDEX2CKE						0x118
 #define EMC_CKE2PDEN						0x11c
 #define EMC_MPC							0x128
+#define EMC_EMRS2						0x12c
+#define EMC_EMRS2_USE_EMRS2_LONG_CNT				BIT(26)
+#define EMC_MRW2						0x134
+#define EMC_MRW3						0x138
+#define EMC_MRW4						0x13c
 #define EMC_R2R							0x144
 #define EMC_EINPUT						0x14c
 #define EMC_EINPUT_DURATION					0x150
 #define EMC_PUTERM_EXTRA					0x154
 #define EMC_TCKESR						0x158
 #define EMC_TPD							0x15c
+#define EMC_AUTO_CAL_CONFIG					0x2a4
+#define EMC_AUTO_CAL_CONFIG_AUTO_CAL_COMPUTE_START		BIT(0)
+#define EMC_AUTO_CAL_CONFIG_AUTO_CAL_MEASURE_STALL		BIT(9)
+#define EMC_AUTO_CAL_CONFIG_AUTO_CAL_UPDATE_STALL		BIT(10)
+#define EMC_AUTO_CAL_CONFIG_AUTO_CAL_ENABLE			BIT(29)
+#define EMC_AUTO_CAL_CONFIG_AUTO_CAL_START			BIT(31)
 #define EMC_EMC_STATUS						0x2b4
+#define EMC_EMC_STATUS_MRR_DIVLD				BIT(20)
 #define EMC_EMC_STATUS_TIMING_UPDATE_STALLED			BIT(23)
+#define EMC_EMC_STATUS_DRAM_IN_POWERDOWN_SHIFT			4
+#define EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK			\
+	(0x3 << EMC_EMC_STATUS_DRAM_IN_POWERDOWN_SHIFT)
+#define EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_SHIFT		8
+#define EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK		\
+	(0x3 << EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_SHIFT)
+
+#define EMC_CFG_2						0x2b8
 #define EMC_CFG_DIG_DLL						0x2bc
 #define EMC_CFG_DIG_DLL_CFG_DLL_EN				BIT(0)
 #define EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_UNTIL_LOCK		BIT(1)
@@ -191,8 +248,17 @@
 #define EMC_WDV_MASK						0x2d0
 #define EMC_RDV_EARLY_MASK					0x2d4
 #define EMC_RDV_EARLY						0x2d8
+#define EMC_AUTO_CAL_CONFIG8					0x2dc
 #define EMC_ZCAL_INTERVAL					0x2e0
 #define EMC_ZCAL_WAIT_CNT					0x2e4
+#define EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK			0x7ff
+#define EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_SHIFT			0
+
+#define EMC_ZQ_CAL						0x2ec
+#define EMC_ZQ_CAL_DEV_SEL_SHIFT				30
+#define EMC_ZQ_CAL_LONG						BIT(4)
+#define EMC_ZQ_CAL_ZQ_LATCH_CMD					BIT(1)
+#define EMC_ZQ_CAL_ZQ_CAL_CMD					BIT(0)
 #define EMC_FDPD_CTRL_DQ					0x310
 #define EMC_FDPD_CTRL_CMD					0x314
 #define EMC_PMACRO_CMD_BRICK_CTRL_FDPD				0x318
@@ -202,6 +268,13 @@
 #define EMC_TR_TIMING_0						0x3b4
 #define EMC_TR_CTRL_1						0x3bc
 #define EMC_TR_RDV						0x3c4
+#define EMC_STALL_THEN_EXE_AFTER_CLKCHANGE			0x3cc
+#define EMC_SEL_DPD_CTRL					0x3d8
+#define EMC_SEL_DPD_CTRL_DATA_SEL_DPD_EN			BIT(8)
+#define EMC_SEL_DPD_CTRL_ODT_SEL_DPD_EN				BIT(5)
+#define EMC_SEL_DPD_CTRL_RESET_SEL_DPD_EN			BIT(4)
+#define EMC_SEL_DPD_CTRL_CA_SEL_DPD_EN				BIT(3)
+#define EMC_SEL_DPD_CTRL_CLK_SEL_DPD_EN				BIT(2)
 #define EMC_PRE_REFRESH_REQ_CNT					0x3dc
 #define EMC_DYN_SELF_REF_CONTROL				0x3e0
 #define EMC_TXSRDLL						0x3e4
@@ -211,6 +284,9 @@
 #define EMC_TR_RDV_MASK						0x3f8
 #define EMC_TR_QSAFE						0x3fc
 #define EMC_TR_QRST						0x400
+#define EMC_ISSUE_QRST						0x428
+#define EMC_AUTO_CAL_CONFIG2					0x458
+#define EMC_AUTO_CAL_CONFIG3					0x45c
 #define EMC_TR_DVFS						0x460
 #define EMC_AUTO_CAL_CHANNEL					0x464
 #define EMC_IBDLY						0x468
@@ -224,19 +300,26 @@
 #define EMC_MRW6						0x4a4
 #define EMC_MRW7						0x4a8
 #define EMC_MRW8						0x4ac
+#define EMC_MRW9						0x4b0
 #define EMC_MRW10						0x4b4
 #define EMC_MRW11						0x4b8
 #define EMC_MRW12						0x4bc
 #define EMC_MRW13						0x4c0
 #define EMC_MRW14						0x4c4
 #define EMC_MRW15						0x4d0
+#define EMC_CFG_SYNC						0x4d4
+#define EMC_FDPD_CTRL_CMD_NO_RAMP				0x4d8
+#define EMC_FDPD_CTRL_CMD_NO_RAMP_CMD_DPD_NO_RAMP_ENABLE	BIT(0)
 #define EMC_WDV_CHK						0x4e0
 #define EMC_CFG_PIPE_2						0x554
+#define EMC_CFG_PIPE_CLK					0x558
+#define EMC_CFG_PIPE_CLK_CLK_ALWAYS_ON				BIT(0)
 #define EMC_CFG_PIPE_1						0x55c
 #define EMC_CFG_PIPE						0x560
 #define EMC_QPOP						0x564
 #define EMC_QUSE_WIDTH						0x568
 #define EMC_PUTERM_WIDTH					0x56c
+#define EMC_AUTO_CAL_CONFIG7					0x574
 #define EMC_REFCTRL2						0x580
 #define EMC_FBIO_CFG7						0x584
 #define EMC_FBIO_CFG7_CH0_ENABLE				BIT(1)
@@ -301,10 +384,13 @@
 #define EMC_CMD_BRLSHFT_2					0x5a4
 #define EMC_CMD_BRLSHFT_3					0x5a8
 #define EMC_QUSE_BRLSHFT_0					0x5ac
+#define EMC_AUTO_CAL_CONFIG4					0x5b0
+#define EMC_AUTO_CAL_CONFIG5					0x5b4
 #define EMC_QUSE_BRLSHFT_1					0x5b8
 #define EMC_QUSE_BRLSHFT_2					0x5bc
 #define EMC_CCDMW						0x5c0
 #define EMC_QUSE_BRLSHFT_3					0x5c4
+#define EMC_AUTO_CAL_CONFIG6					0x5cc
 #define EMC_DLL_CFG_0						0x5e4
 #define EMC_DLL_CFG_1						0x5e8
 #define EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_SHIFT		10
@@ -312,6 +398,11 @@
 	(0x7ff << EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_SHIFT)
 
 #define EMC_CONFIG_SAMPLE_DELAY					0x5f0
+#define EMC_CFG_UPDATE						0x5f4
+#define EMC_CFG_UPDATE_UPDATE_DLL_IN_UPDATE_SHIFT		9
+#define EMC_CFG_UPDATE_UPDATE_DLL_IN_UPDATE_MASK		\
+	(0x3 << EMC_CFG_UPDATE_UPDATE_DLL_IN_UPDATE_SHIFT)
+
 #define EMC_PMACRO_QUSE_DDLL_RANK0_0				0x600
 #define EMC_PMACRO_QUSE_DDLL_RANK0_1				0x604
 #define EMC_PMACRO_QUSE_DDLL_RANK0_2				0x608
@@ -620,9 +711,20 @@
 #define EMC_PMACRO_DDLL_SHORT_CMD_0				0xc20
 #define EMC_PMACRO_DDLL_SHORT_CMD_1				0xc24
 #define EMC_PMACRO_DDLL_SHORT_CMD_2				0xc28
+#define EMC_PMACRO_CFG_PM_GLOBAL_0				0xc30
+#define EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE0		BIT(16)
+#define EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE1		BIT(17)
+#define EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE2		BIT(18)
+#define EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE3		BIT(19)
+#define EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE4		BIT(20)
+#define EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE5		BIT(21)
+#define EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE6		BIT(22)
+#define EMC_PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE7		BIT(23)
 #define EMC_PMACRO_VTTGEN_CTRL_0				0xc34
 #define EMC_PMACRO_VTTGEN_CTRL_1				0xc38
 #define EMC_PMACRO_BG_BIAS_CTRL_0				0xc3c
+#define EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD			BIT(0)
+#define EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD			BIT(2)
 #define EMC_PMACRO_PAD_CFG_CTRL					0xc40
 #define EMC_PMACRO_ZCTRL					0xc44
 #define EMC_PMACRO_CMD_PAD_RX_CTRL				0xc50
@@ -637,15 +739,22 @@
 #define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_DRVFORCEON		BIT(26)
 
 #define EMC_PMACRO_DATA_PAD_TX_CTRL				0xc64
+#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF		BIT(0)
 #define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC		BIT(1)
+#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF		BIT(8)
 #define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC		BIT(9)
 #define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC		BIT(16)
 #define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC		BIT(24)
 
 #define EMC_PMACRO_COMMON_PAD_TX_CTRL				0xc68
 #define EMC_PMACRO_AUTOCAL_CFG_COMMON				0xc78
+#define EMC_PMACRO_AUTOCAL_CFG_COMMON_E_CAL_BYPASS_DVFS		BIT(16)
 #define EMC_PMACRO_VTTGEN_CTRL_2				0xcf0
 #define EMC_PMACRO_IB_RXRT					0xcf4
+#define EMC_PMACRO_TRAINING_CTRL_0				0xcf8
+#define EMC_PMACRO_TRAINING_CTRL_0_CH0_TRAINING_E_WRPTR		BIT(3)
+#define EMC_PMACRO_TRAINING_CTRL_1				0xcfc
+#define EMC_PMACRO_TRAINING_CTRL_1_CH1_TRAINING_E_WRPTR		BIT(3)
 #define EMC_TRAINING_CTRL					0xe04
 #define EMC_TRAINING_QUSE_CORS_CTRL				0xe0c
 #define EMC_TRAINING_QUSE_FINE_CTRL				0xe10
@@ -1173,6 +1282,29 @@ enum {
 	DLL_ON
 };
 
+enum {
+	ONE_RANK = 1,
+	TWO_RANK = 2
+};
+
+enum {
+	T_RP = 0,
+	T_FC_LPDDR4,
+	T_RFC,
+	T_PDEX,
+	RL
+};
+
+enum {
+	AUTO_PD = 0,
+	MAN_SR  = 2
+};
+
+enum {
+	ASSEMBLY = 0,
+	ACTIVE
+};
+
 struct emc_table {
 	u32 rev;
 	char dvfs_ver[60];
@@ -1315,7 +1447,9 @@ void emc_writel_per_ch(struct tegra_emc *emc, u32 val, int type,
 		       unsigned long offset);
 u32  emc1_readl(struct tegra_emc *emc, unsigned long offset);
 
+u32 __do_periodic_emc_compensation_r21021(struct tegra_emc *emc);
 void do_clock_change(struct tegra_emc *emc, u32 clksrc);
+void emc_set_clock_r21021(struct tegra_emc *emc, u32 clksrc);
 void emc_set_shadow_bypass(struct tegra_emc *emc, int set);
 void emc_timing_update(struct tegra_emc *emc, int dual_chan);
 u32 get_dll_state(struct emc_table *next_timing);
diff --git a/drivers/memory/tegra/tegra210-emc.c b/drivers/memory/tegra/tegra210-emc.c
index 26d0de0ab319..9afab67b1a3d 100644
--- a/drivers/memory/tegra/tegra210-emc.c
+++ b/drivers/memory/tegra/tegra210-emc.c
@@ -103,6 +103,11 @@ static const char *emc_src_names[TEGRA_EMC_SRC_COUNT] = {
 };
 static struct emc_stats emc_stats;
 static struct supported_sequence supported_seqs[] = {
+	{
+		0x7,
+		emc_set_clock_r21021,
+		__do_periodic_emc_compensation_r21021,
+	},
 	{
 		0,
 		NULL,
-- 
2.21.0


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* [PATCH 6/8] arm64: tegra: Add external memory controller node for Tegra210
  2019-03-25  7:45 [PATCH 0/8] Add EMC scaling support for Tegra210 Joseph Lo
                   ` (4 preceding siblings ...)
  2019-03-25  7:45 ` [PATCH 5/8] memory: tegra: Add EMC scaling sequence " Joseph Lo
@ 2019-03-25  7:45 ` Joseph Lo
  2019-03-29 14:41 ` [PATCH 0/8] Add EMC scaling support " Peter De Schrijver
  6 siblings, 0 replies; 28+ messages in thread
From: Joseph Lo @ 2019-03-25  7:45 UTC (permalink / raw)
  To: Thierry Reding, Peter De Schrijver, Jonathan Hunter, Rob Herring,
	Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree, Joseph Lo

Add external memory controller (EMC) node for Tegra210

Signed-off-by: Joseph Lo <josephl@nvidia.com>
---
 arch/arm64/boot/dts/nvidia/tegra210.dtsi | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/arch/arm64/boot/dts/nvidia/tegra210.dtsi b/arch/arm64/boot/dts/nvidia/tegra210.dtsi
index bc71ef8f9a09..f9c01de2843e 100644
--- a/arch/arm64/boot/dts/nvidia/tegra210.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra210.dtsi
@@ -872,6 +872,29 @@
 		#iommu-cells = <1>;
 	};
 
+	external-memory-controller@7001b000 {
+		compatible = "nvidia,tegra21-emc", "nvidia,tegra210-emc";
+		reg = <0x0 0x7001b000 0x0 0x1000>,
+		      <0x0 0x7001e000 0x0 0x1000>,
+		      <0x0 0x7001f000 0x0 0x1000>;
+		clocks = <&tegra_car TEGRA210_CLK_EMC>,
+			 <&tegra_car TEGRA210_CLK_PLL_M>,
+			 <&tegra_car TEGRA210_CLK_PLL_C>,
+			 <&tegra_car TEGRA210_CLK_PLL_P>,
+			 <&tegra_car TEGRA210_CLK_CLK_M>,
+			 <&tegra_car TEGRA210_CLK_PLL_M_UD>,
+			 <&tegra_car TEGRA210_CLK_PLL_MB_UD>,
+			 <&tegra_car TEGRA210_CLK_PLL_MB>,
+			 <&tegra_car TEGRA210_CLK_PLL_P_UD>;
+		clock-names = "emc", "pll_m", "pll_c", "pll_p", "clk_m",
+			      "pll_m_ud", "pll_mb_ud", "pll_mb", "pll_p_ud";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		nvidia,memory-controller = <&mc>;
+		nvidia,use-ram-code;
+	};
+
 	sata@70020000 {
 		compatible = "nvidia,tegra210-ahci";
 		reg = <0x0 0x70027000 0x0 0x2000>, /* AHCI */
-- 
2.21.0


^ permalink raw reply related	[flat|nested] 28+ messages in thread

* Re: [PATCH 0/8] Add EMC scaling support for Tegra210
  2019-03-25  7:45 [PATCH 0/8] Add EMC scaling support for Tegra210 Joseph Lo
                   ` (5 preceding siblings ...)
  2019-03-25  7:45 ` [PATCH 6/8] arm64: tegra: Add external memory controller node " Joseph Lo
@ 2019-03-29 14:41 ` Peter De Schrijver
  6 siblings, 0 replies; 28+ messages in thread
From: Peter De Schrijver @ 2019-03-29 14:41 UTC (permalink / raw)
  To: Joseph Lo
  Cc: Thierry Reding, Jonathan Hunter, Rob Herring, Stephen Boyd,
	linux-arm-kernel, linux-tegra, linux-clk, devicetree

Looks ok to me.

Acked-By: Peter De Schrijver <pdeschrijver@nvidia.com>

On Mon, Mar 25, 2019 at 03:45:15PM +0800, Joseph Lo wrote:
> This series introduces the EMC clock scaling support for Tegra210. There
> are three parts of this series.
> 
> i. The DT bindings of the EMC table and how to deal with them.
> To support LPDDR4 DRAM devices, the EMC table with timing data must be
> trained before it can be used. The bootloader firmware will help to do
> that at the early boot stage. And help to merge the trained timing data
> into the DTB that EMC driver in the kernel will use later for EMC clock
> scaling.
> 
> And for backward compatibility to support the upstreaming devices, like
> Jetson TX1 and Shield, the bindings should remain the same. Which means
> we may not able to change the bindings currently.
> 
> ii. The EMC driver will use the trained EMC table with the ram code that
> the platform used to update the EMC registers for clock scaling. And to
> support higher rates (above 800MHz), periodic training is needed. This
> will be done by a timer. And two EMC sources will be used (PLL_M and
> 		PLL_MB) for the rate switching above or equal to 665MHz.
> It will check the current source and switch to another one dynamically.
> 
> iii. The detail sequence for EMC scaling support, this includes how we
> update the table to EMC registers and the periodic training support.
> 
> Here are the details and dependency of these patches.
> Patch 1 is the binding document of the EMC node and table.
> Patch 2 is the clock driver update for EMC scaling support. Need to note
> that with the update of the MC clock, the patch 3 (EMC clock driver) and
> patch 6 (EMC node in DT) will be needed to make MC clock to work
> normally.
> Patch 3 is the EMC clock driver. It can get the trained table from the
> firmware and know the current EMC status, like the rate and source
> clock.
> Patch 4 adds more APIs and support code. Tha will be needed for the EMC
> scaling sequence.
> Patch 5 adds the EMC scaling sequence code. With this, we can support
> EMC clock scaling now.
> Patch 6 introduces the EMC node in DT.
> Patch 7 adds EMC table of ram code 0.
> Patch 8 adds EMC table of ram code 1.
> 
> Joseph Lo (8):
>   dt-bindings: memory: tegra: Add Tegra210 EMC bindings
>   clk: tegra: clock changes for emc scaling support on Tegra210
>   memory: tegra: Add Tegra210 EMC clock driver
>   memory: tegra: add EMC scaling support code for Tegra210
>   memory: tegra: Add EMC scaling sequence code for Tegra210
>   arm64: tegra: Add external memory controller node for Tegra210
>   arm64: tegra: Add EMC table of ram code 0 for Tegra210 Shield platform
>   arm64: tegra: Add EMC table of ram code 1 for Tegra210 Shield platform
> 
>  .../nvidia,tegra210-emc.txt                   |   605 +
>  .../boot/dts/nvidia/tegra210-p2894-emc.dtsi   | 24851 ++++++++++++++++
>  .../arm64/boot/dts/nvidia/tegra210-p2894.dtsi |     1 +
>  arch/arm64/boot/dts/nvidia/tegra210.dtsi      |    23 +
>  drivers/clk/tegra/clk-tegra210.c              |   112 +-
>  drivers/memory/tegra/Kconfig                  |    10 +
>  drivers/memory/tegra/Makefile                 |     1 +
>  drivers/memory/tegra/tegra210-dt-parse.c      |   340 +
>  drivers/memory/tegra/tegra210-emc-cc-r21021.c |  1962 ++
>  drivers/memory/tegra/tegra210-emc-reg.h       |  1482 +
>  drivers/memory/tegra/tegra210-emc.c           |  1814 ++
>  include/dt-bindings/clock/tegra210-car.h      |     4 +-
>  include/linux/clk/tegra.h                     |     5 +
>  13 files changed, 31192 insertions(+), 18 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>  create mode 100644 arch/arm64/boot/dts/nvidia/tegra210-p2894-emc.dtsi
>  create mode 100644 drivers/memory/tegra/tegra210-dt-parse.c
>  create mode 100644 drivers/memory/tegra/tegra210-emc-cc-r21021.c
>  create mode 100644 drivers/memory/tegra/tegra210-emc-reg.h
>  create mode 100644 drivers/memory/tegra/tegra210-emc.c
> 
> -- 
> 2.21.0
> 

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-03-25  7:45 ` [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings Joseph Lo
@ 2019-03-31  6:41   ` Rob Herring
  2019-04-01  7:57     ` Joseph Lo
  2019-04-01 12:12   ` Dmitry Osipenko
  2019-04-04  9:17   ` Dmitry Osipenko
  2 siblings, 1 reply; 28+ messages in thread
From: Rob Herring @ 2019-03-31  6:41 UTC (permalink / raw)
  To: Joseph Lo
  Cc: Thierry Reding, Peter De Schrijver, Jonathan Hunter,
	Stephen Boyd, linux-arm-kernel, linux-tegra, linux-clk,
	devicetree

On Mon, Mar 25, 2019 at 03:45:16PM +0800, Joseph Lo wrote:
> Add the binding document for the external memory controller (EMC) which
> communicates with external LPDDR4 devices. It includes the bindings of
> the EMC node and the EMC table of different rates.
> 
> To support high rates for LPDDR4, the EMC table must be trained before
> it can be used for runtime clock switching. It has been done by firmware
> and merged to the table that Linux kernel uses. For backward
> compatibility with the devices that had been launched on the market, like
> Shield and Jetson platforms, the bindings in the EMC table should remain
> the same. So the firmware can recognize them and merge the trained EMC
> table for the kernel.

Overall seems pretty bloated. How much of this really varies by board 
vs. just being a dump of all the register values to stuff?

Primarily, I'm leary of getting a similar binding for every vendor's DDR 
setup.

Some mostly trivial comments follow.

> 
> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
> 
> Signed-off-by: Joseph Lo <josephl@nvidia.com>
> ---
>  .../nvidia,tegra210-emc.txt                   | 605 ++++++++++++++++++
>  1 file changed, 605 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
> 
> diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
> new file mode 100644
> index 000000000000..1f6b6df6d37b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
> @@ -0,0 +1,605 @@
> +NVIDIA Tegra210 SoC EMC (external memory controller)
> +====================================================
> +
> +Required properties :
> +- compatible : should be "nvidia,tegra21-emc", "nvidia,tegra210-emc".
> +- reg : physical base address and length of the controller's registers.
> +- clocks : phandles of the possible source clocks
> +- clock-names : names of the possible source clocks
> +- #address-cells : should be 1
> +- #size-cells : should be 0
> +- nvidia,memory-controller : phandle of the memory controller.
> +- nvidia,use-ram-code : boolean, indicates whether we should use RAM_CODE in
> +		        the register to find matching emc-table nodes
> +
> +The node should contain a "emc-table" subnode for each supported RAM type
> +(see field RAM_CODE in register APB_MISC_PP_STRAPPING_OPT_A), with its unit
> +address being its RAM_CODE.
> +
> +Required properties for "emc-table" nodes :
> +- nvidia,ram-code : Should contain the value of RAM_CODE this timing set is
> +		    used for.
> +
> +Each "emc-table" node should contain a "emc-table" subnode for every supported
> +EMC clock rate. The "emc-table" subnodes should have the clock rate in
> +kilohertz  as their unit address.
> +
> +Required properties for "emc-table" nodes :
> +- compatible :  "nvidia,tegra21-emc-table", "nvidia,tegra210-emc-table"
> +- nvidia,revision : revision of the parameter set used for this node. All
> +                    nodes in the same "emc-table" should have the same revision
> +- nvidia,dvfs-version : string for the DVFS version of this table
> +- clock-frequency : frequency in kilohertz

The units of this property are Hz.

> +- nvidia,emc-min-mv : minimum voltage in millivolt for this rate
> +- nvidia,gk20a-min-mv : minimum GPU voltage in millivolt for this rate

Use standard unit suffixes.

> +- nvidia,source : name of clock source to be used for this rate

We have standard clock properties.

> +- nvidia,src-sel-reg : value of EMC CAR register to be used for this rate
> +- nvidia,needs-training : 1 if needs training at boot, 0 otherwise
> +- nvidia,trained : 1 if initial training has been done by firmware, 0 otherwise
> +- nvidia,periodic_training : 1 if needs periodic training, 0 otherwise
> +- nvidia,trained_dram_clktree_c0d0u0 : training data word

s/_/-/

> +- nvidia,trained_dram_clktree_c0d0u1 : training data word
> +- nvidia,trained_dram_clktree_c0d1u0 : training data word
> +- nvidia,trained_dram_clktree_c0d1u1 : training data word
> +- nvidia,trained_dram_clktree_c1d0u0 : training data word
> +- nvidia,trained_dram_clktree_c1d0u1 : training data word
> +- nvidia,trained_dram_clktree_c1d1u0 : training data word
> +- nvidia,trained_dram_clktree_c1d1u1 : training data word
> +- nvidia,current_dram_clktree_c0d0u0 : training data word
> +- nvidia,current_dram_clktree_c0d0u1 : training data word
> +- nvidia,current_dram_clktree_c0d1u0 : training data word
> +- nvidia,current_dram_clktree_c0d1u1 : training data word
> +- nvidia,current_dram_clktree_c1d0u0 : training data word
> +- nvidia,current_dram_clktree_c1d0u1 : training data word
> +- nvidia,current_dram_clktree_c1d1u0 : training data word
> +- nvidia,current_dram_clktree_c1d1u1 : training data word
> +- nvidia,run_clocks : training data
> +- nvidia,tree_margin : training data
> +- nvidia,burst-regs-num : number of values in nvidia,emc-registers
> +- nvidia,burst-regs-per-ch-num : number of values in
> +				 nvidia,emc-burst-regs-per-ch
> +- nvidia,trim-regs-num : number of values in nvidia,emc-trim-regs
> +- nvidia,trim-regs-per-ch-num : number of values in nvidia,emc-trim-regs-per-ch
> +- nvidia,burst-mc-regs-num : number of values in nvidia,emc-burst-mc-regs
> +- nvidia,la-scale-regs-num : number of values in nvidia,emc-la-scale-regs
> +- nvidia,vref-regs-num : number of values in nvidia,emc-vref-regs
> +- nvidia,dram-timing-regs-num : number of values in nvidia,emc-dram-timing-regs
> +- nvidia,min-mrs-wait : value of the EMC_MRS register
> +- nvidia,emc-mrw : value of the EMC_MRW register
> +- nvidia,emc-mrw2 : value of the EMC_MRW2 register
> +- nvidia,emc-mrw3 : value of the EMC_MRW3 register
> +- nvidia,emc-mrw4 : value of the EMC_MRW4 register
> +- nvidia,emc-mrw9 : value of the EMC_MRW4 register
> +- nvidia,emc-mrs : value of the EMC_MRS register
> +- nvidia,emc-emrs : value of the EMC_EMRS register
> +- nvidia,emc-emrs2 : value of the EMC_EMRS2 register
> +- nvidia,emc-auto-cal-config : value of the EMC_AUTO_CAL_CONFIG register
> +- nvidia,emc-auto-cal-config2 : value of the EMC_AUTO_CAL_CONFIG2 register
> +- nvidia,emc-auto-cal-config3 : value of the EMC_AUTO_CAL_CONFIG3 register
> +- nvidia,emc-auto-cal-config4 : value of the EMC_AUTO_CAL_CONFIG4 register
> +- nvidia,emc-auto-cal-config5 : value of the EMC_AUTO_CAL_CONFIG5 register
> +- nvidia,emc-auto-cal-config6 : value of the EMC_AUTO_CAL_CONFIG6 register
> +- nvidia,emc-auto-cal-config7 : value of the EMC_AUTO_CAL_CONFIG7 register
> +- nvidia,emc-auto-cal-config8 : value of the EMC_AUTO_CAL_CONFIG8 register
> +- nvidia,emc-cfg-2 : value of the EMC_CFG_2 register
> +- nvidia,emc-sel-dpd-ctrl : value of the EMC_SEL_DPD_CTRL register
> +- nvidia,emc-fdpd-ctrl-cmd-no-ramp : value of the EMC_FDPD_CTRL_CMD_NO_RAMP
> +				     register
> +- nvidia,dll-clk-src : value of the CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL
> +		       register
> +- nvidia,clk-out-enb-x-0-clk-enb-emc-dll : boolean, enable EMC_DLL in the
> +					   CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET,
> +					   or clear in the
> +					   CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR
> +- nvidia,emc-clock-latency-change : clock latency value in micro seconds
> +- nvidia,ptfv : control data for periodic training
> +- nvidia,emc-registers :
> +- nvidia,emc-shadow-regs-ca-train :
> +- nvidia,emc-shadow-regs-quse-train :
> +- nvidia,emc-shadow-regs-rdwr-train :
> +  values for the following registers (See TRM 18.10.2 for register descriptions)
> +	EMC_RC
> +	EMC_RFC
> +	EMC_RFCPB
> +	EMC_REFCTRL2
> +	EMC_RFC_SLR
> +	EMC_RAS
> +	EMC_RP
> +	EMC_R2W
> +	EMC_W2R
> +	EMC_R2P
> +	EMC_W2P
> +	EMC_R2R
> +	EMC_TPPD
> +	EMC_CCDMW
> +	EMC_RD_RCD
> +	EMC_WR_RCD
> +	EMC_RRD
> +	EMC_REXT
> +	EMC_WEXT
> +	EMC_WDV_CHK
> +	EMC_WDV
> +	EMC_WSV
> +	EMC_WEV
> +	EMC_WDV_MASK
> +	EMC_WS_DURATION
> +	EMC_WE_DURATION
> +	EMC_QUSE
> +	EMC_QUSE_WIDTH
> +	EMC_IBDLY
> +	EMC_OBDLY
> +	EMC_EINPUT
> +	EMC_MRW6
> +	EMC_EINPUT_DURATION
> +	EMC_PUTERM_EXTRA
> +	EMC_PUTERM_WIDTH
> +	EMC_QRST
> +	EMC_QSAFE
> +	EMC_RDV
> +	EMC_RDV_MASK
> +	EMC_RDV_EARLY
> +	EMC_RDV_EARLY_MASK
> +	EMC_REFRESH
> +	EMC_BURST_REFRESH_NUM
> +	EMC_PRE_REFRESH_REQ_CNT
> +	EMC_PDEX2WR
> +	EMC_PDEX2RD
> +	EMC_PCHG2PDEN
> +	EMC_ACT2PDEN
> +	EMC_AR2PDEN
> +	EMC_RW2PDEN
> +	EMC_CKE2PDEN
> +	EMC_PDEX2CKE
> +	EMC_PDEX2MRR
> +	EMC_TXSR
> +	EMC_TXSRDLL
> +	EMC_TCKE
> +	EMC_TCKESR
> +	EMC_TPD
> +	EMC_TFAW
> +	EMC_TRPAB
> +	EMC_TCLKSTABLE
> +	EMC_TCLKSTOP
> +	EMC_MRW7
> +	EMC_TREFBW
> +	EMC_ODT_WRITE
> +	EMC_FBIO_CFG5
> +	EMC_FBIO_CFG7
> +	EMC_CFG_DIG_DLL
> +	EMC_CFG_DIG_DLL_PERIOD
> +	EMC_PMACRO_IB_RXRT
> +	EMC_CFG_PIPE_1
> +	EMC_CFG_PIPE_2
> +	EMC_PMACRO_QUSE_DDLL_RANK0_4
> +	EMC_PMACRO_QUSE_DDLL_RANK0_5
> +	EMC_PMACRO_QUSE_DDLL_RANK1_4
> +	EMC_PMACRO_QUSE_DDLL_RANK1_5
> +	EMC_MRW8
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5
> +	EMC_PMACRO_DDLL_LONG_CMD_0
> +	EMC_PMACRO_DDLL_LONG_CMD_1
> +	EMC_PMACRO_DDLL_LONG_CMD_2
> +	EMC_PMACRO_DDLL_LONG_CMD_3
> +	EMC_PMACRO_DDLL_LONG_CMD_4
> +	EMC_PMACRO_DDLL_SHORT_CMD_0
> +	EMC_PMACRO_DDLL_SHORT_CMD_1
> +	EMC_PMACRO_DDLL_SHORT_CMD_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_3
> +	EMC_TXDSRVTTGEN
> +	EMC_FDPD_CTRL_DQ
> +	EMC_FDPD_CTRL_CMD
> +	EMC_FBIO_SPARE
> +	EMC_ZCAL_INTERVAL
> +	EMC_ZCAL_WAIT_CNT
> +	EMC_MRS_WAIT_CNT
> +	EMC_MRS_WAIT_CNT2
> +	EMC_AUTO_CAL_CHANNEL
> +	EMC_DLL_CFG_0
> +	EMC_DLL_CFG_1
> +	EMC_PMACRO_AUTOCAL_CFG_COMMON
> +	EMC_PMACRO_ZCTRL
> +	EMC_CFG
> +	EMC_CFG_PIPE
> +	EMC_DYN_SELF_REF_CONTROL
> +	EMC_QPOP
> +	EMC_DQS_BRLSHFT_0
> +	EMC_DQS_BRLSHFT_1
> +	EMC_CMD_BRLSHFT_2
> +	EMC_CMD_BRLSHFT_3
> +	EMC_PMACRO_PAD_CFG_CTRL
> +	EMC_PMACRO_DATA_PAD_RX_CTRL
> +	EMC_PMACRO_CMD_PAD_RX_CTRL
> +	EMC_PMACRO_DATA_RX_TERM_MODE
> +	EMC_PMACRO_CMD_RX_TERM_MODE
> +	EMC_PMACRO_CMD_PAD_TX_CTRL
> +	EMC_PMACRO_DATA_PAD_TX_CTRL
> +	EMC_PMACRO_COMMON_PAD_TX_CTRL
> +	EMC_PMACRO_VTTGEN_CTRL_0
> +	EMC_PMACRO_VTTGEN_CTRL_1
> +	EMC_PMACRO_VTTGEN_CTRL_2
> +	EMC_PMACRO_BRICK_CTRL_RFU1
> +	EMC_PMACRO_CMD_BRICK_CTRL_FDPD
> +	EMC_PMACRO_BRICK_CTRL_RFU2
> +	EMC_PMACRO_DATA_BRICK_CTRL_FDPD
> +	EMC_PMACRO_BG_BIAS_CTRL_0
> +	EMC_CFG_3
> +	EMC_PMACRO_TX_PWRD_0
> +	EMC_PMACRO_TX_PWRD_1
> +	EMC_PMACRO_TX_PWRD_2
> +	EMC_PMACRO_TX_PWRD_3
> +	EMC_PMACRO_TX_PWRD_4
> +	EMC_PMACRO_TX_PWRD_5
> +	EMC_CONFIG_SAMPLE_DELAY
> +	EMC_PMACRO_TX_SEL_CLK_SRC_0
> +	EMC_PMACRO_TX_SEL_CLK_SRC_1
> +	EMC_PMACRO_TX_SEL_CLK_SRC_2
> +	EMC_PMACRO_TX_SEL_CLK_SRC_3
> +	EMC_PMACRO_TX_SEL_CLK_SRC_4
> +	EMC_PMACRO_TX_SEL_CLK_SRC_5
> +	EMC_PMACRO_DDLL_BYPASS
> +	EMC_PMACRO_DDLL_PWRD_0
> +	EMC_PMACRO_DDLL_PWRD_1
> +	EMC_PMACRO_DDLL_PWRD_2
> +	EMC_PMACRO_CMD_CTRL_0
> +	EMC_PMACRO_CMD_CTRL_1
> +	EMC_PMACRO_CMD_CTRL_2
> +	EMC_TR_TIMING_0
> +	EMC_TR_DVFS
> +	EMC_TR_CTRL_1
> +	EMC_TR_RDV
> +	EMC_TR_QPOP
> +	EMC_TR_RDV_MASK
> +	EMC_MRW14
> +	EMC_TR_QSAFE
> +	EMC_TR_QRST
> +	EMC_TRAINING_CTRL
> +	EMC_TRAINING_SETTLE
> +	EMC_TRAINING_VREF_SETTLE
> +	EMC_TRAINING_CA_FINE_CTRL
> +	EMC_TRAINING_CA_CTRL_MISC
> +	EMC_TRAINING_CA_CTRL_MISC1
> +	EMC_TRAINING_CA_VREF_CTRL
> +	EMC_TRAINING_QUSE_CORS_CTRL
> +	EMC_TRAINING_QUSE_FINE_CTRL
> +	EMC_TRAINING_QUSE_CTRL_MISC
> +	EMC_TRAINING_QUSE_VREF_CTRL
> +	EMC_TRAINING_READ_FINE_CTRL
> +	EMC_TRAINING_READ_CTRL_MISC
> +	EMC_TRAINING_READ_VREF_CTRL
> +	EMC_TRAINING_WRITE_FINE_CTRL
> +	EMC_TRAINING_WRITE_CTRL_MISC
> +	EMC_TRAINING_WRITE_VREF_CTRL
> +	EMC_TRAINING_MPC
> +	EMC_MRW15
> +- nvidia,emc-burst-regs-per-ch : values for the following registers (See TRM
> +				 18.10.2 for register descriptions) the array
> +				 containts 2 values for each register, one per
> +				 channel.
> +	EMC_MRW10
> +	EMC_MRW11
> +	EMC_MRW12
> +	EMC_MRW13
> +- nvidia,emc-trim-regs : values for the following registers (See TRM 18.10.2
> +			 for register descriptions)
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2
> +	EMC_PMACRO_IB_VREF_DQS_0
> +	EMC_PMACRO_IB_VREF_DQS_1
> +	EMC_PMACRO_IB_VREF_DQ_0
> +	EMC_PMACRO_IB_VREF_DQ_1
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2
> +	EMC_PMACRO_QUSE_DDLL_RANK0_0
> +	EMC_PMACRO_QUSE_DDLL_RANK0_1
> +	EMC_PMACRO_QUSE_DDLL_RANK0_2
> +	EMC_PMACRO_QUSE_DDLL_RANK0_3
> +	EMC_PMACRO_QUSE_DDLL_RANK1_0
> +	EMC_PMACRO_QUSE_DDLL_RANK1_1
> +	EMC_PMACRO_QUSE_DDLL_RANK1_2
> +	EMC_PMACRO_QUSE_DDLL_RANK1_3
> +- nvidia,emc-trim-regs-per-ch : values for the following registers (See TRM
> +				18.10.2 for register descriptions)
> +	EMC_CMD_BRLSHFT_0
> +	EMC_CMD_BRLSHFT_1
> +	EMC_DATA_BRLSHFT_0 (channel 0)
> +	EMC_DATA_BRLSHFT_0 (channel 1)
> +	EMC_DATA_BRLSHFT_1 (channel 0)
> +	EMC_DATA_BRLSHFT_1 (channel 1)
> +	EMC_QUSE_BRLSHFT_0
> +	EMC_QUSE_BRLSHFT_1
> +	EMC_QUSE_BRLSHFT_2
> +	EMC_QUSE_BRLSHFT_3
> +- nvidia,emc-vref-regs : values for the following registers (See TRM 18.10.2
> +			 for register descriptions) the array containts 2
> +			 values for each register, one per channel.
> +	 EMC_TRAINING_OPT_DQS_IB_VREF_RANK0
> +	 EMC_TRAINING_OPT_DQS_IB_VREF_RANK1
> +- nvidia,emc-dram-timing-regs : DRAM timing values. These are not written to
> +				registers but used during the sequence.
> +	T_RP : row pre-charge delay
> +	T_FC_LPDDR4 : frequency change time
> +	T_RFC : refresh cycle time
> +	T_PDEX : power down exit delay
> +	RL : mode register read latency
> +- nvidia,emc-training-mod-regs :
> +- nvidia,emc-save-restore-mod-regs :
> +	deprecated, keep for for backward compatibility
> +- nvidia,emc-burst-mc-regs : values for the following registers
> +			     (See TRM 18.10.1 for register descriptions)
> +	MC_EMEM_ARB_CFG
> +	MC_EMEM_ARB_OUTSTANDING_REQ
> +	MC_EMEM_ARB_REFPB_HP_CTRL
> +	MC_EMEM_ARB_REFPB_BANK_CTRL
> +	MC_EMEM_ARB_TIMING_RCD
> +	MC_EMEM_ARB_TIMING_RP
> +	MC_EMEM_ARB_TIMING_RC
> +	MC_EMEM_ARB_TIMING_RAS
> +	MC_EMEM_ARB_TIMING_FAW
> +	MC_EMEM_ARB_TIMING_RRD
> +	MC_EMEM_ARB_TIMING_RAP2PRE
> +	MC_EMEM_ARB_TIMING_WAP2PRE
> +	MC_EMEM_ARB_TIMING_R2R
> +	MC_EMEM_ARB_TIMING_W2W
> +	MC_EMEM_ARB_TIMING_R2W
> +	MC_EMEM_ARB_TIMING_CCDMW
> +	MC_EMEM_ARB_TIMING_W2R
> +	MC_EMEM_ARB_TIMING_RFCPB
> +	MC_EMEM_ARB_DA_TURNS
> +	MC_EMEM_ARB_DA_COVERS
> +	MC_EMEM_ARB_MISC0
> +	MC_EMEM_ARB_MISC1
> +	MC_EMEM_ARB_MISC2
> +	MC_EMEM_ARB_RING1_THROTTLE
> +	MC_EMEM_ARB_DHYST_CTRL
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7
> +- nvidia,emc-la-scale-regs : values for the following registers
> +			     (See TRM 18.10.1 for register descriptions)
> +	MC_MLL_MPCORER_PTSA_RATE
> +	MC_FTOP_PTSA_RATE
> +	MC_PTSA_GRANT_DECREMENT
> +	MC_LATENCY_ALLOWANCE_XUSB_0
> +	MC_LATENCY_ALLOWANCE_XUSB_1
> +	MC_LATENCY_ALLOWANCE_TSEC_0
> +	MC_LATENCY_ALLOWANCE_SDMMCA_0
> +	MC_LATENCY_ALLOWANCE_SDMMCAA_0
> +	MC_LATENCY_ALLOWANCE_SDMMC_0
> +	MC_LATENCY_ALLOWANCE_SDMMCAB_0
> +	MC_LATENCY_ALLOWANCE_PPCS_0
> +	MC_LATENCY_ALLOWANCE_PPCS_1
> +	MC_LATENCY_ALLOWANCE_MPCORE_0
> +	MC_LATENCY_ALLOWANCE_HC_0
> +	MC_LATENCY_ALLOWANCE_HC_1
> +	MC_LATENCY_ALLOWANCE_AVPC_0
> +	MC_LATENCY_ALLOWANCE_GPU_0
> +	MC_LATENCY_ALLOWANCE_GPU2_0
> +	MC_LATENCY_ALLOWANCE_NVENC_0
> +	MC_LATENCY_ALLOWANCE_NVDEC_0
> +	MC_LATENCY_ALLOWANCE_VIC_0
> +	MC_LATENCY_ALLOWANCE_VI2_0
> +	MC_LATENCY_ALLOWANCE_ISP2_0
> +	MC_LATENCY_ALLOWANCE_ISP2_1
> +
> +Example:
> +	external-memory-controller@7001b000 {
> +		compatible = "nvidia,tegra21-emc", "nvidia,tegra210-emc";
> +		reg = <0x0 0x7001b000 0x0 0x1000>,
> +		      <0x0 0x7001e000 0x0 0x1000>,
> +		      <0x0 0x7001f000 0x0 0x1000>;
> +		clocks = <&tegra_car TEGRA210_CLK_EMC>,
> +		         <&tegra_car TEGRA210_CLK_PLL_M>,
> +			 <&tegra_car TEGRA210_CLK_PLL_C>,
> +			 <&tegra_car TEGRA210_CLK_PLL_P>,
> +			 <&tegra_car TEGRA210_CLK_CLK_M>,
> +			 <&tegra_car TEGRA210_CLK_PLL_M_UD>,
> +			 <&tegra_car TEGRA210_CLK_PLL_MB_UD>,
> +			 <&tegra_car TEGRA210_CLK_PLL_MB>,
> +			 <&tegra_car TEGRA210_CLK_PLL_P_UD>;
> +		clock-names = "emc", "pll_m", "pll_c", "pll_p", "clk_m",
> +			      "pll_m_ud", "pll_mb_ud", "pll_mb", "pll_p_ud";
> +		#address-cells = <1>;
> +		#size-cells = <0>;
> +
> +		nvidia,memory-controller = <&mc>;
> +		nvidia,use-ram-code;
> +
> +		emc-table@0 {

Unit address without reg is not valid.

> +			nvidia,ram-code = <0>;
> +			emc-table@40800 {

And here.

> +				compatible = "nvidia,tegra21-emc-table";
> +				...
> +			};
> +			emc-table@204000 {
> +				...
> +			};
> +			...
> +		};
> +
> +		emc-table@1 {
> +			nvidia,ram-code = <1>;
> +			emc-table@40800 {
> +				compatible = "nvidia,tegra21-emc-table";
> +				...
> +			};
> +			emc-table@204000 {
> +				...
> +			};
> +			...
> +		};
> +	};
> -- 
> 2.21.0
> 


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-03-31  6:41   ` Rob Herring
@ 2019-04-01  7:57     ` Joseph Lo
  2019-04-03  4:26       ` Rob Herring
  0 siblings, 1 reply; 28+ messages in thread
From: Joseph Lo @ 2019-04-01  7:57 UTC (permalink / raw)
  To: Rob Herring
  Cc: Thierry Reding, Peter De Schrijver, Jonathan Hunter,
	Stephen Boyd, linux-arm-kernel, linux-tegra, linux-clk,
	devicetree

On 3/31/19 2:41 PM, Rob Herring wrote:
> On Mon, Mar 25, 2019 at 03:45:16PM +0800, Joseph Lo wrote:
>> Add the binding document for the external memory controller (EMC) which
>> communicates with external LPDDR4 devices. It includes the bindings of
>> the EMC node and the EMC table of different rates.
>>
>> To support high rates for LPDDR4, the EMC table must be trained before
>> it can be used for runtime clock switching. It has been done by firmware
>> and merged to the table that Linux kernel uses. For backward
>> compatibility with the devices that had been launched on the market, like
>> Shield and Jetson platforms, the bindings in the EMC table should remain
>> the same. So the firmware can recognize them and merge the trained EMC
>> table for the kernel.

Hi Rob,

Thanks for reviewing.

> 
> Overall seems pretty bloated. How much of this really varies by board
> vs. just being a dump of all the register values to stuff?

Most of them are register values. And by different SDRAM devices that 
could be used on the same platform (use ram code to identify them), the 
value could be different.

> 
> Primarily, I'm leary of getting a similar binding for every vendor's DDR
> setup.
> 
> Some mostly trivial comments follow.

Really sorry about that. I understand these basic rules for DT bindings, 
but the case here is that these un-reviewed bindings have been used in 
the firmware on the shipped products. To support the same with the 
upstream kernel and consider the firmware blob may not be updated, we 
have no choice to just use the same bindings in the upstream kernel.

How can we deal with this case?

> 
snip.
>> +Example:
>> +	external-memory-controller@7001b000 {
>> +		compatible = "nvidia,tegra21-emc", "nvidia,tegra210-emc";
>> +		reg = <0x0 0x7001b000 0x0 0x1000>,
>> +		      <0x0 0x7001e000 0x0 0x1000>,
>> +		      <0x0 0x7001f000 0x0 0x1000>;
>> +		clocks = <&tegra_car TEGRA210_CLK_EMC>,
>> +		         <&tegra_car TEGRA210_CLK_PLL_M>,
>> +			 <&tegra_car TEGRA210_CLK_PLL_C>,
>> +			 <&tegra_car TEGRA210_CLK_PLL_P>,
>> +			 <&tegra_car TEGRA210_CLK_CLK_M>,
>> +			 <&tegra_car TEGRA210_CLK_PLL_M_UD>,
>> +			 <&tegra_car TEGRA210_CLK_PLL_MB_UD>,
>> +			 <&tegra_car TEGRA210_CLK_PLL_MB>,
>> +			 <&tegra_car TEGRA210_CLK_PLL_P_UD>;
>> +		clock-names = "emc", "pll_m", "pll_c", "pll_p", "clk_m",
>> +			      "pll_m_ud", "pll_mb_ud", "pll_mb", "pll_p_ud";
>> +		#address-cells = <1>;
>> +		#size-cells = <0>;
>> +
>> +		nvidia,memory-controller = <&mc>;
>> +		nvidia,use-ram-code;
>> +
>> +		emc-table@0 {
> 
> Unit address without reg is not valid.
> 
>> +			nvidia,ram-code = <0>;
>> +			emc-table@40800 {
> 
> And here.
> 

Will fix this.
Thanks,
Joseph

>> +				compatible = "nvidia,tegra21-emc-table";
>> +				...
>> +			};
>> +			emc-table@204000 {
>> +				...
>> +			};
>> +			...
>> +		};
>> +
>> +		emc-table@1 {
>> +			nvidia,ram-code = <1>;
>> +			emc-table@40800 {
>> +				compatible = "nvidia,tegra21-emc-table";
>> +				...
>> +			};
>> +			emc-table@204000 {
>> +				...
>> +			};
>> +			...
>> +		};
>> +	};
>> -- 
>> 2.21.0
>>
> 

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-03-25  7:45 ` [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings Joseph Lo
  2019-03-31  6:41   ` Rob Herring
@ 2019-04-01 12:12   ` Dmitry Osipenko
  2019-04-02  2:26     ` Joseph Lo
  2019-04-04  9:17   ` Dmitry Osipenko
  2 siblings, 1 reply; 28+ messages in thread
From: Dmitry Osipenko @ 2019-04-01 12:12 UTC (permalink / raw)
  To: Joseph Lo, Thierry Reding, Peter De Schrijver, Jonathan Hunter,
	Rob Herring, Stephen Boyd
  Cc: linux-tegra, devicetree, linux-clk, linux-arm-kernel

25.03.2019 10:45, Joseph Lo пишет:
> Add the binding document for the external memory controller (EMC) which
> communicates with external LPDDR4 devices. It includes the bindings of
> the EMC node and the EMC table of different rates.
> 
> To support high rates for LPDDR4, the EMC table must be trained before
> it can be used for runtime clock switching. It has been done by firmware
> and merged to the table that Linux kernel uses. For backward
> compatibility with the devices that had been launched on the market, like
> Shield and Jetson platforms, the bindings in the EMC table should remain
> the same. So the firmware can recognize them and merge the trained EMC
> table for the kernel.
> 
> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
> 
> Signed-off-by: Joseph Lo <josephl@nvidia.com>
> ---
>  .../nvidia,tegra210-emc.txt                   | 605 ++++++++++++++++++
>  1 file changed, 605 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
> 
> diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
> new file mode 100644
> index 000000000000..1f6b6df6d37b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
> @@ -0,0 +1,605 @@
> +NVIDIA Tegra210 SoC EMC (external memory controller)
> +====================================================
> +
> +Required properties :
> +- compatible : should be "nvidia,tegra21-emc", "nvidia,tegra210-emc".
> +- reg : physical base address and length of the controller's registers.
> +- clocks : phandles of the possible source clocks
> +- clock-names : names of the possible source clocks
> +- #address-cells : should be 1
> +- #size-cells : should be 0
> +- nvidia,memory-controller : phandle of the memory controller.
> +- nvidia,use-ram-code : boolean, indicates whether we should use RAM_CODE in
> +		        the register to find matching emc-table nodes
> +
> +The node should contain a "emc-table" subnode for each supported RAM type
> +(see field RAM_CODE in register APB_MISC_PP_STRAPPING_OPT_A), with its unit
> +address being its RAM_CODE.
> +
> +Required properties for "emc-table" nodes :
> +- nvidia,ram-code : Should contain the value of RAM_CODE this timing set is
> +		    used for.
> +
> +Each "emc-table" node should contain a "emc-table" subnode for every supported
> +EMC clock rate. The "emc-table" subnodes should have the clock rate in
> +kilohertz  as their unit address.
> +
> +Required properties for "emc-table" nodes :
> +- compatible :  "nvidia,tegra21-emc-table", "nvidia,tegra210-emc-table"
> +- nvidia,revision : revision of the parameter set used for this node. All
> +                    nodes in the same "emc-table" should have the same revision
> +- nvidia,dvfs-version : string for the DVFS version of this table
> +- clock-frequency : frequency in kilohertz
> +- nvidia,emc-min-mv : minimum voltage in millivolt for this rate
> +- nvidia,gk20a-min-mv : minimum GPU voltage in millivolt for this rate
> +- nvidia,source : name of clock source to be used for this rate
> +- nvidia,src-sel-reg : value of EMC CAR register to be used for this rate
> +- nvidia,needs-training : 1 if needs training at boot, 0 otherwise
> +- nvidia,trained : 1 if initial training has been done by firmware, 0 otherwise
> +- nvidia,periodic_training : 1 if needs periodic training, 0 otherwise
> +- nvidia,trained_dram_clktree_c0d0u0 : training data word
> +- nvidia,trained_dram_clktree_c0d0u1 : training data word
> +- nvidia,trained_dram_clktree_c0d1u0 : training data word
> +- nvidia,trained_dram_clktree_c0d1u1 : training data word
> +- nvidia,trained_dram_clktree_c1d0u0 : training data word
> +- nvidia,trained_dram_clktree_c1d0u1 : training data word
> +- nvidia,trained_dram_clktree_c1d1u0 : training data word
> +- nvidia,trained_dram_clktree_c1d1u1 : training data word
> +- nvidia,current_dram_clktree_c0d0u0 : training data word
> +- nvidia,current_dram_clktree_c0d0u1 : training data word
> +- nvidia,current_dram_clktree_c0d1u0 : training data word
> +- nvidia,current_dram_clktree_c0d1u1 : training data word
> +- nvidia,current_dram_clktree_c1d0u0 : training data word
> +- nvidia,current_dram_clktree_c1d0u1 : training data word
> +- nvidia,current_dram_clktree_c1d1u0 : training data word
> +- nvidia,current_dram_clktree_c1d1u1 : training data word
> +- nvidia,run_clocks : training data
> +- nvidia,tree_margin : training data
> +- nvidia,burst-regs-num : number of values in nvidia,emc-registers
> +- nvidia,burst-regs-per-ch-num : number of values in
> +				 nvidia,emc-burst-regs-per-ch
> +- nvidia,trim-regs-num : number of values in nvidia,emc-trim-regs
> +- nvidia,trim-regs-per-ch-num : number of values in nvidia,emc-trim-regs-per-ch
> +- nvidia,burst-mc-regs-num : number of values in nvidia,emc-burst-mc-regs
> +- nvidia,la-scale-regs-num : number of values in nvidia,emc-la-scale-regs
> +- nvidia,vref-regs-num : number of values in nvidia,emc-vref-regs
> +- nvidia,dram-timing-regs-num : number of values in nvidia,emc-dram-timing-regs
> +- nvidia,min-mrs-wait : value of the EMC_MRS register
> +- nvidia,emc-mrw : value of the EMC_MRW register
> +- nvidia,emc-mrw2 : value of the EMC_MRW2 register
> +- nvidia,emc-mrw3 : value of the EMC_MRW3 register
> +- nvidia,emc-mrw4 : value of the EMC_MRW4 register
> +- nvidia,emc-mrw9 : value of the EMC_MRW4 register
> +- nvidia,emc-mrs : value of the EMC_MRS register
> +- nvidia,emc-emrs : value of the EMC_EMRS register
> +- nvidia,emc-emrs2 : value of the EMC_EMRS2 register
> +- nvidia,emc-auto-cal-config : value of the EMC_AUTO_CAL_CONFIG register
> +- nvidia,emc-auto-cal-config2 : value of the EMC_AUTO_CAL_CONFIG2 register
> +- nvidia,emc-auto-cal-config3 : value of the EMC_AUTO_CAL_CONFIG3 register
> +- nvidia,emc-auto-cal-config4 : value of the EMC_AUTO_CAL_CONFIG4 register
> +- nvidia,emc-auto-cal-config5 : value of the EMC_AUTO_CAL_CONFIG5 register
> +- nvidia,emc-auto-cal-config6 : value of the EMC_AUTO_CAL_CONFIG6 register
> +- nvidia,emc-auto-cal-config7 : value of the EMC_AUTO_CAL_CONFIG7 register
> +- nvidia,emc-auto-cal-config8 : value of the EMC_AUTO_CAL_CONFIG8 register
> +- nvidia,emc-cfg-2 : value of the EMC_CFG_2 register
> +- nvidia,emc-sel-dpd-ctrl : value of the EMC_SEL_DPD_CTRL register
> +- nvidia,emc-fdpd-ctrl-cmd-no-ramp : value of the EMC_FDPD_CTRL_CMD_NO_RAMP
> +				     register
> +- nvidia,dll-clk-src : value of the CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL
> +		       register
> +- nvidia,clk-out-enb-x-0-clk-enb-emc-dll : boolean, enable EMC_DLL in the
> +					   CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET,
> +					   or clear in the
> +					   CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR
> +- nvidia,emc-clock-latency-change : clock latency value in micro seconds
> +- nvidia,ptfv : control data for periodic training
> +- nvidia,emc-registers :
> +- nvidia,emc-shadow-regs-ca-train :
> +- nvidia,emc-shadow-regs-quse-train :
> +- nvidia,emc-shadow-regs-rdwr-train :
> +  values for the following registers (See TRM 18.10.2 for register descriptions)
> +	EMC_RC
> +	EMC_RFC
> +	EMC_RFCPB
> +	EMC_REFCTRL2
> +	EMC_RFC_SLR
> +	EMC_RAS
> +	EMC_RP
> +	EMC_R2W
> +	EMC_W2R
> +	EMC_R2P
> +	EMC_W2P
> +	EMC_R2R
> +	EMC_TPPD
> +	EMC_CCDMW
> +	EMC_RD_RCD
> +	EMC_WR_RCD
> +	EMC_RRD
> +	EMC_REXT
> +	EMC_WEXT
> +	EMC_WDV_CHK
> +	EMC_WDV
> +	EMC_WSV
> +	EMC_WEV
> +	EMC_WDV_MASK
> +	EMC_WS_DURATION
> +	EMC_WE_DURATION
> +	EMC_QUSE
> +	EMC_QUSE_WIDTH
> +	EMC_IBDLY
> +	EMC_OBDLY
> +	EMC_EINPUT
> +	EMC_MRW6
> +	EMC_EINPUT_DURATION
> +	EMC_PUTERM_EXTRA
> +	EMC_PUTERM_WIDTH
> +	EMC_QRST
> +	EMC_QSAFE
> +	EMC_RDV
> +	EMC_RDV_MASK
> +	EMC_RDV_EARLY
> +	EMC_RDV_EARLY_MASK
> +	EMC_REFRESH
> +	EMC_BURST_REFRESH_NUM
> +	EMC_PRE_REFRESH_REQ_CNT
> +	EMC_PDEX2WR
> +	EMC_PDEX2RD
> +	EMC_PCHG2PDEN
> +	EMC_ACT2PDEN
> +	EMC_AR2PDEN
> +	EMC_RW2PDEN
> +	EMC_CKE2PDEN
> +	EMC_PDEX2CKE
> +	EMC_PDEX2MRR
> +	EMC_TXSR
> +	EMC_TXSRDLL
> +	EMC_TCKE
> +	EMC_TCKESR
> +	EMC_TPD
> +	EMC_TFAW
> +	EMC_TRPAB
> +	EMC_TCLKSTABLE
> +	EMC_TCLKSTOP
> +	EMC_MRW7
> +	EMC_TREFBW
> +	EMC_ODT_WRITE
> +	EMC_FBIO_CFG5
> +	EMC_FBIO_CFG7
> +	EMC_CFG_DIG_DLL
> +	EMC_CFG_DIG_DLL_PERIOD
> +	EMC_PMACRO_IB_RXRT
> +	EMC_CFG_PIPE_1
> +	EMC_CFG_PIPE_2
> +	EMC_PMACRO_QUSE_DDLL_RANK0_4
> +	EMC_PMACRO_QUSE_DDLL_RANK0_5
> +	EMC_PMACRO_QUSE_DDLL_RANK1_4
> +	EMC_PMACRO_QUSE_DDLL_RANK1_5
> +	EMC_MRW8
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4
> +	EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5
> +	EMC_PMACRO_DDLL_LONG_CMD_0
> +	EMC_PMACRO_DDLL_LONG_CMD_1
> +	EMC_PMACRO_DDLL_LONG_CMD_2
> +	EMC_PMACRO_DDLL_LONG_CMD_3
> +	EMC_PMACRO_DDLL_LONG_CMD_4
> +	EMC_PMACRO_DDLL_SHORT_CMD_0
> +	EMC_PMACRO_DDLL_SHORT_CMD_1
> +	EMC_PMACRO_DDLL_SHORT_CMD_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_3
> +	EMC_TXDSRVTTGEN
> +	EMC_FDPD_CTRL_DQ
> +	EMC_FDPD_CTRL_CMD
> +	EMC_FBIO_SPARE
> +	EMC_ZCAL_INTERVAL
> +	EMC_ZCAL_WAIT_CNT
> +	EMC_MRS_WAIT_CNT
> +	EMC_MRS_WAIT_CNT2
> +	EMC_AUTO_CAL_CHANNEL
> +	EMC_DLL_CFG_0
> +	EMC_DLL_CFG_1
> +	EMC_PMACRO_AUTOCAL_CFG_COMMON
> +	EMC_PMACRO_ZCTRL
> +	EMC_CFG
> +	EMC_CFG_PIPE
> +	EMC_DYN_SELF_REF_CONTROL
> +	EMC_QPOP
> +	EMC_DQS_BRLSHFT_0
> +	EMC_DQS_BRLSHFT_1
> +	EMC_CMD_BRLSHFT_2
> +	EMC_CMD_BRLSHFT_3
> +	EMC_PMACRO_PAD_CFG_CTRL
> +	EMC_PMACRO_DATA_PAD_RX_CTRL
> +	EMC_PMACRO_CMD_PAD_RX_CTRL
> +	EMC_PMACRO_DATA_RX_TERM_MODE
> +	EMC_PMACRO_CMD_RX_TERM_MODE
> +	EMC_PMACRO_CMD_PAD_TX_CTRL
> +	EMC_PMACRO_DATA_PAD_TX_CTRL
> +	EMC_PMACRO_COMMON_PAD_TX_CTRL
> +	EMC_PMACRO_VTTGEN_CTRL_0
> +	EMC_PMACRO_VTTGEN_CTRL_1
> +	EMC_PMACRO_VTTGEN_CTRL_2
> +	EMC_PMACRO_BRICK_CTRL_RFU1
> +	EMC_PMACRO_CMD_BRICK_CTRL_FDPD
> +	EMC_PMACRO_BRICK_CTRL_RFU2
> +	EMC_PMACRO_DATA_BRICK_CTRL_FDPD
> +	EMC_PMACRO_BG_BIAS_CTRL_0
> +	EMC_CFG_3
> +	EMC_PMACRO_TX_PWRD_0
> +	EMC_PMACRO_TX_PWRD_1
> +	EMC_PMACRO_TX_PWRD_2
> +	EMC_PMACRO_TX_PWRD_3
> +	EMC_PMACRO_TX_PWRD_4
> +	EMC_PMACRO_TX_PWRD_5
> +	EMC_CONFIG_SAMPLE_DELAY
> +	EMC_PMACRO_TX_SEL_CLK_SRC_0
> +	EMC_PMACRO_TX_SEL_CLK_SRC_1
> +	EMC_PMACRO_TX_SEL_CLK_SRC_2
> +	EMC_PMACRO_TX_SEL_CLK_SRC_3
> +	EMC_PMACRO_TX_SEL_CLK_SRC_4
> +	EMC_PMACRO_TX_SEL_CLK_SRC_5
> +	EMC_PMACRO_DDLL_BYPASS
> +	EMC_PMACRO_DDLL_PWRD_0
> +	EMC_PMACRO_DDLL_PWRD_1
> +	EMC_PMACRO_DDLL_PWRD_2
> +	EMC_PMACRO_CMD_CTRL_0
> +	EMC_PMACRO_CMD_CTRL_1
> +	EMC_PMACRO_CMD_CTRL_2
> +	EMC_TR_TIMING_0
> +	EMC_TR_DVFS
> +	EMC_TR_CTRL_1
> +	EMC_TR_RDV
> +	EMC_TR_QPOP
> +	EMC_TR_RDV_MASK
> +	EMC_MRW14
> +	EMC_TR_QSAFE
> +	EMC_TR_QRST
> +	EMC_TRAINING_CTRL
> +	EMC_TRAINING_SETTLE
> +	EMC_TRAINING_VREF_SETTLE
> +	EMC_TRAINING_CA_FINE_CTRL
> +	EMC_TRAINING_CA_CTRL_MISC
> +	EMC_TRAINING_CA_CTRL_MISC1
> +	EMC_TRAINING_CA_VREF_CTRL
> +	EMC_TRAINING_QUSE_CORS_CTRL
> +	EMC_TRAINING_QUSE_FINE_CTRL
> +	EMC_TRAINING_QUSE_CTRL_MISC
> +	EMC_TRAINING_QUSE_VREF_CTRL
> +	EMC_TRAINING_READ_FINE_CTRL
> +	EMC_TRAINING_READ_CTRL_MISC
> +	EMC_TRAINING_READ_VREF_CTRL
> +	EMC_TRAINING_WRITE_FINE_CTRL
> +	EMC_TRAINING_WRITE_CTRL_MISC
> +	EMC_TRAINING_WRITE_VREF_CTRL
> +	EMC_TRAINING_MPC
> +	EMC_MRW15
> +- nvidia,emc-burst-regs-per-ch : values for the following registers (See TRM
> +				 18.10.2 for register descriptions) the array
> +				 containts 2 values for each register, one per
> +				 channel.
> +	EMC_MRW10
> +	EMC_MRW11
> +	EMC_MRW12
> +	EMC_MRW13
> +- nvidia,emc-trim-regs : values for the following registers (See TRM 18.10.2
> +			 for register descriptions)
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2
> +	EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1
> +	EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2
> +	EMC_PMACRO_IB_VREF_DQS_0
> +	EMC_PMACRO_IB_VREF_DQS_1
> +	EMC_PMACRO_IB_VREF_DQ_0
> +	EMC_PMACRO_IB_VREF_DQ_1
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1
> +	EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2
> +	EMC_PMACRO_QUSE_DDLL_RANK0_0
> +	EMC_PMACRO_QUSE_DDLL_RANK0_1
> +	EMC_PMACRO_QUSE_DDLL_RANK0_2
> +	EMC_PMACRO_QUSE_DDLL_RANK0_3
> +	EMC_PMACRO_QUSE_DDLL_RANK1_0
> +	EMC_PMACRO_QUSE_DDLL_RANK1_1
> +	EMC_PMACRO_QUSE_DDLL_RANK1_2
> +	EMC_PMACRO_QUSE_DDLL_RANK1_3
> +- nvidia,emc-trim-regs-per-ch : values for the following registers (See TRM
> +				18.10.2 for register descriptions)
> +	EMC_CMD_BRLSHFT_0
> +	EMC_CMD_BRLSHFT_1
> +	EMC_DATA_BRLSHFT_0 (channel 0)
> +	EMC_DATA_BRLSHFT_0 (channel 1)
> +	EMC_DATA_BRLSHFT_1 (channel 0)
> +	EMC_DATA_BRLSHFT_1 (channel 1)
> +	EMC_QUSE_BRLSHFT_0
> +	EMC_QUSE_BRLSHFT_1
> +	EMC_QUSE_BRLSHFT_2
> +	EMC_QUSE_BRLSHFT_3
> +- nvidia,emc-vref-regs : values for the following registers (See TRM 18.10.2
> +			 for register descriptions) the array containts 2
> +			 values for each register, one per channel.
> +	 EMC_TRAINING_OPT_DQS_IB_VREF_RANK0
> +	 EMC_TRAINING_OPT_DQS_IB_VREF_RANK1
> +- nvidia,emc-dram-timing-regs : DRAM timing values. These are not written to
> +				registers but used during the sequence.
> +	T_RP : row pre-charge delay
> +	T_FC_LPDDR4 : frequency change time
> +	T_RFC : refresh cycle time
> +	T_PDEX : power down exit delay
> +	RL : mode register read latency
> +- nvidia,emc-training-mod-regs :
> +- nvidia,emc-save-restore-mod-regs :
> +	deprecated, keep for for backward compatibility

This is a new binding, so.. backward compatibility with what? Some old version of downstream bootloader? Do you really need it?

> +- nvidia,emc-burst-mc-regs : values for the following registers
> +			     (See TRM 18.10.1 for register descriptions)
> +	MC_EMEM_ARB_CFG
> +	MC_EMEM_ARB_OUTSTANDING_REQ
> +	MC_EMEM_ARB_REFPB_HP_CTRL
> +	MC_EMEM_ARB_REFPB_BANK_CTRL
> +	MC_EMEM_ARB_TIMING_RCD
> +	MC_EMEM_ARB_TIMING_RP
> +	MC_EMEM_ARB_TIMING_RC
> +	MC_EMEM_ARB_TIMING_RAS
> +	MC_EMEM_ARB_TIMING_FAW
> +	MC_EMEM_ARB_TIMING_RRD
> +	MC_EMEM_ARB_TIMING_RAP2PRE
> +	MC_EMEM_ARB_TIMING_WAP2PRE
> +	MC_EMEM_ARB_TIMING_R2R
> +	MC_EMEM_ARB_TIMING_W2W
> +	MC_EMEM_ARB_TIMING_R2W
> +	MC_EMEM_ARB_TIMING_CCDMW
> +	MC_EMEM_ARB_TIMING_W2R
> +	MC_EMEM_ARB_TIMING_RFCPB
> +	MC_EMEM_ARB_DA_TURNS
> +	MC_EMEM_ARB_DA_COVERS
> +	MC_EMEM_ARB_MISC0
> +	MC_EMEM_ARB_MISC1
> +	MC_EMEM_ARB_MISC2
> +	MC_EMEM_ARB_RING1_THROTTLE
> +	MC_EMEM_ARB_DHYST_CTRL
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6
> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7
> +- nvidia,emc-la-scale-regs : values for the following registers
> +			     (See TRM 18.10.1 for register descriptions)
> +	MC_MLL_MPCORER_PTSA_RATE
> +	MC_FTOP_PTSA_RATE
> +	MC_PTSA_GRANT_DECREMENT
> +	MC_LATENCY_ALLOWANCE_XUSB_0
> +	MC_LATENCY_ALLOWANCE_XUSB_1
> +	MC_LATENCY_ALLOWANCE_TSEC_0
> +	MC_LATENCY_ALLOWANCE_SDMMCA_0
> +	MC_LATENCY_ALLOWANCE_SDMMCAA_0
> +	MC_LATENCY_ALLOWANCE_SDMMC_0
> +	MC_LATENCY_ALLOWANCE_SDMMCAB_0
> +	MC_LATENCY_ALLOWANCE_PPCS_0
> +	MC_LATENCY_ALLOWANCE_PPCS_1
> +	MC_LATENCY_ALLOWANCE_MPCORE_0
> +	MC_LATENCY_ALLOWANCE_HC_0
> +	MC_LATENCY_ALLOWANCE_HC_1
> +	MC_LATENCY_ALLOWANCE_AVPC_0
> +	MC_LATENCY_ALLOWANCE_GPU_0
> +	MC_LATENCY_ALLOWANCE_GPU2_0
> +	MC_LATENCY_ALLOWANCE_NVENC_0
> +	MC_LATENCY_ALLOWANCE_NVDEC_0
> +	MC_LATENCY_ALLOWANCE_VIC_0
> +	MC_LATENCY_ALLOWANCE_VI2_0
> +	MC_LATENCY_ALLOWANCE_ISP2_0
> +	MC_LATENCY_ALLOWANCE_ISP2_1

Shouldn't this be a part of a "Memory Controller" binding like it is done for T124?

-- 
Dmitry

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-04-01 12:12   ` Dmitry Osipenko
@ 2019-04-02  2:26     ` Joseph Lo
  2019-04-02 10:21       ` Dmitry Osipenko
  0 siblings, 1 reply; 28+ messages in thread
From: Joseph Lo @ 2019-04-02  2:26 UTC (permalink / raw)
  To: Dmitry Osipenko, Thierry Reding, Peter De Schrijver,
	Jonathan Hunter, Rob Herring, Stephen Boyd
  Cc: linux-tegra, devicetree, linux-clk, linux-arm-kernel

On 4/1/19 8:12 PM, Dmitry Osipenko wrote:
> 25.03.2019 10:45, Joseph Lo пишет:
>> Add the binding document for the external memory controller (EMC) which
>> communicates with external LPDDR4 devices. It includes the bindings of
>> the EMC node and the EMC table of different rates.
>>
>> To support high rates for LPDDR4, the EMC table must be trained before
>> it can be used for runtime clock switching. It has been done by firmware
>> and merged to the table that Linux kernel uses. For backward
>> compatibility with the devices that had been launched on the market, like
>> Shield and Jetson platforms, the bindings in the EMC table should remain
>> the same. So the firmware can recognize them and merge the trained EMC
>> table for the kernel.
>>
>> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
>>
>> Signed-off-by: Joseph Lo <josephl@nvidia.com>
>> ---
>>   .../nvidia,tegra210-emc.txt                   | 605 ++++++++++++++++++
>>   1 file changed, 605 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>>
>> diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>> new file mode 100644
>> index 000000000000..1f6b6df6d37b
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>> @@ -0,0 +1,605 @@
>> +NVIDIA Tegra210 SoC EMC (external memory controller)
>> +====================================================
snip
>> +- nvidia,emc-training-mod-regs :
>> +- nvidia,emc-save-restore-mod-regs :
>> +	deprecated, keep for for backward compatibility
> 
> This is a new binding, so.. backward compatibility with what? Some old version of downstream bootloader? Do you really need it?
> 
Yes, backward compatibility for the firmware that helps to do the 
initial DDR4 training before we can use that. Although the kernel 
doesn't use that for EMC scaling, the firmware still uses these bindings 
and updates them.

>> +- nvidia,emc-burst-mc-regs : values for the following registers
>> +			     (See TRM 18.10.1 for register descriptions)
>> +	MC_EMEM_ARB_CFG
>> +	MC_EMEM_ARB_OUTSTANDING_REQ
>> +	MC_EMEM_ARB_REFPB_HP_CTRL
>> +	MC_EMEM_ARB_REFPB_BANK_CTRL
>> +	MC_EMEM_ARB_TIMING_RCD
>> +	MC_EMEM_ARB_TIMING_RP
>> +	MC_EMEM_ARB_TIMING_RC
>> +	MC_EMEM_ARB_TIMING_RAS
>> +	MC_EMEM_ARB_TIMING_FAW
>> +	MC_EMEM_ARB_TIMING_RRD
>> +	MC_EMEM_ARB_TIMING_RAP2PRE
>> +	MC_EMEM_ARB_TIMING_WAP2PRE
>> +	MC_EMEM_ARB_TIMING_R2R
>> +	MC_EMEM_ARB_TIMING_W2W
>> +	MC_EMEM_ARB_TIMING_R2W
>> +	MC_EMEM_ARB_TIMING_CCDMW
>> +	MC_EMEM_ARB_TIMING_W2R
>> +	MC_EMEM_ARB_TIMING_RFCPB
>> +	MC_EMEM_ARB_DA_TURNS
>> +	MC_EMEM_ARB_DA_COVERS
>> +	MC_EMEM_ARB_MISC0
>> +	MC_EMEM_ARB_MISC1
>> +	MC_EMEM_ARB_MISC2
>> +	MC_EMEM_ARB_RING1_THROTTLE
>> +	MC_EMEM_ARB_DHYST_CTRL
>> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0
>> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1
>> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2
>> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3
>> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4
>> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5
>> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6
>> +	MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7
>> +- nvidia,emc-la-scale-regs : values for the following registers
>> +			     (See TRM 18.10.1 for register descriptions)
>> +	MC_MLL_MPCORER_PTSA_RATE
>> +	MC_FTOP_PTSA_RATE
>> +	MC_PTSA_GRANT_DECREMENT
>> +	MC_LATENCY_ALLOWANCE_XUSB_0
>> +	MC_LATENCY_ALLOWANCE_XUSB_1
>> +	MC_LATENCY_ALLOWANCE_TSEC_0
>> +	MC_LATENCY_ALLOWANCE_SDMMCA_0
>> +	MC_LATENCY_ALLOWANCE_SDMMCAA_0
>> +	MC_LATENCY_ALLOWANCE_SDMMC_0
>> +	MC_LATENCY_ALLOWANCE_SDMMCAB_0
>> +	MC_LATENCY_ALLOWANCE_PPCS_0
>> +	MC_LATENCY_ALLOWANCE_PPCS_1
>> +	MC_LATENCY_ALLOWANCE_MPCORE_0
>> +	MC_LATENCY_ALLOWANCE_HC_0
>> +	MC_LATENCY_ALLOWANCE_HC_1
>> +	MC_LATENCY_ALLOWANCE_AVPC_0
>> +	MC_LATENCY_ALLOWANCE_GPU_0
>> +	MC_LATENCY_ALLOWANCE_GPU2_0
>> +	MC_LATENCY_ALLOWANCE_NVENC_0
>> +	MC_LATENCY_ALLOWANCE_NVDEC_0
>> +	MC_LATENCY_ALLOWANCE_VIC_0
>> +	MC_LATENCY_ALLOWANCE_VI2_0
>> +	MC_LATENCY_ALLOWANCE_ISP2_0
>> +	MC_LATENCY_ALLOWANCE_ISP2_1
> 
> Shouldn't this be a part of a "Memory Controller" binding like it is done for T124?
> 

The Tegra SoCs before Tegra132 only supports DDR3 or DDR2. The EMC table 
doesn't include the settings of latency allowance registers. For the 
DDR4 support on Tegra210, we will dynamically scale these latency 
allowance registers as well to support faster clock rate. So the 
arbitration can be adapted according to the rate switching.

And the EMC scaling sequence for DDR3 and DDR4 is quite different. So 
the EMC scaling sequence for Tegra124 cannot be used for Tegra210. Same 
as the EMC table bindings.

Thanks,
Joseph

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-04-02  2:26     ` Joseph Lo
@ 2019-04-02 10:21       ` Dmitry Osipenko
  0 siblings, 0 replies; 28+ messages in thread
From: Dmitry Osipenko @ 2019-04-02 10:21 UTC (permalink / raw)
  To: Joseph Lo, Thierry Reding, Peter De Schrijver, Jonathan Hunter,
	Rob Herring, Stephen Boyd
  Cc: linux-tegra, devicetree, linux-clk, linux-arm-kernel

02.04.2019 5:26, Joseph Lo пишет:
> On 4/1/19 8:12 PM, Dmitry Osipenko wrote:
>> 25.03.2019 10:45, Joseph Lo пишет:
>>> Add the binding document for the external memory controller (EMC) which
>>> communicates with external LPDDR4 devices. It includes the bindings of
>>> the EMC node and the EMC table of different rates.
>>>
>>> To support high rates for LPDDR4, the EMC table must be trained before
>>> it can be used for runtime clock switching. It has been done by firmware
>>> and merged to the table that Linux kernel uses. For backward
>>> compatibility with the devices that had been launched on the market, like
>>> Shield and Jetson platforms, the bindings in the EMC table should remain
>>> the same. So the firmware can recognize them and merge the trained EMC
>>> table for the kernel.
>>>
>>> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
>>>
>>> Signed-off-by: Joseph Lo <josephl@nvidia.com>
>>> ---
>>>   .../nvidia,tegra210-emc.txt                   | 605 ++++++++++++++++++
>>>   1 file changed, 605 insertions(+)
>>>   create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>>>
>>> diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>>> new file mode 100644
>>> index 000000000000..1f6b6df6d37b
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>>> @@ -0,0 +1,605 @@
>>> +NVIDIA Tegra210 SoC EMC (external memory controller)
>>> +====================================================
> snip
>>> +- nvidia,emc-training-mod-regs :
>>> +- nvidia,emc-save-restore-mod-regs :
>>> +    deprecated, keep for for backward compatibility
>>
>> This is a new binding, so.. backward compatibility with what? Some old version of downstream bootloader? Do you really need it?
>>
> Yes, backward compatibility for the firmware that helps to do the initial DDR4 training before we can use that. Although the kernel doesn't use that for EMC scaling, the firmware still uses these bindings and updates them.
> 
>>> +- nvidia,emc-burst-mc-regs : values for the following registers
>>> +                 (See TRM 18.10.1 for register descriptions)
>>> +    MC_EMEM_ARB_CFG
>>> +    MC_EMEM_ARB_OUTSTANDING_REQ
>>> +    MC_EMEM_ARB_REFPB_HP_CTRL
>>> +    MC_EMEM_ARB_REFPB_BANK_CTRL
>>> +    MC_EMEM_ARB_TIMING_RCD
>>> +    MC_EMEM_ARB_TIMING_RP
>>> +    MC_EMEM_ARB_TIMING_RC
>>> +    MC_EMEM_ARB_TIMING_RAS
>>> +    MC_EMEM_ARB_TIMING_FAW
>>> +    MC_EMEM_ARB_TIMING_RRD
>>> +    MC_EMEM_ARB_TIMING_RAP2PRE
>>> +    MC_EMEM_ARB_TIMING_WAP2PRE
>>> +    MC_EMEM_ARB_TIMING_R2R
>>> +    MC_EMEM_ARB_TIMING_W2W
>>> +    MC_EMEM_ARB_TIMING_R2W
>>> +    MC_EMEM_ARB_TIMING_CCDMW
>>> +    MC_EMEM_ARB_TIMING_W2R
>>> +    MC_EMEM_ARB_TIMING_RFCPB
>>> +    MC_EMEM_ARB_DA_TURNS
>>> +    MC_EMEM_ARB_DA_COVERS
>>> +    MC_EMEM_ARB_MISC0
>>> +    MC_EMEM_ARB_MISC1
>>> +    MC_EMEM_ARB_MISC2
>>> +    MC_EMEM_ARB_RING1_THROTTLE
>>> +    MC_EMEM_ARB_DHYST_CTRL
>>> +    MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0
>>> +    MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1
>>> +    MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2
>>> +    MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3
>>> +    MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4
>>> +    MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5
>>> +    MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6
>>> +    MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7
>>> +- nvidia,emc-la-scale-regs : values for the following registers
>>> +                 (See TRM 18.10.1 for register descriptions)
>>> +    MC_MLL_MPCORER_PTSA_RATE
>>> +    MC_FTOP_PTSA_RATE
>>> +    MC_PTSA_GRANT_DECREMENT
>>> +    MC_LATENCY_ALLOWANCE_XUSB_0
>>> +    MC_LATENCY_ALLOWANCE_XUSB_1
>>> +    MC_LATENCY_ALLOWANCE_TSEC_0
>>> +    MC_LATENCY_ALLOWANCE_SDMMCA_0
>>> +    MC_LATENCY_ALLOWANCE_SDMMCAA_0
>>> +    MC_LATENCY_ALLOWANCE_SDMMC_0
>>> +    MC_LATENCY_ALLOWANCE_SDMMCAB_0
>>> +    MC_LATENCY_ALLOWANCE_PPCS_0
>>> +    MC_LATENCY_ALLOWANCE_PPCS_1
>>> +    MC_LATENCY_ALLOWANCE_MPCORE_0
>>> +    MC_LATENCY_ALLOWANCE_HC_0
>>> +    MC_LATENCY_ALLOWANCE_HC_1
>>> +    MC_LATENCY_ALLOWANCE_AVPC_0
>>> +    MC_LATENCY_ALLOWANCE_GPU_0
>>> +    MC_LATENCY_ALLOWANCE_GPU2_0
>>> +    MC_LATENCY_ALLOWANCE_NVENC_0
>>> +    MC_LATENCY_ALLOWANCE_NVDEC_0
>>> +    MC_LATENCY_ALLOWANCE_VIC_0
>>> +    MC_LATENCY_ALLOWANCE_VI2_0
>>> +    MC_LATENCY_ALLOWANCE_ISP2_0
>>> +    MC_LATENCY_ALLOWANCE_ISP2_1
>>
>> Shouldn't this be a part of a "Memory Controller" binding like it is done for T124?
>>
> 
> The Tegra SoCs before Tegra132 only supports DDR3 or DDR2. The EMC table doesn't include the settings of latency allowance registers. For the DDR4 support on Tegra210, we will dynamically scale these latency allowance registers as well to support faster clock rate. So the arbitration can be adapted according to the rate switching.
> 
> And the EMC scaling sequence for DDR3 and DDR4 is quite different. So the EMC scaling sequence for Tegra124 cannot be used for Tegra210. Same as the EMC table bindings.

This is not true, the MC configuration part is exactly the same for all of the Tegra's, see T124 driver and the tegra_mc_write_emem_configuration() which writes MC registers defined per-SoC and T210 could easily include the LA registers. But I now see that you mentioned in the cover letter that you're not going to change the binding and want upstream to accept the downstream binding as-is, in this case it's fine to mix EMC with MC. Ultimately it will be nicer if the bootloader firmware could be updated to conform with upstream to have all of the bindings consistent across all of SoC generations, AFAIK that's what other vendors are doing.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 5/8] memory: tegra: Add EMC scaling sequence code for Tegra210
  2019-03-25  7:45 ` [PATCH 5/8] memory: tegra: Add EMC scaling sequence " Joseph Lo
@ 2019-04-02 11:36   ` Dmitry Osipenko
  2019-04-02 14:49     ` Joseph Lo
  0 siblings, 1 reply; 28+ messages in thread
From: Dmitry Osipenko @ 2019-04-02 11:36 UTC (permalink / raw)
  To: Joseph Lo, Thierry Reding, Peter De Schrijver, Jonathan Hunter,
	Rob Herring, Stephen Boyd
  Cc: linux-tegra, devicetree, linux-clk, linux-arm-kernel

25.03.2019 10:45, Joseph Lo пишет:
> This patch includes the sequence for controlling the rate changing for
> EMC frequency and the dynamic training mechanism when the rate reaches
> the higher rates of EMC rate.
> 
> And historically there have been different sequences to change the EMC
> clock. The sequence to be used is specified in the scaling data.
> However, for the currently supported upstreaming platform, only the most
> recent sequence is used. So only support that in this patch.
> 
> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
> 
> Signed-off-by: Joseph Lo <josephl@nvidia.com>
> ---
>  drivers/memory/tegra/Makefile                 |    2 +-
>  drivers/memory/tegra/tegra210-emc-cc-r21021.c | 1962 +++++++++++++++++
>  drivers/memory/tegra/tegra210-emc-reg.h       |  134 ++
>  drivers/memory/tegra/tegra210-emc.c           |    5 +
>  4 files changed, 2102 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/memory/tegra/tegra210-emc-cc-r21021.c
> 
> diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile
> index 36a835620bbd..dcc245b2ef45 100644
> --- a/drivers/memory/tegra/Makefile
> +++ b/drivers/memory/tegra/Makefile
> @@ -12,5 +12,5 @@ obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
>  
>  obj-$(CONFIG_TEGRA20_EMC)  += tegra20-emc.o
>  obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o
> -obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o tegra210-dt-parse.o
> +obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o tegra210-dt-parse.o tegra210-emc-cc-r21021.o
>  obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o
> diff --git a/drivers/memory/tegra/tegra210-emc-cc-r21021.c b/drivers/memory/tegra/tegra210-emc-cc-r21021.c
> new file mode 100644
> index 000000000000..f577a8c3aa95
> --- /dev/null
> +++ b/drivers/memory/tegra/tegra210-emc-cc-r21021.c
> @@ -0,0 +1,1962 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2014-2019, NVIDIA CORPORATION.  All rights reserved.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/io.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/of.h>
> +#include <soc/tegra/mc.h>
> +
> +#include "mc.h"
> +#include "tegra210-emc-reg.h"
> +
> +#define DVFS_CLOCK_CHANGE_VERSION	21021
> +#define EMC_PRELOCK_VERSION		2101
> +
> +#define emc_cc_dbg(t, ...) pr_debug(__VA_ARGS__)
> +
> +/*
> + * Enable flags for specifying verbosity.
> + */
> +#define INFO            (1 << 0)
> +#define STEPS           (1 << 1)
> +#define SUB_STEPS       (1 << 2)
> +#define PRELOCK         (1 << 3)
> +#define PRELOCK_STEPS   (1 << 4)
> +#define ACTIVE_EN       (1 << 5)
> +#define PRAMP_UP        (1 << 6)
> +#define PRAMP_DN        (1 << 7)
> +#define EMA_WRITES      (1 << 10)
> +#define EMA_UPDATES     (1 << 11)
> +#define PER_TRAIN       (1 << 16)
> +#define CC_PRINT        (1 << 17)
> +#define CCFIFO          (1 << 29)
> +#define REGS            (1 << 30)
> +#define REG_LISTS       (1 << 31)
> +
> +enum {
> +	DVFS_SEQUENCE = 1,
> +	WRITE_TRAINING_SEQUENCE = 2,
> +	PERIODIC_TRAINING_SEQUENCE = 3,
> +	DVFS_PT1 = 10,
> +	DVFS_UPDATE = 11,
> +	TRAINING_PT1 = 12,
> +	TRAINING_UPDATE = 13,
> +	PERIODIC_TRAINING_UPDATE = 14
> +};
> +
> +/*
> + * PTFV defines - basically just indexes into the per table PTFV array.
> + */
> +#define PTFV_DQSOSC_MOVAVG_C0D0U0_INDEX		0
> +#define PTFV_DQSOSC_MOVAVG_C0D0U1_INDEX		1
> +#define PTFV_DQSOSC_MOVAVG_C0D1U0_INDEX		2
> +#define PTFV_DQSOSC_MOVAVG_C0D1U1_INDEX		3
> +#define PTFV_DQSOSC_MOVAVG_C1D0U0_INDEX		4
> +#define PTFV_DQSOSC_MOVAVG_C1D0U1_INDEX		5
> +#define PTFV_DQSOSC_MOVAVG_C1D1U0_INDEX		6
> +#define PTFV_DQSOSC_MOVAVG_C1D1U1_INDEX		7
> +#define PTFV_DVFS_SAMPLES_INDEX			9
> +#define PTFV_MOVAVG_WEIGHT_INDEX		10
> +#define PTFV_CONFIG_CTRL_INDEX			11
> +
> +#define PTFV_CONFIG_CTRL_USE_PREVIOUS_EMA	(1 << 0)
> +
> +/*
> + * Do arithmetic in fixed point.
> + */
> +#define MOVAVG_PRECISION_FACTOR		100
> +
> +/*
> + * The division portion of the average operation.
> + */
> +#define __AVERAGE_PTFV(dev)						\
> +	({ next_timing->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] = \
> +	   next_timing->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] / \
> +	   next_timing->ptfv_list[PTFV_DVFS_SAMPLES_INDEX]; })
> +
> +/*
> + * Convert val to fixed point and add it to the temporary average.
> + */
> +#define __INCREMENT_PTFV(dev, val)					\
> +	({ next_timing->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] += \
> +	   ((val) * MOVAVG_PRECISION_FACTOR); })
> +
> +/*
> + * Convert a moving average back to integral form and return the value.
> + */
> +#define __MOVAVG_AC(timing, dev)					\
> +	((timing)->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] /	\
> +	 MOVAVG_PRECISION_FACTOR)
> +
> +/* Weighted update. */
> +#define __WEIGHTED_UPDATE_PTFV(dev, nval)				\
> +	do {								\
> +		int w = PTFV_MOVAVG_WEIGHT_INDEX;			\
> +		int dqs = PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX;		\
> +									\
> +		next_timing->ptfv_list[dqs] =				\
> +			((nval * MOVAVG_PRECISION_FACTOR) +		\
> +			 (next_timing->ptfv_list[dqs] *			\
> +			  next_timing->ptfv_list[w])) /			\
> +			(next_timing->ptfv_list[w] + 1);		\
> +									\
> +		emc_cc_dbg(EMA_UPDATES, "%s: (s=%u) EMA: %u\n",		\
> +			   __stringify(dev), nval,			\
> +			   next_timing->ptfv_list[dqs]);		\
> +	} while (0)
> +
> +/* Access a particular average. */
> +#define __MOVAVG(timing, dev)                      \
> +	((timing)->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX])
> +
> +static u32 update_clock_tree_delay(struct tegra_emc *emc,
> +				   u32 dram_dev_num, u32 channel_mode, int type)
> +{
> +	u32 mrr_req = 0, mrr_data = 0;
> +	u32 temp0_0 = 0, temp0_1 = 0, temp1_0 = 0, temp1_1 = 0;
> +	s32 tdel = 0, tmdel = 0, adel = 0;
> +	u32 cval = 0;
> +	struct emc_table *last_timing = emc->current_timing;
> +	struct emc_table *next_timing = emc->next_timing;
> +	u32 last_timing_rate_mhz = last_timing->rate / 1000;
> +	u32 next_timing_rate_mhz = next_timing->rate / 1000;
> +	int dvfs_pt1 = type == DVFS_PT1;
> +	int dvfs_update = type == DVFS_UPDATE;
> +	int periodic_training_update = type == PERIODIC_TRAINING_UPDATE;
> +
> +	/*
> +	 * Dev0 MSB.
> +	 */
> +	if (dvfs_pt1 || periodic_training_update) {
> +		mrr_req = (2 << EMC_MRR_DEV_SEL_SHIFT) |
> +			(19 << EMC_MRR_MA_SHIFT);
> +		emc_writel(emc, mrr_req, EMC_MRR);
> +
> +		WARN(wait_for_update(emc, EMC_EMC_STATUS,
> +				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
> +		     "Timed out waiting for MRR 19 (ch=0)\n");
> +		if (channel_mode == DUAL_CHANNEL)
> +			WARN(wait_for_update(emc, EMC_EMC_STATUS,
> +					     EMC_EMC_STATUS_MRR_DIVLD, 1,
> +					     REG_EMC1),
> +			     "Timed out waiting for MRR 19 (ch=1)\n");
> +
> +		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
> +			   EMC_MRR_DATA_SHIFT;
> +
> +		temp0_0 = (mrr_data & 0xff) << 8;
> +		temp0_1 = mrr_data & 0xff00;
> +
> +		if (channel_mode == DUAL_CHANNEL) {
> +			mrr_data = (emc1_readl(emc, EMC_MRR) &
> +				    EMC_MRR_DATA_MASK) <<
> +				   EMC_MRR_DATA_SHIFT;
> +			temp1_0 = (mrr_data & 0xff) << 8;
> +			temp1_1 = mrr_data & 0xff00;
> +		}
> +
> +		/*
> +		 * Dev0 LSB.
> +		 */
> +		mrr_req = (mrr_req & ~EMC_MRR_MA_MASK) |
> +			  (18 << EMC_MRR_MA_SHIFT);
> +		emc_writel(emc, mrr_req, EMC_MRR);
> +
> +		WARN(wait_for_update(emc, EMC_EMC_STATUS,
> +				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
> +		     "Timed out waiting for MRR 18 (ch=0)\n");
> +		if (channel_mode == DUAL_CHANNEL)
> +			WARN(wait_for_update(emc, EMC_EMC_STATUS,
> +					     EMC_EMC_STATUS_MRR_DIVLD, 1,
> +					     REG_EMC1),
> +			     "Timed out waiting for MRR 18 (ch=1)\n");
> +
> +		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
> +			   EMC_MRR_DATA_SHIFT;
> +
> +		temp0_0 |= mrr_data & 0xff;
> +		temp0_1 |= (mrr_data & 0xff00) >> 8;
> +
> +		if (channel_mode == DUAL_CHANNEL) {
> +			mrr_data = (emc1_readl(emc, EMC_MRR) &
> +				    EMC_MRR_DATA_MASK) <<
> +				   EMC_MRR_DATA_SHIFT;
> +			temp1_0 |= (mrr_data & 0xff);
> +			temp1_1 |= (mrr_data & 0xff00) >> 8;
> +		}
> +	}
> +
> +	if (dvfs_pt1 || periodic_training_update)
> +		cval = (1000000 * tegra210_actual_osc_clocks(
> +					last_timing->run_clocks)) /
> +		       (last_timing_rate_mhz * 2 * temp0_0);
> +
> +	if (dvfs_pt1)
> +		__INCREMENT_PTFV(C0D0U0, cval);
> +	else if (dvfs_update)
> +		__AVERAGE_PTFV(C0D0U0);
> +	else if (periodic_training_update)
> +		__WEIGHTED_UPDATE_PTFV(C0D0U0, cval);
> +
> +	if (dvfs_update || periodic_training_update) {
> +		tdel = next_timing->current_dram_clktree_c0d0u0 -
> +			__MOVAVG_AC(next_timing, C0D0U0);
> +		tmdel = (tdel < 0) ? -1 * tdel : tdel;
> +		adel = tmdel;
> +		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
> +		    next_timing->tree_margin)
> +			next_timing->current_dram_clktree_c0d0u0 =
> +				__MOVAVG_AC(next_timing, C0D0U0);
> +	}
> +
> +	if (dvfs_pt1 || periodic_training_update)
> +		cval = (1000000 * tegra210_actual_osc_clocks(
> +					last_timing->run_clocks)) /
> +		       (last_timing_rate_mhz * 2 * temp0_1);
> +
> +	if (dvfs_pt1)
> +		__INCREMENT_PTFV(C0D0U1, cval);
> +	else if (dvfs_update)
> +		__AVERAGE_PTFV(C0D0U1);
> +	else if (periodic_training_update)
> +		__WEIGHTED_UPDATE_PTFV(C0D0U1, cval);
> +
> +	if (dvfs_update || periodic_training_update) {
> +		tdel = next_timing->current_dram_clktree_c0d0u1 -
> +			__MOVAVG_AC(next_timing, C0D0U1);
> +		tmdel = (tdel < 0) ? -1 * tdel : tdel;
> +
> +		if (tmdel > adel)
> +			adel = tmdel;
> +
> +		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
> +		    next_timing->tree_margin)
> +			next_timing->current_dram_clktree_c0d0u1 =
> +				__MOVAVG_AC(next_timing, C0D0U1);
> +	}
> +
> +	if (channel_mode == DUAL_CHANNEL) {
> +		if (dvfs_pt1 || periodic_training_update)
> +			cval = (1000000 * tegra210_actual_osc_clocks(
> +						last_timing->run_clocks)) /
> +			       (last_timing_rate_mhz * 2 * temp1_0);
> +		if (dvfs_pt1)
> +			__INCREMENT_PTFV(C1D0U0, cval);
> +		else if (dvfs_update)
> +			__AVERAGE_PTFV(C1D0U0);
> +		else if (periodic_training_update)
> +			__WEIGHTED_UPDATE_PTFV(C1D0U0, cval);
> +
> +		if (dvfs_update || periodic_training_update) {
> +			tdel = next_timing->current_dram_clktree_c1d0u0 -
> +				__MOVAVG_AC(next_timing, C1D0U0);
> +			tmdel = (tdel < 0) ? -1 * tdel : tdel;
> +
> +			if (tmdel > adel)
> +				adel = tmdel;
> +
> +			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
> +			    next_timing->tree_margin)
> +				next_timing->current_dram_clktree_c1d0u0 =
> +					__MOVAVG_AC(next_timing, C1D0U0);
> +		}
> +
> +		if (dvfs_pt1 || periodic_training_update)
> +			cval = (1000000 * tegra210_actual_osc_clocks(
> +						last_timing->run_clocks)) /
> +			       (last_timing_rate_mhz * 2 * temp1_1);
> +		if (dvfs_pt1)
> +			__INCREMENT_PTFV(C1D0U1, cval);
> +		else if (dvfs_update)
> +			__AVERAGE_PTFV(C1D0U1);
> +		else if (periodic_training_update)
> +			__WEIGHTED_UPDATE_PTFV(C1D0U1, cval);
> +
> +		if (dvfs_update || periodic_training_update) {
> +			tdel = next_timing->current_dram_clktree_c1d0u1 -
> +				__MOVAVG_AC(next_timing, C1D0U1);
> +			tmdel = (tdel < 0) ? -1 * tdel : tdel;
> +
> +			if (tmdel > adel)
> +				adel = tmdel;
> +
> +			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
> +			    next_timing->tree_margin)
> +				next_timing->current_dram_clktree_c1d0u1 =
> +					__MOVAVG_AC(next_timing, C1D0U1);
> +		}
> +	}
> +
> +	if (emc->dram_dev_num != TWO_RANK)
> +		goto done;
> +
> +	/*
> +	 * Dev1 MSB.
> +	 */
> +	if (dvfs_pt1 || periodic_training_update) {
> +		mrr_req = (1 << EMC_MRR_DEV_SEL_SHIFT) |
> +			  (19 << EMC_MRR_MA_SHIFT);
> +		emc_writel(emc, mrr_req, EMC_MRR);
> +
> +		WARN(wait_for_update(emc, EMC_EMC_STATUS,
> +				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
> +		     "Timed out waiting for MRR 19 (ch=0)\n");
> +		if (channel_mode == DUAL_CHANNEL)
> +			WARN(wait_for_update(emc, EMC_EMC_STATUS,
> +					     EMC_EMC_STATUS_MRR_DIVLD, 1,
> +					     REG_EMC1),
> +			     "Timed out waiting for MRR 19 (ch=1)\n");
> +
> +		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
> +			   EMC_MRR_DATA_SHIFT;
> +
> +		temp0_0 = (mrr_data & 0xff) << 8;
> +		temp0_1 = mrr_data & 0xff00;
> +
> +		if (channel_mode == DUAL_CHANNEL) {
> +			mrr_data = (emc1_readl(emc, EMC_MRR) &
> +				    EMC_MRR_DATA_MASK) <<
> +				   EMC_MRR_DATA_SHIFT;
> +			temp1_0 = (mrr_data & 0xff) << 8;
> +			temp1_1 = mrr_data & 0xff00;
> +		}
> +
> +		/*
> +		 * Dev1 LSB.
> +		 */
> +		mrr_req = (mrr_req & ~EMC_MRR_MA_MASK) |
> +			  (18 << EMC_MRR_MA_SHIFT);
> +		emc_writel(emc, mrr_req, EMC_MRR);
> +
> +		WARN(wait_for_update(emc, EMC_EMC_STATUS,
> +				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
> +		     "Timed out waiting for MRR 18 (ch=0)\n");
> +		if (channel_mode == DUAL_CHANNEL)
> +			WARN(wait_for_update(emc, EMC_EMC_STATUS,
> +					     EMC_EMC_STATUS_MRR_DIVLD, 1,
> +					     REG_EMC1),
> +			     "Timed out waiting for MRR 18 (ch=1)\n");
> +
> +		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
> +			   EMC_MRR_DATA_SHIFT;
> +
> +		temp0_0 |= mrr_data & 0xff;
> +		temp0_1 |= (mrr_data & 0xff00) >> 8;
> +
> +		if (channel_mode == DUAL_CHANNEL) {
> +			mrr_data = (emc1_readl(emc, EMC_MRR) &
> +				    EMC_MRR_DATA_MASK) <<
> +				   EMC_MRR_DATA_SHIFT;
> +			temp1_0 |= (mrr_data & 0xff);
> +			temp1_1 |= (mrr_data & 0xff00) >> 8;
> +		}
> +	}
> +
> +	if (dvfs_pt1 || periodic_training_update)
> +		cval = (1000000 * tegra210_actual_osc_clocks(
> +					last_timing->run_clocks)) /
> +		       (last_timing_rate_mhz * 2 * temp0_0);
> +
> +	if (dvfs_pt1)
> +		__INCREMENT_PTFV(C0D1U0, cval);
> +	else if (dvfs_update)
> +		__AVERAGE_PTFV(C0D1U0);
> +	else if (periodic_training_update)
> +		__WEIGHTED_UPDATE_PTFV(C0D1U0, cval);
> +
> +	if (dvfs_update || periodic_training_update) {
> +		tdel = next_timing->current_dram_clktree_c0d1u0 -
> +		       __MOVAVG_AC(next_timing, C0D1U0);
> +		tmdel = (tdel < 0) ? -1 * tdel : tdel;
> +		if (tmdel > adel)
> +			adel = tmdel;
> +
> +		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
> +		    next_timing->tree_margin)
> +			next_timing->current_dram_clktree_c0d1u0 =
> +				__MOVAVG_AC(next_timing, C0D1U0);
> +	}
> +
> +	if (dvfs_pt1 || periodic_training_update)
> +		cval = (1000000 * tegra210_actual_osc_clocks(
> +					last_timing->run_clocks)) /
> +		       (last_timing_rate_mhz * 2 * temp0_1);
> +
> +	if (dvfs_pt1)
> +		__INCREMENT_PTFV(C0D1U1, cval);
> +	else if (dvfs_update)
> +		__AVERAGE_PTFV(C0D1U1);
> +	else if (periodic_training_update)
> +		__WEIGHTED_UPDATE_PTFV(C0D1U1, cval);
> +
> +	if (dvfs_update || periodic_training_update) {
> +		tdel = next_timing->current_dram_clktree_c0d1u1 -
> +		       __MOVAVG_AC(next_timing, C0D1U1);
> +		tmdel = (tdel < 0) ? -1 * tdel : tdel;
> +		if (tmdel > adel)
> +			adel = tmdel;
> +
> +		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
> +		    next_timing->tree_margin)
> +			next_timing->current_dram_clktree_c0d1u1 =
> +				__MOVAVG_AC(next_timing, C0D1U1);
> +	}
> +
> +	if (channel_mode == DUAL_CHANNEL) {
> +		if (dvfs_pt1 || periodic_training_update)
> +			cval = (1000000 * tegra210_actual_osc_clocks(
> +						last_timing->run_clocks)) /
> +			       (last_timing_rate_mhz * 2 * temp1_0);
> +
> +		if (dvfs_pt1)
> +			__INCREMENT_PTFV(C1D1U0, cval);
> +		else if (dvfs_update)
> +			__AVERAGE_PTFV(C1D1U0);
> +		else if (periodic_training_update)
> +			__WEIGHTED_UPDATE_PTFV(C1D1U0, cval);
> +
> +		if (dvfs_update || periodic_training_update) {
> +			tdel = next_timing->current_dram_clktree_c1d1u0 -
> +			       __MOVAVG_AC(next_timing, C1D1U0);
> +			tmdel = (tdel < 0) ? -1 * tdel : tdel;
> +			if (tmdel > adel)
> +				adel = tmdel;
> +
> +			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
> +			    next_timing->tree_margin)
> +				next_timing->current_dram_clktree_c1d1u0 =
> +					__MOVAVG_AC(next_timing, C1D1U0);
> +		}
> +
> +		if (dvfs_pt1 || periodic_training_update)
> +			cval = (1000000 * tegra210_actual_osc_clocks(
> +						last_timing->run_clocks)) /
> +			       (last_timing_rate_mhz * 2 * temp1_1);
> +
> +		if (dvfs_pt1)
> +			__INCREMENT_PTFV(C1D1U1, cval);
> +		else if (dvfs_update)
> +			__AVERAGE_PTFV(C1D1U1);
> +		else if (periodic_training_update)
> +			__WEIGHTED_UPDATE_PTFV(C1D1U1, cval);
> +
> +		if (dvfs_update || periodic_training_update) {
> +			tdel = next_timing->current_dram_clktree_c1d1u1 -
> +			       __MOVAVG_AC(next_timing, C1D1U1);
> +			tmdel = (tdel < 0) ? -1 * tdel : tdel;
> +			if (tmdel > adel)
> +				adel = tmdel;
> +
> +			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
> +			    next_timing->tree_margin)
> +				next_timing->current_dram_clktree_c1d1u1 =
> +					__MOVAVG_AC(next_timing, C1D1U1);
> +		}
> +	}
> +
> +done:
> +	return adel;
> +}
> +
> +static u32 periodic_compensation_handler(struct tegra_emc *emc, u32 type,
> +					 u32 dram_dev_num,
> +					 u32 channel_mode,
> +					 struct emc_table *last_timing,
> +					 struct emc_table *next_timing)
> +{
> +#define __COPY_EMA(nt, lt, dev)						\
> +	({ __MOVAVG(nt, dev) = __MOVAVG(lt, dev) *			\
> +	   (nt)->ptfv_list[PTFV_DVFS_SAMPLES_INDEX]; })
> +
> +	u32 i;
> +	u32 adel = 0;
> +	u32 samples = next_timing->ptfv_list[PTFV_DVFS_SAMPLES_INDEX];
> +	u32 delay = 2 +
> +		(1000 * tegra210_actual_osc_clocks(last_timing->run_clocks) /
> +		last_timing->rate);
> +
> +	if (!next_timing->periodic_training)
> +		return 0;
> +
> +	if (type == DVFS_SEQUENCE) {
> +		if (last_timing->periodic_training &&
> +		    (next_timing->ptfv_list[PTFV_CONFIG_CTRL_INDEX] &
> +		     PTFV_CONFIG_CTRL_USE_PREVIOUS_EMA)) {
> +			/*
> +			 * If the previous frequency was using periodic
> +			 * calibration then we can reuse the previous
> +			 * frequencies EMA data.
> +			 */
> +			__COPY_EMA(next_timing, last_timing, C0D0U0);
> +			__COPY_EMA(next_timing, last_timing, C0D0U1);
> +			__COPY_EMA(next_timing, last_timing, C1D0U0);
> +			__COPY_EMA(next_timing, last_timing, C1D0U1);
> +			__COPY_EMA(next_timing, last_timing, C0D1U0);
> +			__COPY_EMA(next_timing, last_timing, C0D1U1);
> +			__COPY_EMA(next_timing, last_timing, C1D1U0);
> +			__COPY_EMA(next_timing, last_timing, C1D1U1);
> +		} else {
> +			/* Reset the EMA.*/
> +			__MOVAVG(next_timing, C0D0U0) = 0;
> +			__MOVAVG(next_timing, C0D0U1) = 0;
> +			__MOVAVG(next_timing, C1D0U0) = 0;
> +			__MOVAVG(next_timing, C1D0U1) = 0;
> +			__MOVAVG(next_timing, C0D1U0) = 0;
> +			__MOVAVG(next_timing, C0D1U1) = 0;
> +			__MOVAVG(next_timing, C1D1U0) = 0;
> +			__MOVAVG(next_timing, C1D1U1) = 0;
> +
> +			for (i = 0; i < samples; i++) {
> +				tegra210_start_periodic_compensation(emc);
> +				udelay(delay);
> +
> +				/*
> +				 * Generate next sample of data.
> +				 */
> +				adel = update_clock_tree_delay(emc,
> +							emc->dram_dev_num,
> +							channel_mode,
> +							DVFS_PT1);
> +			}
> +		}
> +
> +		/*
> +		 * Seems like it should be part of the
> +		 * 'if (last_timing->periodic_training)' conditional
> +		 * since is already done for the else clause.
> +		 */
> +		adel = update_clock_tree_delay(emc,
> +					       emc->dram_dev_num,
> +					       channel_mode,
> +					       DVFS_UPDATE);
> +	}
> +
> +	if (type == PERIODIC_TRAINING_SEQUENCE) {
> +		tegra210_start_periodic_compensation(emc);
> +		udelay(delay);
> +
> +		adel = update_clock_tree_delay(emc,
> +					       emc->dram_dev_num,
> +					       channel_mode,
> +					       PERIODIC_TRAINING_UPDATE);
> +	}
> +
> +	return adel;
> +}
> +
> +u32 __do_periodic_emc_compensation_r21021(struct tegra_emc *emc)
> +{
> +	u32 dram_dev_num;
> +	u32 channel_mode;
> +	u32 emc_cfg, emc_cfg_o;
> +	u32 emc_dbg_o;
> +	u32 del, i;
> +	u32 list[] = {
> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0,
> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1,
> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2,
> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3,
> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0,
> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1,
> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2,
> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3,
> +		EMC_DATA_BRLSHFT_0,
> +		EMC_DATA_BRLSHFT_1
> +	};
> +	u32 items = ARRAY_SIZE(list);
> +	u32 emc_cfg_update;
> +	struct emc_table *current_timing = emc->current_timing;
> +
> +	if (current_timing->periodic_training) {
> +		channel_mode =
> +			!!(current_timing->burst_regs[EMC_FBIO_CFG7_INDEX] &
> +			(1 << 2));
> +		dram_dev_num = 1 + (mc_readl(emc->mc, MC_EMEM_ADR_CFG) & 0x1);
> +
> +		emc_cc_dbg(PER_TRAIN, "Periodic training starting\n");
> +
> +		emc_dbg_o = emc_readl(emc, EMC_DBG);
> +		emc_cfg_o = emc_readl(emc, EMC_CFG);
> +		emc_cfg = emc_cfg_o & ~(EMC_CFG_DYN_SELF_REF |
> +					EMC_CFG_DRAM_ACPD |
> +					EMC_CFG_DRAM_CLKSTOP_PD |
> +					EMC_CFG_DRAM_CLKSTOP_PD);
> +
> +
> +		/*
> +		 * 1. Power optimizations should be off.
> +		 */
> +		emc_writel(emc, emc_cfg, EMC_CFG);
> +
> +		/* Does emc_timing_update() for above changes. */
> +		tegra210_dll_disable(emc, channel_mode);
> +
> +		wait_for_update(emc, EMC_EMC_STATUS,
> +				EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK, 0,
> +				REG_EMC);
> +		if (channel_mode)
> +			wait_for_update(emc, EMC_EMC_STATUS,
> +					EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK,
> +					0, REG_EMC1);
> +
> +		wait_for_update(emc, EMC_EMC_STATUS,
> +				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
> +				REG_EMC);
> +		if (channel_mode)
> +			wait_for_update(emc, EMC_EMC_STATUS,
> +				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
> +				REG_EMC1);
> +
> +		emc_cfg_update = emc_readl(emc, EMC_CFG_UPDATE);
> +		emc_writel(emc, (emc_cfg_update &
> +			    ~EMC_CFG_UPDATE_UPDATE_DLL_IN_UPDATE_MASK) |
> +			   (2 << EMC_CFG_UPDATE_UPDATE_DLL_IN_UPDATE_SHIFT),
> +			   EMC_CFG_UPDATE);
> +
> +		/*
> +		 * 2. osc kick off - this assumes training and dvfs have set
> +		 *    correct MR23.
> +		 */
> +		tegra210_start_periodic_compensation(emc);
> +
> +		/*
> +		 * 3. Let dram capture its clock tree delays.
> +		 */
> +		udelay((tegra210_actual_osc_clocks(current_timing->run_clocks) *
> +			1000) /
> +		       current_timing->rate + 1);
> +
> +		/*
> +		 * 4. Check delta wrt previous values (save value if margin
> +		 *    exceeds what is set in table).
> +		 */
> +		del = periodic_compensation_handler(emc,
> +						    PERIODIC_TRAINING_SEQUENCE,
> +						    dram_dev_num,
> +						    channel_mode,
> +						    current_timing,
> +						    current_timing);
> +
> +		/*
> +		 * 5. Apply compensation w.r.t. trained values (if clock tree
> +		 *    has drifted more than the set margin).
> +		 */
> +		if (current_timing->tree_margin <
> +		    ((del * 128 * (current_timing->rate / 1000)) / 1000000)) {
> +			for (i = 0; i < items; i++) {
> +				u32 tmp =
> +				tegra210_apply_periodic_compensation_trimmer(
> +				current_timing, list[i]);
> +
> +				emc_cc_dbg(EMA_WRITES, "0x%08x <= 0x%08x\n",
> +					   list[i], tmp);
> +				emc_writel(emc, tmp, list[i]);
> +			}
> +		}
> +
> +		emc_writel(emc, emc_cfg_o, EMC_CFG);
> +
> +		/*
> +		 * 6. Timing update actally applies the new trimmers.
> +		 */
> +		emc_timing_update(emc, channel_mode);
> +
> +		/* 6.1. Restore the UPDATE_DLL_IN_UPDATE field. */
> +		emc_writel(emc, emc_cfg_update, EMC_CFG_UPDATE);
> +
> +		/* 6.2. Restore the DLL. */
> +		tegra210_dll_enable(emc, channel_mode);
> +
> +		/*
> +		 * 7. Copy over the periodic training registers that we updated
> +		 *    here to the corresponding derated/non-derated table.
> +		 */
> +		tegra210_update_emc_alt_timing(emc, current_timing);
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * Do the clock change sequence.
> + */
> +void emc_set_clock_r21021(struct tegra_emc *emc, u32 clksrc)
> +{
> +	/*
> +	 * This is the timing table for the source frequency. It does _not_
> +	 * necessarily correspond to the actual timing values in the EMC at the
> +	 * moment. If the boot BCT differs from the table then this can happen.
> +	 * However, we need it for accessing the dram_timings (which are not
> +	 * really registers) array for the current frequency.
> +	 */
> +	struct emc_table *fake_timing;
> +	struct emc_table *last_timing = emc->current_timing;
> +	struct emc_table *next_timing = emc->next_timing;
> +
> +	u32 i, tmp;
> +
> +	u32 cya_allow_ref_cc = 0, ref_b4_sref_en = 0, cya_issue_pc_ref = 0;
> +
> +	u32 zqcal_before_cc_cutoff = 2400; /* In picoseconds */
> +	u32 ref_delay_mult;
> +	u32 ref_delay;
> +	s32 zq_latch_dvfs_wait_time;
> +	s32 tZQCAL_lpddr4_fc_adj;
> +	/* Scaled by x1000 */
> +	u32 tFC_lpddr4 = 1000 * next_timing->dram_timings[T_FC_LPDDR4];
> +	u32 tZQCAL_lpddr4 = 1000000;
> +
> +	u32 dram_type, dram_dev_num, shared_zq_resistor;
> +	u32 channel_mode;
> +	u32 is_lpddr3;
> +
> +	u32 emc_cfg, emc_sel_dpd_ctrl, emc_cfg_reg;
> +
> +	u32 emc_dbg;
> +	u32 emc_zcal_interval;
> +	u32 emc_zcal_wait_cnt_old;
> +	u32 emc_zcal_wait_cnt_new;
> +	u32 emc_dbg_active;
> +	u32 zq_op;
> +	u32 zcal_wait_time_clocks;
> +	u32 zcal_wait_time_ps;
> +
> +	u32 emc_auto_cal_config;
> +	u32 auto_cal_en;
> +
> +	u32 mr13_catr_enable;
> +
> +	u32 ramp_up_wait = 0, ramp_down_wait = 0;
> +
> +	/* In picoseconds. */
> +	u32 source_clock_period;
> +	u32 destination_clock_period;
> +
> +	u32 emc_dbg_o;
> +	u32 emc_cfg_pipe_clk_o;
> +	u32 emc_pin_o;
> +
> +	u32 mr13_flip_fspwr;
> +	u32 mr13_flip_fspop;
> +
> +	u32 opt_zcal_en_cc;
> +	u32 opt_do_sw_qrst = 1;
> +	u32 opt_dvfs_mode;
> +	u32 opt_dll_mode;
> +	u32 opt_cc_short_zcal = 1;
> +	u32 opt_short_zcal = 1;
> +	u32 save_restore_clkstop_pd = 1;
> +
> +	u32 prelock_dll_en = 0, dll_out;
> +
> +	int next_push, next_dq_e_ivref, next_dqs_e_ivref;
> +
> +	u32 opt_war_200024907;
> +	u32 zq_wait_long;
> +	u32 zq_wait_short;
> +
> +	u32 bg_regulator_switch_complete_wait_clks;
> +	u32 bg_regulator_mode_change;
> +	u32 enable_bglp_regulator;
> +	u32 enable_bg_regulator;
> +
> +	u32 tRTM;
> +	u32 RP_war;
> +	u32 R2P_war;
> +	u32 TRPab_war;
> +	s32 nRTP;
> +	u32 deltaTWATM;
> +	u32 W2P_war;
> +	u32 tRPST;
> +
> +	u32 mrw_req;
> +	u32 adel = 0, compensate_trimmer_applicable = 0;
> +	u32 next_timing_rate_mhz = next_timing->rate / 1000;
> +
> +	static u32 fsp_for_next_freq;
> +
> +	emc_cc_dbg(INFO, "Running clock change.\n");
> +
> +	fake_timing = get_timing_from_freq(emc, last_timing->rate);
> +
> +	fsp_for_next_freq = !fsp_for_next_freq;
> +
> +	dram_type = emc_readl(emc, EMC_FBIO_CFG5) &
> +		    EMC_FBIO_CFG5_DRAM_TYPE_MASK >>
> +		    EMC_FBIO_CFG5_DRAM_TYPE_SHIFT;
> +	shared_zq_resistor = last_timing->burst_regs[EMC_ZCAL_WAIT_CNT_INDEX] &
> +			     1 << 31;
> +	channel_mode = !!(last_timing->burst_regs[EMC_FBIO_CFG7_INDEX] &
> +			  1 << 2);
> +	opt_zcal_en_cc = (next_timing->burst_regs[EMC_ZCAL_INTERVAL_INDEX] &&
> +			  !last_timing->burst_regs[EMC_ZCAL_INTERVAL_INDEX]) ||
> +			  dram_type == DRAM_TYPE_LPDDR4;
> +	opt_dll_mode = (dram_type == DRAM_TYPE_DDR3) ?
> +		       get_dll_state(next_timing) : DLL_OFF;
> +	is_lpddr3 = (dram_type == DRAM_TYPE_LPDDR2) &&
> +		    next_timing->burst_regs[EMC_FBIO_CFG5_INDEX] &
> +		    1 << 25;
> +	opt_war_200024907 = (dram_type == DRAM_TYPE_LPDDR4);
> +	opt_dvfs_mode = MAN_SR;
> +	dram_dev_num = (mc_readl(emc->mc, MC_EMEM_ADR_CFG) & 0x1) + 1;
> +
> +	emc_cfg_reg = emc_readl(emc, EMC_CFG);
> +	emc_auto_cal_config = emc_readl(emc, EMC_AUTO_CAL_CONFIG);
> +
> +	source_clock_period = 1000000000 / last_timing->rate;
> +	destination_clock_period = 1000000000 / next_timing->rate;
> +
> +	tZQCAL_lpddr4_fc_adj = (destination_clock_period >
> +				zqcal_before_cc_cutoff) ?
> +		tZQCAL_lpddr4 / destination_clock_period :
> +		(tZQCAL_lpddr4 - tFC_lpddr4) / destination_clock_period;
> +	emc_dbg_o = emc_readl(emc, EMC_DBG);
> +	emc_pin_o = emc_readl(emc, EMC_PIN);
> +	emc_cfg_pipe_clk_o = emc_readl(emc, EMC_CFG_PIPE_CLK);
> +	emc_dbg = emc_dbg_o;
> +
> +	emc_cfg = next_timing->burst_regs[EMC_CFG_INDEX];
> +	emc_cfg &= ~(EMC_CFG_DYN_SELF_REF | EMC_CFG_DRAM_ACPD |
> +		     EMC_CFG_DRAM_CLKSTOP_SR | EMC_CFG_DRAM_CLKSTOP_PD);
> +	emc_sel_dpd_ctrl = next_timing->emc_sel_dpd_ctrl;
> +	emc_sel_dpd_ctrl &= ~(EMC_SEL_DPD_CTRL_CLK_SEL_DPD_EN |
> +			      EMC_SEL_DPD_CTRL_CA_SEL_DPD_EN |
> +			      EMC_SEL_DPD_CTRL_RESET_SEL_DPD_EN |
> +			      EMC_SEL_DPD_CTRL_ODT_SEL_DPD_EN |
> +			      EMC_SEL_DPD_CTRL_DATA_SEL_DPD_EN);
> +
> +	emc_cc_dbg(INFO, "Clock change version: %d\n",
> +		   DVFS_CLOCK_CHANGE_VERSION);
> +	emc_cc_dbg(INFO, "DRAM type = %d\n", emc->dram_type);
> +	emc_cc_dbg(INFO, "DRAM dev #: %d\n", dram_dev_num);
> +	emc_cc_dbg(INFO, "Next EMC clksrc: 0x%08x\n", clksrc);
> +	emc_cc_dbg(INFO, "DLL clksrc:      0x%08x\n", next_timing->dll_clk_src);
> +	emc_cc_dbg(INFO, "last rate: %u, next rate %u\n", last_timing->rate,
> +		   next_timing->rate);
> +	emc_cc_dbg(INFO, "last period: %u, next period: %u\n",
> +		   source_clock_period, destination_clock_period);
> +	emc_cc_dbg(INFO, "  shared_zq_resistor: %d\n", !!shared_zq_resistor);
> +	emc_cc_dbg(INFO, "  channel_mode: %d\n", channel_mode);
> +	emc_cc_dbg(INFO, "  opt_dll_mode: %d\n", opt_dll_mode);
> +
> +	/*
> +	 * Step 1:
> +	 *   Pre DVFS SW sequence.
> +	 */
> +	emc_cc_dbg(STEPS, "Step 1\n");
> +	emc_cc_dbg(STEPS, "Step 1.1: Disable DLL temporarily.\n");
> +	tmp = emc_readl(emc, EMC_CFG_DIG_DLL);
> +	tmp &= ~EMC_CFG_DIG_DLL_CFG_DLL_EN;
> +	emc_writel(emc, tmp, EMC_CFG_DIG_DLL);
> +
> +	emc_timing_update(emc, channel_mode);
> +	wait_for_update(emc, EMC_CFG_DIG_DLL,
> +			EMC_CFG_DIG_DLL_CFG_DLL_EN, 0, REG_EMC);
> +	if (channel_mode)
> +		wait_for_update(emc, EMC_CFG_DIG_DLL,
> +				EMC_CFG_DIG_DLL_CFG_DLL_EN, 0, REG_EMC1);
> +
> +	emc_cc_dbg(STEPS, "Step 1.2: Disable AUTOCAL temporarily.\n");
> +	emc_auto_cal_config = next_timing->emc_auto_cal_config;
> +	auto_cal_en = emc_auto_cal_config & EMC_AUTO_CAL_CONFIG_AUTO_CAL_ENABLE;
> +	emc_auto_cal_config &= ~EMC_AUTO_CAL_CONFIG_AUTO_CAL_START;
> +	emc_auto_cal_config |=  EMC_AUTO_CAL_CONFIG_AUTO_CAL_MEASURE_STALL;
> +	emc_auto_cal_config |=  EMC_AUTO_CAL_CONFIG_AUTO_CAL_UPDATE_STALL;
> +	emc_auto_cal_config |=  auto_cal_en;
> +	emc_writel(emc, emc_auto_cal_config, EMC_AUTO_CAL_CONFIG);
> +	emc_readl(emc, EMC_AUTO_CAL_CONFIG); /* Flush write. */
> +
> +	emc_cc_dbg(STEPS, "Step 1.3: Disable other power features.\n");
> +	emc_set_shadow_bypass(emc, ACTIVE);
> +	emc_writel(emc, emc_cfg, EMC_CFG);
> +	emc_writel(emc, emc_sel_dpd_ctrl, EMC_SEL_DPD_CTRL);
> +	emc_set_shadow_bypass(emc, ASSEMBLY);
> +
> +	if (next_timing->periodic_training) {
> +		tegra210_reset_dram_clktree_values(next_timing);
> +
> +		wait_for_update(emc, EMC_EMC_STATUS,
> +				EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK, 0,
> +				REG_EMC);
> +		if (channel_mode)
> +			wait_for_update(emc, EMC_EMC_STATUS,
> +					EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK,
> +					0, REG_EMC1);
> +
> +		wait_for_update(emc, EMC_EMC_STATUS,
> +				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
> +				REG_EMC);
> +		if (channel_mode)
> +			wait_for_update(emc, EMC_EMC_STATUS,
> +				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
> +				REG_EMC1);
> +
> +		tegra210_start_periodic_compensation(emc);
> +
> +		udelay(((1000 *
> +			 tegra210_actual_osc_clocks(last_timing->run_clocks)) /
> +			last_timing->rate) + 2);
> +		adel = periodic_compensation_handler(emc, DVFS_SEQUENCE,
> +						     dram_dev_num,
> +						     channel_mode,
> +						     fake_timing, next_timing);
> +		compensate_trimmer_applicable =
> +			next_timing->periodic_training &&
> +			((adel * 128 * next_timing_rate_mhz) / 1000000) >
> +			next_timing->tree_margin;
> +	}
> +
> +	emc_writel(emc, EMC_INTSTATUS_CLKCHANGE_COMPLETE, EMC_INTSTATUS);
> +	emc_set_shadow_bypass(emc, ACTIVE);
> +	emc_writel(emc, emc_cfg, EMC_CFG);
> +	emc_writel(emc, emc_sel_dpd_ctrl, EMC_SEL_DPD_CTRL);
> +	emc_writel(emc, emc_cfg_pipe_clk_o | EMC_CFG_PIPE_CLK_CLK_ALWAYS_ON,
> +		   EMC_CFG_PIPE_CLK);
> +	emc_writel(emc, next_timing->emc_fdpd_ctrl_cmd_no_ramp &
> +		   ~EMC_FDPD_CTRL_CMD_NO_RAMP_CMD_DPD_NO_RAMP_ENABLE,
> +		   EMC_FDPD_CTRL_CMD_NO_RAMP);
> +
> +	bg_regulator_mode_change =
> +		((next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
> +		  EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD) ^
> +		 (last_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
> +		  EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD)) ||
> +		((next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
> +		  EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD) ^
> +		 (last_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
> +		  EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD));
> +	enable_bglp_regulator =
> +		(next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
> +		 EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD) == 0;
> +	enable_bg_regulator =
> +		(next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
> +		 EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD) == 0;
> +
> +	if (bg_regulator_mode_change) {
> +		if (enable_bg_regulator)
> +			emc_writel(emc, last_timing->burst_regs
> +				   [EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
> +				   ~EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD,
> +				   EMC_PMACRO_BG_BIAS_CTRL_0);
> +		else
> +			emc_writel(emc, last_timing->burst_regs
> +				   [EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
> +				   ~EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD,
> +				   EMC_PMACRO_BG_BIAS_CTRL_0);
> +	}
> +
> +	/* Check if we need to turn on VREF generator. */
> +	if ((((last_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
> +	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF) == 0) &&
> +	     ((next_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
> +	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF) == 1)) ||
> +	    (((last_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
> +	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF) == 0) &&
> +	     ((next_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
> +	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF) == 1))) {
> +		u32 pad_tx_ctrl =
> +		    next_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX];
> +		u32 last_pad_tx_ctrl =
> +		    last_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX];
> +
> +		next_dqs_e_ivref = pad_tx_ctrl &
> +				   EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF;
> +		next_dq_e_ivref = pad_tx_ctrl &
> +				  EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF;
> +		next_push = (last_pad_tx_ctrl &
> +			     ~EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF &
> +			     ~EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF) |
> +			    next_dq_e_ivref | next_dqs_e_ivref;
> +		emc_writel(emc, next_push, EMC_PMACRO_DATA_PAD_TX_CTRL);
> +		udelay(1);
> +	} else if (bg_regulator_mode_change) {
> +		udelay(1);
> +	}
> +
> +	emc_set_shadow_bypass(emc, ASSEMBLY);
> +
> +	/*
> +	 * Step 2:
> +	 *   Prelock the DLL.
> +	 */
> +	emc_cc_dbg(STEPS, "Step 2\n");
> +	if (next_timing->burst_regs[EMC_CFG_DIG_DLL_INDEX] &
> +	    EMC_CFG_DIG_DLL_CFG_DLL_EN) {
> +		emc_cc_dbg(INFO, "Prelock enabled for target frequency.\n");
> +		dll_out = tegra210_dll_prelock(emc, 0, clksrc);
> +		emc_cc_dbg(INFO, "DLL out: 0x%03x\n", dll_out);
> +		prelock_dll_en = 1;
> +	} else {
> +		emc_cc_dbg(INFO, "Disabling DLL for target frequency.\n");
> +		tegra210_dll_disable(emc, channel_mode);
> +	}
> +
> +	/*
> +	 * Step 3:
> +	 *   Prepare autocal for the clock change.
> +	 */
> +	emc_cc_dbg(STEPS, "Step 3\n");
> +	emc_set_shadow_bypass(emc, ACTIVE);
> +	emc_writel(emc, next_timing->emc_auto_cal_config2,
> +		   EMC_AUTO_CAL_CONFIG2);
> +	emc_writel(emc, next_timing->emc_auto_cal_config3,
> +		   EMC_AUTO_CAL_CONFIG3);
> +	emc_writel(emc, next_timing->emc_auto_cal_config4,
> +		   EMC_AUTO_CAL_CONFIG4);
> +	emc_writel(emc, next_timing->emc_auto_cal_config5,
> +		   EMC_AUTO_CAL_CONFIG5);
> +	emc_writel(emc, next_timing->emc_auto_cal_config6,
> +		   EMC_AUTO_CAL_CONFIG6);
> +	emc_writel(emc, next_timing->emc_auto_cal_config7,
> +		   EMC_AUTO_CAL_CONFIG7);
> +	emc_writel(emc, next_timing->emc_auto_cal_config8,
> +		   EMC_AUTO_CAL_CONFIG8);
> +	emc_set_shadow_bypass(emc, ASSEMBLY);
> +
> +	emc_auto_cal_config |= (EMC_AUTO_CAL_CONFIG_AUTO_CAL_COMPUTE_START |
> +				auto_cal_en);
> +	emc_writel(emc, emc_auto_cal_config, EMC_AUTO_CAL_CONFIG);
> +
> +	/*
> +	 * Step 4:
> +	 *   Update EMC_CFG. (??)
> +	 */
> +	emc_cc_dbg(STEPS, "Step 4\n");
> +	if (source_clock_period > 50000 && dram_type == DRAM_TYPE_LPDDR4)
> +		ccfifo_writel(emc, 1, EMC_SELF_REF, 0);
> +	else
> +		emc_writel(emc, next_timing->emc_cfg_2, EMC_CFG_2);
> +
> +	/*
> +	 * Step 5:
> +	 *   Prepare reference variables for ZQCAL regs.
> +	 */
> +	emc_cc_dbg(STEPS, "Step 5\n");
> +	emc_zcal_interval = 0;
> +	emc_zcal_wait_cnt_old =
> +		last_timing->burst_regs[EMC_ZCAL_WAIT_CNT_INDEX];
> +	emc_zcal_wait_cnt_new =
> +		next_timing->burst_regs[EMC_ZCAL_WAIT_CNT_INDEX];
> +	emc_zcal_wait_cnt_old &= ~EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK;
> +	emc_zcal_wait_cnt_new &= ~EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK;
> +
> +	if (dram_type == DRAM_TYPE_LPDDR4)
> +		zq_wait_long = max((u32)1,
> +				   div_o3(1000000, destination_clock_period));
> +	else if (dram_type == DRAM_TYPE_LPDDR2 || is_lpddr3)
> +		zq_wait_long = max(next_timing->min_mrs_wait,
> +				   div_o3(360000, destination_clock_period)) +
> +			       4;
> +	else if (dram_type == DRAM_TYPE_DDR3)
> +		zq_wait_long = max((u32)256,
> +				   div_o3(320000, destination_clock_period) +
> +				   2);
> +	else
> +		zq_wait_long = 0;
> +
> +	if (dram_type == DRAM_TYPE_LPDDR2 || is_lpddr3)
> +		zq_wait_short = max(max(next_timing->min_mrs_wait, (u32)6),
> +				    div_o3(90000, destination_clock_period)) +
> +				4;
> +	else if (dram_type == DRAM_TYPE_DDR3)
> +		zq_wait_short = max((u32)64,
> +				    div_o3(80000, destination_clock_period)) +
> +				2;
> +	else
> +		zq_wait_short = 0;
> +
> +	/*
> +	 * Step 6:
> +	 *   Training code - removed.
> +	 */
> +	emc_cc_dbg(STEPS, "Step 6\n");
> +
> +	/*
> +	 * Step 7:
> +	 *   Program FSP reference registers and send MRWs to new FSPWR.
> +	 */
> +	emc_cc_dbg(STEPS, "Step 7\n");
> +	emc_cc_dbg(SUB_STEPS, "Step 7.1: Bug 200024907 - Patch RP R2P");
> +	if (opt_war_200024907) {
> +		nRTP = 16;
> +		if (source_clock_period >= 1000000/1866) /* 535.91 ps */
> +			nRTP = 14;
> +		if (source_clock_period >= 1000000/1600) /* 625.00 ps */
> +			nRTP = 12;
> +		if (source_clock_period >= 1000000/1333) /* 750.19 ps */
> +			nRTP = 10;
> +		if (source_clock_period >= 1000000/1066) /* 938.09 ps */
> +			nRTP = 8;
> +
> +		deltaTWATM = max_t(u32, div_o3(7500, source_clock_period), 8);
> +
> +		/*
> +		 * Originally there was a + .5 in the tRPST calculation.
> +		 * However since we can't do FP in the kernel and the tRTM
> +		 * computation was in a floating point ceiling function, adding
> +		 * one to tRTP should be ok. There is no other source of non
> +		 * integer values, so the result was always going to be
> +		 * something for the form: f_ceil(N + .5) = N + 1;
> +		 */
> +		tRPST = ((last_timing->emc_mrw & 0x80) >> 7);
> +		tRTM = fake_timing->dram_timings[RL] +
> +		       div_o3(3600, source_clock_period) +
> +		       max_t(u32, div_o3(7500, source_clock_period), 8) +
> +		       tRPST + 1 + nRTP;
> +
> +		emc_cc_dbg(INFO, "tRTM = %u, EMC_RP = %u\n", tRTM,
> +			   next_timing->burst_regs[EMC_RP_INDEX]);
> +
> +		if (last_timing->burst_regs[EMC_RP_INDEX] < tRTM) {
> +			if (tRTM > (last_timing->burst_regs[EMC_R2P_INDEX] +
> +				    last_timing->burst_regs[EMC_RP_INDEX])) {
> +				R2P_war = tRTM -
> +					  last_timing->burst_regs[EMC_RP_INDEX];
> +				RP_war = last_timing->burst_regs[EMC_RP_INDEX];
> +				TRPab_war = last_timing->burst_regs[
> +					    EMC_TRPAB_INDEX];
> +				if (R2P_war > 63) {
> +					RP_war = R2P_war +
> +						 last_timing->burst_regs[
> +						 EMC_RP_INDEX] - 63;
> +					if (TRPab_war < RP_war)
> +						TRPab_war = RP_war;
> +					R2P_war = 63;
> +				}
> +			} else {
> +				R2P_war = last_timing->burst_regs[
> +					  EMC_R2P_INDEX];
> +				RP_war = last_timing->burst_regs[EMC_RP_INDEX];
> +				TRPab_war = last_timing->burst_regs[
> +					    EMC_TRPAB_INDEX];
> +			}
> +
> +			if (RP_war < deltaTWATM) {
> +				W2P_war = last_timing->burst_regs[EMC_W2P_INDEX]
> +					  + deltaTWATM - RP_war;
> +				if (W2P_war > 63) {
> +					RP_war = RP_war + W2P_war - 63;
> +					if (TRPab_war < RP_war)
> +						TRPab_war = RP_war;
> +					W2P_war = 63;
> +				}
> +			} else {
> +				W2P_war = last_timing->burst_regs[
> +					  EMC_W2P_INDEX];
> +			}
> +
> +			if ((last_timing->burst_regs[EMC_W2P_INDEX] ^
> +			     W2P_war) ||
> +			    (last_timing->burst_regs[EMC_R2P_INDEX] ^
> +			     R2P_war) ||
> +			    (last_timing->burst_regs[EMC_RP_INDEX] ^
> +			     RP_war) ||
> +			    (last_timing->burst_regs[EMC_TRPAB_INDEX] ^
> +			     TRPab_war)) {
> +				emc_writel(emc, RP_war, EMC_RP);
> +				emc_writel(emc, R2P_war, EMC_R2P);
> +				emc_writel(emc, W2P_war, EMC_W2P);
> +				emc_writel(emc, TRPab_war, EMC_TRPAB);
> +			}
> +			emc_timing_update(emc, DUAL_CHANNEL);
> +		} else {
> +			emc_cc_dbg(INFO, "Skipped WAR\n");
> +		}
> +	}
> +
> +	if (!fsp_for_next_freq) {
> +		mr13_flip_fspwr = (next_timing->emc_mrw3 & 0xffffff3f) | 0x80;
> +		mr13_flip_fspop = (next_timing->emc_mrw3 & 0xffffff3f) | 0x00;
> +	} else {
> +		mr13_flip_fspwr = (next_timing->emc_mrw3 & 0xffffff3f) | 0x40;
> +		mr13_flip_fspop = (next_timing->emc_mrw3 & 0xffffff3f) | 0xc0;
> +	}
> +
> +	mr13_catr_enable = (mr13_flip_fspwr & 0xFFFFFFFE) | 0x01;
> +	if (dram_dev_num == TWO_RANK)
> +		mr13_catr_enable = (mr13_catr_enable & 0x3fffffff) | 0x80000000;
> +
> +	if (dram_type == DRAM_TYPE_LPDDR4) {
> +		emc_writel(emc, mr13_flip_fspwr, EMC_MRW3);
> +		emc_writel(emc, next_timing->emc_mrw, EMC_MRW);
> +		emc_writel(emc, next_timing->emc_mrw2, EMC_MRW2);
> +	}
> +
> +	/*
> +	 * Step 8:
> +	 *   Program the shadow registers.
> +	 */
> +	emc_cc_dbg(STEPS, "Step 8\n");
> +	emc_cc_dbg(SUB_STEPS, "Writing burst_regs\n");
> +	for (i = 0; i < next_timing->num_burst; i++) {
> +		u32 var;
> +		u32 wval;
> +
> +		if (!burst_regs_off[i])
> +			continue;
> +
> +		var = burst_regs_off[i];
> +		wval = next_timing->burst_regs[i];
> +
> +		if (dram_type != DRAM_TYPE_LPDDR4 &&
> +		    (var == EMC_MRW6      || var == EMC_MRW7 ||
> +		     var == EMC_MRW8      || var == EMC_MRW9 ||
> +		     var == EMC_MRW10     || var == EMC_MRW11 ||
> +		     var == EMC_MRW12     || var == EMC_MRW13 ||
> +		     var == EMC_MRW14     || var == EMC_MRW15 ||
> +		     var == EMC_TRAINING_CTRL))
> +			continue;
> +
> +		/* Pain... And suffering. */
> +		if (var == EMC_CFG) {
> +			wval &= ~EMC_CFG_DRAM_ACPD;
> +			wval &= ~EMC_CFG_DYN_SELF_REF;
> +			if (dram_type == DRAM_TYPE_LPDDR4) {
> +				wval &= ~EMC_CFG_DRAM_CLKSTOP_SR;
> +				wval &= ~EMC_CFG_DRAM_CLKSTOP_PD;
> +			}
> +		} else if (var == EMC_MRS_WAIT_CNT &&
> +			   dram_type == DRAM_TYPE_LPDDR2 &&
> +			   opt_zcal_en_cc && !opt_cc_short_zcal &&
> +			   opt_short_zcal) {
> +			wval = (wval & ~(EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK <<
> +					 EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT)) |
> +			   ((zq_wait_long & EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK) <<
> +			    EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT);
> +		} else if (var == EMC_ZCAL_WAIT_CNT &&
> +			   dram_type == DRAM_TYPE_DDR3 && opt_zcal_en_cc &&
> +			   !opt_cc_short_zcal && opt_short_zcal) {
> +			wval = (wval & ~(EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK <<
> +					 EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_SHIFT))
> +			       | ((zq_wait_long &
> +				   EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK) <<
> +				  EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT);
> +		} else if (var == EMC_ZCAL_INTERVAL && opt_zcal_en_cc) {
> +			wval = 0; /* EMC_ZCAL_INTERVAL reset value. */
> +		} else if (var == EMC_PMACRO_AUTOCAL_CFG_COMMON) {
> +			wval |= EMC_PMACRO_AUTOCAL_CFG_COMMON_E_CAL_BYPASS_DVFS;
> +		} else if (var == EMC_PMACRO_DATA_PAD_TX_CTRL) {
> +			wval &=
> +			     ~(EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC |
> +			       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC |
> +			       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC |
> +			       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC);
> +		} else if (var == EMC_PMACRO_CMD_PAD_TX_CTRL) {
> +			wval |= EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_DRVFORCEON;
> +			wval &= ~(EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSP_TX_E_DCC |
> +				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSN_TX_E_DCC |
> +				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_E_DCC |
> +				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_CMD_TX_E_DCC);
> +		} else if (var == EMC_PMACRO_BRICK_CTRL_RFU1) {
> +			wval &= 0xf800f800;
> +		} else if (var == EMC_PMACRO_COMMON_PAD_TX_CTRL) {
> +			wval &= 0xfffffff0;
> +		}
> +
> +		emc_writel(emc, wval, var);
> +	}
> +
> +	/* SW addition: do EMC refresh adjustment here. */
> +	set_over_temp_timing(emc, next_timing, dram_over_temp_state);
> +
> +	if (dram_type == DRAM_TYPE_LPDDR4) {
> +		mrw_req = (23 << EMC_MRW_MRW_MA_SHIFT) |
> +			  (next_timing->run_clocks & EMC_MRW_MRW_OP_MASK);
> +		emc_writel(emc, mrw_req, EMC_MRW);
> +	}
> +
> +	/* Per channel burst registers. */
> +	emc_cc_dbg(SUB_STEPS, "Writing burst_regs_per_ch\n");
> +	for (i = 0; i < next_timing->num_burst_per_ch; i++) {
> +		if (!burst_regs_per_ch_off[i])
> +			continue;
> +
> +		if (dram_type != DRAM_TYPE_LPDDR4 &&
> +		    (burst_regs_per_ch_off[i] == EMC_MRW6 ||
> +		     burst_regs_per_ch_off[i] == EMC_MRW7 ||
> +		     burst_regs_per_ch_off[i] == EMC_MRW8 ||
> +		     burst_regs_per_ch_off[i] == EMC_MRW9 ||
> +		     burst_regs_per_ch_off[i] == EMC_MRW10 ||
> +		     burst_regs_per_ch_off[i] == EMC_MRW11 ||
> +		     burst_regs_per_ch_off[i] == EMC_MRW12 ||
> +		     burst_regs_per_ch_off[i] == EMC_MRW13 ||
> +		     burst_regs_per_ch_off[i] == EMC_MRW14 ||
> +		     burst_regs_per_ch_off[i] == EMC_MRW15))
> +			continue;
> +
> +		/* Filter out second channel if not in DUAL_CHANNEL mode. */
> +		if (channel_mode != DUAL_CHANNEL &&
> +		    burst_regs_per_ch_type[i] >= REG_EMC1)
> +			continue;
> +
> +		emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
> +			   i, next_timing->burst_reg_per_ch[i],
> +			   burst_regs_per_ch_off[i]);
> +		emc_writel_per_ch(emc, next_timing->burst_reg_per_ch[i],
> +				  burst_regs_per_ch_type[i],
> +				  burst_regs_per_ch_off[i]);
> +	}
> +
> +	/* Vref regs. */
> +	emc_cc_dbg(SUB_STEPS, "Writing vref_regs\n");
> +	for (i = 0; i < next_timing->vref_num; i++) {
> +		if (!vref_regs_per_ch_off[i])
> +			continue;
> +
> +		if (channel_mode != DUAL_CHANNEL &&
> +			vref_regs_per_ch_type[i] >= REG_EMC1)
> +			continue;
> +
> +		emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
> +			   i, next_timing->vref_perch_regs[i],
> +			   vref_regs_per_ch_off[i]);
> +		emc_writel_per_ch(emc, next_timing->vref_perch_regs[i],
> +				  vref_regs_per_ch_type[i],
> +				  vref_regs_per_ch_off[i]);
> +	}
> +
> +	/* Trimmers. */
> +	emc_cc_dbg(SUB_STEPS, "Writing trim_regs\n");
> +	for (i = 0; i < next_timing->num_trim; i++) {
> +		u64 trim_reg;
> +
> +		if (!trim_regs_off[i])
> +			continue;
> +
> +		trim_reg = trim_regs_off[i];
> +		if (compensate_trimmer_applicable &&
> +		    (trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3 ||
> +		     trim_reg == EMC_DATA_BRLSHFT_0 ||
> +		     trim_reg == EMC_DATA_BRLSHFT_1)) {
> +			u32 reg = tegra210_apply_periodic_compensation_trimmer(
> +				  next_timing, trim_reg);
> +			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n", i, reg,
> +				   trim_regs_off[i]);
> +			emc_cc_dbg(EMA_WRITES, "0x%08x <= 0x%08x\n",
> +				   (u32)(u64)trim_regs_off[i], reg);
> +			emc_writel(emc, reg, trim_regs_off[i]);
> +		} else {
> +			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
> +				   i, next_timing->trim_regs[i],
> +				   trim_regs_off[i]);
> +			emc_writel(emc, next_timing->trim_regs[i],
> +				   trim_regs_off[i]);
> +		}
> +	}
> +
> +	/* Per channel trimmers. */
> +	emc_cc_dbg(SUB_STEPS, "Writing trim_regs_per_ch\n");
> +	for (i = 0; i < next_timing->num_trim_per_ch; i++) {
> +		u32 trim_reg;
> +
> +		if (!trim_regs_per_ch_off[i])
> +			continue;
> +
> +		if (channel_mode != DUAL_CHANNEL &&
> +			trim_regs_per_ch_type[i] >= REG_EMC1)
> +			continue;
> +
> +		trim_reg = trim_regs_per_ch_off[i];
> +		if (compensate_trimmer_applicable &&
> +		    (trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2 ||
> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3 ||
> +		     trim_reg == EMC_DATA_BRLSHFT_0 ||
> +		     trim_reg == EMC_DATA_BRLSHFT_1)) {
> +			u32 reg =
> +				tegra210_apply_periodic_compensation_trimmer(
> +						next_timing, trim_reg);
> +			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
> +				   i, reg, trim_regs_per_ch_off[i]);
> +			emc_cc_dbg(EMA_WRITES, "0x%08x <= 0x%08x\n",
> +				   trim_regs_per_ch_off[i], reg);
> +			emc_writel_per_ch(emc, reg, trim_regs_per_ch_type[i],
> +					  trim_regs_per_ch_off[i]);
> +		} else {
> +			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
> +				   i, next_timing->trim_perch_regs[i],
> +				   trim_regs_per_ch_off[i]);
> +			emc_writel_per_ch(emc, next_timing->trim_perch_regs[i],
> +					  trim_regs_per_ch_type[i],
> +					  trim_regs_per_ch_off[i]);
> +		}
> +	}
> +
> +	emc_cc_dbg(SUB_STEPS, "Writing burst_mc_regs\n");
> +	for (i = 0; i < next_timing->num_mc_regs; i++) {
> +		emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
> +			   i, next_timing->burst_mc_regs[i],
> +			   burst_mc_regs_off[i]);
> +		mc_writel(emc->mc, next_timing->burst_mc_regs[i],
> +			  burst_mc_regs_off[i]);
> +	}
> +
> +	/* Registers to be programmed on the faster clock. */
> +	if (next_timing->rate < last_timing->rate) {
> +		emc_cc_dbg(SUB_STEPS, "Writing la_scale_regs\n");
> +		for (i = 0; i < next_timing->num_up_down; i++) {
> +			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
> +				   i, next_timing->la_scale_regs[i],
> +				   la_scale_regs_off[i]);
> +			mc_writel(emc->mc, next_timing->la_scale_regs[i],
> +				  la_scale_regs_off[i]);
> +		}
> +	}
> +
> +	/* Flush all the burst register writes. */
> +	wmb();

Won't it be a bit more optimal to just read back the lastly written register rather than to flush all of memory writes? 

-- 
Dmitry

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 4/8] memory: tegra: add EMC scaling support code for Tegra210
  2019-03-25  7:45 ` [PATCH 4/8] memory: tegra: add EMC scaling support code for Tegra210 Joseph Lo
@ 2019-04-02 11:39   ` Dmitry Osipenko
  2019-04-02 14:53     ` Joseph Lo
  0 siblings, 1 reply; 28+ messages in thread
From: Dmitry Osipenko @ 2019-04-02 11:39 UTC (permalink / raw)
  To: Joseph Lo, Thierry Reding, Peter De Schrijver, Jonathan Hunter,
	Rob Herring, Stephen Boyd
  Cc: linux-tegra, devicetree, linux-clk, linux-arm-kernel

25.03.2019 10:45, Joseph Lo пишет:
> This patch adds the required APIs and variables for the EMC scaling
> sequence code on Tegra210.
> 
> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
> 
> Signed-off-by: Joseph Lo <josephl@nvidia.com>
> ---
>  drivers/memory/tegra/tegra210-emc-reg.h | 265 +++++++
>  drivers/memory/tegra/tegra210-emc.c     | 923 ++++++++++++++++++++++++
>  2 files changed, 1188 insertions(+)
> 
> diff --git a/drivers/memory/tegra/tegra210-emc-reg.h b/drivers/memory/tegra/tegra210-emc-reg.h
> index 84fcc85f3b6d..31a69e718dbc 100644
> --- a/drivers/memory/tegra/tegra210-emc-reg.h
> +++ b/drivers/memory/tegra/tegra210-emc-reg.h
> @@ -12,6 +12,13 @@
>  
>  #include "mc.h"
>  
> +#define DVFS_FGCG_HIGH_SPEED_THRESHOLD				1000
> +#define IOBRICK_DCC_THRESHOLD					2400
> +#define DVFS_FGCG_MID_SPEED_THRESHOLD				600
> +
> +#define EMC_STATUS_UPDATE_TIMEOUT				1000
> +
> +#define MC_EMEM_ADR_CFG						0x54
>  #define MC_EMEM_ARB_CFG						0x90
>  #define MC_EMEM_ARB_OUTSTANDING_REQ				0x94
>  #define MC_EMEM_ARB_TIMING_RCD					0x98
> @@ -75,12 +82,33 @@
>  #define EMC_CLK_EMC_2X_CLK_SRC_SHIFT				29
>  #define EMC_CLK_EMC_2X_CLK_SRC_MASK				\
>  	(0x7 << EMC_CLK_EMC_2X_CLK_SRC_SHIFT)
> +#define EMC_CLK_SOURCE_PLLM_LJ					0x4
> +#define EMC_CLK_SOURCE_PLLMB_LJ					0x5
>  #define	EMC_CLK_MC_EMC_SAME_FREQ				BIT(16)
>  #define EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT			0
>  #define EMC_CLK_EMC_2X_CLK_DIVISOR_MASK				\
>  	(0xff << EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT)
>  
> +#define CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL			0x664
> +#define DLL_CLK_EMC_DLL_CLK_SRC_SHIFT				29
> +#define DLL_CLK_EMC_DLL_CLK_SRC_MASK				\
> +	(0x7 << DLL_CLK_EMC_DLL_CLK_SRC_SHIFT)
> +#define DLL_CLK_EMC_DLL_DDLL_CLK_SEL_SHIFT			10
> +#define DLL_CLK_EMC_DLL_DDLL_CLK_SEL_MASK			\
> +	(0x3 << DLL_CLK_EMC_DLL_DDLL_CLK_SEL_SHIFT)
> +#define PLLM_VCOA						0
> +#define PLLM_VCOB						1
> +#define EMC_DLL_SWITCH_OUT					2
> +#define DLL_CLK_EMC_DLL_CLK_DIVISOR_SHIFT			0
> +#define DLL_CLK_EMC_DLL_CLK_DIVISOR_MASK			\
> +	(0xff << DLL_CLK_EMC_DLL_CLK_DIVISOR_SHIFT)
> +
> +#define EMC_INTSTATUS						0x0
> +#define EMC_INTSTATUS_CLKCHANGE_COMPLETE			BIT(4)
> +#define EMC_DBG							0x8
> +#define EMC_DBG_WRITE_MUX_ACTIVE				BIT(1)
>  #define EMC_CFG							0xc
> +#define EMC_TIMING_CONTROL					0x28
>  #define EMC_RC							0x2c
>  #define EMC_RFC							0x30
>  #define EMC_RAS							0x34
> @@ -125,16 +153,40 @@
>  #define EMC_FBIO_CFG5_DRAM_TYPE_SHIFT				0
>  #define EMC_FBIO_CFG5_DRAM_TYPE_MASK				\
>  	(0x3 <<	EMC_FBIO_CFG5_DRAM_TYPE_SHIFT)
> +#define EMC_FBIO_CFG5_CMD_TX_DIS				BIT(8)
> +
>  #define EMC_PDEX2CKE						0x118
>  #define EMC_CKE2PDEN						0x11c
> +#define EMC_MPC							0x128
>  #define EMC_R2R							0x144
>  #define EMC_EINPUT						0x14c
>  #define EMC_EINPUT_DURATION					0x150
>  #define EMC_PUTERM_EXTRA					0x154
>  #define EMC_TCKESR						0x158
>  #define EMC_TPD							0x15c
> +#define EMC_EMC_STATUS						0x2b4
> +#define EMC_EMC_STATUS_TIMING_UPDATE_STALLED			BIT(23)
>  #define EMC_CFG_DIG_DLL						0x2bc
> +#define EMC_CFG_DIG_DLL_CFG_DLL_EN				BIT(0)
> +#define EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_UNTIL_LOCK		BIT(1)
> +#define EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_TRAFFIC		BIT(3)
> +#define EMC_CFG_DIG_DLL_CFG_DLL_STALL_RW_UNTIL_LOCK		BIT(4)
> +#define EMC_CFG_DIG_DLL_CFG_DLL_MODE_SHIFT			6
> +#define EMC_CFG_DIG_DLL_CFG_DLL_MODE_MASK			\
> +	(0x3 << EMC_CFG_DIG_DLL_CFG_DLL_MODE_SHIFT)
> +#define EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_SHIFT		8
> +#define EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_MASK			\
> +	(0x7 << EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_SHIFT)
> +
>  #define EMC_CFG_DIG_DLL_PERIOD					0x2c0
> +#define EMC_DIG_DLL_STATUS					0x2c4
> +#define EMC_DIG_DLL_STATUS_DLL_LOCK				BIT(15)
> +#define EMC_DIG_DLL_STATUS_DLL_PRIV_UPDATED			BIT(17)
> +#define EMC_DIG_DLL_STATUS_DLL_OUT_SHIFT			0
> +#define EMC_DIG_DLL_STATUS_DLL_OUT_MASK				\
> +	(0x7ff << EMC_DIG_DLL_STATUS_DLL_OUT_SHIFT)
> +
> +#define EMC_CFG_DIG_DLL_1					0x2c8
>  #define EMC_RDV_MASK						0x2cc
>  #define EMC_WDV_MASK						0x2d0
>  #define EMC_RDV_EARLY_MASK					0x2d4
> @@ -153,6 +205,8 @@
>  #define EMC_PRE_REFRESH_REQ_CNT					0x3dc
>  #define EMC_DYN_SELF_REF_CONTROL				0x3e0
>  #define EMC_TXSRDLL						0x3e4
> +#define EMC_CCFIFO_ADDR						0x3e8
> +#define EMC_CCFIFO_DATA						0x3ec
>  #define EMC_TR_QPOP						0x3f4
>  #define EMC_TR_RDV_MASK						0x3f8
>  #define EMC_TR_QSAFE						0x3fc
> @@ -185,8 +239,60 @@
>  #define EMC_PUTERM_WIDTH					0x56c
>  #define EMC_REFCTRL2						0x580
>  #define EMC_FBIO_CFG7						0x584
> +#define EMC_FBIO_CFG7_CH0_ENABLE				BIT(1)
> +#define EMC_FBIO_CFG7_CH1_ENABLE				BIT(2)
>  #define EMC_DATA_BRLSHFT_0					0x588
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_SHIFT	21
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_SHIFT	18
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_SHIFT	15
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_SHIFT	12
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_SHIFT	9
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_SHIFT	6
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_SHIFT	3
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_SHIFT	0
> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_SHIFT)
> +
>  #define EMC_DATA_BRLSHFT_1					0x58c
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_SHIFT	21
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_SHIFT	18
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_SHIFT	15
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_SHIFT	12
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_SHIFT	9
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_SHIFT	6
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_SHIFT	3
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_SHIFT)
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_SHIFT	0
> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_MASK	\
> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_SHIFT)
> +
>  #define EMC_RFCPB						0x590
>  #define EMC_DQS_BRLSHFT_0					0x594
>  #define EMC_DQS_BRLSHFT_1					0x598
> @@ -201,6 +307,10 @@
>  #define EMC_QUSE_BRLSHFT_3					0x5c4
>  #define EMC_DLL_CFG_0						0x5e4
>  #define EMC_DLL_CFG_1						0x5e8
> +#define EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_SHIFT		10
> +#define EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_MASK		\
> +	(0x7ff << EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_SHIFT)
> +
>  #define EMC_CONFIG_SAMPLE_DELAY					0x5f0
>  #define EMC_PMACRO_QUSE_DDLL_RANK0_0				0x600
>  #define EMC_PMACRO_QUSE_DDLL_RANK0_1				0x604
> @@ -215,15 +325,103 @@
>  #define EMC_PMACRO_QUSE_DDLL_RANK1_4				0x630
>  #define EMC_PMACRO_QUSE_DDLL_RANK1_5				0x634
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0			0x640
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_SHIFT \
> +	16
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_SHIFT)
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_SHIFT \
> +	0
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_MASK \
> +	(0x3ff <<							    \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_SHIFT)
> +
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1			0x644
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_SHIFT \
> +	16
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_SHIFT)
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_SHIFT \
> +	0
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_SHIFT)
> +
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2			0x648
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_SHIFT  \
> +	16
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_SHIFT)
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_SHIFT \
> +	0
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_SHIFT)
> +
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3			0x64c
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_SHIFT \
> +	16
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_SHIFT)
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_SHIFT \
> +	0
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_SHIFT)
> +
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4			0x650
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5			0x654
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0			0x660
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_SHIFT \
> +	16
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_SHIFT)
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_SHIFT \
> +	0
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_SHIFT)
> +
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1			0x664
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_SHIFT \
> +	16
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_SHIFT)
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_SHIFT \
> +	0
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_SHIFT)
> +
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2			0x668
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_SHIFT \
> +	16
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_SHIFT)
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_SHIFT \
> +	0
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_SHIFT)
> +
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3			0x66c
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_SHIFT \
> +	16
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_SHIFT)
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_SHIFT \
> +	0
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_MASK  \
> +	(0x3ff <<							     \
> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_SHIFT)
> +
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4			0x670
>  #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5			0x674
>  #define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0			0x680
> @@ -432,7 +630,18 @@
>  #define EMC_PMACRO_CMD_RX_TERM_MODE				0xc58
>  #define EMC_PMACRO_DATA_RX_TERM_MODE				0xc5c
>  #define EMC_PMACRO_CMD_PAD_TX_CTRL				0xc60
> +#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_E_DCC		BIT(1)
> +#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSP_TX_E_DCC		BIT(9)
> +#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSN_TX_E_DCC		BIT(16)
> +#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_CMD_TX_E_DCC		BIT(24)
> +#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_DRVFORCEON		BIT(26)
> +
>  #define EMC_PMACRO_DATA_PAD_TX_CTRL				0xc64
> +#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC		BIT(1)
> +#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC		BIT(9)
> +#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC		BIT(16)
> +#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC		BIT(24)
> +
>  #define EMC_PMACRO_COMMON_PAD_TX_CTRL				0xc68
>  #define EMC_PMACRO_AUTOCAL_CFG_COMMON				0xc78
>  #define EMC_PMACRO_VTTGEN_CTRL_2				0xcf0
> @@ -954,6 +1163,16 @@ enum {
>  	DRAM_TYPE_DDR2 = 3,
>  };
>  
> +enum {
> +	SINGLE_CHANNEL = 0,
> +	DUAL_CHANNEL
> +};
> +
> +enum {
> +	DLL_OFF,
> +	DLL_ON
> +};
> +
>  struct emc_table {
>  	u32 rev;
>  	char dvfs_ver[60];
> @@ -1075,9 +1294,55 @@ struct supported_sequence {
>  	char  *seq_rev;
>  };
>  
> +extern u32 burst_mc_regs_off[];
> +extern u32 burst_regs_off[];
> +extern u32 burst_regs_per_ch_off[];
> +extern u32 burst_regs_per_ch_type[];
> +extern unsigned long dram_over_temp_state;
> +extern u32 la_scale_regs_off[];
> +extern u32 trim_regs_off[];
> +extern u32 trim_regs_per_ch_off[];
> +extern u32 trim_regs_per_ch_type[];
> +extern u32 vref_regs_per_ch_off[];
> +extern u32 vref_regs_per_ch_type[];
> +
> +void ccfifo_writel(struct tegra_emc *emc, u32 val, unsigned long addr,
> +		   u32 delay);
> +u32 div_o3(u32 a, u32 b);
> +void emc_writel(struct tegra_emc *emc, u32 val, unsigned long offset);
> +u32  emc_readl(struct tegra_emc *emc, unsigned long offset);
> +void emc_writel_per_ch(struct tegra_emc *emc, u32 val, int type,
> +		       unsigned long offset);
> +u32  emc1_readl(struct tegra_emc *emc, unsigned long offset);
> +
> +void do_clock_change(struct tegra_emc *emc, u32 clksrc);
> +void emc_set_shadow_bypass(struct tegra_emc *emc, int set);
> +void emc_timing_update(struct tegra_emc *emc, int dual_chan);
> +u32 get_dll_state(struct emc_table *next_timing);
> +struct emc_table *get_timing_from_freq(struct tegra_emc *emc,
> +				       unsigned long rate);
> +void set_over_temp_timing(struct tegra_emc *emc, struct emc_table *timing,
> +			  unsigned long state);
>  int tegra_emc_dt_parse_pdata(struct platform_device *pdev,
>  			     struct emc_table **tables,
>  			     struct emc_table **derated_tables,
>  			     int *num_entries);
> +u32 tegra210_actual_osc_clocks(u32 in);
> +u32 tegra210_apply_periodic_compensation_trimmer(struct emc_table *next_timing,
> +						 u32 offset);
> +void tegra210_dll_disable(struct tegra_emc *emc, int channel_mode);
> +void tegra210_dll_enable(struct tegra_emc *emc, int channel_mode);
> +u32 tegra210_dll_prelock(struct tegra_emc *emc, int dvfs_with_training,
> +			 u32 clksrc);
> +u32 tegra210_dvfs_power_ramp_down(struct tegra_emc *emc, u32 clk,
> +				  int flip_backward);
> +u32 tegra210_dvfs_power_ramp_up(struct tegra_emc *emc, u32 clk,
> +				int flip_backward);
> +void tegra210_update_emc_alt_timing(struct tegra_emc *emc,
> +				    struct emc_table *current_timing);
> +void tegra210_reset_dram_clktree_values(struct emc_table *table);
> +void tegra210_start_periodic_compensation(struct tegra_emc *emc);
> +int wait_for_update(struct tegra_emc *emc, u32 status_reg, u32 bit_mask,
> +		    bool updated_state, int chan);
>  
>  #endif
> diff --git a/drivers/memory/tegra/tegra210-emc.c b/drivers/memory/tegra/tegra210-emc.c
> index 0c20bcd0e6de..26d0de0ab319 100644
> --- a/drivers/memory/tegra/tegra210-emc.c
> +++ b/drivers/memory/tegra/tegra210-emc.c
> @@ -20,6 +20,37 @@
>  #define TEGRA_EMC_TABLE_MAX_SIZE		16
>  #define TEGRA210_EMC_SUSPEND_RATE		204000000
>  
> +#define EMC0_EMC_DATA_BRLSHFT_0_INDEX	2
> +#define EMC1_EMC_DATA_BRLSHFT_0_INDEX	3
> +#define EMC0_EMC_DATA_BRLSHFT_1_INDEX	4
> +#define EMC1_EMC_DATA_BRLSHFT_1_INDEX	5
> +
> +#define TRIM_REG(chan, rank, reg, byte)					\
> +	(((EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
> +	   _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte ## _MASK &	\
> +	   next_timing->trim_regs[EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ##	\
> +				 rank ## _ ## reg ## _INDEX]) >>	\
> +	  EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
> +	  _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte ## _SHIFT)	\
> +	 +								\
> +	 (((EMC_DATA_BRLSHFT_ ## rank ## _RANK ## rank ## _BYTE ##	\
> +	    byte ## _DATA_BRLSHFT_MASK &				\
> +	    next_timing->trim_perch_regs[EMC ## chan ##			\
> +			      _EMC_DATA_BRLSHFT_ ## rank ## _INDEX]) >>	\
> +	   EMC_DATA_BRLSHFT_ ## rank ## _RANK ## rank ## _BYTE ##	\
> +	   byte ## _DATA_BRLSHFT_SHIFT) * 64))
> +
> +#define CALC_TEMP(rank, reg, byte1, byte2, n)				\
> +	(((new[n] << EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ##	\
> +	   reg ## _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte1 ## _SHIFT) & \
> +	  EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
> +	  _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte1 ## _MASK)	\
> +	 |								\
> +	 ((new[n + 1] << EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ##\
> +	   reg ## _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte2 ## _SHIFT) & \
> +	  EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
> +	  _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte2 ## _MASK))	\
> +
>  enum TEGRA_EMC_SOURCE {
>  	TEGRA_EMC_SRC_PLLM,
>  	TEGRA_EMC_SRC_PLLC,
> @@ -32,6 +63,14 @@ enum TEGRA_EMC_SOURCE {
>  	TEGRA_EMC_SRC_COUNT,
>  };
>  
> +enum {
> +	TEGRA_DRAM_OVER_TEMP_NONE = 0,
> +	TEGRA_DRAM_OVER_TEMP_REFRESH_X2,
> +	TEGRA_DRAM_OVER_TEMP_REFRESH_X4,
> +	TEGRA_DRAM_OVER_TEMP_THROTTLE, /* 4x Refresh + derating. */
> +	TEGRA_DRAM_OVER_TEMP_MAX,
> +};
> +
>  struct emc_sel {
>  	struct clk *input;
>  	u32 value;
> @@ -76,6 +115,7 @@ static struct tegra_emc *tegra_emc;
>  static DEFINE_SPINLOCK(emc_access_lock);
>  static ktime_t clkchange_time;
>  static int clkchange_delay = 100;
> +unsigned long dram_over_temp_state = TEGRA_DRAM_OVER_TEMP_NONE;
>  
>  static void emc_train(struct timer_list *tmr);
>  DEFINE_TIMER(emc_training_timer, emc_train);
> @@ -102,11 +142,33 @@ static bool emc_suspend;
>  static unsigned long emc_resume_rate;
>  #endif
>  
> +inline void emc_writel(struct tegra_emc *emc, u32 val, unsigned long offset)
> +{
> +	writel(val, emc->emc_base + offset);
> +}
> +
>  inline u32 emc_readl(struct tegra_emc *emc, unsigned long offset)
>  {
>  	return readl(emc->emc_base + offset);
>  }
>  
> +inline u32 emc1_readl(struct tegra_emc *emc, unsigned long offset)
> +{
> +	return readl(emc->emc1_base + offset);
> +}

Do you really need to insert a memory barrier on each readl/writel? Why not to use the relaxed versions?

-- 
Dmitry

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 5/8] memory: tegra: Add EMC scaling sequence code for Tegra210
  2019-04-02 11:36   ` Dmitry Osipenko
@ 2019-04-02 14:49     ` Joseph Lo
  0 siblings, 0 replies; 28+ messages in thread
From: Joseph Lo @ 2019-04-02 14:49 UTC (permalink / raw)
  To: Dmitry Osipenko, Thierry Reding, Peter De Schrijver,
	Jonathan Hunter, Rob Herring, Stephen Boyd
  Cc: linux-tegra, devicetree, linux-clk, linux-arm-kernel

On 4/2/19 7:36 PM, Dmitry Osipenko wrote:
> 25.03.2019 10:45, Joseph Lo пишет:
>> This patch includes the sequence for controlling the rate changing for
>> EMC frequency and the dynamic training mechanism when the rate reaches
>> the higher rates of EMC rate.
>>
>> And historically there have been different sequences to change the EMC
>> clock. The sequence to be used is specified in the scaling data.
>> However, for the currently supported upstreaming platform, only the most
>> recent sequence is used. So only support that in this patch.
>>
>> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
>>
>> Signed-off-by: Joseph Lo <josephl@nvidia.com>
>> ---
>>   drivers/memory/tegra/Makefile                 |    2 +-
>>   drivers/memory/tegra/tegra210-emc-cc-r21021.c | 1962 +++++++++++++++++
>>   drivers/memory/tegra/tegra210-emc-reg.h       |  134 ++
>>   drivers/memory/tegra/tegra210-emc.c           |    5 +
>>   4 files changed, 2102 insertions(+), 1 deletion(-)
>>   create mode 100644 drivers/memory/tegra/tegra210-emc-cc-r21021.c
>>
>> diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile
>> index 36a835620bbd..dcc245b2ef45 100644
>> --- a/drivers/memory/tegra/Makefile
>> +++ b/drivers/memory/tegra/Makefile
>> @@ -12,5 +12,5 @@ obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
>>   
>>   obj-$(CONFIG_TEGRA20_EMC)  += tegra20-emc.o
>>   obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o
>> -obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o tegra210-dt-parse.o
>> +obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o tegra210-dt-parse.o tegra210-emc-cc-r21021.o
>>   obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o
>> diff --git a/drivers/memory/tegra/tegra210-emc-cc-r21021.c b/drivers/memory/tegra/tegra210-emc-cc-r21021.c
>> new file mode 100644
>> index 000000000000..f577a8c3aa95
>> --- /dev/null
>> +++ b/drivers/memory/tegra/tegra210-emc-cc-r21021.c
>> @@ -0,0 +1,1962 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (c) 2014-2019, NVIDIA CORPORATION.  All rights reserved.
>> + */
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/io.h>
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/of.h>
>> +#include <soc/tegra/mc.h>
>> +
>> +#include "mc.h"
>> +#include "tegra210-emc-reg.h"
>> +
>> +#define DVFS_CLOCK_CHANGE_VERSION	21021
>> +#define EMC_PRELOCK_VERSION		2101
>> +
>> +#define emc_cc_dbg(t, ...) pr_debug(__VA_ARGS__)
>> +
>> +/*
>> + * Enable flags for specifying verbosity.
>> + */
>> +#define INFO            (1 << 0)
>> +#define STEPS           (1 << 1)
>> +#define SUB_STEPS       (1 << 2)
>> +#define PRELOCK         (1 << 3)
>> +#define PRELOCK_STEPS   (1 << 4)
>> +#define ACTIVE_EN       (1 << 5)
>> +#define PRAMP_UP        (1 << 6)
>> +#define PRAMP_DN        (1 << 7)
>> +#define EMA_WRITES      (1 << 10)
>> +#define EMA_UPDATES     (1 << 11)
>> +#define PER_TRAIN       (1 << 16)
>> +#define CC_PRINT        (1 << 17)
>> +#define CCFIFO          (1 << 29)
>> +#define REGS            (1 << 30)
>> +#define REG_LISTS       (1 << 31)
>> +
>> +enum {
>> +	DVFS_SEQUENCE = 1,
>> +	WRITE_TRAINING_SEQUENCE = 2,
>> +	PERIODIC_TRAINING_SEQUENCE = 3,
>> +	DVFS_PT1 = 10,
>> +	DVFS_UPDATE = 11,
>> +	TRAINING_PT1 = 12,
>> +	TRAINING_UPDATE = 13,
>> +	PERIODIC_TRAINING_UPDATE = 14
>> +};
>> +
>> +/*
>> + * PTFV defines - basically just indexes into the per table PTFV array.
>> + */
>> +#define PTFV_DQSOSC_MOVAVG_C0D0U0_INDEX		0
>> +#define PTFV_DQSOSC_MOVAVG_C0D0U1_INDEX		1
>> +#define PTFV_DQSOSC_MOVAVG_C0D1U0_INDEX		2
>> +#define PTFV_DQSOSC_MOVAVG_C0D1U1_INDEX		3
>> +#define PTFV_DQSOSC_MOVAVG_C1D0U0_INDEX		4
>> +#define PTFV_DQSOSC_MOVAVG_C1D0U1_INDEX		5
>> +#define PTFV_DQSOSC_MOVAVG_C1D1U0_INDEX		6
>> +#define PTFV_DQSOSC_MOVAVG_C1D1U1_INDEX		7
>> +#define PTFV_DVFS_SAMPLES_INDEX			9
>> +#define PTFV_MOVAVG_WEIGHT_INDEX		10
>> +#define PTFV_CONFIG_CTRL_INDEX			11
>> +
>> +#define PTFV_CONFIG_CTRL_USE_PREVIOUS_EMA	(1 << 0)
>> +
>> +/*
>> + * Do arithmetic in fixed point.
>> + */
>> +#define MOVAVG_PRECISION_FACTOR		100
>> +
>> +/*
>> + * The division portion of the average operation.
>> + */
>> +#define __AVERAGE_PTFV(dev)						\
>> +	({ next_timing->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] = \
>> +	   next_timing->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] / \
>> +	   next_timing->ptfv_list[PTFV_DVFS_SAMPLES_INDEX]; })
>> +
>> +/*
>> + * Convert val to fixed point and add it to the temporary average.
>> + */
>> +#define __INCREMENT_PTFV(dev, val)					\
>> +	({ next_timing->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] += \
>> +	   ((val) * MOVAVG_PRECISION_FACTOR); })
>> +
>> +/*
>> + * Convert a moving average back to integral form and return the value.
>> + */
>> +#define __MOVAVG_AC(timing, dev)					\
>> +	((timing)->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX] /	\
>> +	 MOVAVG_PRECISION_FACTOR)
>> +
>> +/* Weighted update. */
>> +#define __WEIGHTED_UPDATE_PTFV(dev, nval)				\
>> +	do {								\
>> +		int w = PTFV_MOVAVG_WEIGHT_INDEX;			\
>> +		int dqs = PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX;		\
>> +									\
>> +		next_timing->ptfv_list[dqs] =				\
>> +			((nval * MOVAVG_PRECISION_FACTOR) +		\
>> +			 (next_timing->ptfv_list[dqs] *			\
>> +			  next_timing->ptfv_list[w])) /			\
>> +			(next_timing->ptfv_list[w] + 1);		\
>> +									\
>> +		emc_cc_dbg(EMA_UPDATES, "%s: (s=%u) EMA: %u\n",		\
>> +			   __stringify(dev), nval,			\
>> +			   next_timing->ptfv_list[dqs]);		\
>> +	} while (0)
>> +
>> +/* Access a particular average. */
>> +#define __MOVAVG(timing, dev)                      \
>> +	((timing)->ptfv_list[PTFV_DQSOSC_MOVAVG_ ## dev ## _INDEX])
>> +
>> +static u32 update_clock_tree_delay(struct tegra_emc *emc,
>> +				   u32 dram_dev_num, u32 channel_mode, int type)
>> +{
>> +	u32 mrr_req = 0, mrr_data = 0;
>> +	u32 temp0_0 = 0, temp0_1 = 0, temp1_0 = 0, temp1_1 = 0;
>> +	s32 tdel = 0, tmdel = 0, adel = 0;
>> +	u32 cval = 0;
>> +	struct emc_table *last_timing = emc->current_timing;
>> +	struct emc_table *next_timing = emc->next_timing;
>> +	u32 last_timing_rate_mhz = last_timing->rate / 1000;
>> +	u32 next_timing_rate_mhz = next_timing->rate / 1000;
>> +	int dvfs_pt1 = type == DVFS_PT1;
>> +	int dvfs_update = type == DVFS_UPDATE;
>> +	int periodic_training_update = type == PERIODIC_TRAINING_UPDATE;
>> +
>> +	/*
>> +	 * Dev0 MSB.
>> +	 */
>> +	if (dvfs_pt1 || periodic_training_update) {
>> +		mrr_req = (2 << EMC_MRR_DEV_SEL_SHIFT) |
>> +			(19 << EMC_MRR_MA_SHIFT);
>> +		emc_writel(emc, mrr_req, EMC_MRR);
>> +
>> +		WARN(wait_for_update(emc, EMC_EMC_STATUS,
>> +				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
>> +		     "Timed out waiting for MRR 19 (ch=0)\n");
>> +		if (channel_mode == DUAL_CHANNEL)
>> +			WARN(wait_for_update(emc, EMC_EMC_STATUS,
>> +					     EMC_EMC_STATUS_MRR_DIVLD, 1,
>> +					     REG_EMC1),
>> +			     "Timed out waiting for MRR 19 (ch=1)\n");
>> +
>> +		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
>> +			   EMC_MRR_DATA_SHIFT;
>> +
>> +		temp0_0 = (mrr_data & 0xff) << 8;
>> +		temp0_1 = mrr_data & 0xff00;
>> +
>> +		if (channel_mode == DUAL_CHANNEL) {
>> +			mrr_data = (emc1_readl(emc, EMC_MRR) &
>> +				    EMC_MRR_DATA_MASK) <<
>> +				   EMC_MRR_DATA_SHIFT;
>> +			temp1_0 = (mrr_data & 0xff) << 8;
>> +			temp1_1 = mrr_data & 0xff00;
>> +		}
>> +
>> +		/*
>> +		 * Dev0 LSB.
>> +		 */
>> +		mrr_req = (mrr_req & ~EMC_MRR_MA_MASK) |
>> +			  (18 << EMC_MRR_MA_SHIFT);
>> +		emc_writel(emc, mrr_req, EMC_MRR);
>> +
>> +		WARN(wait_for_update(emc, EMC_EMC_STATUS,
>> +				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
>> +		     "Timed out waiting for MRR 18 (ch=0)\n");
>> +		if (channel_mode == DUAL_CHANNEL)
>> +			WARN(wait_for_update(emc, EMC_EMC_STATUS,
>> +					     EMC_EMC_STATUS_MRR_DIVLD, 1,
>> +					     REG_EMC1),
>> +			     "Timed out waiting for MRR 18 (ch=1)\n");
>> +
>> +		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
>> +			   EMC_MRR_DATA_SHIFT;
>> +
>> +		temp0_0 |= mrr_data & 0xff;
>> +		temp0_1 |= (mrr_data & 0xff00) >> 8;
>> +
>> +		if (channel_mode == DUAL_CHANNEL) {
>> +			mrr_data = (emc1_readl(emc, EMC_MRR) &
>> +				    EMC_MRR_DATA_MASK) <<
>> +				   EMC_MRR_DATA_SHIFT;
>> +			temp1_0 |= (mrr_data & 0xff);
>> +			temp1_1 |= (mrr_data & 0xff00) >> 8;
>> +		}
>> +	}
>> +
>> +	if (dvfs_pt1 || periodic_training_update)
>> +		cval = (1000000 * tegra210_actual_osc_clocks(
>> +					last_timing->run_clocks)) /
>> +		       (last_timing_rate_mhz * 2 * temp0_0);
>> +
>> +	if (dvfs_pt1)
>> +		__INCREMENT_PTFV(C0D0U0, cval);
>> +	else if (dvfs_update)
>> +		__AVERAGE_PTFV(C0D0U0);
>> +	else if (periodic_training_update)
>> +		__WEIGHTED_UPDATE_PTFV(C0D0U0, cval);
>> +
>> +	if (dvfs_update || periodic_training_update) {
>> +		tdel = next_timing->current_dram_clktree_c0d0u0 -
>> +			__MOVAVG_AC(next_timing, C0D0U0);
>> +		tmdel = (tdel < 0) ? -1 * tdel : tdel;
>> +		adel = tmdel;
>> +		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
>> +		    next_timing->tree_margin)
>> +			next_timing->current_dram_clktree_c0d0u0 =
>> +				__MOVAVG_AC(next_timing, C0D0U0);
>> +	}
>> +
>> +	if (dvfs_pt1 || periodic_training_update)
>> +		cval = (1000000 * tegra210_actual_osc_clocks(
>> +					last_timing->run_clocks)) /
>> +		       (last_timing_rate_mhz * 2 * temp0_1);
>> +
>> +	if (dvfs_pt1)
>> +		__INCREMENT_PTFV(C0D0U1, cval);
>> +	else if (dvfs_update)
>> +		__AVERAGE_PTFV(C0D0U1);
>> +	else if (periodic_training_update)
>> +		__WEIGHTED_UPDATE_PTFV(C0D0U1, cval);
>> +
>> +	if (dvfs_update || periodic_training_update) {
>> +		tdel = next_timing->current_dram_clktree_c0d0u1 -
>> +			__MOVAVG_AC(next_timing, C0D0U1);
>> +		tmdel = (tdel < 0) ? -1 * tdel : tdel;
>> +
>> +		if (tmdel > adel)
>> +			adel = tmdel;
>> +
>> +		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
>> +		    next_timing->tree_margin)
>> +			next_timing->current_dram_clktree_c0d0u1 =
>> +				__MOVAVG_AC(next_timing, C0D0U1);
>> +	}
>> +
>> +	if (channel_mode == DUAL_CHANNEL) {
>> +		if (dvfs_pt1 || periodic_training_update)
>> +			cval = (1000000 * tegra210_actual_osc_clocks(
>> +						last_timing->run_clocks)) /
>> +			       (last_timing_rate_mhz * 2 * temp1_0);
>> +		if (dvfs_pt1)
>> +			__INCREMENT_PTFV(C1D0U0, cval);
>> +		else if (dvfs_update)
>> +			__AVERAGE_PTFV(C1D0U0);
>> +		else if (periodic_training_update)
>> +			__WEIGHTED_UPDATE_PTFV(C1D0U0, cval);
>> +
>> +		if (dvfs_update || periodic_training_update) {
>> +			tdel = next_timing->current_dram_clktree_c1d0u0 -
>> +				__MOVAVG_AC(next_timing, C1D0U0);
>> +			tmdel = (tdel < 0) ? -1 * tdel : tdel;
>> +
>> +			if (tmdel > adel)
>> +				adel = tmdel;
>> +
>> +			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
>> +			    next_timing->tree_margin)
>> +				next_timing->current_dram_clktree_c1d0u0 =
>> +					__MOVAVG_AC(next_timing, C1D0U0);
>> +		}
>> +
>> +		if (dvfs_pt1 || periodic_training_update)
>> +			cval = (1000000 * tegra210_actual_osc_clocks(
>> +						last_timing->run_clocks)) /
>> +			       (last_timing_rate_mhz * 2 * temp1_1);
>> +		if (dvfs_pt1)
>> +			__INCREMENT_PTFV(C1D0U1, cval);
>> +		else if (dvfs_update)
>> +			__AVERAGE_PTFV(C1D0U1);
>> +		else if (periodic_training_update)
>> +			__WEIGHTED_UPDATE_PTFV(C1D0U1, cval);
>> +
>> +		if (dvfs_update || periodic_training_update) {
>> +			tdel = next_timing->current_dram_clktree_c1d0u1 -
>> +				__MOVAVG_AC(next_timing, C1D0U1);
>> +			tmdel = (tdel < 0) ? -1 * tdel : tdel;
>> +
>> +			if (tmdel > adel)
>> +				adel = tmdel;
>> +
>> +			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
>> +			    next_timing->tree_margin)
>> +				next_timing->current_dram_clktree_c1d0u1 =
>> +					__MOVAVG_AC(next_timing, C1D0U1);
>> +		}
>> +	}
>> +
>> +	if (emc->dram_dev_num != TWO_RANK)
>> +		goto done;
>> +
>> +	/*
>> +	 * Dev1 MSB.
>> +	 */
>> +	if (dvfs_pt1 || periodic_training_update) {
>> +		mrr_req = (1 << EMC_MRR_DEV_SEL_SHIFT) |
>> +			  (19 << EMC_MRR_MA_SHIFT);
>> +		emc_writel(emc, mrr_req, EMC_MRR);
>> +
>> +		WARN(wait_for_update(emc, EMC_EMC_STATUS,
>> +				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
>> +		     "Timed out waiting for MRR 19 (ch=0)\n");
>> +		if (channel_mode == DUAL_CHANNEL)
>> +			WARN(wait_for_update(emc, EMC_EMC_STATUS,
>> +					     EMC_EMC_STATUS_MRR_DIVLD, 1,
>> +					     REG_EMC1),
>> +			     "Timed out waiting for MRR 19 (ch=1)\n");
>> +
>> +		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
>> +			   EMC_MRR_DATA_SHIFT;
>> +
>> +		temp0_0 = (mrr_data & 0xff) << 8;
>> +		temp0_1 = mrr_data & 0xff00;
>> +
>> +		if (channel_mode == DUAL_CHANNEL) {
>> +			mrr_data = (emc1_readl(emc, EMC_MRR) &
>> +				    EMC_MRR_DATA_MASK) <<
>> +				   EMC_MRR_DATA_SHIFT;
>> +			temp1_0 = (mrr_data & 0xff) << 8;
>> +			temp1_1 = mrr_data & 0xff00;
>> +		}
>> +
>> +		/*
>> +		 * Dev1 LSB.
>> +		 */
>> +		mrr_req = (mrr_req & ~EMC_MRR_MA_MASK) |
>> +			  (18 << EMC_MRR_MA_SHIFT);
>> +		emc_writel(emc, mrr_req, EMC_MRR);
>> +
>> +		WARN(wait_for_update(emc, EMC_EMC_STATUS,
>> +				     EMC_EMC_STATUS_MRR_DIVLD, 1, REG_EMC),
>> +		     "Timed out waiting for MRR 18 (ch=0)\n");
>> +		if (channel_mode == DUAL_CHANNEL)
>> +			WARN(wait_for_update(emc, EMC_EMC_STATUS,
>> +					     EMC_EMC_STATUS_MRR_DIVLD, 1,
>> +					     REG_EMC1),
>> +			     "Timed out waiting for MRR 18 (ch=1)\n");
>> +
>> +		mrr_data = (emc_readl(emc, EMC_MRR) & EMC_MRR_DATA_MASK) <<
>> +			   EMC_MRR_DATA_SHIFT;
>> +
>> +		temp0_0 |= mrr_data & 0xff;
>> +		temp0_1 |= (mrr_data & 0xff00) >> 8;
>> +
>> +		if (channel_mode == DUAL_CHANNEL) {
>> +			mrr_data = (emc1_readl(emc, EMC_MRR) &
>> +				    EMC_MRR_DATA_MASK) <<
>> +				   EMC_MRR_DATA_SHIFT;
>> +			temp1_0 |= (mrr_data & 0xff);
>> +			temp1_1 |= (mrr_data & 0xff00) >> 8;
>> +		}
>> +	}
>> +
>> +	if (dvfs_pt1 || periodic_training_update)
>> +		cval = (1000000 * tegra210_actual_osc_clocks(
>> +					last_timing->run_clocks)) /
>> +		       (last_timing_rate_mhz * 2 * temp0_0);
>> +
>> +	if (dvfs_pt1)
>> +		__INCREMENT_PTFV(C0D1U0, cval);
>> +	else if (dvfs_update)
>> +		__AVERAGE_PTFV(C0D1U0);
>> +	else if (periodic_training_update)
>> +		__WEIGHTED_UPDATE_PTFV(C0D1U0, cval);
>> +
>> +	if (dvfs_update || periodic_training_update) {
>> +		tdel = next_timing->current_dram_clktree_c0d1u0 -
>> +		       __MOVAVG_AC(next_timing, C0D1U0);
>> +		tmdel = (tdel < 0) ? -1 * tdel : tdel;
>> +		if (tmdel > adel)
>> +			adel = tmdel;
>> +
>> +		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
>> +		    next_timing->tree_margin)
>> +			next_timing->current_dram_clktree_c0d1u0 =
>> +				__MOVAVG_AC(next_timing, C0D1U0);
>> +	}
>> +
>> +	if (dvfs_pt1 || periodic_training_update)
>> +		cval = (1000000 * tegra210_actual_osc_clocks(
>> +					last_timing->run_clocks)) /
>> +		       (last_timing_rate_mhz * 2 * temp0_1);
>> +
>> +	if (dvfs_pt1)
>> +		__INCREMENT_PTFV(C0D1U1, cval);
>> +	else if (dvfs_update)
>> +		__AVERAGE_PTFV(C0D1U1);
>> +	else if (periodic_training_update)
>> +		__WEIGHTED_UPDATE_PTFV(C0D1U1, cval);
>> +
>> +	if (dvfs_update || periodic_training_update) {
>> +		tdel = next_timing->current_dram_clktree_c0d1u1 -
>> +		       __MOVAVG_AC(next_timing, C0D1U1);
>> +		tmdel = (tdel < 0) ? -1 * tdel : tdel;
>> +		if (tmdel > adel)
>> +			adel = tmdel;
>> +
>> +		if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
>> +		    next_timing->tree_margin)
>> +			next_timing->current_dram_clktree_c0d1u1 =
>> +				__MOVAVG_AC(next_timing, C0D1U1);
>> +	}
>> +
>> +	if (channel_mode == DUAL_CHANNEL) {
>> +		if (dvfs_pt1 || periodic_training_update)
>> +			cval = (1000000 * tegra210_actual_osc_clocks(
>> +						last_timing->run_clocks)) /
>> +			       (last_timing_rate_mhz * 2 * temp1_0);
>> +
>> +		if (dvfs_pt1)
>> +			__INCREMENT_PTFV(C1D1U0, cval);
>> +		else if (dvfs_update)
>> +			__AVERAGE_PTFV(C1D1U0);
>> +		else if (periodic_training_update)
>> +			__WEIGHTED_UPDATE_PTFV(C1D1U0, cval);
>> +
>> +		if (dvfs_update || periodic_training_update) {
>> +			tdel = next_timing->current_dram_clktree_c1d1u0 -
>> +			       __MOVAVG_AC(next_timing, C1D1U0);
>> +			tmdel = (tdel < 0) ? -1 * tdel : tdel;
>> +			if (tmdel > adel)
>> +				adel = tmdel;
>> +
>> +			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
>> +			    next_timing->tree_margin)
>> +				next_timing->current_dram_clktree_c1d1u0 =
>> +					__MOVAVG_AC(next_timing, C1D1U0);
>> +		}
>> +
>> +		if (dvfs_pt1 || periodic_training_update)
>> +			cval = (1000000 * tegra210_actual_osc_clocks(
>> +						last_timing->run_clocks)) /
>> +			       (last_timing_rate_mhz * 2 * temp1_1);
>> +
>> +		if (dvfs_pt1)
>> +			__INCREMENT_PTFV(C1D1U1, cval);
>> +		else if (dvfs_update)
>> +			__AVERAGE_PTFV(C1D1U1);
>> +		else if (periodic_training_update)
>> +			__WEIGHTED_UPDATE_PTFV(C1D1U1, cval);
>> +
>> +		if (dvfs_update || periodic_training_update) {
>> +			tdel = next_timing->current_dram_clktree_c1d1u1 -
>> +			       __MOVAVG_AC(next_timing, C1D1U1);
>> +			tmdel = (tdel < 0) ? -1 * tdel : tdel;
>> +			if (tmdel > adel)
>> +				adel = tmdel;
>> +
>> +			if (tmdel * 128 * next_timing_rate_mhz / 1000000 >
>> +			    next_timing->tree_margin)
>> +				next_timing->current_dram_clktree_c1d1u1 =
>> +					__MOVAVG_AC(next_timing, C1D1U1);
>> +		}
>> +	}
>> +
>> +done:
>> +	return adel;
>> +}
>> +
>> +static u32 periodic_compensation_handler(struct tegra_emc *emc, u32 type,
>> +					 u32 dram_dev_num,
>> +					 u32 channel_mode,
>> +					 struct emc_table *last_timing,
>> +					 struct emc_table *next_timing)
>> +{
>> +#define __COPY_EMA(nt, lt, dev)						\
>> +	({ __MOVAVG(nt, dev) = __MOVAVG(lt, dev) *			\
>> +	   (nt)->ptfv_list[PTFV_DVFS_SAMPLES_INDEX]; })
>> +
>> +	u32 i;
>> +	u32 adel = 0;
>> +	u32 samples = next_timing->ptfv_list[PTFV_DVFS_SAMPLES_INDEX];
>> +	u32 delay = 2 +
>> +		(1000 * tegra210_actual_osc_clocks(last_timing->run_clocks) /
>> +		last_timing->rate);
>> +
>> +	if (!next_timing->periodic_training)
>> +		return 0;
>> +
>> +	if (type == DVFS_SEQUENCE) {
>> +		if (last_timing->periodic_training &&
>> +		    (next_timing->ptfv_list[PTFV_CONFIG_CTRL_INDEX] &
>> +		     PTFV_CONFIG_CTRL_USE_PREVIOUS_EMA)) {
>> +			/*
>> +			 * If the previous frequency was using periodic
>> +			 * calibration then we can reuse the previous
>> +			 * frequencies EMA data.
>> +			 */
>> +			__COPY_EMA(next_timing, last_timing, C0D0U0);
>> +			__COPY_EMA(next_timing, last_timing, C0D0U1);
>> +			__COPY_EMA(next_timing, last_timing, C1D0U0);
>> +			__COPY_EMA(next_timing, last_timing, C1D0U1);
>> +			__COPY_EMA(next_timing, last_timing, C0D1U0);
>> +			__COPY_EMA(next_timing, last_timing, C0D1U1);
>> +			__COPY_EMA(next_timing, last_timing, C1D1U0);
>> +			__COPY_EMA(next_timing, last_timing, C1D1U1);
>> +		} else {
>> +			/* Reset the EMA.*/
>> +			__MOVAVG(next_timing, C0D0U0) = 0;
>> +			__MOVAVG(next_timing, C0D0U1) = 0;
>> +			__MOVAVG(next_timing, C1D0U0) = 0;
>> +			__MOVAVG(next_timing, C1D0U1) = 0;
>> +			__MOVAVG(next_timing, C0D1U0) = 0;
>> +			__MOVAVG(next_timing, C0D1U1) = 0;
>> +			__MOVAVG(next_timing, C1D1U0) = 0;
>> +			__MOVAVG(next_timing, C1D1U1) = 0;
>> +
>> +			for (i = 0; i < samples; i++) {
>> +				tegra210_start_periodic_compensation(emc);
>> +				udelay(delay);
>> +
>> +				/*
>> +				 * Generate next sample of data.
>> +				 */
>> +				adel = update_clock_tree_delay(emc,
>> +							emc->dram_dev_num,
>> +							channel_mode,
>> +							DVFS_PT1);
>> +			}
>> +		}
>> +
>> +		/*
>> +		 * Seems like it should be part of the
>> +		 * 'if (last_timing->periodic_training)' conditional
>> +		 * since is already done for the else clause.
>> +		 */
>> +		adel = update_clock_tree_delay(emc,
>> +					       emc->dram_dev_num,
>> +					       channel_mode,
>> +					       DVFS_UPDATE);
>> +	}
>> +
>> +	if (type == PERIODIC_TRAINING_SEQUENCE) {
>> +		tegra210_start_periodic_compensation(emc);
>> +		udelay(delay);
>> +
>> +		adel = update_clock_tree_delay(emc,
>> +					       emc->dram_dev_num,
>> +					       channel_mode,
>> +					       PERIODIC_TRAINING_UPDATE);
>> +	}
>> +
>> +	return adel;
>> +}
>> +
>> +u32 __do_periodic_emc_compensation_r21021(struct tegra_emc *emc)
>> +{
>> +	u32 dram_dev_num;
>> +	u32 channel_mode;
>> +	u32 emc_cfg, emc_cfg_o;
>> +	u32 emc_dbg_o;
>> +	u32 del, i;
>> +	u32 list[] = {
>> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0,
>> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1,
>> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2,
>> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3,
>> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0,
>> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1,
>> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2,
>> +		EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3,
>> +		EMC_DATA_BRLSHFT_0,
>> +		EMC_DATA_BRLSHFT_1
>> +	};
>> +	u32 items = ARRAY_SIZE(list);
>> +	u32 emc_cfg_update;
>> +	struct emc_table *current_timing = emc->current_timing;
>> +
>> +	if (current_timing->periodic_training) {
>> +		channel_mode =
>> +			!!(current_timing->burst_regs[EMC_FBIO_CFG7_INDEX] &
>> +			(1 << 2));
>> +		dram_dev_num = 1 + (mc_readl(emc->mc, MC_EMEM_ADR_CFG) & 0x1);
>> +
>> +		emc_cc_dbg(PER_TRAIN, "Periodic training starting\n");
>> +
>> +		emc_dbg_o = emc_readl(emc, EMC_DBG);
>> +		emc_cfg_o = emc_readl(emc, EMC_CFG);
>> +		emc_cfg = emc_cfg_o & ~(EMC_CFG_DYN_SELF_REF |
>> +					EMC_CFG_DRAM_ACPD |
>> +					EMC_CFG_DRAM_CLKSTOP_PD |
>> +					EMC_CFG_DRAM_CLKSTOP_PD);
>> +
>> +
>> +		/*
>> +		 * 1. Power optimizations should be off.
>> +		 */
>> +		emc_writel(emc, emc_cfg, EMC_CFG);
>> +
>> +		/* Does emc_timing_update() for above changes. */
>> +		tegra210_dll_disable(emc, channel_mode);
>> +
>> +		wait_for_update(emc, EMC_EMC_STATUS,
>> +				EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK, 0,
>> +				REG_EMC);
>> +		if (channel_mode)
>> +			wait_for_update(emc, EMC_EMC_STATUS,
>> +					EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK,
>> +					0, REG_EMC1);
>> +
>> +		wait_for_update(emc, EMC_EMC_STATUS,
>> +				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
>> +				REG_EMC);
>> +		if (channel_mode)
>> +			wait_for_update(emc, EMC_EMC_STATUS,
>> +				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
>> +				REG_EMC1);
>> +
>> +		emc_cfg_update = emc_readl(emc, EMC_CFG_UPDATE);
>> +		emc_writel(emc, (emc_cfg_update &
>> +			    ~EMC_CFG_UPDATE_UPDATE_DLL_IN_UPDATE_MASK) |
>> +			   (2 << EMC_CFG_UPDATE_UPDATE_DLL_IN_UPDATE_SHIFT),
>> +			   EMC_CFG_UPDATE);
>> +
>> +		/*
>> +		 * 2. osc kick off - this assumes training and dvfs have set
>> +		 *    correct MR23.
>> +		 */
>> +		tegra210_start_periodic_compensation(emc);
>> +
>> +		/*
>> +		 * 3. Let dram capture its clock tree delays.
>> +		 */
>> +		udelay((tegra210_actual_osc_clocks(current_timing->run_clocks) *
>> +			1000) /
>> +		       current_timing->rate + 1);
>> +
>> +		/*
>> +		 * 4. Check delta wrt previous values (save value if margin
>> +		 *    exceeds what is set in table).
>> +		 */
>> +		del = periodic_compensation_handler(emc,
>> +						    PERIODIC_TRAINING_SEQUENCE,
>> +						    dram_dev_num,
>> +						    channel_mode,
>> +						    current_timing,
>> +						    current_timing);
>> +
>> +		/*
>> +		 * 5. Apply compensation w.r.t. trained values (if clock tree
>> +		 *    has drifted more than the set margin).
>> +		 */
>> +		if (current_timing->tree_margin <
>> +		    ((del * 128 * (current_timing->rate / 1000)) / 1000000)) {
>> +			for (i = 0; i < items; i++) {
>> +				u32 tmp =
>> +				tegra210_apply_periodic_compensation_trimmer(
>> +				current_timing, list[i]);
>> +
>> +				emc_cc_dbg(EMA_WRITES, "0x%08x <= 0x%08x\n",
>> +					   list[i], tmp);
>> +				emc_writel(emc, tmp, list[i]);
>> +			}
>> +		}
>> +
>> +		emc_writel(emc, emc_cfg_o, EMC_CFG);
>> +
>> +		/*
>> +		 * 6. Timing update actally applies the new trimmers.
>> +		 */
>> +		emc_timing_update(emc, channel_mode);
>> +
>> +		/* 6.1. Restore the UPDATE_DLL_IN_UPDATE field. */
>> +		emc_writel(emc, emc_cfg_update, EMC_CFG_UPDATE);
>> +
>> +		/* 6.2. Restore the DLL. */
>> +		tegra210_dll_enable(emc, channel_mode);
>> +
>> +		/*
>> +		 * 7. Copy over the periodic training registers that we updated
>> +		 *    here to the corresponding derated/non-derated table.
>> +		 */
>> +		tegra210_update_emc_alt_timing(emc, current_timing);
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +/*
>> + * Do the clock change sequence.
>> + */
>> +void emc_set_clock_r21021(struct tegra_emc *emc, u32 clksrc)
>> +{
>> +	/*
>> +	 * This is the timing table for the source frequency. It does _not_
>> +	 * necessarily correspond to the actual timing values in the EMC at the
>> +	 * moment. If the boot BCT differs from the table then this can happen.
>> +	 * However, we need it for accessing the dram_timings (which are not
>> +	 * really registers) array for the current frequency.
>> +	 */
>> +	struct emc_table *fake_timing;
>> +	struct emc_table *last_timing = emc->current_timing;
>> +	struct emc_table *next_timing = emc->next_timing;
>> +
>> +	u32 i, tmp;
>> +
>> +	u32 cya_allow_ref_cc = 0, ref_b4_sref_en = 0, cya_issue_pc_ref = 0;
>> +
>> +	u32 zqcal_before_cc_cutoff = 2400; /* In picoseconds */
>> +	u32 ref_delay_mult;
>> +	u32 ref_delay;
>> +	s32 zq_latch_dvfs_wait_time;
>> +	s32 tZQCAL_lpddr4_fc_adj;
>> +	/* Scaled by x1000 */
>> +	u32 tFC_lpddr4 = 1000 * next_timing->dram_timings[T_FC_LPDDR4];
>> +	u32 tZQCAL_lpddr4 = 1000000;
>> +
>> +	u32 dram_type, dram_dev_num, shared_zq_resistor;
>> +	u32 channel_mode;
>> +	u32 is_lpddr3;
>> +
>> +	u32 emc_cfg, emc_sel_dpd_ctrl, emc_cfg_reg;
>> +
>> +	u32 emc_dbg;
>> +	u32 emc_zcal_interval;
>> +	u32 emc_zcal_wait_cnt_old;
>> +	u32 emc_zcal_wait_cnt_new;
>> +	u32 emc_dbg_active;
>> +	u32 zq_op;
>> +	u32 zcal_wait_time_clocks;
>> +	u32 zcal_wait_time_ps;
>> +
>> +	u32 emc_auto_cal_config;
>> +	u32 auto_cal_en;
>> +
>> +	u32 mr13_catr_enable;
>> +
>> +	u32 ramp_up_wait = 0, ramp_down_wait = 0;
>> +
>> +	/* In picoseconds. */
>> +	u32 source_clock_period;
>> +	u32 destination_clock_period;
>> +
>> +	u32 emc_dbg_o;
>> +	u32 emc_cfg_pipe_clk_o;
>> +	u32 emc_pin_o;
>> +
>> +	u32 mr13_flip_fspwr;
>> +	u32 mr13_flip_fspop;
>> +
>> +	u32 opt_zcal_en_cc;
>> +	u32 opt_do_sw_qrst = 1;
>> +	u32 opt_dvfs_mode;
>> +	u32 opt_dll_mode;
>> +	u32 opt_cc_short_zcal = 1;
>> +	u32 opt_short_zcal = 1;
>> +	u32 save_restore_clkstop_pd = 1;
>> +
>> +	u32 prelock_dll_en = 0, dll_out;
>> +
>> +	int next_push, next_dq_e_ivref, next_dqs_e_ivref;
>> +
>> +	u32 opt_war_200024907;
>> +	u32 zq_wait_long;
>> +	u32 zq_wait_short;
>> +
>> +	u32 bg_regulator_switch_complete_wait_clks;
>> +	u32 bg_regulator_mode_change;
>> +	u32 enable_bglp_regulator;
>> +	u32 enable_bg_regulator;
>> +
>> +	u32 tRTM;
>> +	u32 RP_war;
>> +	u32 R2P_war;
>> +	u32 TRPab_war;
>> +	s32 nRTP;
>> +	u32 deltaTWATM;
>> +	u32 W2P_war;
>> +	u32 tRPST;
>> +
>> +	u32 mrw_req;
>> +	u32 adel = 0, compensate_trimmer_applicable = 0;
>> +	u32 next_timing_rate_mhz = next_timing->rate / 1000;
>> +
>> +	static u32 fsp_for_next_freq;
>> +
>> +	emc_cc_dbg(INFO, "Running clock change.\n");
>> +
>> +	fake_timing = get_timing_from_freq(emc, last_timing->rate);
>> +
>> +	fsp_for_next_freq = !fsp_for_next_freq;
>> +
>> +	dram_type = emc_readl(emc, EMC_FBIO_CFG5) &
>> +		    EMC_FBIO_CFG5_DRAM_TYPE_MASK >>
>> +		    EMC_FBIO_CFG5_DRAM_TYPE_SHIFT;
>> +	shared_zq_resistor = last_timing->burst_regs[EMC_ZCAL_WAIT_CNT_INDEX] &
>> +			     1 << 31;
>> +	channel_mode = !!(last_timing->burst_regs[EMC_FBIO_CFG7_INDEX] &
>> +			  1 << 2);
>> +	opt_zcal_en_cc = (next_timing->burst_regs[EMC_ZCAL_INTERVAL_INDEX] &&
>> +			  !last_timing->burst_regs[EMC_ZCAL_INTERVAL_INDEX]) ||
>> +			  dram_type == DRAM_TYPE_LPDDR4;
>> +	opt_dll_mode = (dram_type == DRAM_TYPE_DDR3) ?
>> +		       get_dll_state(next_timing) : DLL_OFF;
>> +	is_lpddr3 = (dram_type == DRAM_TYPE_LPDDR2) &&
>> +		    next_timing->burst_regs[EMC_FBIO_CFG5_INDEX] &
>> +		    1 << 25;
>> +	opt_war_200024907 = (dram_type == DRAM_TYPE_LPDDR4);
>> +	opt_dvfs_mode = MAN_SR;
>> +	dram_dev_num = (mc_readl(emc->mc, MC_EMEM_ADR_CFG) & 0x1) + 1;
>> +
>> +	emc_cfg_reg = emc_readl(emc, EMC_CFG);
>> +	emc_auto_cal_config = emc_readl(emc, EMC_AUTO_CAL_CONFIG);
>> +
>> +	source_clock_period = 1000000000 / last_timing->rate;
>> +	destination_clock_period = 1000000000 / next_timing->rate;
>> +
>> +	tZQCAL_lpddr4_fc_adj = (destination_clock_period >
>> +				zqcal_before_cc_cutoff) ?
>> +		tZQCAL_lpddr4 / destination_clock_period :
>> +		(tZQCAL_lpddr4 - tFC_lpddr4) / destination_clock_period;
>> +	emc_dbg_o = emc_readl(emc, EMC_DBG);
>> +	emc_pin_o = emc_readl(emc, EMC_PIN);
>> +	emc_cfg_pipe_clk_o = emc_readl(emc, EMC_CFG_PIPE_CLK);
>> +	emc_dbg = emc_dbg_o;
>> +
>> +	emc_cfg = next_timing->burst_regs[EMC_CFG_INDEX];
>> +	emc_cfg &= ~(EMC_CFG_DYN_SELF_REF | EMC_CFG_DRAM_ACPD |
>> +		     EMC_CFG_DRAM_CLKSTOP_SR | EMC_CFG_DRAM_CLKSTOP_PD);
>> +	emc_sel_dpd_ctrl = next_timing->emc_sel_dpd_ctrl;
>> +	emc_sel_dpd_ctrl &= ~(EMC_SEL_DPD_CTRL_CLK_SEL_DPD_EN |
>> +			      EMC_SEL_DPD_CTRL_CA_SEL_DPD_EN |
>> +			      EMC_SEL_DPD_CTRL_RESET_SEL_DPD_EN |
>> +			      EMC_SEL_DPD_CTRL_ODT_SEL_DPD_EN |
>> +			      EMC_SEL_DPD_CTRL_DATA_SEL_DPD_EN);
>> +
>> +	emc_cc_dbg(INFO, "Clock change version: %d\n",
>> +		   DVFS_CLOCK_CHANGE_VERSION);
>> +	emc_cc_dbg(INFO, "DRAM type = %d\n", emc->dram_type);
>> +	emc_cc_dbg(INFO, "DRAM dev #: %d\n", dram_dev_num);
>> +	emc_cc_dbg(INFO, "Next EMC clksrc: 0x%08x\n", clksrc);
>> +	emc_cc_dbg(INFO, "DLL clksrc:      0x%08x\n", next_timing->dll_clk_src);
>> +	emc_cc_dbg(INFO, "last rate: %u, next rate %u\n", last_timing->rate,
>> +		   next_timing->rate);
>> +	emc_cc_dbg(INFO, "last period: %u, next period: %u\n",
>> +		   source_clock_period, destination_clock_period);
>> +	emc_cc_dbg(INFO, "  shared_zq_resistor: %d\n", !!shared_zq_resistor);
>> +	emc_cc_dbg(INFO, "  channel_mode: %d\n", channel_mode);
>> +	emc_cc_dbg(INFO, "  opt_dll_mode: %d\n", opt_dll_mode);
>> +
>> +	/*
>> +	 * Step 1:
>> +	 *   Pre DVFS SW sequence.
>> +	 */
>> +	emc_cc_dbg(STEPS, "Step 1\n");
>> +	emc_cc_dbg(STEPS, "Step 1.1: Disable DLL temporarily.\n");
>> +	tmp = emc_readl(emc, EMC_CFG_DIG_DLL);
>> +	tmp &= ~EMC_CFG_DIG_DLL_CFG_DLL_EN;
>> +	emc_writel(emc, tmp, EMC_CFG_DIG_DLL);
>> +
>> +	emc_timing_update(emc, channel_mode);
>> +	wait_for_update(emc, EMC_CFG_DIG_DLL,
>> +			EMC_CFG_DIG_DLL_CFG_DLL_EN, 0, REG_EMC);
>> +	if (channel_mode)
>> +		wait_for_update(emc, EMC_CFG_DIG_DLL,
>> +				EMC_CFG_DIG_DLL_CFG_DLL_EN, 0, REG_EMC1);
>> +
>> +	emc_cc_dbg(STEPS, "Step 1.2: Disable AUTOCAL temporarily.\n");
>> +	emc_auto_cal_config = next_timing->emc_auto_cal_config;
>> +	auto_cal_en = emc_auto_cal_config & EMC_AUTO_CAL_CONFIG_AUTO_CAL_ENABLE;
>> +	emc_auto_cal_config &= ~EMC_AUTO_CAL_CONFIG_AUTO_CAL_START;
>> +	emc_auto_cal_config |=  EMC_AUTO_CAL_CONFIG_AUTO_CAL_MEASURE_STALL;
>> +	emc_auto_cal_config |=  EMC_AUTO_CAL_CONFIG_AUTO_CAL_UPDATE_STALL;
>> +	emc_auto_cal_config |=  auto_cal_en;
>> +	emc_writel(emc, emc_auto_cal_config, EMC_AUTO_CAL_CONFIG);
>> +	emc_readl(emc, EMC_AUTO_CAL_CONFIG); /* Flush write. */
>> +
>> +	emc_cc_dbg(STEPS, "Step 1.3: Disable other power features.\n");
>> +	emc_set_shadow_bypass(emc, ACTIVE);
>> +	emc_writel(emc, emc_cfg, EMC_CFG);
>> +	emc_writel(emc, emc_sel_dpd_ctrl, EMC_SEL_DPD_CTRL);
>> +	emc_set_shadow_bypass(emc, ASSEMBLY);
>> +
>> +	if (next_timing->periodic_training) {
>> +		tegra210_reset_dram_clktree_values(next_timing);
>> +
>> +		wait_for_update(emc, EMC_EMC_STATUS,
>> +				EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK, 0,
>> +				REG_EMC);
>> +		if (channel_mode)
>> +			wait_for_update(emc, EMC_EMC_STATUS,
>> +					EMC_EMC_STATUS_DRAM_IN_POWERDOWN_MASK,
>> +					0, REG_EMC1);
>> +
>> +		wait_for_update(emc, EMC_EMC_STATUS,
>> +				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
>> +				REG_EMC);
>> +		if (channel_mode)
>> +			wait_for_update(emc, EMC_EMC_STATUS,
>> +				EMC_EMC_STATUS_DRAM_IN_SELF_REFRESH_MASK, 0,
>> +				REG_EMC1);
>> +
>> +		tegra210_start_periodic_compensation(emc);
>> +
>> +		udelay(((1000 *
>> +			 tegra210_actual_osc_clocks(last_timing->run_clocks)) /
>> +			last_timing->rate) + 2);
>> +		adel = periodic_compensation_handler(emc, DVFS_SEQUENCE,
>> +						     dram_dev_num,
>> +						     channel_mode,
>> +						     fake_timing, next_timing);
>> +		compensate_trimmer_applicable =
>> +			next_timing->periodic_training &&
>> +			((adel * 128 * next_timing_rate_mhz) / 1000000) >
>> +			next_timing->tree_margin;
>> +	}
>> +
>> +	emc_writel(emc, EMC_INTSTATUS_CLKCHANGE_COMPLETE, EMC_INTSTATUS);
>> +	emc_set_shadow_bypass(emc, ACTIVE);
>> +	emc_writel(emc, emc_cfg, EMC_CFG);
>> +	emc_writel(emc, emc_sel_dpd_ctrl, EMC_SEL_DPD_CTRL);
>> +	emc_writel(emc, emc_cfg_pipe_clk_o | EMC_CFG_PIPE_CLK_CLK_ALWAYS_ON,
>> +		   EMC_CFG_PIPE_CLK);
>> +	emc_writel(emc, next_timing->emc_fdpd_ctrl_cmd_no_ramp &
>> +		   ~EMC_FDPD_CTRL_CMD_NO_RAMP_CMD_DPD_NO_RAMP_ENABLE,
>> +		   EMC_FDPD_CTRL_CMD_NO_RAMP);
>> +
>> +	bg_regulator_mode_change =
>> +		((next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
>> +		  EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD) ^
>> +		 (last_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
>> +		  EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD)) ||
>> +		((next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
>> +		  EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD) ^
>> +		 (last_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
>> +		  EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD));
>> +	enable_bglp_regulator =
>> +		(next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
>> +		 EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD) == 0;
>> +	enable_bg_regulator =
>> +		(next_timing->burst_regs[EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
>> +		 EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD) == 0;
>> +
>> +	if (bg_regulator_mode_change) {
>> +		if (enable_bg_regulator)
>> +			emc_writel(emc, last_timing->burst_regs
>> +				   [EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
>> +				   ~EMC_PMACRO_BG_BIAS_CTRL_0_BG_E_PWRD,
>> +				   EMC_PMACRO_BG_BIAS_CTRL_0);
>> +		else
>> +			emc_writel(emc, last_timing->burst_regs
>> +				   [EMC_PMACRO_BG_BIAS_CTRL_0_INDEX] &
>> +				   ~EMC_PMACRO_BG_BIAS_CTRL_0_BGLP_E_PWRD,
>> +				   EMC_PMACRO_BG_BIAS_CTRL_0);
>> +	}
>> +
>> +	/* Check if we need to turn on VREF generator. */
>> +	if ((((last_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
>> +	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF) == 0) &&
>> +	     ((next_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
>> +	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF) == 1)) ||
>> +	    (((last_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
>> +	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF) == 0) &&
>> +	     ((next_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX] &
>> +	       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF) == 1))) {
>> +		u32 pad_tx_ctrl =
>> +		    next_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX];
>> +		u32 last_pad_tx_ctrl =
>> +		    last_timing->burst_regs[EMC_PMACRO_DATA_PAD_TX_CTRL_INDEX];
>> +
>> +		next_dqs_e_ivref = pad_tx_ctrl &
>> +				   EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF;
>> +		next_dq_e_ivref = pad_tx_ctrl &
>> +				  EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF;
>> +		next_push = (last_pad_tx_ctrl &
>> +			     ~EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_E_IVREF &
>> +			     ~EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQS_E_IVREF) |
>> +			    next_dq_e_ivref | next_dqs_e_ivref;
>> +		emc_writel(emc, next_push, EMC_PMACRO_DATA_PAD_TX_CTRL);
>> +		udelay(1);
>> +	} else if (bg_regulator_mode_change) {
>> +		udelay(1);
>> +	}
>> +
>> +	emc_set_shadow_bypass(emc, ASSEMBLY);
>> +
>> +	/*
>> +	 * Step 2:
>> +	 *   Prelock the DLL.
>> +	 */
>> +	emc_cc_dbg(STEPS, "Step 2\n");
>> +	if (next_timing->burst_regs[EMC_CFG_DIG_DLL_INDEX] &
>> +	    EMC_CFG_DIG_DLL_CFG_DLL_EN) {
>> +		emc_cc_dbg(INFO, "Prelock enabled for target frequency.\n");
>> +		dll_out = tegra210_dll_prelock(emc, 0, clksrc);
>> +		emc_cc_dbg(INFO, "DLL out: 0x%03x\n", dll_out);
>> +		prelock_dll_en = 1;
>> +	} else {
>> +		emc_cc_dbg(INFO, "Disabling DLL for target frequency.\n");
>> +		tegra210_dll_disable(emc, channel_mode);
>> +	}
>> +
>> +	/*
>> +	 * Step 3:
>> +	 *   Prepare autocal for the clock change.
>> +	 */
>> +	emc_cc_dbg(STEPS, "Step 3\n");
>> +	emc_set_shadow_bypass(emc, ACTIVE);
>> +	emc_writel(emc, next_timing->emc_auto_cal_config2,
>> +		   EMC_AUTO_CAL_CONFIG2);
>> +	emc_writel(emc, next_timing->emc_auto_cal_config3,
>> +		   EMC_AUTO_CAL_CONFIG3);
>> +	emc_writel(emc, next_timing->emc_auto_cal_config4,
>> +		   EMC_AUTO_CAL_CONFIG4);
>> +	emc_writel(emc, next_timing->emc_auto_cal_config5,
>> +		   EMC_AUTO_CAL_CONFIG5);
>> +	emc_writel(emc, next_timing->emc_auto_cal_config6,
>> +		   EMC_AUTO_CAL_CONFIG6);
>> +	emc_writel(emc, next_timing->emc_auto_cal_config7,
>> +		   EMC_AUTO_CAL_CONFIG7);
>> +	emc_writel(emc, next_timing->emc_auto_cal_config8,
>> +		   EMC_AUTO_CAL_CONFIG8);
>> +	emc_set_shadow_bypass(emc, ASSEMBLY);
>> +
>> +	emc_auto_cal_config |= (EMC_AUTO_CAL_CONFIG_AUTO_CAL_COMPUTE_START |
>> +				auto_cal_en);
>> +	emc_writel(emc, emc_auto_cal_config, EMC_AUTO_CAL_CONFIG);
>> +
>> +	/*
>> +	 * Step 4:
>> +	 *   Update EMC_CFG. (??)
>> +	 */
>> +	emc_cc_dbg(STEPS, "Step 4\n");
>> +	if (source_clock_period > 50000 && dram_type == DRAM_TYPE_LPDDR4)
>> +		ccfifo_writel(emc, 1, EMC_SELF_REF, 0);
>> +	else
>> +		emc_writel(emc, next_timing->emc_cfg_2, EMC_CFG_2);
>> +
>> +	/*
>> +	 * Step 5:
>> +	 *   Prepare reference variables for ZQCAL regs.
>> +	 */
>> +	emc_cc_dbg(STEPS, "Step 5\n");
>> +	emc_zcal_interval = 0;
>> +	emc_zcal_wait_cnt_old =
>> +		last_timing->burst_regs[EMC_ZCAL_WAIT_CNT_INDEX];
>> +	emc_zcal_wait_cnt_new =
>> +		next_timing->burst_regs[EMC_ZCAL_WAIT_CNT_INDEX];
>> +	emc_zcal_wait_cnt_old &= ~EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK;
>> +	emc_zcal_wait_cnt_new &= ~EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK;
>> +
>> +	if (dram_type == DRAM_TYPE_LPDDR4)
>> +		zq_wait_long = max((u32)1,
>> +				   div_o3(1000000, destination_clock_period));
>> +	else if (dram_type == DRAM_TYPE_LPDDR2 || is_lpddr3)
>> +		zq_wait_long = max(next_timing->min_mrs_wait,
>> +				   div_o3(360000, destination_clock_period)) +
>> +			       4;
>> +	else if (dram_type == DRAM_TYPE_DDR3)
>> +		zq_wait_long = max((u32)256,
>> +				   div_o3(320000, destination_clock_period) +
>> +				   2);
>> +	else
>> +		zq_wait_long = 0;
>> +
>> +	if (dram_type == DRAM_TYPE_LPDDR2 || is_lpddr3)
>> +		zq_wait_short = max(max(next_timing->min_mrs_wait, (u32)6),
>> +				    div_o3(90000, destination_clock_period)) +
>> +				4;
>> +	else if (dram_type == DRAM_TYPE_DDR3)
>> +		zq_wait_short = max((u32)64,
>> +				    div_o3(80000, destination_clock_period)) +
>> +				2;
>> +	else
>> +		zq_wait_short = 0;
>> +
>> +	/*
>> +	 * Step 6:
>> +	 *   Training code - removed.
>> +	 */
>> +	emc_cc_dbg(STEPS, "Step 6\n");
>> +
>> +	/*
>> +	 * Step 7:
>> +	 *   Program FSP reference registers and send MRWs to new FSPWR.
>> +	 */
>> +	emc_cc_dbg(STEPS, "Step 7\n");
>> +	emc_cc_dbg(SUB_STEPS, "Step 7.1: Bug 200024907 - Patch RP R2P");
>> +	if (opt_war_200024907) {
>> +		nRTP = 16;
>> +		if (source_clock_period >= 1000000/1866) /* 535.91 ps */
>> +			nRTP = 14;
>> +		if (source_clock_period >= 1000000/1600) /* 625.00 ps */
>> +			nRTP = 12;
>> +		if (source_clock_period >= 1000000/1333) /* 750.19 ps */
>> +			nRTP = 10;
>> +		if (source_clock_period >= 1000000/1066) /* 938.09 ps */
>> +			nRTP = 8;
>> +
>> +		deltaTWATM = max_t(u32, div_o3(7500, source_clock_period), 8);
>> +
>> +		/*
>> +		 * Originally there was a + .5 in the tRPST calculation.
>> +		 * However since we can't do FP in the kernel and the tRTM
>> +		 * computation was in a floating point ceiling function, adding
>> +		 * one to tRTP should be ok. There is no other source of non
>> +		 * integer values, so the result was always going to be
>> +		 * something for the form: f_ceil(N + .5) = N + 1;
>> +		 */
>> +		tRPST = ((last_timing->emc_mrw & 0x80) >> 7);
>> +		tRTM = fake_timing->dram_timings[RL] +
>> +		       div_o3(3600, source_clock_period) +
>> +		       max_t(u32, div_o3(7500, source_clock_period), 8) +
>> +		       tRPST + 1 + nRTP;
>> +
>> +		emc_cc_dbg(INFO, "tRTM = %u, EMC_RP = %u\n", tRTM,
>> +			   next_timing->burst_regs[EMC_RP_INDEX]);
>> +
>> +		if (last_timing->burst_regs[EMC_RP_INDEX] < tRTM) {
>> +			if (tRTM > (last_timing->burst_regs[EMC_R2P_INDEX] +
>> +				    last_timing->burst_regs[EMC_RP_INDEX])) {
>> +				R2P_war = tRTM -
>> +					  last_timing->burst_regs[EMC_RP_INDEX];
>> +				RP_war = last_timing->burst_regs[EMC_RP_INDEX];
>> +				TRPab_war = last_timing->burst_regs[
>> +					    EMC_TRPAB_INDEX];
>> +				if (R2P_war > 63) {
>> +					RP_war = R2P_war +
>> +						 last_timing->burst_regs[
>> +						 EMC_RP_INDEX] - 63;
>> +					if (TRPab_war < RP_war)
>> +						TRPab_war = RP_war;
>> +					R2P_war = 63;
>> +				}
>> +			} else {
>> +				R2P_war = last_timing->burst_regs[
>> +					  EMC_R2P_INDEX];
>> +				RP_war = last_timing->burst_regs[EMC_RP_INDEX];
>> +				TRPab_war = last_timing->burst_regs[
>> +					    EMC_TRPAB_INDEX];
>> +			}
>> +
>> +			if (RP_war < deltaTWATM) {
>> +				W2P_war = last_timing->burst_regs[EMC_W2P_INDEX]
>> +					  + deltaTWATM - RP_war;
>> +				if (W2P_war > 63) {
>> +					RP_war = RP_war + W2P_war - 63;
>> +					if (TRPab_war < RP_war)
>> +						TRPab_war = RP_war;
>> +					W2P_war = 63;
>> +				}
>> +			} else {
>> +				W2P_war = last_timing->burst_regs[
>> +					  EMC_W2P_INDEX];
>> +			}
>> +
>> +			if ((last_timing->burst_regs[EMC_W2P_INDEX] ^
>> +			     W2P_war) ||
>> +			    (last_timing->burst_regs[EMC_R2P_INDEX] ^
>> +			     R2P_war) ||
>> +			    (last_timing->burst_regs[EMC_RP_INDEX] ^
>> +			     RP_war) ||
>> +			    (last_timing->burst_regs[EMC_TRPAB_INDEX] ^
>> +			     TRPab_war)) {
>> +				emc_writel(emc, RP_war, EMC_RP);
>> +				emc_writel(emc, R2P_war, EMC_R2P);
>> +				emc_writel(emc, W2P_war, EMC_W2P);
>> +				emc_writel(emc, TRPab_war, EMC_TRPAB);
>> +			}
>> +			emc_timing_update(emc, DUAL_CHANNEL);
>> +		} else {
>> +			emc_cc_dbg(INFO, "Skipped WAR\n");
>> +		}
>> +	}
>> +
>> +	if (!fsp_for_next_freq) {
>> +		mr13_flip_fspwr = (next_timing->emc_mrw3 & 0xffffff3f) | 0x80;
>> +		mr13_flip_fspop = (next_timing->emc_mrw3 & 0xffffff3f) | 0x00;
>> +	} else {
>> +		mr13_flip_fspwr = (next_timing->emc_mrw3 & 0xffffff3f) | 0x40;
>> +		mr13_flip_fspop = (next_timing->emc_mrw3 & 0xffffff3f) | 0xc0;
>> +	}
>> +
>> +	mr13_catr_enable = (mr13_flip_fspwr & 0xFFFFFFFE) | 0x01;
>> +	if (dram_dev_num == TWO_RANK)
>> +		mr13_catr_enable = (mr13_catr_enable & 0x3fffffff) | 0x80000000;
>> +
>> +	if (dram_type == DRAM_TYPE_LPDDR4) {
>> +		emc_writel(emc, mr13_flip_fspwr, EMC_MRW3);
>> +		emc_writel(emc, next_timing->emc_mrw, EMC_MRW);
>> +		emc_writel(emc, next_timing->emc_mrw2, EMC_MRW2);
>> +	}
>> +
>> +	/*
>> +	 * Step 8:
>> +	 *   Program the shadow registers.
>> +	 */
>> +	emc_cc_dbg(STEPS, "Step 8\n");
>> +	emc_cc_dbg(SUB_STEPS, "Writing burst_regs\n");
>> +	for (i = 0; i < next_timing->num_burst; i++) {
>> +		u32 var;
>> +		u32 wval;
>> +
>> +		if (!burst_regs_off[i])
>> +			continue;
>> +
>> +		var = burst_regs_off[i];
>> +		wval = next_timing->burst_regs[i];
>> +
>> +		if (dram_type != DRAM_TYPE_LPDDR4 &&
>> +		    (var == EMC_MRW6      || var == EMC_MRW7 ||
>> +		     var == EMC_MRW8      || var == EMC_MRW9 ||
>> +		     var == EMC_MRW10     || var == EMC_MRW11 ||
>> +		     var == EMC_MRW12     || var == EMC_MRW13 ||
>> +		     var == EMC_MRW14     || var == EMC_MRW15 ||
>> +		     var == EMC_TRAINING_CTRL))
>> +			continue;
>> +
>> +		/* Pain... And suffering. */
>> +		if (var == EMC_CFG) {
>> +			wval &= ~EMC_CFG_DRAM_ACPD;
>> +			wval &= ~EMC_CFG_DYN_SELF_REF;
>> +			if (dram_type == DRAM_TYPE_LPDDR4) {
>> +				wval &= ~EMC_CFG_DRAM_CLKSTOP_SR;
>> +				wval &= ~EMC_CFG_DRAM_CLKSTOP_PD;
>> +			}
>> +		} else if (var == EMC_MRS_WAIT_CNT &&
>> +			   dram_type == DRAM_TYPE_LPDDR2 &&
>> +			   opt_zcal_en_cc && !opt_cc_short_zcal &&
>> +			   opt_short_zcal) {
>> +			wval = (wval & ~(EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK <<
>> +					 EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT)) |
>> +			   ((zq_wait_long & EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK) <<
>> +			    EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT);
>> +		} else if (var == EMC_ZCAL_WAIT_CNT &&
>> +			   dram_type == DRAM_TYPE_DDR3 && opt_zcal_en_cc &&
>> +			   !opt_cc_short_zcal && opt_short_zcal) {
>> +			wval = (wval & ~(EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK <<
>> +					 EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_SHIFT))
>> +			       | ((zq_wait_long &
>> +				   EMC_ZCAL_WAIT_CNT_ZCAL_WAIT_CNT_MASK) <<
>> +				  EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT);
>> +		} else if (var == EMC_ZCAL_INTERVAL && opt_zcal_en_cc) {
>> +			wval = 0; /* EMC_ZCAL_INTERVAL reset value. */
>> +		} else if (var == EMC_PMACRO_AUTOCAL_CFG_COMMON) {
>> +			wval |= EMC_PMACRO_AUTOCAL_CFG_COMMON_E_CAL_BYPASS_DVFS;
>> +		} else if (var == EMC_PMACRO_DATA_PAD_TX_CTRL) {
>> +			wval &=
>> +			     ~(EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC |
>> +			       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC |
>> +			       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC |
>> +			       EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC);
>> +		} else if (var == EMC_PMACRO_CMD_PAD_TX_CTRL) {
>> +			wval |= EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_DRVFORCEON;
>> +			wval &= ~(EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSP_TX_E_DCC |
>> +				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSN_TX_E_DCC |
>> +				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_E_DCC |
>> +				  EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_CMD_TX_E_DCC);
>> +		} else if (var == EMC_PMACRO_BRICK_CTRL_RFU1) {
>> +			wval &= 0xf800f800;
>> +		} else if (var == EMC_PMACRO_COMMON_PAD_TX_CTRL) {
>> +			wval &= 0xfffffff0;
>> +		}
>> +
>> +		emc_writel(emc, wval, var);
>> +	}
>> +
>> +	/* SW addition: do EMC refresh adjustment here. */
>> +	set_over_temp_timing(emc, next_timing, dram_over_temp_state);
>> +
>> +	if (dram_type == DRAM_TYPE_LPDDR4) {
>> +		mrw_req = (23 << EMC_MRW_MRW_MA_SHIFT) |
>> +			  (next_timing->run_clocks & EMC_MRW_MRW_OP_MASK);
>> +		emc_writel(emc, mrw_req, EMC_MRW);
>> +	}
>> +
>> +	/* Per channel burst registers. */
>> +	emc_cc_dbg(SUB_STEPS, "Writing burst_regs_per_ch\n");
>> +	for (i = 0; i < next_timing->num_burst_per_ch; i++) {
>> +		if (!burst_regs_per_ch_off[i])
>> +			continue;
>> +
>> +		if (dram_type != DRAM_TYPE_LPDDR4 &&
>> +		    (burst_regs_per_ch_off[i] == EMC_MRW6 ||
>> +		     burst_regs_per_ch_off[i] == EMC_MRW7 ||
>> +		     burst_regs_per_ch_off[i] == EMC_MRW8 ||
>> +		     burst_regs_per_ch_off[i] == EMC_MRW9 ||
>> +		     burst_regs_per_ch_off[i] == EMC_MRW10 ||
>> +		     burst_regs_per_ch_off[i] == EMC_MRW11 ||
>> +		     burst_regs_per_ch_off[i] == EMC_MRW12 ||
>> +		     burst_regs_per_ch_off[i] == EMC_MRW13 ||
>> +		     burst_regs_per_ch_off[i] == EMC_MRW14 ||
>> +		     burst_regs_per_ch_off[i] == EMC_MRW15))
>> +			continue;
>> +
>> +		/* Filter out second channel if not in DUAL_CHANNEL mode. */
>> +		if (channel_mode != DUAL_CHANNEL &&
>> +		    burst_regs_per_ch_type[i] >= REG_EMC1)
>> +			continue;
>> +
>> +		emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
>> +			   i, next_timing->burst_reg_per_ch[i],
>> +			   burst_regs_per_ch_off[i]);
>> +		emc_writel_per_ch(emc, next_timing->burst_reg_per_ch[i],
>> +				  burst_regs_per_ch_type[i],
>> +				  burst_regs_per_ch_off[i]);
>> +	}
>> +
>> +	/* Vref regs. */
>> +	emc_cc_dbg(SUB_STEPS, "Writing vref_regs\n");
>> +	for (i = 0; i < next_timing->vref_num; i++) {
>> +		if (!vref_regs_per_ch_off[i])
>> +			continue;
>> +
>> +		if (channel_mode != DUAL_CHANNEL &&
>> +			vref_regs_per_ch_type[i] >= REG_EMC1)
>> +			continue;
>> +
>> +		emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
>> +			   i, next_timing->vref_perch_regs[i],
>> +			   vref_regs_per_ch_off[i]);
>> +		emc_writel_per_ch(emc, next_timing->vref_perch_regs[i],
>> +				  vref_regs_per_ch_type[i],
>> +				  vref_regs_per_ch_off[i]);
>> +	}
>> +
>> +	/* Trimmers. */
>> +	emc_cc_dbg(SUB_STEPS, "Writing trim_regs\n");
>> +	for (i = 0; i < next_timing->num_trim; i++) {
>> +		u64 trim_reg;
>> +
>> +		if (!trim_regs_off[i])
>> +			continue;
>> +
>> +		trim_reg = trim_regs_off[i];
>> +		if (compensate_trimmer_applicable &&
>> +		    (trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3 ||
>> +		     trim_reg == EMC_DATA_BRLSHFT_0 ||
>> +		     trim_reg == EMC_DATA_BRLSHFT_1)) {
>> +			u32 reg = tegra210_apply_periodic_compensation_trimmer(
>> +				  next_timing, trim_reg);
>> +			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n", i, reg,
>> +				   trim_regs_off[i]);
>> +			emc_cc_dbg(EMA_WRITES, "0x%08x <= 0x%08x\n",
>> +				   (u32)(u64)trim_regs_off[i], reg);
>> +			emc_writel(emc, reg, trim_regs_off[i]);
>> +		} else {
>> +			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
>> +				   i, next_timing->trim_regs[i],
>> +				   trim_regs_off[i]);
>> +			emc_writel(emc, next_timing->trim_regs[i],
>> +				   trim_regs_off[i]);
>> +		}
>> +	}
>> +
>> +	/* Per channel trimmers. */
>> +	emc_cc_dbg(SUB_STEPS, "Writing trim_regs_per_ch\n");
>> +	for (i = 0; i < next_timing->num_trim_per_ch; i++) {
>> +		u32 trim_reg;
>> +
>> +		if (!trim_regs_per_ch_off[i])
>> +			continue;
>> +
>> +		if (channel_mode != DUAL_CHANNEL &&
>> +			trim_regs_per_ch_type[i] >= REG_EMC1)
>> +			continue;
>> +
>> +		trim_reg = trim_regs_per_ch_off[i];
>> +		if (compensate_trimmer_applicable &&
>> +		    (trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2 ||
>> +		     trim_reg == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3 ||
>> +		     trim_reg == EMC_DATA_BRLSHFT_0 ||
>> +		     trim_reg == EMC_DATA_BRLSHFT_1)) {
>> +			u32 reg =
>> +				tegra210_apply_periodic_compensation_trimmer(
>> +						next_timing, trim_reg);
>> +			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
>> +				   i, reg, trim_regs_per_ch_off[i]);
>> +			emc_cc_dbg(EMA_WRITES, "0x%08x <= 0x%08x\n",
>> +				   trim_regs_per_ch_off[i], reg);
>> +			emc_writel_per_ch(emc, reg, trim_regs_per_ch_type[i],
>> +					  trim_regs_per_ch_off[i]);
>> +		} else {
>> +			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
>> +				   i, next_timing->trim_perch_regs[i],
>> +				   trim_regs_per_ch_off[i]);
>> +			emc_writel_per_ch(emc, next_timing->trim_perch_regs[i],
>> +					  trim_regs_per_ch_type[i],
>> +					  trim_regs_per_ch_off[i]);
>> +		}
>> +	}
>> +
>> +	emc_cc_dbg(SUB_STEPS, "Writing burst_mc_regs\n");
>> +	for (i = 0; i < next_timing->num_mc_regs; i++) {
>> +		emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
>> +			   i, next_timing->burst_mc_regs[i],
>> +			   burst_mc_regs_off[i]);
>> +		mc_writel(emc->mc, next_timing->burst_mc_regs[i],
>> +			  burst_mc_regs_off[i]);
>> +	}
>> +
>> +	/* Registers to be programmed on the faster clock. */
>> +	if (next_timing->rate < last_timing->rate) {
>> +		emc_cc_dbg(SUB_STEPS, "Writing la_scale_regs\n");
>> +		for (i = 0; i < next_timing->num_up_down; i++) {
>> +			emc_cc_dbg(REG_LISTS, "(%u) 0x%08x => 0x%08x\n",
>> +				   i, next_timing->la_scale_regs[i],
>> +				   la_scale_regs_off[i]);
>> +			mc_writel(emc->mc, next_timing->la_scale_regs[i],
>> +				  la_scale_regs_off[i]);
>> +		}
>> +	}
>> +
>> +	/* Flush all the burst register writes. */
>> +	wmb();
> 
> Won't it be a bit more optimal to just read back the lastly written register rather than to flush all of memory writes?
> 
Yes, should be fine. I'll give it a try.

Thanks,
Joseph

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 4/8] memory: tegra: add EMC scaling support code for Tegra210
  2019-04-02 11:39   ` Dmitry Osipenko
@ 2019-04-02 14:53     ` Joseph Lo
  0 siblings, 0 replies; 28+ messages in thread
From: Joseph Lo @ 2019-04-02 14:53 UTC (permalink / raw)
  To: Dmitry Osipenko, Thierry Reding, Peter De Schrijver,
	Jonathan Hunter, Rob Herring, Stephen Boyd
  Cc: linux-tegra, devicetree, linux-clk, linux-arm-kernel

On 4/2/19 7:39 PM, Dmitry Osipenko wrote:
> 25.03.2019 10:45, Joseph Lo пишет:
>> This patch adds the required APIs and variables for the EMC scaling
>> sequence code on Tegra210.
>>
>> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
>>
>> Signed-off-by: Joseph Lo <josephl@nvidia.com>
>> ---
>>   drivers/memory/tegra/tegra210-emc-reg.h | 265 +++++++
>>   drivers/memory/tegra/tegra210-emc.c     | 923 ++++++++++++++++++++++++
>>   2 files changed, 1188 insertions(+)
>>
>> diff --git a/drivers/memory/tegra/tegra210-emc-reg.h b/drivers/memory/tegra/tegra210-emc-reg.h
>> index 84fcc85f3b6d..31a69e718dbc 100644
>> --- a/drivers/memory/tegra/tegra210-emc-reg.h
>> +++ b/drivers/memory/tegra/tegra210-emc-reg.h
>> @@ -12,6 +12,13 @@
>>   
>>   #include "mc.h"
>>   
>> +#define DVFS_FGCG_HIGH_SPEED_THRESHOLD				1000
>> +#define IOBRICK_DCC_THRESHOLD					2400
>> +#define DVFS_FGCG_MID_SPEED_THRESHOLD				600
>> +
>> +#define EMC_STATUS_UPDATE_TIMEOUT				1000
>> +
>> +#define MC_EMEM_ADR_CFG						0x54
>>   #define MC_EMEM_ARB_CFG						0x90
>>   #define MC_EMEM_ARB_OUTSTANDING_REQ				0x94
>>   #define MC_EMEM_ARB_TIMING_RCD					0x98
>> @@ -75,12 +82,33 @@
>>   #define EMC_CLK_EMC_2X_CLK_SRC_SHIFT				29
>>   #define EMC_CLK_EMC_2X_CLK_SRC_MASK				\
>>   	(0x7 << EMC_CLK_EMC_2X_CLK_SRC_SHIFT)
>> +#define EMC_CLK_SOURCE_PLLM_LJ					0x4
>> +#define EMC_CLK_SOURCE_PLLMB_LJ					0x5
>>   #define	EMC_CLK_MC_EMC_SAME_FREQ				BIT(16)
>>   #define EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT			0
>>   #define EMC_CLK_EMC_2X_CLK_DIVISOR_MASK				\
>>   	(0xff << EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT)
>>   
>> +#define CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL			0x664
>> +#define DLL_CLK_EMC_DLL_CLK_SRC_SHIFT				29
>> +#define DLL_CLK_EMC_DLL_CLK_SRC_MASK				\
>> +	(0x7 << DLL_CLK_EMC_DLL_CLK_SRC_SHIFT)
>> +#define DLL_CLK_EMC_DLL_DDLL_CLK_SEL_SHIFT			10
>> +#define DLL_CLK_EMC_DLL_DDLL_CLK_SEL_MASK			\
>> +	(0x3 << DLL_CLK_EMC_DLL_DDLL_CLK_SEL_SHIFT)
>> +#define PLLM_VCOA						0
>> +#define PLLM_VCOB						1
>> +#define EMC_DLL_SWITCH_OUT					2
>> +#define DLL_CLK_EMC_DLL_CLK_DIVISOR_SHIFT			0
>> +#define DLL_CLK_EMC_DLL_CLK_DIVISOR_MASK			\
>> +	(0xff << DLL_CLK_EMC_DLL_CLK_DIVISOR_SHIFT)
>> +
>> +#define EMC_INTSTATUS						0x0
>> +#define EMC_INTSTATUS_CLKCHANGE_COMPLETE			BIT(4)
>> +#define EMC_DBG							0x8
>> +#define EMC_DBG_WRITE_MUX_ACTIVE				BIT(1)
>>   #define EMC_CFG							0xc
>> +#define EMC_TIMING_CONTROL					0x28
>>   #define EMC_RC							0x2c
>>   #define EMC_RFC							0x30
>>   #define EMC_RAS							0x34
>> @@ -125,16 +153,40 @@
>>   #define EMC_FBIO_CFG5_DRAM_TYPE_SHIFT				0
>>   #define EMC_FBIO_CFG5_DRAM_TYPE_MASK				\
>>   	(0x3 <<	EMC_FBIO_CFG5_DRAM_TYPE_SHIFT)
>> +#define EMC_FBIO_CFG5_CMD_TX_DIS				BIT(8)
>> +
>>   #define EMC_PDEX2CKE						0x118
>>   #define EMC_CKE2PDEN						0x11c
>> +#define EMC_MPC							0x128
>>   #define EMC_R2R							0x144
>>   #define EMC_EINPUT						0x14c
>>   #define EMC_EINPUT_DURATION					0x150
>>   #define EMC_PUTERM_EXTRA					0x154
>>   #define EMC_TCKESR						0x158
>>   #define EMC_TPD							0x15c
>> +#define EMC_EMC_STATUS						0x2b4
>> +#define EMC_EMC_STATUS_TIMING_UPDATE_STALLED			BIT(23)
>>   #define EMC_CFG_DIG_DLL						0x2bc
>> +#define EMC_CFG_DIG_DLL_CFG_DLL_EN				BIT(0)
>> +#define EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_UNTIL_LOCK		BIT(1)
>> +#define EMC_CFG_DIG_DLL_CFG_DLL_STALL_ALL_TRAFFIC		BIT(3)
>> +#define EMC_CFG_DIG_DLL_CFG_DLL_STALL_RW_UNTIL_LOCK		BIT(4)
>> +#define EMC_CFG_DIG_DLL_CFG_DLL_MODE_SHIFT			6
>> +#define EMC_CFG_DIG_DLL_CFG_DLL_MODE_MASK			\
>> +	(0x3 << EMC_CFG_DIG_DLL_CFG_DLL_MODE_SHIFT)
>> +#define EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_SHIFT		8
>> +#define EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_MASK			\
>> +	(0x7 << EMC_CFG_DIG_DLL_CFG_DLL_LOCK_LIMIT_SHIFT)
>> +
>>   #define EMC_CFG_DIG_DLL_PERIOD					0x2c0
>> +#define EMC_DIG_DLL_STATUS					0x2c4
>> +#define EMC_DIG_DLL_STATUS_DLL_LOCK				BIT(15)
>> +#define EMC_DIG_DLL_STATUS_DLL_PRIV_UPDATED			BIT(17)
>> +#define EMC_DIG_DLL_STATUS_DLL_OUT_SHIFT			0
>> +#define EMC_DIG_DLL_STATUS_DLL_OUT_MASK				\
>> +	(0x7ff << EMC_DIG_DLL_STATUS_DLL_OUT_SHIFT)
>> +
>> +#define EMC_CFG_DIG_DLL_1					0x2c8
>>   #define EMC_RDV_MASK						0x2cc
>>   #define EMC_WDV_MASK						0x2d0
>>   #define EMC_RDV_EARLY_MASK					0x2d4
>> @@ -153,6 +205,8 @@
>>   #define EMC_PRE_REFRESH_REQ_CNT					0x3dc
>>   #define EMC_DYN_SELF_REF_CONTROL				0x3e0
>>   #define EMC_TXSRDLL						0x3e4
>> +#define EMC_CCFIFO_ADDR						0x3e8
>> +#define EMC_CCFIFO_DATA						0x3ec
>>   #define EMC_TR_QPOP						0x3f4
>>   #define EMC_TR_RDV_MASK						0x3f8
>>   #define EMC_TR_QSAFE						0x3fc
>> @@ -185,8 +239,60 @@
>>   #define EMC_PUTERM_WIDTH					0x56c
>>   #define EMC_REFCTRL2						0x580
>>   #define EMC_FBIO_CFG7						0x584
>> +#define EMC_FBIO_CFG7_CH0_ENABLE				BIT(1)
>> +#define EMC_FBIO_CFG7_CH1_ENABLE				BIT(2)
>>   #define EMC_DATA_BRLSHFT_0					0x588
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_SHIFT	21
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_SHIFT	18
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_SHIFT	15
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_SHIFT	12
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_SHIFT	9
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_SHIFT	6
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_SHIFT	3
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_SHIFT	0
>> +#define EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_SHIFT)
>> +
>>   #define EMC_DATA_BRLSHFT_1					0x58c
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_SHIFT	21
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_SHIFT	18
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_SHIFT	15
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_SHIFT	12
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_SHIFT	9
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_SHIFT	6
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_SHIFT	3
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_SHIFT)
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_SHIFT	0
>> +#define EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_MASK	\
>> +	(0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_SHIFT)
>> +
>>   #define EMC_RFCPB						0x590
>>   #define EMC_DQS_BRLSHFT_0					0x594
>>   #define EMC_DQS_BRLSHFT_1					0x598
>> @@ -201,6 +307,10 @@
>>   #define EMC_QUSE_BRLSHFT_3					0x5c4
>>   #define EMC_DLL_CFG_0						0x5e4
>>   #define EMC_DLL_CFG_1						0x5e8
>> +#define EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_SHIFT		10
>> +#define EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_MASK		\
>> +	(0x7ff << EMC_DLL_CFG_1_DDLLCAL_CTRL_START_TRIM_SHIFT)
>> +
>>   #define EMC_CONFIG_SAMPLE_DELAY					0x5f0
>>   #define EMC_PMACRO_QUSE_DDLL_RANK0_0				0x600
>>   #define EMC_PMACRO_QUSE_DDLL_RANK0_1				0x604
>> @@ -215,15 +325,103 @@
>>   #define EMC_PMACRO_QUSE_DDLL_RANK1_4				0x630
>>   #define EMC_PMACRO_QUSE_DDLL_RANK1_5				0x634
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0			0x640
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_SHIFT \
>> +	16
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_SHIFT)
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_SHIFT \
>> +	0
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_MASK \
>> +	(0x3ff <<							    \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_SHIFT)
>> +
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1			0x644
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_SHIFT \
>> +	16
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_SHIFT)
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_SHIFT \
>> +	0
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_SHIFT)
>> +
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2			0x648
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_SHIFT  \
>> +	16
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_SHIFT)
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_SHIFT \
>> +	0
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_SHIFT)
>> +
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3			0x64c
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_SHIFT \
>> +	16
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_SHIFT)
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_SHIFT \
>> +	0
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_SHIFT)
>> +
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4			0x650
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5			0x654
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0			0x660
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_SHIFT \
>> +	16
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_SHIFT)
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_SHIFT \
>> +	0
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_SHIFT)
>> +
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1			0x664
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_SHIFT \
>> +	16
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_SHIFT)
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_SHIFT \
>> +	0
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_SHIFT)
>> +
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2			0x668
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_SHIFT \
>> +	16
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_SHIFT)
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_SHIFT \
>> +	0
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_SHIFT)
>> +
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3			0x66c
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_SHIFT \
>> +	16
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_SHIFT)
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_SHIFT \
>> +	0
>> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_MASK  \
>> +	(0x3ff <<							     \
>> +	EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_SHIFT)
>> +
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4			0x670
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5			0x674
>>   #define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0			0x680
>> @@ -432,7 +630,18 @@
>>   #define EMC_PMACRO_CMD_RX_TERM_MODE				0xc58
>>   #define EMC_PMACRO_DATA_RX_TERM_MODE				0xc5c
>>   #define EMC_PMACRO_CMD_PAD_TX_CTRL				0xc60
>> +#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_E_DCC		BIT(1)
>> +#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSP_TX_E_DCC		BIT(9)
>> +#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQSN_TX_E_DCC		BIT(16)
>> +#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_CMD_TX_E_DCC		BIT(24)
>> +#define EMC_PMACRO_CMD_PAD_TX_CTRL_CMD_DQ_TX_DRVFORCEON		BIT(26)
>> +
>>   #define EMC_PMACRO_DATA_PAD_TX_CTRL				0xc64
>> +#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQ_TX_E_DCC		BIT(1)
>> +#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSP_TX_E_DCC		BIT(9)
>> +#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_DQSN_TX_E_DCC		BIT(16)
>> +#define EMC_PMACRO_DATA_PAD_TX_CTRL_DATA_CMD_TX_E_DCC		BIT(24)
>> +
>>   #define EMC_PMACRO_COMMON_PAD_TX_CTRL				0xc68
>>   #define EMC_PMACRO_AUTOCAL_CFG_COMMON				0xc78
>>   #define EMC_PMACRO_VTTGEN_CTRL_2				0xcf0
>> @@ -954,6 +1163,16 @@ enum {
>>   	DRAM_TYPE_DDR2 = 3,
>>   };
>>   
>> +enum {
>> +	SINGLE_CHANNEL = 0,
>> +	DUAL_CHANNEL
>> +};
>> +
>> +enum {
>> +	DLL_OFF,
>> +	DLL_ON
>> +};
>> +
>>   struct emc_table {
>>   	u32 rev;
>>   	char dvfs_ver[60];
>> @@ -1075,9 +1294,55 @@ struct supported_sequence {
>>   	char  *seq_rev;
>>   };
>>   
>> +extern u32 burst_mc_regs_off[];
>> +extern u32 burst_regs_off[];
>> +extern u32 burst_regs_per_ch_off[];
>> +extern u32 burst_regs_per_ch_type[];
>> +extern unsigned long dram_over_temp_state;
>> +extern u32 la_scale_regs_off[];
>> +extern u32 trim_regs_off[];
>> +extern u32 trim_regs_per_ch_off[];
>> +extern u32 trim_regs_per_ch_type[];
>> +extern u32 vref_regs_per_ch_off[];
>> +extern u32 vref_regs_per_ch_type[];
>> +
>> +void ccfifo_writel(struct tegra_emc *emc, u32 val, unsigned long addr,
>> +		   u32 delay);
>> +u32 div_o3(u32 a, u32 b);
>> +void emc_writel(struct tegra_emc *emc, u32 val, unsigned long offset);
>> +u32  emc_readl(struct tegra_emc *emc, unsigned long offset);
>> +void emc_writel_per_ch(struct tegra_emc *emc, u32 val, int type,
>> +		       unsigned long offset);
>> +u32  emc1_readl(struct tegra_emc *emc, unsigned long offset);
>> +
>> +void do_clock_change(struct tegra_emc *emc, u32 clksrc);
>> +void emc_set_shadow_bypass(struct tegra_emc *emc, int set);
>> +void emc_timing_update(struct tegra_emc *emc, int dual_chan);
>> +u32 get_dll_state(struct emc_table *next_timing);
>> +struct emc_table *get_timing_from_freq(struct tegra_emc *emc,
>> +				       unsigned long rate);
>> +void set_over_temp_timing(struct tegra_emc *emc, struct emc_table *timing,
>> +			  unsigned long state);
>>   int tegra_emc_dt_parse_pdata(struct platform_device *pdev,
>>   			     struct emc_table **tables,
>>   			     struct emc_table **derated_tables,
>>   			     int *num_entries);
>> +u32 tegra210_actual_osc_clocks(u32 in);
>> +u32 tegra210_apply_periodic_compensation_trimmer(struct emc_table *next_timing,
>> +						 u32 offset);
>> +void tegra210_dll_disable(struct tegra_emc *emc, int channel_mode);
>> +void tegra210_dll_enable(struct tegra_emc *emc, int channel_mode);
>> +u32 tegra210_dll_prelock(struct tegra_emc *emc, int dvfs_with_training,
>> +			 u32 clksrc);
>> +u32 tegra210_dvfs_power_ramp_down(struct tegra_emc *emc, u32 clk,
>> +				  int flip_backward);
>> +u32 tegra210_dvfs_power_ramp_up(struct tegra_emc *emc, u32 clk,
>> +				int flip_backward);
>> +void tegra210_update_emc_alt_timing(struct tegra_emc *emc,
>> +				    struct emc_table *current_timing);
>> +void tegra210_reset_dram_clktree_values(struct emc_table *table);
>> +void tegra210_start_periodic_compensation(struct tegra_emc *emc);
>> +int wait_for_update(struct tegra_emc *emc, u32 status_reg, u32 bit_mask,
>> +		    bool updated_state, int chan);
>>   
>>   #endif
>> diff --git a/drivers/memory/tegra/tegra210-emc.c b/drivers/memory/tegra/tegra210-emc.c
>> index 0c20bcd0e6de..26d0de0ab319 100644
>> --- a/drivers/memory/tegra/tegra210-emc.c
>> +++ b/drivers/memory/tegra/tegra210-emc.c
>> @@ -20,6 +20,37 @@
>>   #define TEGRA_EMC_TABLE_MAX_SIZE		16
>>   #define TEGRA210_EMC_SUSPEND_RATE		204000000
>>   
>> +#define EMC0_EMC_DATA_BRLSHFT_0_INDEX	2
>> +#define EMC1_EMC_DATA_BRLSHFT_0_INDEX	3
>> +#define EMC0_EMC_DATA_BRLSHFT_1_INDEX	4
>> +#define EMC1_EMC_DATA_BRLSHFT_1_INDEX	5
>> +
>> +#define TRIM_REG(chan, rank, reg, byte)					\
>> +	(((EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
>> +	   _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte ## _MASK &	\
>> +	   next_timing->trim_regs[EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ##	\
>> +				 rank ## _ ## reg ## _INDEX]) >>	\
>> +	  EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
>> +	  _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte ## _SHIFT)	\
>> +	 +								\
>> +	 (((EMC_DATA_BRLSHFT_ ## rank ## _RANK ## rank ## _BYTE ##	\
>> +	    byte ## _DATA_BRLSHFT_MASK &				\
>> +	    next_timing->trim_perch_regs[EMC ## chan ##			\
>> +			      _EMC_DATA_BRLSHFT_ ## rank ## _INDEX]) >>	\
>> +	   EMC_DATA_BRLSHFT_ ## rank ## _RANK ## rank ## _BYTE ##	\
>> +	   byte ## _DATA_BRLSHFT_SHIFT) * 64))
>> +
>> +#define CALC_TEMP(rank, reg, byte1, byte2, n)				\
>> +	(((new[n] << EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ##	\
>> +	   reg ## _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte1 ## _SHIFT) & \
>> +	  EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
>> +	  _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte1 ## _MASK)	\
>> +	 |								\
>> +	 ((new[n + 1] << EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ##\
>> +	   reg ## _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte2 ## _SHIFT) & \
>> +	  EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ##	\
>> +	  _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte2 ## _MASK))	\
>> +
>>   enum TEGRA_EMC_SOURCE {
>>   	TEGRA_EMC_SRC_PLLM,
>>   	TEGRA_EMC_SRC_PLLC,
>> @@ -32,6 +63,14 @@ enum TEGRA_EMC_SOURCE {
>>   	TEGRA_EMC_SRC_COUNT,
>>   };
>>   
>> +enum {
>> +	TEGRA_DRAM_OVER_TEMP_NONE = 0,
>> +	TEGRA_DRAM_OVER_TEMP_REFRESH_X2,
>> +	TEGRA_DRAM_OVER_TEMP_REFRESH_X4,
>> +	TEGRA_DRAM_OVER_TEMP_THROTTLE, /* 4x Refresh + derating. */
>> +	TEGRA_DRAM_OVER_TEMP_MAX,
>> +};
>> +
>>   struct emc_sel {
>>   	struct clk *input;
>>   	u32 value;
>> @@ -76,6 +115,7 @@ static struct tegra_emc *tegra_emc;
>>   static DEFINE_SPINLOCK(emc_access_lock);
>>   static ktime_t clkchange_time;
>>   static int clkchange_delay = 100;
>> +unsigned long dram_over_temp_state = TEGRA_DRAM_OVER_TEMP_NONE;
>>   
>>   static void emc_train(struct timer_list *tmr);
>>   DEFINE_TIMER(emc_training_timer, emc_train);
>> @@ -102,11 +142,33 @@ static bool emc_suspend;
>>   static unsigned long emc_resume_rate;
>>   #endif
>>   
>> +inline void emc_writel(struct tegra_emc *emc, u32 val, unsigned long offset)
>> +{
>> +	writel(val, emc->emc_base + offset);
>> +}
>> +
>>   inline u32 emc_readl(struct tegra_emc *emc, unsigned long offset)
>>   {
>>   	return readl(emc->emc_base + offset);
>>   }
>>   
>> +inline u32 emc1_readl(struct tegra_emc *emc, unsigned long offset)
>> +{
>> +	return readl(emc->emc1_base + offset);
>> +}
> 
> Do you really need to insert a memory barrier on each readl/writel? Why not to use the relaxed versions?
> 

Yes, should be fine to use the relaxed version. Will try that.

Thanks,
Joseph

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-04-01  7:57     ` Joseph Lo
@ 2019-04-03  4:26       ` Rob Herring
  2019-04-10  2:41         ` Joseph Lo
  0 siblings, 1 reply; 28+ messages in thread
From: Rob Herring @ 2019-04-03  4:26 UTC (permalink / raw)
  To: Joseph Lo
  Cc: Thierry Reding, Peter De Schrijver, Jonathan Hunter,
	Stephen Boyd,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
	linux-tegra, linux-clk, devicetree

On Mon, Apr 1, 2019 at 2:58 AM Joseph Lo <josephl@nvidia.com> wrote:
>
> On 3/31/19 2:41 PM, Rob Herring wrote:
> > On Mon, Mar 25, 2019 at 03:45:16PM +0800, Joseph Lo wrote:
> >> Add the binding document for the external memory controller (EMC) which
> >> communicates with external LPDDR4 devices. It includes the bindings of
> >> the EMC node and the EMC table of different rates.
> >>
> >> To support high rates for LPDDR4, the EMC table must be trained before
> >> it can be used for runtime clock switching. It has been done by firmware
> >> and merged to the table that Linux kernel uses. For backward
> >> compatibility with the devices that had been launched on the market, like
> >> Shield and Jetson platforms, the bindings in the EMC table should remain
> >> the same. So the firmware can recognize them and merge the trained EMC
> >> table for the kernel.
>
> Hi Rob,
>
> Thanks for reviewing.
>
> >
> > Overall seems pretty bloated. How much of this really varies by board
> > vs. just being a dump of all the register values to stuff?
>
> Most of them are register values. And by different SDRAM devices that
> could be used on the same platform (use ram code to identify them), the
> value could be different.
>
> >
> > Primarily, I'm leary of getting a similar binding for every vendor's DDR
> > setup.
> >
> > Some mostly trivial comments follow.
>
> Really sorry about that. I understand these basic rules for DT bindings,
> but the case here is that these un-reviewed bindings have been used in
> the firmware on the shipped products. To support the same with the
> upstream kernel and consider the firmware blob may not be updated, we
> have no choice to just use the same bindings in the upstream kernel.
>
> How can we deal with this case?

Simply, we do not accept bindings as-is. If we did, then there would
be no point in documenting and reviewing bindings. NVidia chose this
path and now gets to live with it.

Rob

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 2/8] clk: tegra: clock changes for emc scaling support on Tegra210
  2019-03-25  7:45 ` [PATCH 2/8] clk: tegra: clock changes for emc scaling support on Tegra210 Joseph Lo
@ 2019-04-03  9:22   ` Thierry Reding
  2019-04-08  7:52     ` Joseph Lo
  2019-04-08  9:15     ` Peter De Schrijver
  0 siblings, 2 replies; 28+ messages in thread
From: Thierry Reding @ 2019-04-03  9:22 UTC (permalink / raw)
  To: Joseph Lo
  Cc: Peter De Schrijver, Jonathan Hunter, Rob Herring, Stephen Boyd,
	linux-arm-kernel, linux-tegra, linux-clk, devicetree

[-- Attachment #1: Type: text/plain, Size: 5248 bytes --]

On Mon, Mar 25, 2019 at 03:45:17PM +0800, Joseph Lo wrote:
> 1) Introduce low jitter paths for pllp and pll_mb used by the EMC driver.
> 2) Remove the old emc_mux clock and don't use the common EMC clock
>    definition. This will be replaced by a new clock defined in the EMC
>    driver.
> 3) Export functions to allow accessing the CAR register required for EMC
>    clock scaling. These functions will be used to access the CAR register
>    as part of the scaling sequence.

The fact that you can enumerate 3 logical changes made by this commit
indicates that it should be split up into smaller patches.

> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
> 
> Signed-off-by: Joseph Lo <josephl@nvidia.com>
> ---
>  drivers/clk/tegra/clk-tegra210.c         | 112 +++++++++++++++++++----
>  include/dt-bindings/clock/tegra210-car.h |   4 +-
>  include/linux/clk/tegra.h                |   5 +
>  3 files changed, 103 insertions(+), 18 deletions(-)
> 
> diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c
> index 7545af763d7a..e17b5279ea69 100644
> --- a/drivers/clk/tegra/clk-tegra210.c
> +++ b/drivers/clk/tegra/clk-tegra210.c
> @@ -47,6 +47,7 @@
>  #define CLK_SOURCE_LA 0x1f8
>  #define CLK_SOURCE_SDMMC2 0x154
>  #define CLK_SOURCE_SDMMC4 0x164
> +#define CLK_SOURCE_EMC_DLL 0x664
>  
>  #define PLLC_BASE 0x80
>  #define PLLC_OUT 0x84
> @@ -234,6 +235,10 @@
>  #define RST_DFLL_DVCO 0x2f4
>  #define DVFS_DFLL_RESET_SHIFT 0
>  
> +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET	0x284
> +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR	0x288
> +#define CLK_OUT_ENB_X_CLK_ENB_EMC_DLL		BIT(14)
> +
>  #define CLK_RST_CONTROLLER_RST_DEV_Y_SET 0x2a8
>  #define CLK_RST_CONTROLLER_RST_DEV_Y_CLR 0x2ac
>  
> @@ -319,12 +324,6 @@ static unsigned long tegra210_input_freq[] = {
>  	[8] = 12000000,
>  };
>  
> -static const char *mux_pllmcp_clkm[] = {
> -	"pll_m", "pll_c", "pll_p", "clk_m", "pll_m_ud", "pll_mb", "pll_mb",
> -	"pll_p",
> -};
> -#define mux_pllmcp_clkm_idx NULL
> -
>  #define PLL_ENABLE			(1 << 30)
>  
>  #define PLLCX_MISC1_IDDQ		(1 << 27)
> @@ -2310,7 +2309,7 @@ static struct tegra_clk tegra210_clks[tegra_clk_max] __initdata = {
>  	[tegra_clk_i2c2] = { .dt_id = TEGRA210_CLK_I2C2, .present = true },
>  	[tegra_clk_uartc_8] = { .dt_id = TEGRA210_CLK_UARTC, .present = true },
>  	[tegra_clk_mipi_cal] = { .dt_id = TEGRA210_CLK_MIPI_CAL, .present = true },
> -	[tegra_clk_emc] = { .dt_id = TEGRA210_CLK_EMC, .present = true },
> +	[tegra_clk_emc] = { .dt_id = TEGRA210_CLK_EMC, .present = false },
>  	[tegra_clk_usb2] = { .dt_id = TEGRA210_CLK_USB2, .present = true },
>  	[tegra_clk_bsev] = { .dt_id = TEGRA210_CLK_BSEV, .present = true },
>  	[tegra_clk_uartd_8] = { .dt_id = TEGRA210_CLK_UARTD, .present = true },
> @@ -2921,6 +2920,82 @@ static int tegra210_init_pllu(void)
>  	return 0;
>  }
>  
> +void tegra210_clk_emc_dll_enable(bool flag)
> +{
> +	unsigned long flags = 0;
> +	u32 offset = flag ? CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET :
> +		     CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR;
> +
> +	spin_lock_irqsave(&emc_lock, flags);
> +
> +	writel_relaxed(CLK_OUT_ENB_X_CLK_ENB_EMC_DLL, clk_base + offset);
> +	readl(clk_base + offset);
> +
> +	spin_unlock_irqrestore(&emc_lock, flags);
> +}
> +EXPORT_SYMBOL_GPL(tegra210_clk_emc_dll_enable);
> +
> +void tegra210_clk_emc_dll_update_setting(u32 emc_dll_src_value)

Do we really want to pass the whole register value through this
function? The register has three fields, so perhaps it's safer to pass
the fields individually? Or perhaps we only need to modify a subset of
the fields and can reduce the number of parameters we pass? Letting a
different driver pass any arbitrary value here takes away any means of
checking for validity.

> +{
> +	unsigned long flags = 0;
> +
> +	spin_lock_irqsave(&emc_lock, flags);
> +
> +	writel_relaxed(emc_dll_src_value, clk_base + CLK_SOURCE_EMC_DLL);
> +	readl(clk_base + CLK_SOURCE_EMC_DLL);

Could we not just use a writel() here and do away with the flushing
readl()?

Also, it doesn't look like that spinlock actually protects anything.
You're just writing a value. If anyone else is holding that lock they
will either overwrite our value after we release the lock, or we
overwrite their value when they release the lock.

> +
> +	spin_unlock_irqrestore(&emc_lock, flags);
> +}
> +EXPORT_SYMBOL_GPL(tegra210_clk_emc_dll_update_setting);
> +
> +void tegra210_clk_emc_update_setting(u32 emc_src_value)
> +{
> +	unsigned long flags = 0;
> +
> +	spin_lock_irqsave(&emc_lock, flags);
> +
> +	writel_relaxed(emc_src_value, clk_base + CLK_SOURCE_EMC);
> +	readl(clk_base + CLK_SOURCE_EMC);
> +
> +	spin_unlock_irqrestore(&emc_lock, flags);
> +}
> +EXPORT_SYMBOL_GPL(tegra210_clk_emc_update_setting);

Same comments as above.

> +
> +u32 tegra210_clk_emc_get_setting(void)
> +{
> +	unsigned long flags = 0;
> +	u32 val;
> +
> +	spin_lock_irqsave(&emc_lock, flags);
> +
> +	val = readl_relaxed(clk_base + CLK_SOURCE_EMC);
> +
> +	spin_unlock_irqrestore(&emc_lock, flags);

Similar to the above, the spinlock doesn't protect anything here.

Thierry

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 3/8] memory: tegra: Add Tegra210 EMC clock driver
  2019-03-25  7:45 ` [PATCH 3/8] memory: tegra: Add Tegra210 EMC clock driver Joseph Lo
@ 2019-04-03 11:34   ` Thierry Reding
  2019-04-08  9:25     ` Peter De Schrijver
  2019-04-03 11:55   ` Dmitry Osipenko
  1 sibling, 1 reply; 28+ messages in thread
From: Thierry Reding @ 2019-04-03 11:34 UTC (permalink / raw)
  To: Joseph Lo
  Cc: Peter De Schrijver, Jonathan Hunter, Rob Herring, Stephen Boyd,
	linux-arm-kernel, linux-tegra, linux-clk, devicetree

[-- Attachment #1: Type: text/plain, Size: 57331 bytes --]

On Mon, Mar 25, 2019 at 03:45:18PM +0800, Joseph Lo wrote:
> This is the initial patch for Tegra210 EMC clock driver, which doesn't
> include the support code and detail sequence for clock scaling yet.
> 
> The driver is designed to support LPDDR4 SDRAMs. Because of the LPDDR4
> devices need to do initial time training before it can be used, the
> firmware will help to do that at early boot stage. The trained table for
> the rates that we will support in the kernel will be merged to the
> kernel DTB. So the driver can get the trained table for clock scaling
> support.
> 
> For the higher rate support (above 800MHz), the periodic training is
> needed for the timing compensation. So basically, two methodologies for
> clock scaling support, one is following the clock changing sequence to
> update the EMC table to EMC registers and another is if the rate needs
> periodic training, then we will start a timer to do that periodically
> until it leaves the rate that doesn't need that.
> 
> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
> 
> Signed-off-by: Joseph Lo <josephl@nvidia.com>
> ---
>  drivers/memory/tegra/Kconfig             |   10 +
>  drivers/memory/tegra/Makefile            |    1 +
>  drivers/memory/tegra/tegra210-dt-parse.c |  340 +++++++
>  drivers/memory/tegra/tegra210-emc-reg.h  | 1083 ++++++++++++++++++++++
>  drivers/memory/tegra/tegra210-emc.c      |  886 ++++++++++++++++++
>  5 files changed, 2320 insertions(+)
>  create mode 100644 drivers/memory/tegra/tegra210-dt-parse.c
>  create mode 100644 drivers/memory/tegra/tegra210-emc-reg.h
>  create mode 100644 drivers/memory/tegra/tegra210-emc.c
> 
> diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig
> index 34e0b70f5c5f..614e9b370183 100644
> --- a/drivers/memory/tegra/Kconfig
> +++ b/drivers/memory/tegra/Kconfig
> @@ -25,3 +25,13 @@ config TEGRA124_EMC
>  	  Tegra124 chips. The EMC controls the external DRAM on the board.
>  	  This driver is required to change memory timings / clock rate for
>  	  external memory.
> +
> +config TEGRA210_EMC
> +	bool "NVIDIA Tegra210 External Memory Controller driver"
> +	default y
> +	depends on TEGRA_MC && ARCH_TEGRA_210_SOC
> +	help
> +	  This driver is for the External Memory Controller (EMC) found on
> +	  Tegra210 chips. The EMC controls the external DRAM on the board.
> +	  This driver is required to change memory timings / clock rate for
> +	  external memory.
> diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile
> index 3971a6b7c487..36a835620bbd 100644
> --- a/drivers/memory/tegra/Makefile
> +++ b/drivers/memory/tegra/Makefile
> @@ -12,4 +12,5 @@ obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
>  
>  obj-$(CONFIG_TEGRA20_EMC)  += tegra20-emc.o
>  obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o
> +obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o tegra210-dt-parse.o
>  obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o
> diff --git a/drivers/memory/tegra/tegra210-dt-parse.c b/drivers/memory/tegra/tegra210-dt-parse.c
> new file mode 100644
> index 000000000000..6a3a3a28ac64
> --- /dev/null
> +++ b/drivers/memory/tegra/tegra210-dt-parse.c
> @@ -0,0 +1,340 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2013-2019, NVIDIA CORPORATION.  All rights reserved.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/err.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <soc/tegra/fuse.h>
> +
> +#include "tegra210-emc-reg.h"
> +
> +static struct device_node *tegra_emc_ramcode_devnode(
> +	struct device_node *np)

This is weirdly wrapped. Typically if it doesn't all fit on one line
you'd break after the return type, like so:

    static struct device_node *tegra_emc_ramcode_devnode(struct device_node *np)

That said, the above does seem to fit on a single line, so there'n no
reason to wrap at all. You could still try to make it a little shorter
by using the _node suffix instead of _devnode.

> +{
> +	struct device_node *iter;
> +	u32 reg;

I think this is confusingly named. This makes it sound like you're going
to store a register offset in it. "value" would be be more appropriate
here, I think.

> +
> +	for_each_child_of_node(np, iter) {
> +		if (of_property_read_u32(iter, "nvidia,ram-code", &reg))
> +			continue;
> +		if (reg == tegra_read_ram_code())

Looks like there are quite a few places where you read the RAM code.
Perhaps it'd be better to cache that in ->probe() and pass it to this
function so we avoid rereading that register over and over again.

Maybe also make it explicit in the name that this looks up the node
corresponding to the RAM code:

    static struct device_node *
    tegra_emc_find_ramcode_node(struct device_node *np, u32 ramcode)

?

> +			return of_node_get(iter);

I think this is wrong. of_get_next_child() (via for_each_child_of_node)
already takes a reference to the child node.

> +	}
> +
> +	return NULL;
> +}
> +
> +static void *tegra_emc_dt_parse_pdata_comp(const char *emc_mode,

We know that this function returns struct emc_table *, why not use that
as the return type?

Also, you're not parsing "platform" data here, so I think the _pdata
suffix (and the _comp suffix for that matter) is somewhat misleading.
Why not just call this what it is: tegra_emc_dt_parse_tables()?

Also, emc_mode seems to be unused.

> +					   const char *comp,
> +					   void *pdata,

This seems to be unused.

> +					   struct device_node *tnp,
> +					   struct platform_device *pdev,

You seem to be only using this for log output, so may as well just make
it struct device *dev. Also, it's unusual to see this passed as the
fourth parameter. It's more typical to pass the object that you're
operating on as the first argument.

> +					   int num_tables, int *table_count)

num_tables and table_count can be unsigned int.

> +{
> +#define PNE_U32(node, entry, tbl_entry)					\
> +	do {								\
> +		int __ret__;						\
> +		u32 __tmp__;						\
> +									\
> +		__ret__ = of_property_read_u32((node), (entry), &__tmp__); \
> +		if (__ret__) {						\
> +			dev_err(&pdev->dev, "Failed to parse %s in %s: %d\n", \
> +				(entry), (node)->full_name, __ret__);	\
> +			continue;					\
> +		}							\
> +									\
> +		tables[i].tbl_entry = __tmp__;				\
> +	} while (0)
> +
> +#define PNE_U32_ARRAY(node, entry, tbl_entry, length)			\
> +	do {								\
> +		int __ret__;						\
> +									\
> +		__ret__ = of_property_read_u32_array((node), (entry),	\
> +						     (tbl_entry), (length)); \
> +		if (__ret__) {						\
> +			dev_err(&pdev->dev, "Failed to parse %s in %s: %d\n", \
> +				(entry), (node)->full_name, __ret__);	\
> +			continue;					\
> +		}							\
> +	} while (0)

You're going to be generating a lot of code here. Could we instead turn
this around and have a table that defines the entries and which fields
they will be read into and then just use a loop to iterate over that
table? That should reduce code size for only a slight increase of the
read-only data.

I think you wouldn't even have to special case fields vs. arrays in such
a setup. Instead you could just consider fields a single-element arrays,
which is what of_property_read_u32() does anyway.

Also, use %pOF to print the name of a device tree node.

> +
> +	int i = 0, ret = 0;

i can be unsigned.

> +	struct device_node *iter;
> +	struct emc_table *tables;
> +
> +	tables = devm_kzalloc(&pdev->dev, sizeof(*tables) * num_tables,
> +			      GFP_KERNEL);
> +
> +	if (!tables) {
> +		of_node_put(tnp);
> +		return tables;
> +	}
> +
> +	for_each_child_of_node(tnp, iter) {
> +		if (of_device_is_compatible(iter, comp)) {
> +			const char *source_name;
> +			const char *dvfs_ver;

The level of indentation is getting pretty high here. Perhaps split the
contents of the innermost loop into a separate function?

> +
> +			ret = of_property_read_string(iter, "nvidia,source",
> +						      &source_name);
> +			if (ret) {
> +				dev_err(&pdev->dev, "no source name in %s\n",
> +					iter->full_name);
> +				continue;
> +			}
> +			strlcpy(tables[i].clock_src, source_name,
> +				sizeof(tables[i].clock_src));
> +
> +			ret = of_property_read_string(iter,
> +						      "nvidia,dvfs-version",
> +						      &dvfs_ver);
> +			if (ret) {
> +				dev_err(&pdev->dev, "no dvfs version in %s\n",
> +					iter->full_name);
> +				continue;
> +			}
> +			strlcpy(tables[i].dvfs_ver, dvfs_ver,
> +				sizeof(tables[i].dvfs_ver));
> +
> +			PNE_U32(iter, "nvidia,revision", rev);
> +			PNE_U32(iter, "clock-frequency", rate);
> +			PNE_U32(iter, "nvidia,emc-min-mv", min_volt);
> +			PNE_U32(iter, "nvidia,gk20a-min-mv", gpu_min_volt);
> +			PNE_U32(iter, "nvidia,src-sel-reg", clk_src_emc);
> +			PNE_U32(iter, "nvidia,burst-regs-num", num_burst);
> +			PNE_U32(iter, "nvidia,emc-cfg-2", emc_cfg_2);
> +			PNE_U32(iter, "nvidia,emc-sel-dpd-ctrl",
> +				emc_sel_dpd_ctrl);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config",
> +				emc_auto_cal_config);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config2",
> +				emc_auto_cal_config2);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config3",
> +				emc_auto_cal_config3);
> +			PNE_U32(iter, "nvidia,emc-clock-latency-change",
> +				latency);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-registers",
> +				      tables[i].burst_regs,
> +				      tables[i].num_burst);
> +
> +			PNE_U32(iter, "nvidia,needs-training", needs_training);
> +			PNE_U32(iter, "nvidia,trained", trained);
> +			if (tables[i].rev < 0x6)
> +				goto skip_periodic_training_params;
> +			PNE_U32(iter, "nvidia,periodic_training",
> +				periodic_training);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d0u0",
> +				trained_dram_clktree_c0d0u0);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d0u1",
> +				trained_dram_clktree_c0d0u1);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d1u0",
> +				trained_dram_clktree_c0d1u0);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d1u1",
> +				trained_dram_clktree_c0d1u1);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d0u0",
> +				trained_dram_clktree_c1d0u0);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d0u1",
> +				trained_dram_clktree_c1d0u1);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d1u0",
> +				trained_dram_clktree_c1d1u0);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d1u1",
> +				trained_dram_clktree_c1d1u1);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c0d0u0",
> +				current_dram_clktree_c0d0u0);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c0d0u1",
> +				current_dram_clktree_c0d0u1);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c0d1u0",
> +				current_dram_clktree_c0d1u0);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c0d1u1",
> +				current_dram_clktree_c0d1u1);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c1d0u0",
> +				current_dram_clktree_c1d0u0);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c1d0u1",
> +				current_dram_clktree_c1d0u1);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c1d1u0",
> +				current_dram_clktree_c1d1u0);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c1d1u1",
> +				current_dram_clktree_c1d1u1);
> +			PNE_U32(iter, "nvidia,run_clocks", run_clocks);
> +			PNE_U32(iter, "nvidia,tree_margin", tree_margin);
> +
> +skip_periodic_training_params:
> +			PNE_U32(iter, "nvidia,burst-regs-per-ch-num",
> +				num_burst_per_ch);
> +			PNE_U32(iter, "nvidia,trim-regs-num", num_trim);
> +			PNE_U32(iter, "nvidia,trim-regs-per-ch-num",
> +				num_trim_per_ch);
> +			PNE_U32(iter, "nvidia,burst-mc-regs-num",
> +				num_mc_regs);
> +			PNE_U32(iter, "nvidia,la-scale-regs-num",
> +				num_up_down);
> +			PNE_U32(iter, "nvidia,vref-regs-num", vref_num);
> +			PNE_U32(iter, "nvidia,dram-timing-regs-num",
> +				dram_timing_num);
> +			PNE_U32(iter, "nvidia,min-mrs-wait", min_mrs_wait);
> +			PNE_U32(iter, "nvidia,emc-mrw", emc_mrw);
> +			PNE_U32(iter, "nvidia,emc-mrw2", emc_mrw2);
> +			PNE_U32(iter, "nvidia,emc-mrw3", emc_mrw3);
> +			PNE_U32(iter, "nvidia,emc-mrw4", emc_mrw4);
> +			PNE_U32(iter, "nvidia,emc-mrw9", emc_mrw9);
> +			PNE_U32(iter, "nvidia,emc-mrs", emc_mrs);
> +			PNE_U32(iter, "nvidia,emc-emrs", emc_emrs);
> +			PNE_U32(iter, "nvidia,emc-emrs2", emc_emrs2);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config4",
> +				emc_auto_cal_config4);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config5",
> +				emc_auto_cal_config5);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config6",
> +				emc_auto_cal_config6);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config7",
> +				emc_auto_cal_config7);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config8",
> +				emc_auto_cal_config8);
> +			PNE_U32(iter, "nvidia,emc-fdpd-ctrl-cmd-no-ramp",
> +				emc_fdpd_ctrl_cmd_no_ramp);
> +			PNE_U32(iter, "nvidia,dll-clk-src", dll_clk_src);
> +			PNE_U32(iter, "nvidia,clk-out-enb-x-0-clk-enb-emc-dll",
> +				clk_out_enb_x_0_clk_enb_emc_dll);
> +
> +			if (tables[i].rev >= 0x7)
> +				PNE_U32_ARRAY(iter, "nvidia,ptfv",
> +					      tables[i].ptfv_list,
> +					      sizeof(tables[i].ptfv_list)
> +						     / sizeof(u32));
> +
> +			PNE_U32_ARRAY(iter, "nvidia,emc-burst-regs-per-ch",
> +				      tables[i].burst_reg_per_ch,
> +				      tables[i].num_burst_per_ch);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-shadow-regs-ca-train",
> +				      tables[i].shadow_regs_ca_train,
> +				      tables[i].num_burst);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-shadow-regs-quse-train",
> +				      tables[i].shadow_regs_quse_train,
> +				      tables[i].num_burst);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-shadow-regs-rdwr-train",
> +				      tables[i].shadow_regs_rdwr_train,
> +				      tables[i].num_burst);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-trim-regs",
> +				      tables[i].trim_regs,
> +				      tables[i].num_trim);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-trim-regs-per-ch",
> +				      tables[i].trim_perch_regs,
> +				      tables[i].num_trim_per_ch);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-vref-regs",
> +				      tables[i].vref_perch_regs,
> +				      tables[i].vref_num);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-dram-timing-regs",
> +				      tables[i].dram_timings,
> +				      tables[i].dram_timing_num);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-burst-mc-regs",
> +				      tables[i].burst_mc_regs,
> +				      tables[i].num_mc_regs);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-la-scale-regs",
> +				      tables[i].la_scale_regs,
> +				      tables[i].num_up_down);
> +			i++;
> +		}
> +	}
> +
> +	*table_count = i;
> +
> +	return tables;
> +}
> +
> +static const struct of_device_id emc_table_match[] = {
> +	{
> +		.compatible = "nvidia,tegra210-emc-table",
> +		.data = "nvidia,tegra210-emc-table-derated",
> +	},
> +	{
> +		.compatible = "nvidia,tegra21-emc-table",
> +		.data = "nvidia,tegra21-emc-table-derated",
> +	},
> +	{ },
> +};
> +
> +int tegra_emc_dt_parse_pdata(struct platform_device *pdev,
> +			     struct emc_table **tables,
> +			     struct emc_table **derated_tables,
> +			     int *num_entries)

You don't seem to be parsing any "platform data" here, so I'd just leave
out the _pdata suffix.

Also: unsigned int *num_entries

> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct device_node *tnp, *iter;
> +	int num_tables, table_count;
> +	u32 tegra_bct_strapping;
> +	const char *emc_mode = "nvidia,emc-mode-0";
> +	struct tegra21_emc_pdata *pdata = NULL;
> +	const char *comp = NULL;
> +	const char *comp_derated = NULL;
> +
> +	if (!np) {
> +		dev_err(&pdev->dev,
> +			"Unable to find external-memory-controller node\n");
> +		return -ENODEV;
> +	}

I think you can remove this check. This driver is OF-only, so by
definition the device tree node must exist.

> +
> +	tegra_bct_strapping = tegra_read_ram_code();
> +
> +	if (of_find_property(np, "nvidia,use-ram-code", NULL)) {
> +		tnp = tegra_emc_ramcode_devnode(np);
> +
> +		if (!tnp) {
> +			dev_warn(&pdev->dev,
> +				 "can't find emc table for ram-code 0x%02x\n",
> +				 tegra_bct_strapping);
> +			return -ENODEV;
> +		}
> +	} else
> +		tnp = of_node_get(np);
> +
> +	num_tables = 0;
> +	for_each_child_of_node(tnp, iter) {
> +		if (!comp) {
> +			const struct of_device_id *m =
> +				of_match_node(emc_table_match, iter);
> +			if (m) {
> +				comp = m->compatible;
> +				comp_derated = m->data;
> +				num_tables++;
> +			}
> +			continue;
> +		}
> +		if (of_device_is_compatible(iter, comp))
> +			num_tables++;
> +	}

This seems to require that all tables be of the same type. Should it be
considered a DT error if that's not the case? Should we warn if that's
encountered in a DT?

> +
> +	if (!num_tables) {
> +		*tables = NULL;
> +		goto out;
> +	}
> +
> +	*tables = tegra_emc_dt_parse_pdata_comp(emc_mode, comp, pdata, tnp,
> +						pdev, num_tables, &table_count);
> +	*num_entries = table_count;
> +
> +	/* populate the derated tables */
> +	num_tables = 0;
> +	for_each_child_of_node(tnp, iter) {
> +		if (of_device_is_compatible(iter, comp_derated))
> +			num_tables++;
> +	}
> +
> +	if (!num_tables) {
> +		*derated_tables = NULL;
> +		goto out;
> +	}
> +
> +	*derated_tables = tegra_emc_dt_parse_pdata_comp(emc_mode,
> +							comp_derated,
> +							pdata, tnp, pdev,
> +							num_tables,
> +							&table_count);
> +
> +out:
> +	of_node_put(tnp);
> +	return 0;
> +}
> diff --git a/drivers/memory/tegra/tegra210-emc-reg.h b/drivers/memory/tegra/tegra210-emc-reg.h
> new file mode 100644
> index 000000000000..84fcc85f3b6d
> --- /dev/null
> +++ b/drivers/memory/tegra/tegra210-emc-reg.h
> @@ -0,0 +1,1083 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2015-2019, NVIDIA CORPORATION.  All rights reserved.
> + */
> +
> +#ifndef _TEGRA210_EMC_REG_H
> +#define _TEGRA210_EMC_REG_H
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/platform_device.h>
> +
> +#include "mc.h"
> +
[...]
> +
> +enum {
> +	REG_MC,
> +	REG_EMC,
> +	REG_EMC0,
> +	REG_EMC1,
> +};
> +
> +#define BURST_REGS_PER_CH_LIST						\
> +{									\
> +	DEFINE_REG(REG_EMC0, EMC_MRW10),				\
> +	DEFINE_REG(REG_EMC1, EMC_MRW10),				\
> +	DEFINE_REG(REG_EMC0, EMC_MRW11),				\
> +	DEFINE_REG(REG_EMC1, EMC_MRW11),				\
> +	DEFINE_REG(REG_EMC0, EMC_MRW12),				\
> +	DEFINE_REG(REG_EMC1, EMC_MRW12),				\
> +	DEFINE_REG(REG_EMC0, EMC_MRW13),				\
> +	DEFINE_REG(REG_EMC1, EMC_MRW13),				\
> +}

I'm not at all a fan of this type of list definition where the content
depends on how the DEFINE_REG macro is defined at the time where the
list macro is used.

It also seems like you're later on generating a couple of different
tables based on these lists and using different definitions of
DEFINE_REG to construct them.

Why can't we have a single table that contains everything? That's a lot
easier to maintain and the code becomes a lot easier to follow.

> +#define DEFINE_REG(type, reg)	reg##_INDEX
> +enum BURST_REGS_LIST;
> +enum TRIM_REGS_LIST;
> +enum BURST_MC_REGS_LIST;
> +enum BURST_UP_DOWN_REGS_LIST;
> +#undef DEFINE_REG
> +
> +#define DEFINE_REG(type, reg)	type##_##reg##_INDEX
> +enum BURST_REGS_PER_CH_LIST;
> +enum TRIM_REGS_PER_CH_LIST;
> +enum VREF_REGS_PER_CH_LIST;
> +#undef DEFINE_REG
> +
> +enum {
> +	DRAM_TYPE_DDR3   = 0,

Use consistent padding. Single space around '=' will do.

> +	DRAM_TYPE_LPDDR4 = 1,
> +	DRAM_TYPE_LPDDR2 = 2,
> +	DRAM_TYPE_DDR2 = 3,
> +};
> +
> +struct emc_table {

This structure doesn't really have anything to do with registers, so
perhaps move this to tegra210-emc.c?

> +	u32 rev;
> +	char dvfs_ver[60];

Could this just be a const char * pointing to the device tree property?

> +	u32 rate;

Clock rates are usually stored as unsigned long.

> +	u32 min_volt;
> +	u32 gpu_min_volt;

Maybe make these unsigned int, to match the type used for regulator
voltages?

> +	char clock_src[32];

Same comment as for dvfs_ver.

> +	u32 clk_src_emc;

As I mentioned elsewhere, I think it'd be nicer if we could split this
up into individual fields so that the value can be sanity checked.

> +	u32 needs_training;

bool?

> +	u32 training_parttern;

s/parttern/pattern/

> +	u32 trained;

bool?

> +	u32 periodic_training;

bool?

> +	u32 trained_dram_clktree_c0d0u0;
> +	u32 trained_dram_clktree_c0d0u1;
> +	u32 trained_dram_clktree_c0d1u0;
> +	u32 trained_dram_clktree_c0d1u1;
> +	u32 trained_dram_clktree_c1d0u0;
> +	u32 trained_dram_clktree_c1d0u1;
> +	u32 trained_dram_clktree_c1d1u0;
> +	u32 trained_dram_clktree_c1d1u1;
> +	u32 current_dram_clktree_c0d0u0;
> +	u32 current_dram_clktree_c0d0u1;
> +	u32 current_dram_clktree_c0d1u0;
> +	u32 current_dram_clktree_c0d1u1;
> +	u32 current_dram_clktree_c1d0u0;
> +	u32 current_dram_clktree_c1d0u1;
> +	u32 current_dram_clktree_c1d1u0;
> +	u32 current_dram_clktree_c1d1u1;

There's definitely a pattern here. Could these be arrays?

> +	u32 run_clocks;
> +	u32 tree_margin;
> +
> +	u32 num_burst;
> +	u32 num_burst_per_ch;
> +	u32 num_trim;
> +	u32 num_trim_per_ch;
> +	u32 num_mc_regs;
> +	u32 num_up_down;
> +	u32 vref_num;
> +	u32 training_mod_num;
> +	u32 dram_timing_num;
> +
> +	u32  ptfv_list[12];

Gratuitous space.

> +
> +	u32 burst_regs[221];
> +	u32 burst_reg_per_ch[8];
> +	u32 shadow_regs_ca_train[221];
> +	u32 shadow_regs_quse_train[221];
> +	u32 shadow_regs_rdwr_train[221];
> +
> +	u32 trim_regs[138];
> +	u32 trim_perch_regs[10];
> +
> +	u32 vref_perch_regs[4];
> +
> +	u32 dram_timings[5];
> +	u32 training_mod_regs[20];
> +	u32 save_restore_mod_regs[12];
> +	u32 burst_mc_regs[33];
> +	u32 la_scale_regs[24];

Looks like these are all fixed in length. Why do we ened the
corresponding num_* fields? Same goes for the properties in DT. If they
are always of a fixed length, then let's document that in the bindings
and sanity check it when parsing the tables.

> +
> +	u32 min_mrs_wait;
> +	u32 emc_mrw;
> +	u32 emc_mrw2;
> +	u32 emc_mrw3;
> +	u32 emc_mrw4;
> +	u32 emc_mrw9;
> +	u32 emc_mrs;
> +	u32 emc_emrs;
> +	u32 emc_emrs2;
> +	u32 emc_auto_cal_config;
> +	u32 emc_auto_cal_config2;
> +	u32 emc_auto_cal_config3;
> +	u32 emc_auto_cal_config4;
> +	u32 emc_auto_cal_config5;
> +	u32 emc_auto_cal_config6;
> +	u32 emc_auto_cal_config7;
> +	u32 emc_auto_cal_config8;
> +	u32 emc_cfg_2;
> +	u32 emc_sel_dpd_ctrl;
> +	u32 emc_fdpd_ctrl_cmd_no_ramp;
> +	u32 dll_clk_src;
> +	u32 clk_out_enb_x_0_clk_enb_emc_dll;
> +	u32 latency;
> +};
> +
> +struct tegra_emc {

This is also not related to registers, so maybe move it out into
tegra210-emc.c?

> +	struct clk_hw hw;
> +	struct clk *emc_clk;
> +	struct device *dev;
> +
> +	struct tegra_mc *mc;
> +
> +	void __iomem *emc_base;
> +	void __iomem *emc0_base;
> +	void __iomem *emc1_base;

Should this be an array? Seems like that could make it easier to write
the tables to these registers later on.

> +
> +	struct emc_table *current_timing;
> +	struct emc_table *next_timing;
> +	struct emc_table start_timing;

Why is start_timing not a pointer? It looks to me like that's basically
a copy of emc_table[0], so why not just point it at that?

> +
> +	struct emc_table *emc_table;
> +	struct emc_table *emc_table_normal;
> +	struct emc_table *emc_table_derated;

Seems like emc_table will always point at emc_table_normal, so why have
a second copy?

> +
> +	unsigned int emc_table_size;

Is this the number of entries in emc_table?

> +
> +	int dram_dev_num;

What is this?

> +	u32 dram_type;

Should this perhaps be an enum? All in all, I think it'd be good to add
some kerneldoc to this structure explaining what these fields are.

> +	u32 ram_code;
> +	u32 clk_setting;
> +};
> +#define to_emc(_hw) container_of(_hw, struct tegra_emc, hw)

I prefer static inline functions for this. Error reporting is better for
those.

> +
> +struct supported_sequence {
> +	u8     table_rev;
> +	void (*set_clock)(struct tegra_emc *emc, u32 clksrc);
> +	u32  (*periodic_compensation)(struct tegra_emc *emc);
> +	char  *seq_rev;

Use consistent padding. Either pad everything to the same column, or,
better yet, use a single space for padding.

> +};

Looks like this is mostly unused. Is this something that's more widely
used in a later patch? It could be useful to move this to that later
patch so that it doesn't look out of place.

> +
> +int tegra_emc_dt_parse_pdata(struct platform_device *pdev,
> +			     struct emc_table **tables,
> +			     struct emc_table **derated_tables,
> +			     int *num_entries);
> +
> +#endif
> diff --git a/drivers/memory/tegra/tegra210-emc.c b/drivers/memory/tegra/tegra210-emc.c
> new file mode 100644
> index 000000000000..0c20bcd0e6de
> --- /dev/null
> +++ b/drivers/memory/tegra/tegra210-emc.c
> @@ -0,0 +1,886 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2015-2019, NVIDIA CORPORATION.  All rights reserved.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clk/tegra.h>
> +#include <linux/clk-provider.h>
> +#include <linux/debugfs.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <soc/tegra/fuse.h>
> +#include <soc/tegra/mc.h>
> +
> +#include "mc.h"
> +#include "tegra210-emc-reg.h"
> +
> +#define TEGRA_EMC_TABLE_MAX_SIZE		16

Looks like this is only used for the statistics, so the _TABLE in there
is somewhat confusing. Perhaps make this TEGRA_EMC_MAX_STATS? Or
TEGRA_EMC_MAX_FREQS?

> +#define TEGRA210_EMC_SUSPEND_RATE		204000000

What exactly does this mean? Is this the lowest frequency that is
supported? Or just some default value that was determined to be a good
frequency for EMC during suspend?

What if this doesn't actually feature is the set of supported
frequencies by one set of tables?

> +
> +enum TEGRA_EMC_SOURCE {

enum name should be all lowercase. Or just drop it altogether if you
don't use the name anywhere anyway.

> +	TEGRA_EMC_SRC_PLLM,
> +	TEGRA_EMC_SRC_PLLC,
> +	TEGRA_EMC_SRC_PLLP,
> +	TEGRA_EMC_SRC_CLKM,
> +	TEGRA_EMC_SRC_PLLM_UD,
> +	TEGRA_EMC_SRC_PLLMB_UD,
> +	TEGRA_EMC_SRC_PLLMB,
> +	TEGRA_EMC_SRC_PLLP_UD,
> +	TEGRA_EMC_SRC_COUNT,
> +};
> +
> +struct emc_sel {
> +	struct clk *input;
> +	u32 value;
> +	unsigned long input_rate;
> +
> +	struct clk *input_b;
> +	u32 value_b;
> +	unsigned long input_rate_b;
> +};

What's the difference between the two sets of values? Seems like they
could maybe form an array? Or perhaps make each set a structure and
instantiate it twice. I find that suffixes are a poor way of describing
structure.

> +
> +struct emc_stats {
> +	u64 time_at_clock[TEGRA_EMC_TABLE_MAX_SIZE];
> +	int last_sel;
> +	u64 last_update;
> +	u64 clkchange_count;
> +	spinlock_t spinlock;
> +};
> +
> +static struct emc_sel *emc_clk_sel;
> +static struct clk *emc_src[TEGRA_EMC_SRC_COUNT];
> +static const char *emc_src_names[TEGRA_EMC_SRC_COUNT] = {
> +	[TEGRA_EMC_SRC_PLLM] = "pll_m",
> +	[TEGRA_EMC_SRC_PLLC] = "pll_c",
> +	[TEGRA_EMC_SRC_PLLP] = "pll_p",
> +	[TEGRA_EMC_SRC_CLKM] = "clk_m",
> +	[TEGRA_EMC_SRC_PLLM_UD] = "pll_m_ud",
> +	[TEGRA_EMC_SRC_PLLMB_UD] = "pll_mb_ud",
> +	[TEGRA_EMC_SRC_PLLMB] = "pll_mb",
> +	[TEGRA_EMC_SRC_PLLP_UD] = "pll_p_ud",
> +};
> +static struct emc_stats emc_stats;
> +static struct supported_sequence supported_seqs[] = {
> +	{
> +		0,
> +		NULL,
> +		NULL,
> +		NULL
> +	}
> +};

I haven't gone through the later patches, but is this going to be filled
with data? If it does, it seems to me like the data would end up being
static, in which case this should be const.

> +static struct supported_sequence *seq;

Some here.

> +static struct tegra_emc *tegra_emc;

It seems like you only need this in order to get at struct tegra_emc in
the emc_train() function below. If you move the emc_training_timer below
into struct tegra_emc, you should be able to use container_of() to get
at it and you can remove the global variable.

> +static DEFINE_SPINLOCK(emc_access_lock);
> +static ktime_t clkchange_time;
> +static int clkchange_delay = 100;
> +
> +static void emc_train(struct timer_list *tmr);
> +DEFINE_TIMER(emc_training_timer, emc_train);
> +static u32 timer_period_training = 100;

Why are these all global? Can they not be moved into struct tegra_emc?

> +#define DEFINE_REG(type, reg) (reg)
> +u32 burst_regs_per_ch_off[] = BURST_REGS_PER_CH_LIST;
> +u32 burst_regs_off[] = BURST_REGS_LIST;
> +u32 burst_mc_regs_off[] = BURST_MC_REGS_LIST;
> +u32 la_scale_regs_off[] = BURST_UP_DOWN_REGS_LIST;
> +u32 trim_regs_per_ch_off[] = TRIM_REGS_PER_CH_LIST;
> +u32 trim_regs_off[] = TRIM_REGS_LIST;
> +u32 vref_regs_per_ch_off[] = VREF_REGS_PER_CH_LIST;
> +#undef DEFINE_REG
> +
> +#define DEFINE_REG(type, reg) (type)
> +u32 burst_regs_per_ch_type[] = BURST_REGS_PER_CH_LIST;
> +u32 trim_regs_per_ch_type[] = TRIM_REGS_PER_CH_LIST;
> +u32 vref_regs_per_ch_type[] = VREF_REGS_PER_CH_LIST;
> +#undef DEFINE_REG

Should these all be static const? Like I said earlier, I'd prefer a
single table with all the values rather than splitting this up into
a large number of arrays.

> +
> +#ifdef CONFIG_PM_SLEEP
> +static bool emc_suspend;
> +static unsigned long emc_resume_rate;
> +#endif

Why are these global? Shouldn't they be part of struct tegra_emc?

> +
> +inline u32 emc_readl(struct tegra_emc *emc, unsigned long offset)

static, please.

> +{
> +	return readl(emc->emc_base + offset);
> +}
> +
> +inline u32 emc_readl_per_ch(struct tegra_emc *emc, int type,
> +			    unsigned long offset)
> +{
> +	u32 val = 0;
> +
> +	switch (type) {
> +	case REG_EMC:
> +	case REG_EMC0:
> +		val = readl(emc->emc_base + offset);

So if REG_EMC and REG_EMC0 are the same thing, why not define one in
terms of the other? Why use different enum values for them?

If they are the same, you could define emc_base as an array and use the
type as an index into that array and avoid the need for the complicated
switch here. Also, should "type" really be called "channel"?

> +		break;
> +	case REG_EMC1:
> +		val = readl(emc->emc1_base + offset);
> +		break;
> +	}
> +
> +	return val;
> +}
> +
> +static inline u32 emc_src_val(u32 val)
> +{
> +	return (val & EMC_CLK_EMC_2X_CLK_SRC_MASK) >>
> +		EMC_CLK_EMC_2X_CLK_SRC_SHIFT;
> +}
> +
> +static inline u32 emc_div_val(u32 val)
> +{
> +	return (val & EMC_CLK_EMC_2X_CLK_DIVISOR_MASK) >>
> +		EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT;
> +}

Seems like this is mostly similar to the macros defined in
include/linux/bitfield.h? Could those not be used here instead?

> +
> +static void emc_train(struct timer_list *tmr)
> +{
> +	unsigned long flags;
> +	struct tegra_emc *emc = tegra_emc;
> +
> +	if (!emc->current_timing)
> +		return;

It seems like this can never happen. Training happens as a result of the
EMC clock rate getting set and part of setting the EMC clock rate is to
set emc->current_timing to a valid table.

> +
> +	spin_lock_irqsave(&emc_access_lock, flags);
> +	if (seq->periodic_compensation)
> +		seq->periodic_compensation(emc);
> +	spin_unlock_irqrestore(&emc_access_lock, flags);
> +
> +	mod_timer(&emc_training_timer,
> +		  jiffies + msecs_to_jiffies(timer_period_training));
> +}
> +
> +static void emc_training_timer_start(void)
> +{
> +	mod_timer(&emc_training_timer,
> +		  jiffies + msecs_to_jiffies(timer_period_training));
> +}
> +
> +static void emc_training_timer_stop(void)
> +{
> +	del_timer(&emc_training_timer);

del_timer_sync()?

> +}
> +
> +static void emc_set_clock(struct tegra_emc *emc, u32 clksrc)
> +{
> +	seq->set_clock(emc, clksrc);
> +
> +	if (emc->next_timing->periodic_training)
> +		emc_training_timer_start();
> +	else
> +		emc_training_timer_stop();
> +}

Given that you only use emc_training_timer_{start,stop}() once, I think
you can fold them into the emc_set_clock() function.

> +
> +static inline void emc_get_timing(struct tegra_emc *emc,
> +				  struct emc_table *timing)
> +{
> +	int i, div;
> +	u32 val;
> +	unsigned long rate;
> +
> +	for (i = 0; i < timing->num_burst; i++) {
> +		if (burst_regs_off[i])
> +			timing->burst_regs[i] = emc_readl(emc,
> +							  burst_regs_off[i]);
> +		else
> +			timing->burst_regs[i] = 0;
> +	}
> +
> +	for (i = 0; i < timing->num_burst_per_ch; i++)
> +		timing->burst_reg_per_ch[i] = emc_readl_per_ch(emc,
> +			burst_regs_per_ch_type[i], burst_regs_per_ch_off[i]);
> +
> +	for (i = 0; i < timing->num_trim; i++)
> +		timing->trim_regs[i] = emc_readl(emc, trim_regs_off[i]);
> +
> +	for (i = 0; i < timing->num_trim_per_ch; i++)
> +		timing->trim_perch_regs[i] = emc_readl_per_ch(emc,
> +			trim_regs_per_ch_type[i], trim_regs_per_ch_off[i]);
> +
> +	for (i = 0; i < timing->vref_num; i++)
> +		timing->vref_perch_regs[i] = emc_readl_per_ch(emc,
> +			vref_regs_per_ch_type[i], vref_regs_per_ch_off[i]);
> +
> +	for (i = 0; i < timing->num_mc_regs; i++)
> +		timing->burst_mc_regs[i] = mc_readl(emc->mc,
> +						    burst_mc_regs_off[i]);
> +
> +	for (i = 0; i < timing->num_up_down; i++)
> +		timing->la_scale_regs[i] = mc_readl(emc->mc,
> +						    la_scale_regs_off[i]);
> +
> +	val = tegra210_clk_emc_get_setting();
> +	rate = clk_get_rate(emc_src[emc_src_val(val)]);

I thought we implemented the EMC clock as a CCF clock, in which case we
could just use clk_get_parent() to retrieve the parent clock, couldn't
we?

> +	div = emc_div_val(val);
> +	div += 2;
> +	rate *= 2;
> +	rate += div - 1;
> +	do_div(rate, div);
> +	timing->rate = rate / 1000;

And couldn't we implement a ->recalc_rate() callback to get at the rate?
Looks like we do already implement those, so perhaps I don't understand
how this is being used. Can you clarify?

> +}
> +
> +static void __emc_copy_table_params(struct emc_table *src,
> +				    struct emc_table *dst, int flags)

Flags are typically unsigned long.

> +{
> +	int i;

unsigned int

> +
> +	if (flags & EMC_COPY_TABLE_PARAM_PERIODIC_FIELDS) {
> +		dst->trained_dram_clktree_c0d0u0 =
> +			src->trained_dram_clktree_c0d0u0;
> +		dst->trained_dram_clktree_c0d0u1 =
> +			src->trained_dram_clktree_c0d0u1;
> +		dst->trained_dram_clktree_c0d1u0 =
> +			src->trained_dram_clktree_c0d1u0;
> +		dst->trained_dram_clktree_c0d1u1 =
> +			src->trained_dram_clktree_c0d1u1;
> +		dst->trained_dram_clktree_c1d0u0 =
> +			src->trained_dram_clktree_c1d0u0;
> +		dst->trained_dram_clktree_c1d0u1 =
> +			src->trained_dram_clktree_c1d0u1;
> +		dst->trained_dram_clktree_c1d1u0 =
> +			src->trained_dram_clktree_c1d1u0;
> +		dst->trained_dram_clktree_c1d1u1 =
> +			src->trained_dram_clktree_c1d1u1;
> +		dst->current_dram_clktree_c0d0u0 =
> +			src->current_dram_clktree_c0d0u0;
> +		dst->current_dram_clktree_c0d0u1 =
> +			src->current_dram_clktree_c0d0u1;
> +		dst->current_dram_clktree_c0d1u0 =
> +			src->current_dram_clktree_c0d1u0;
> +		dst->current_dram_clktree_c0d1u1 =
> +			src->current_dram_clktree_c0d1u1;
> +		dst->current_dram_clktree_c1d0u0 =
> +			src->current_dram_clktree_c1d0u0;
> +		dst->current_dram_clktree_c1d0u1 =
> +			src->current_dram_clktree_c1d0u1;
> +		dst->current_dram_clktree_c1d1u0 =
> +			src->current_dram_clktree_c1d1u0;
> +		dst->current_dram_clktree_c1d1u1 =
> +			src->current_dram_clktree_c1d1u1;
> +	}

Yeah, this definitely should be an array.

> +
> +	if (flags & EMC_COPY_TABLE_PARAM_TRIM_REGS) {
> +		for (i = 0; i < src->num_trim_per_ch; i++)
> +			dst->trim_perch_regs[i] = src->trim_perch_regs[i];
> +
> +		for (i = 0; i < src->num_trim; i++)
> +			dst->trim_regs[i] = src->trim_regs[i];
> +
> +		for (i = 0; i < src->num_burst_per_ch; i++)
> +			dst->burst_reg_per_ch[i] = src->burst_reg_per_ch[i];
> +
> +		dst->trained = src->trained;
> +	}
> +}
> +
> +static void emc_copy_table_params(struct emc_table *src,
> +				  struct emc_table *dst,
> +				  int table_size,

unsigned int

> +				  int flags)

unsigned long

> +{
> +	int i;

unsigned int

> +
> +	for (i = 0; i < table_size; i++)
> +		__emc_copy_table_params(&src[i], &dst[i], flags);
> +}
> +
> +static void emc_last_stats_update(int last_sel)

unsigned int last_sel

Maybe also pass in struct tegra_emc * here so that you can store the
stats in the EMC instance instead of having a global variable for it.

> +{
> +	unsigned long flags;
> +	u64 cur_jiffies = get_jiffies_64();
> +
> +	spin_lock_irqsave(&emc_stats.spinlock, flags);
> +
> +	if (emc_stats.last_sel < TEGRA_EMC_TABLE_MAX_SIZE)
> +		emc_stats.time_at_clock[emc_stats.last_sel] =
> +			emc_stats.time_at_clock[emc_stats.last_sel]
> +			+ (cur_jiffies - emc_stats.last_update);

Maybe use += here?

> +
> +	emc_stats.last_update = cur_jiffies;
> +
> +	if (last_sel < TEGRA_EMC_TABLE_MAX_SIZE) {
> +		emc_stats.clkchange_count++;
> +		emc_stats.last_sel = last_sel;
> +	}
> +
> +	spin_unlock_irqrestore(&emc_stats.spinlock, flags);
> +}
> +
> +static int emc_table_lookup(struct tegra_emc *emc, unsigned long rate)
> +{
> +	int i;

unsigned int

> +
> +	for (i = 0; i < emc->emc_table_size; i++) {
> +		if (emc_clk_sel[i].input == NULL)
> +			continue;
> +
> +		if (emc->emc_table[i].rate == rate)
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static struct clk *emc_predict_parent(struct tegra_emc *emc,
> +				      unsigned long rate)
> +{
> +	struct clk *old_parent, *new_parent;
> +	unsigned long parent_rate;
> +	int idx;
> +
> +	idx = emc_table_lookup(emc, rate / 1000);
> +	if (idx < 0)
> +		return ERR_PTR(-EINVAL);

Propagate idx

> +
> +	parent_rate = emc_clk_sel[idx].input_rate * 1000;
> +	new_parent = emc_clk_sel[idx].input;
> +	old_parent = clk_get_parent(emc->emc_clk);
> +
> +	if (parent_rate == clk_get_rate(old_parent))
> +		return old_parent;
> +
> +	if (clk_is_match(new_parent, old_parent))
> +		new_parent = emc_clk_sel[idx].input_b;
> +
> +	if (parent_rate != clk_get_rate(new_parent))
> +		clk_set_rate(new_parent, parent_rate);
> +
> +	return new_parent;
> +}
> +
> +static int emc_set_rate(struct tegra_emc *emc, unsigned long rate)
> +{
> +	int i;
> +	unsigned long flags;
> +	s64 last_change_delay;
> +	struct clk *parent;
> +
> +	if (emc_suspend)
> +		rate = TEGRA210_EMC_SUSPEND_RATE;
> +
> +	if (rate == emc->current_timing->rate)
> +		return 0;
> +
> +	i = emc_table_lookup(emc, rate / 1000);
> +
> +	if (i < 0)

No need for a blank line between the above two.

> +		return i;
> +
> +	if (rate > 204000000 && !emc->emc_table[i].trained)
> +		return -EINVAL;

Where does that 204 MHz come from? Maybe that should be a parameter? Is
it coincidence that it's the same as TEGRA210_EMC_SUSPEND_RATE?

> +
> +	parent = emc_predict_parent(emc, rate);
> +	if (clk_is_match(parent, emc_clk_sel[i].input))

Could use a blank line between the above two lines for readability.

> +		emc->clk_setting = emc_clk_sel[i].value;
> +	else
> +		emc->clk_setting = emc_clk_sel[i].value_b;
> +
> +	emc->next_timing = &emc->emc_table[i];
> +	last_change_delay = ktime_us_delta(ktime_get(), clkchange_time);
> +	if ((last_change_delay >= 0) && (last_change_delay < clkchange_delay))

Could also use a blank line for readability.

> +		udelay(clkchange_delay - (int)last_change_delay);
> +
> +	spin_lock_irqsave(&emc_access_lock, flags);
> +	emc_set_clock(emc, emc->clk_setting);
> +	clkchange_time = ktime_get();
> +	emc->current_timing = &emc->emc_table[i];
> +	spin_unlock_irqrestore(&emc_access_lock, flags);
> +
> +	emc_last_stats_update(i);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_DEBUG_FS
> +static int emc_stats_show(struct seq_file *s, void *data)
> +{
> +	int i;

unsigned int.

> +	struct tegra_emc *emc = (struct tegra_emc *)s->private;

I don't think the cast is needed here. s->private is void *.

> +
> +	if (!emc->emc_table_size || !seq)
> +		return 0;
> +
> +	emc_last_stats_update(TEGRA_EMC_TABLE_MAX_SIZE);

Isn't this going to falsify the statistics? This causes the last update
time to be captured, which effectively resets to 0 the duration for
which the current timing was applied, doesn't it?

> +
> +	seq_printf(s, "%-10s %-10s\n", "rate kHz", "time");
> +	for (i = 0; i < emc->emc_table_size; i++) {
> +		if (emc_clk_sel[i].input == NULL)
> +			continue;
> +
> +		seq_printf(s, "%-10u %-10llu\n",
> +			   emc->emc_table[i].rate,
> +			   jiffies_64_to_clock_t(
> +			   emc_stats.time_at_clock[i]));
> +	}
> +	seq_printf(s, "%-15s %llu\n", "transitions:",
> +		   emc_stats.clkchange_count);
> +	seq_printf(s, "%-15s %llu\n", "time-stamp:",
> +		   jiffies_64_to_clock_t(emc_stats.last_update));
> +
> +	return 0;
> +}
> +
> +static int emc_stats_open(struct inode *inode, struct file *file)
> +{
> +	return single_open(file, emc_stats_show, inode->i_private);
> +}
> +
> +static const struct file_operations emc_stats_fops = {
> +	.open		= emc_stats_open,
> +	.read		= seq_read,
> +	.llseek		= seq_lseek,
> +	.release	= single_release,
> +};
> +
> +static int debug_emc_get_rate(void *data, u64 *val)
> +{
> +	struct clk *c = data;
> +
> +	*val = clk_get_rate(c);
> +
> +	return 0;
> +}
> +
> +static int debug_emc_set_rate(void *data, u64 val)
> +{
> +	struct clk *c = data;
> +
> +	return clk_set_rate(c, val);
> +}
> +DEFINE_SIMPLE_ATTRIBUTE(emc_rate_fops, debug_emc_get_rate,
> +			debug_emc_set_rate, "%llu\n");
> +
> +static int tegra_emc_debug_init(struct tegra_emc *emc)
> +{
> +	struct dentry *emc_debugfs_root;
> +
> +	emc_debugfs_root = debugfs_create_dir("tegra_emc", NULL);
> +	if (!emc_debugfs_root)
> +		return -ENOMEM;
> +
> +	if (!debugfs_create_file("stats", 0444, emc_debugfs_root, emc,
> +				 &emc_stats_fops))
> +		goto err_out;
> +
> +	if (!debugfs_create_file("rate", 0644, emc_debugfs_root, emc->emc_clk,
> +				 &emc_rate_fops))
> +		goto err_out;
> +
> +	return 0;
> +
> +err_out:
> +	debugfs_remove_recursive(emc_debugfs_root);
> +	return -ENOMEM;
> +}
> +#endif /* CONFIG_DEBUG_FS */
> +
> +static u8 clk_emc_get_parent(struct clk_hw *hw)
> +{
> +	struct tegra_emc *emc = to_emc(hw);
> +
> +	if (!emc->clk_setting)
> +		emc->clk_setting = tegra210_clk_emc_get_setting();
> +
> +	return emc_src_val(emc->clk_setting);
> +}
> +
> +static unsigned long clk_emc_recalc_rate(struct clk_hw *hw,
> +					 unsigned long parent_rate)
> +{
> +	struct tegra_emc *emc = to_emc(hw);
> +
> +	if (!emc->emc_table_size || !seq) {
> +		u32 emc_setting = tegra210_clk_emc_get_setting();
> +
> +		return clk_get_rate(emc_src[emc_src_val(emc_setting)]);
> +	}
> +
> +	return emc->current_timing->rate * 1000;

There's a lot of conversion between CCF rates and timing rates. Can we
not settle on the timing rates to be stored in Hz (rather than kHz) as
well?

> +}
> +
> +static long clk_emc_round_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long *prate)
> +{
> +	struct tegra_emc *emc = to_emc(hw);
> +	int i;
> +
> +	if (!emc->emc_table_size || !seq) {
> +		u32 emc_setting = tegra210_clk_emc_get_setting();
> +
> +		return clk_get_rate(emc_src[emc_src_val(emc_setting)]);
> +	}
> +
> +	if (emc_suspend)
> +		return TEGRA210_EMC_SUSPEND_RATE;
> +
> +	rate /= 1000;
> +
> +	for (i = 0; i < emc->emc_table_size; i++) {
> +		if (emc->emc_table[i].rate >= rate)
> +			return emc->emc_table[i].rate * 1000;
> +	}
> +
> +	return emc->emc_table[i - 1].rate * 1000;
> +}
> +
> +static int clk_emc_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long parent_rate)
> +{
> +	struct tegra_emc *emc = to_emc(hw);
> +	struct clk *old_parent, *new_parent;
> +	int ret = -EINVAL;
> +
> +	if (!emc->emc_table_size || !seq)
> +		return ret;
> +
> +	if (emc_suspend)
> +		rate = TEGRA210_EMC_SUSPEND_RATE;
> +
> +	old_parent = clk_get_parent(hw->clk);
> +	new_parent = emc_predict_parent(emc, rate);
> +	if (IS_ERR(new_parent))
> +		goto out;
> +
> +	if (!clk_is_match(new_parent, old_parent))
> +		clk_prepare_enable(new_parent);
> +
> +	ret = emc_set_rate(emc, rate);
> +	if (ret) {
> +		if (new_parent != old_parent)
> +			clk_disable_unprepare(new_parent);
> +		goto out;
> +	}
> +
> +	if (!clk_is_match(new_parent, old_parent)) {
> +		clk_hw_reparent(hw, __clk_get_hw(new_parent));
> +		clk_disable_unprepare(old_parent);
> +	}
> +
> +out:
> +	return ret;
> +}
> +
> +static const struct clk_ops tegra_clk_emc_ops = {
> +	.get_parent = clk_emc_get_parent,
> +	.recalc_rate = clk_emc_recalc_rate,
> +	.round_rate = clk_emc_round_rate,
> +	.set_rate = clk_emc_set_rate,
> +};
> +
> +static int find_matching_input(struct emc_table *table, struct emc_sel *sel)
> +{
> +	u32 div_value;
> +	u32 src_value;
> +	unsigned long input_rate = 0;
> +	struct clk *input_clk;
> +
> +	div_value = emc_div_val(table->clk_src_emc);
> +	src_value = emc_src_val(table->clk_src_emc);
> +
> +	if (div_value & 0x1) {
> +		pr_warn("Tegra EMC: invalid odd divider for EMC rate %u\n",
> +			table->rate);
> +		return -EINVAL;
> +	}
> +
> +	if (!(table->clk_src_emc & EMC_CLK_MC_EMC_SAME_FREQ) !=
> +	    !(MC_EMEM_ARB_MISC0_EMC_SAME_FREQ &
> +	    table->burst_regs[MC_EMEM_ARB_MISC0_INDEX])) {
> +		pr_warn("Tegra EMC: ambiguous EMC to MC ratio for rate %u\n",
> +			table->rate);
> +		return -EINVAL;
> +	}
> +
> +	input_clk = emc_src[src_value];
> +	if (input_clk == emc_src[TEGRA_EMC_SRC_PLLM]
> +		|| input_clk == emc_src[TEGRA_EMC_SRC_PLLM_UD]) {
> +		input_rate = table->rate * (1 + div_value / 2);
> +	} else {
> +		input_rate = clk_get_rate(input_clk) / 1000;
> +		if (input_rate != (table->rate * (1 + div_value / 2))) {
> +			pr_warn("Tegra EMC: rate %u doesn't match input\n",
> +				table->rate);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	sel->input = input_clk;
> +	sel->input_rate = input_rate;
> +	sel->value = table->clk_src_emc;
> +	sel->input_b = input_clk;
> +	sel->input_rate_b = input_rate;
> +	sel->value_b = table->clk_src_emc;
> +
> +	if (input_clk == emc_src[TEGRA_EMC_SRC_PLLM]) {
> +		sel->input_b = emc_src[TEGRA_EMC_SRC_PLLMB];
> +		sel->value_b = table->clk_src_emc &
> +			       ~EMC_CLK_EMC_2X_CLK_SRC_MASK;
> +		sel->value_b |= TEGRA_EMC_SRC_PLLMB <<
> +				EMC_CLK_EMC_2X_CLK_SRC_SHIFT;
> +	}
> +
> +	if (input_clk == emc_src[TEGRA_EMC_SRC_PLLM_UD]) {
> +		sel->input_b = emc_src[TEGRA_EMC_SRC_PLLMB_UD];
> +		sel->value_b = table->clk_src_emc &
> +			       ~EMC_CLK_EMC_2X_CLK_SRC_MASK;
> +		sel->value_b |= TEGRA_EMC_SRC_PLLMB_UD <<
> +				EMC_CLK_EMC_2X_CLK_SRC_SHIFT;
> +	}
> +
> +	return 0;
> +}
> +
> +static int tegra210_emc_probe(struct platform_device *pdev)
> +{
> +	int i, div;

i and div can be unsigned int.

> +	unsigned long table_rate;
> +	unsigned long current_rate;
> +	struct device_node *np;
> +	struct platform_device *mc;
> +	struct tegra_emc *emc;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	struct resource *r;
> +	u32 emc_setting;
> +
> +	emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL);
> +	if (!emc)
> +		return -ENOMEM;
> +
> +	np = of_parse_phandle(pdev->dev.of_node, "nvidia,memory-controller", 0);
> +	if (!np) {
> +		dev_err(&pdev->dev, "could not get memory controller\n");
> +		return -ENOENT;
> +	}
> +
> +	mc = of_find_device_by_node(np);
> +	of_node_put(np);
> +	if (!mc)
> +		return -ENOENT;
> +
> +	emc->mc = platform_get_drvdata(mc);
> +	if (!emc->mc)
> +		return -EPROBE_DEFER;
> +
> +	emc->ram_code = tegra_read_ram_code();

Oh, we do already cache the value here. Might as well reuse this
everywhere instead of calling tegra_read_ram_code() over and over again.

> +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	emc->emc_base = devm_ioremap_resource(&pdev->dev, r);
> +	r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +	emc->emc0_base = devm_ioremap_resource(&pdev->dev, r);
> +	r = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> +	emc->emc1_base = devm_ioremap_resource(&pdev->dev, r);

That's odd. In emc_readl_per_ch() you use emc->emc_base to access
registers pertaining to the EMC0 channel. Why do we have different
apertures listed here? emc->emc0_base is not at all used right now.

> +
> +	for (i = 0; i < TEGRA_EMC_SRC_COUNT; i++) {
> +		emc_src[i] = devm_clk_get(&pdev->dev,
> +						emc_src_names[i]);

No need to split this across multiple lines.

> +		if (IS_ERR(emc_src[i])) {
> +			dev_err(&pdev->dev, "Can not find EMC source clock\n");
> +			return -ENODATA;

Propagate the error store in emc_src[i].

> +		}
> +	}
> +
> +	/* Init EMC rate statistic data */
> +	emc_stats.clkchange_count = 0;
> +	spin_lock_init(&emc_stats.spinlock);
> +	emc_stats.last_update = get_jiffies_64();
> +	emc_stats.last_sel = TEGRA_EMC_TABLE_MAX_SIZE;
> +
> +	emc->dram_type = (emc_readl(emc, EMC_FBIO_CFG5) &
> +			  EMC_FBIO_CFG5_DRAM_TYPE_MASK) >>
> +			  EMC_FBIO_CFG5_DRAM_TYPE_SHIFT;
> +	if (emc->dram_type != DRAM_TYPE_DDR3 &&
> +	    emc->dram_type != DRAM_TYPE_LPDDR2 &&
> +	    emc->dram_type != DRAM_TYPE_LPDDR4) {
> +		dev_err(&pdev->dev, "DRAM not supported\n");
> +		return -ENODATA;

This is not a very good error code for this situation. Perhaps use
something like -ENODEV or -ENXIO.

> +	}
> +
> +	emc->dram_dev_num = tegra_mc_get_emem_device_count(emc->mc);
> +
> +	tegra_emc_dt_parse_pdata(pdev, &emc->emc_table_normal,
> +				 &emc->emc_table_derated,
> +				 &emc->emc_table_size);

Don't you want to handle errors from this function?

> +	if (!emc->emc_table_size ||
> +	    emc->emc_table_size > TEGRA_EMC_TABLE_MAX_SIZE) {
> +		dev_err(&pdev->dev, "Invalid table size %d\n",
> +			emc->emc_table_size);
> +		goto emc_clk_register;
> +	}
> +	emc->emc_table = emc->emc_table_normal;
> +
> +	/*
> +	 * Copy trained trimmers from the normal table to the derated
> +	 * table for LP4. Bootloader trains only the normal table.
> +	 * Trimmers are the same for derated and normal tables.
> +	 */
> +	if (emc->emc_table_derated && emc->dram_type == DRAM_TYPE_LPDDR4)
> +		emc_copy_table_params(emc->emc_table_normal,
> +				      emc->emc_table_derated,
> +				      emc->emc_table_size,
> +				      EMC_COPY_TABLE_PARAM_PERIODIC_FIELDS |
> +				      EMC_COPY_TABLE_PARAM_TRIM_REGS);
> +
> +	seq = supported_seqs;
> +	while (seq->table_rev) {
> +		if (seq->table_rev == emc->emc_table[0].rev)
> +			break;
> +		seq++;
> +	}
> +	if (!seq->set_clock) {
> +		seq = NULL;
> +		dev_err(&pdev->dev, "Invalid EMC sequence for table Rev. %d\n",
> +			emc->emc_table[0].rev);
> +		goto emc_clk_register;
> +	}
> +
> +	emc_clk_sel = devm_kcalloc(&pdev->dev,
> +				   emc->emc_table_size,
> +				   sizeof(struct emc_sel),
> +				   GFP_KERNEL);
> +	if (!emc_clk_sel) {
> +		dev_err(&pdev->dev, "Memory allocation failed\n");

No need to output an error message in this case since the allocator will
already be very noisy when this happens.

> +		return -ENOMEM;
> +	}
> +
> +	/* calculate the rate from source clock */
> +	emc_setting = tegra210_clk_emc_get_setting();
> +	current_rate = clk_get_rate(emc_src[emc_src_val(emc_setting)]);
> +	div = emc_div_val(emc_setting);
> +	div += 2;
> +	current_rate *= 2;
> +	current_rate += div - 1;
> +	do_div(current_rate, div);
> +	current_rate /=  1000;
> +
> +	for (i = 0; i < emc->emc_table_size; i++) {
> +		table_rate = emc->emc_table[i].rate;
> +		if (!table_rate)
> +			continue;
> +
> +		if (i && ((table_rate <= emc->emc_table[i-1].rate) ||
> +		   (emc->emc_table[i].min_volt <
> +		    emc->emc_table[i-1].min_volt)))
> +			continue;
> +
> +		if (emc->emc_table[i].rev != emc->emc_table[0].rev)
> +			continue;
> +
> +		if (find_matching_input(&emc->emc_table[i], &emc_clk_sel[i]))
> +			continue;
> +
> +		if (table_rate == current_rate)
> +			emc_stats.last_sel = i;
> +	}
> +
> +	dev_info(&pdev->dev, "validated EMC DFS table\n");

dev_dbg(). Be verbose when unexpected things happen. No need to let the
user know if everything went as expected.

> +
> +	/* Update the start_timing base on the settings from firmware */
> +	emc->start_timing.num_burst = emc->emc_table[0].num_burst;
> +	emc->start_timing.num_burst_per_ch =
> +		emc->emc_table[0].num_burst_per_ch;
> +	emc->start_timing.num_trim = emc->emc_table[0].num_trim;
> +	emc->start_timing.num_trim_per_ch =
> +		emc->emc_table[0].num_trim_per_ch;
> +	emc->start_timing.num_mc_regs = emc->emc_table[0].num_mc_regs;
> +	emc->start_timing.num_up_down = emc->emc_table[0].num_up_down;
> +	emc->start_timing.vref_num = emc->emc_table[0].vref_num;
> +
> +	emc_get_timing(emc, &emc->start_timing);
> +	emc->current_timing = &emc->start_timing;
> +	emc->clk_setting = emc_setting;
> +
> +emc_clk_register:
> +	init.name = "emc";
> +	init.ops = &tegra_clk_emc_ops;
> +	init.flags = CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE;
> +	init.parent_names = emc_src_names;
> +	init.num_parents = ARRAY_SIZE(emc_src_names);
> +	emc->hw.init = &init;
> +
> +	clk = clk_register(&pdev->dev, &emc->hw);
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);
> +	emc->emc_clk = clk;
> +	emc->dev = &pdev->dev;
> +	tegra_emc = emc;
> +	dev_set_drvdata(emc->dev, emc);
> +
> +	if (emc->emc_table_size && seq) {
> +		for (i = 0; i < emc->emc_table_size; i++) {
> +			table_rate = emc->emc_table[i].rate * 1000;
> +			if (clk_set_rate(clk, table_rate))
> +				dev_info(&pdev->dev,
> +					 "rate: %lu validation fail\n",
> +					 table_rate);

This should be dev_err() and you may want to exit at this point?

> +			dev_info(&pdev->dev, "rate: %lu validation success\n",
> +				 table_rate);

Again, no need to let the user know if everything went as expected. Also
in the above error case, you'd be outputing that setting the rate failed
and immediately report that it also succeeded.

Also, do I understand correctly that the above will try to set each rate
and keep the EMC rate set at the one in the last entry in the table? It
would presumably be the highest and I think that's a good default. Just
want to make sure I understand what's happening and that it is on
purpose.

> +		}
> +	}
> +
> +	if (IS_ENABLED(CONFIG_DEBUG_FS))
> +		tegra_emc_debug_init(emc);

You'll have to decide whether you want to use #ifdef or a C conditional
with IS_ENABLED(). If DEBUG_FS is disabled, the above will fail to build
because tegra_emc_debug_init() won't be defined. I would recommend just
dropping the #ifdef around the debugfs implementation and let the
compiler's DCE pass remove debugfs support if DEBUG_FS=n.

> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int tegra210_emc_suspend(struct device *dev)
> +{
> +	struct tegra_emc *emc = dev_get_drvdata(dev);
> +
> +	if (!IS_ERR(emc->emc_clk)) {
> +		emc_suspend = true;
> +		emc_resume_rate = clk_get_rate(emc->emc_clk);
> +		clk_set_rate(emc->emc_clk, TEGRA210_EMC_SUSPEND_RATE);
> +
> +		pr_debug("%s at rate %lu\n", __func__,
> +			 clk_get_rate(emc->emc_clk));

Why not dev_dbg()? Also, perhaps use something like this for better
readability:

	dev_dbg(dev, "suspending at %lu Hz\n", clk_get_rate(emc->emc_clk));

> +	}
> +
> +	return 0;
> +}
> +
> +static int tegra210_emc_resume(struct device *dev)
> +{
> +	struct tegra_emc *emc = dev_get_drvdata(dev);
> +
> +	if (!IS_ERR(emc->emc_clk)) {
> +		emc_suspend = false;
> +		clk_set_rate(emc->emc_clk, emc_resume_rate);
> +
> +		pr_debug("%s at rate %lu\n", __func__,
> +			 clk_get_rate(emc->emc_clk));
> +	}
> +
> +	return 0;
> +}

Same comments as for suspend.

> +
> +static const struct dev_pm_ops tegra210_emc_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(tegra210_emc_suspend, tegra210_emc_resume)
> +};
> +#endif
> +
> +static const struct of_device_id tegra210_emc_of_match[] = {
> +	{ .compatible = "nvidia,tegra210-emc", },
> +	{ },
> +};
> +
> +static struct platform_driver tegra210_emc_driver = {
> +	.driver	= {
> +		.name = "tegra210-emc",
> +		.of_match_table = tegra210_emc_of_match,
> +		.pm = &tegra210_emc_pm_ops,

This is going to fail if PM_SLEEP is unset. Better to always declare
tegra210_emc_pm_ops, SET_SYSTEM_SLEEP_PM_OPS makes sure to replace the
undefined symbols with NULL if PM_SLEEP is unset.

> +	},
> +	.probe = tegra210_emc_probe,
> +};
> +
> +static int __init tegra210_emc_init(void)
> +{
> +	return platform_driver_register(&tegra210_emc_driver);
> +}
> +subsys_initcall(tegra210_emc_init);

Since this driver is not meant to be removed, you may want to prevent
users from forcefully removing it using sysfs by setting:

	.suppress_bind_attrs = true

in the driver structure.

Thierry

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 3/8] memory: tegra: Add Tegra210 EMC clock driver
  2019-03-25  7:45 ` [PATCH 3/8] memory: tegra: Add Tegra210 EMC clock driver Joseph Lo
  2019-04-03 11:34   ` Thierry Reding
@ 2019-04-03 11:55   ` Dmitry Osipenko
  1 sibling, 0 replies; 28+ messages in thread
From: Dmitry Osipenko @ 2019-04-03 11:55 UTC (permalink / raw)
  To: Joseph Lo, Thierry Reding, Peter De Schrijver, Jonathan Hunter,
	Rob Herring, Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree

25.03.2019 10:45, Joseph Lo пишет:
> This is the initial patch for Tegra210 EMC clock driver, which doesn't
> include the support code and detail sequence for clock scaling yet.
> 
> The driver is designed to support LPDDR4 SDRAMs. Because of the LPDDR4
> devices need to do initial time training before it can be used, the
> firmware will help to do that at early boot stage. The trained table for
> the rates that we will support in the kernel will be merged to the
> kernel DTB. So the driver can get the trained table for clock scaling
> support.
> 
> For the higher rate support (above 800MHz), the periodic training is
> needed for the timing compensation. So basically, two methodologies for
> clock scaling support, one is following the clock changing sequence to
> update the EMC table to EMC registers and another is if the rate needs
> periodic training, then we will start a timer to do that periodically
> until it leaves the rate that doesn't need that.
> 
> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
> 
> Signed-off-by: Joseph Lo <josephl@nvidia.com>
> ---
>  drivers/memory/tegra/Kconfig             |   10 +
>  drivers/memory/tegra/Makefile            |    1 +
>  drivers/memory/tegra/tegra210-dt-parse.c |  340 +++++++
>  drivers/memory/tegra/tegra210-emc-reg.h  | 1083 ++++++++++++++++++++++
>  drivers/memory/tegra/tegra210-emc.c      |  886 ++++++++++++++++++
>  5 files changed, 2320 insertions(+)
>  create mode 100644 drivers/memory/tegra/tegra210-dt-parse.c
>  create mode 100644 drivers/memory/tegra/tegra210-emc-reg.h
>  create mode 100644 drivers/memory/tegra/tegra210-emc.c
> 
> diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig
> index 34e0b70f5c5f..614e9b370183 100644
> --- a/drivers/memory/tegra/Kconfig
> +++ b/drivers/memory/tegra/Kconfig
> @@ -25,3 +25,13 @@ config TEGRA124_EMC
>  	  Tegra124 chips. The EMC controls the external DRAM on the board.
>  	  This driver is required to change memory timings / clock rate for
>  	  external memory.
> +
> +config TEGRA210_EMC
> +	bool "NVIDIA Tegra210 External Memory Controller driver"
> +	default y
> +	depends on TEGRA_MC && ARCH_TEGRA_210_SOC
> +	help
> +	  This driver is for the External Memory Controller (EMC) found on
> +	  Tegra210 chips. The EMC controls the external DRAM on the board.
> +	  This driver is required to change memory timings / clock rate for
> +	  external memory.
> diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile
> index 3971a6b7c487..36a835620bbd 100644
> --- a/drivers/memory/tegra/Makefile
> +++ b/drivers/memory/tegra/Makefile
> @@ -12,4 +12,5 @@ obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
>  
>  obj-$(CONFIG_TEGRA20_EMC)  += tegra20-emc.o
>  obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o
> +obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o tegra210-dt-parse.o
>  obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o
> diff --git a/drivers/memory/tegra/tegra210-dt-parse.c b/drivers/memory/tegra/tegra210-dt-parse.c
> new file mode 100644
> index 000000000000..6a3a3a28ac64
> --- /dev/null
> +++ b/drivers/memory/tegra/tegra210-dt-parse.c
> @@ -0,0 +1,340 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2013-2019, NVIDIA CORPORATION.  All rights reserved.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/err.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <soc/tegra/fuse.h>
> +
> +#include "tegra210-emc-reg.h"
> +
> +static struct device_node *tegra_emc_ramcode_devnode(
> +	struct device_node *np)
> +{
> +	struct device_node *iter;
> +	u32 reg;
> +
> +	for_each_child_of_node(np, iter) {
> +		if (of_property_read_u32(iter, "nvidia,ram-code", &reg))
> +			continue;
> +		if (reg == tegra_read_ram_code())
> +			return of_node_get(iter);
> +	}
> +
> +	return NULL;
> +}
> +
> +static void *tegra_emc_dt_parse_pdata_comp(const char *emc_mode,
> +					   const char *comp,
> +					   void *pdata,
> +					   struct device_node *tnp,
> +					   struct platform_device *pdev,
> +					   int num_tables, int *table_count)
> +{
> +#define PNE_U32(node, entry, tbl_entry)					\
> +	do {								\
> +		int __ret__;						\
> +		u32 __tmp__;						\
> +									\
> +		__ret__ = of_property_read_u32((node), (entry), &__tmp__); \
> +		if (__ret__) {						\
> +			dev_err(&pdev->dev, "Failed to parse %s in %s: %d\n", \
> +				(entry), (node)->full_name, __ret__);	\
> +			continue;					\
> +		}							\
> +									\
> +		tables[i].tbl_entry = __tmp__;				\
> +	} while (0)
> +
> +#define PNE_U32_ARRAY(node, entry, tbl_entry, length)			\
> +	do {								\
> +		int __ret__;						\
> +									\
> +		__ret__ = of_property_read_u32_array((node), (entry),	\
> +						     (tbl_entry), (length)); \
> +		if (__ret__) {						\
> +			dev_err(&pdev->dev, "Failed to parse %s in %s: %d\n", \
> +				(entry), (node)->full_name, __ret__);	\
> +			continue;					\
> +		}							\
> +	} while (0)
> +
> +	int i = 0, ret = 0;
> +	struct device_node *iter;
> +	struct emc_table *tables;
> +
> +	tables = devm_kzalloc(&pdev->dev, sizeof(*tables) * num_tables,
> +			      GFP_KERNEL);
> +
> +	if (!tables) {
> +		of_node_put(tnp);
> +		return tables;
> +	}
> +
> +	for_each_child_of_node(tnp, iter) {
> +		if (of_device_is_compatible(iter, comp)) {
> +			const char *source_name;
> +			const char *dvfs_ver;
> +
> +			ret = of_property_read_string(iter, "nvidia,source",
> +						      &source_name);
> +			if (ret) {
> +				dev_err(&pdev->dev, "no source name in %s\n",
> +					iter->full_name);
> +				continue;
> +			}
> +			strlcpy(tables[i].clock_src, source_name,
> +				sizeof(tables[i].clock_src));
> +
> +			ret = of_property_read_string(iter,
> +						      "nvidia,dvfs-version",
> +						      &dvfs_ver);
> +			if (ret) {
> +				dev_err(&pdev->dev, "no dvfs version in %s\n",
> +					iter->full_name);
> +				continue;
> +			}
> +			strlcpy(tables[i].dvfs_ver, dvfs_ver,
> +				sizeof(tables[i].dvfs_ver));
> +
> +			PNE_U32(iter, "nvidia,revision", rev);
> +			PNE_U32(iter, "clock-frequency", rate);
> +			PNE_U32(iter, "nvidia,emc-min-mv", min_volt);
> +			PNE_U32(iter, "nvidia,gk20a-min-mv", gpu_min_volt);
> +			PNE_U32(iter, "nvidia,src-sel-reg", clk_src_emc);
> +			PNE_U32(iter, "nvidia,burst-regs-num", num_burst);
> +			PNE_U32(iter, "nvidia,emc-cfg-2", emc_cfg_2);
> +			PNE_U32(iter, "nvidia,emc-sel-dpd-ctrl",
> +				emc_sel_dpd_ctrl);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config",
> +				emc_auto_cal_config);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config2",
> +				emc_auto_cal_config2);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config3",
> +				emc_auto_cal_config3);
> +			PNE_U32(iter, "nvidia,emc-clock-latency-change",
> +				latency);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-registers",
> +				      tables[i].burst_regs,
> +				      tables[i].num_burst);
> +
> +			PNE_U32(iter, "nvidia,needs-training", needs_training);
> +			PNE_U32(iter, "nvidia,trained", trained);
> +			if (tables[i].rev < 0x6)
> +				goto skip_periodic_training_params;
> +			PNE_U32(iter, "nvidia,periodic_training",
> +				periodic_training);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d0u0",
> +				trained_dram_clktree_c0d0u0);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d0u1",
> +				trained_dram_clktree_c0d0u1);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d1u0",
> +				trained_dram_clktree_c0d1u0);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c0d1u1",
> +				trained_dram_clktree_c0d1u1);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d0u0",
> +				trained_dram_clktree_c1d0u0);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d0u1",
> +				trained_dram_clktree_c1d0u1);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d1u0",
> +				trained_dram_clktree_c1d1u0);
> +			PNE_U32(iter, "nvidia,trained_dram_clktree_c1d1u1",
> +				trained_dram_clktree_c1d1u1);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c0d0u0",
> +				current_dram_clktree_c0d0u0);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c0d0u1",
> +				current_dram_clktree_c0d0u1);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c0d1u0",
> +				current_dram_clktree_c0d1u0);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c0d1u1",
> +				current_dram_clktree_c0d1u1);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c1d0u0",
> +				current_dram_clktree_c1d0u0);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c1d0u1",
> +				current_dram_clktree_c1d0u1);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c1d1u0",
> +				current_dram_clktree_c1d1u0);
> +			PNE_U32(iter, "nvidia,current_dram_clktree_c1d1u1",
> +				current_dram_clktree_c1d1u1);
> +			PNE_U32(iter, "nvidia,run_clocks", run_clocks);
> +			PNE_U32(iter, "nvidia,tree_margin", tree_margin);
> +
> +skip_periodic_training_params:
> +			PNE_U32(iter, "nvidia,burst-regs-per-ch-num",
> +				num_burst_per_ch);
> +			PNE_U32(iter, "nvidia,trim-regs-num", num_trim);
> +			PNE_U32(iter, "nvidia,trim-regs-per-ch-num",
> +				num_trim_per_ch);
> +			PNE_U32(iter, "nvidia,burst-mc-regs-num",
> +				num_mc_regs);
> +			PNE_U32(iter, "nvidia,la-scale-regs-num",
> +				num_up_down);
> +			PNE_U32(iter, "nvidia,vref-regs-num", vref_num);
> +			PNE_U32(iter, "nvidia,dram-timing-regs-num",
> +				dram_timing_num);
> +			PNE_U32(iter, "nvidia,min-mrs-wait", min_mrs_wait);
> +			PNE_U32(iter, "nvidia,emc-mrw", emc_mrw);
> +			PNE_U32(iter, "nvidia,emc-mrw2", emc_mrw2);
> +			PNE_U32(iter, "nvidia,emc-mrw3", emc_mrw3);
> +			PNE_U32(iter, "nvidia,emc-mrw4", emc_mrw4);
> +			PNE_U32(iter, "nvidia,emc-mrw9", emc_mrw9);
> +			PNE_U32(iter, "nvidia,emc-mrs", emc_mrs);
> +			PNE_U32(iter, "nvidia,emc-emrs", emc_emrs);
> +			PNE_U32(iter, "nvidia,emc-emrs2", emc_emrs2);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config4",
> +				emc_auto_cal_config4);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config5",
> +				emc_auto_cal_config5);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config6",
> +				emc_auto_cal_config6);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config7",
> +				emc_auto_cal_config7);
> +			PNE_U32(iter, "nvidia,emc-auto-cal-config8",
> +				emc_auto_cal_config8);
> +			PNE_U32(iter, "nvidia,emc-fdpd-ctrl-cmd-no-ramp",
> +				emc_fdpd_ctrl_cmd_no_ramp);
> +			PNE_U32(iter, "nvidia,dll-clk-src", dll_clk_src);
> +			PNE_U32(iter, "nvidia,clk-out-enb-x-0-clk-enb-emc-dll",
> +				clk_out_enb_x_0_clk_enb_emc_dll);
> +
> +			if (tables[i].rev >= 0x7)
> +				PNE_U32_ARRAY(iter, "nvidia,ptfv",
> +					      tables[i].ptfv_list,
> +					      sizeof(tables[i].ptfv_list)
> +						     / sizeof(u32));
> +
> +			PNE_U32_ARRAY(iter, "nvidia,emc-burst-regs-per-ch",
> +				      tables[i].burst_reg_per_ch,
> +				      tables[i].num_burst_per_ch);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-shadow-regs-ca-train",
> +				      tables[i].shadow_regs_ca_train,
> +				      tables[i].num_burst);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-shadow-regs-quse-train",
> +				      tables[i].shadow_regs_quse_train,
> +				      tables[i].num_burst);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-shadow-regs-rdwr-train",
> +				      tables[i].shadow_regs_rdwr_train,
> +				      tables[i].num_burst);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-trim-regs",
> +				      tables[i].trim_regs,
> +				      tables[i].num_trim);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-trim-regs-per-ch",
> +				      tables[i].trim_perch_regs,
> +				      tables[i].num_trim_per_ch);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-vref-regs",
> +				      tables[i].vref_perch_regs,
> +				      tables[i].vref_num);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-dram-timing-regs",
> +				      tables[i].dram_timings,
> +				      tables[i].dram_timing_num);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-burst-mc-regs",
> +				      tables[i].burst_mc_regs,
> +				      tables[i].num_mc_regs);
> +			PNE_U32_ARRAY(iter, "nvidia,emc-la-scale-regs",
> +				      tables[i].la_scale_regs,
> +				      tables[i].num_up_down);
> +			i++;
> +		}
> +	}
> +
> +	*table_count = i;
> +
> +	return tables;
> +}
> +
> +static const struct of_device_id emc_table_match[] = {
> +	{
> +		.compatible = "nvidia,tegra210-emc-table",
> +		.data = "nvidia,tegra210-emc-table-derated",
> +	},
> +	{
> +		.compatible = "nvidia,tegra21-emc-table",
> +		.data = "nvidia,tegra21-emc-table-derated",
> +	},
> +	{ },
> +};
> +
> +int tegra_emc_dt_parse_pdata(struct platform_device *pdev,
> +			     struct emc_table **tables,
> +			     struct emc_table **derated_tables,
> +			     int *num_entries)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct device_node *tnp, *iter;
> +	int num_tables, table_count;
> +	u32 tegra_bct_strapping;
> +	const char *emc_mode = "nvidia,emc-mode-0";
> +	struct tegra21_emc_pdata *pdata = NULL;
> +	const char *comp = NULL;
> +	const char *comp_derated = NULL;
> +
> +	if (!np) {
> +		dev_err(&pdev->dev,
> +			"Unable to find external-memory-controller node\n");
> +		return -ENODEV;
> +	}
> +
> +	tegra_bct_strapping = tegra_read_ram_code();
> +
> +	if (of_find_property(np, "nvidia,use-ram-code", NULL)) {
> +		tnp = tegra_emc_ramcode_devnode(np);
> +
> +		if (!tnp) {
> +			dev_warn(&pdev->dev,
> +				 "can't find emc table for ram-code 0x%02x\n",
> +				 tegra_bct_strapping);
> +			return -ENODEV;
> +		}
> +	} else
> +		tnp = of_node_get(np);
> +
> +	num_tables = 0;
> +	for_each_child_of_node(tnp, iter) {
> +		if (!comp) {
> +			const struct of_device_id *m =
> +				of_match_node(emc_table_match, iter);
> +			if (m) {
> +				comp = m->compatible;
> +				comp_derated = m->data;
> +				num_tables++;
> +			}
> +			continue;
> +		}
> +		if (of_device_is_compatible(iter, comp))
> +			num_tables++;
> +	}
> +
> +	if (!num_tables) {
> +		*tables = NULL;
> +		goto out;
> +	}
> +
> +	*tables = tegra_emc_dt_parse_pdata_comp(emc_mode, comp, pdata, tnp,
> +						pdev, num_tables, &table_count);
> +	*num_entries = table_count;
> +
> +	/* populate the derated tables */
> +	num_tables = 0;
> +	for_each_child_of_node(tnp, iter) {
> +		if (of_device_is_compatible(iter, comp_derated))
> +			num_tables++;
> +	}
> +
> +	if (!num_tables) {
> +		*derated_tables = NULL;
> +		goto out;
> +	}
> +
> +	*derated_tables = tegra_emc_dt_parse_pdata_comp(emc_mode,
> +							comp_derated,
> +							pdata, tnp, pdev,
> +							num_tables,
> +							&table_count);
> +
> +out:
> +	of_node_put(tnp);
> +	return 0;
> +}
> diff --git a/drivers/memory/tegra/tegra210-emc-reg.h b/drivers/memory/tegra/tegra210-emc-reg.h
> new file mode 100644
> index 000000000000..84fcc85f3b6d
> --- /dev/null
> +++ b/drivers/memory/tegra/tegra210-emc-reg.h
> @@ -0,0 +1,1083 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2015-2019, NVIDIA CORPORATION.  All rights reserved.
> + */
> +
> +#ifndef _TEGRA210_EMC_REG_H
> +#define _TEGRA210_EMC_REG_H
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/platform_device.h>
> +
> +#include "mc.h"
> +
> +#define MC_EMEM_ARB_CFG						0x90
> +#define MC_EMEM_ARB_OUTSTANDING_REQ				0x94
> +#define MC_EMEM_ARB_TIMING_RCD					0x98
> +#define MC_EMEM_ARB_TIMING_RP					0x9c
> +#define MC_EMEM_ARB_TIMING_RC					0xa0
> +#define MC_EMEM_ARB_TIMING_RAS					0xa4
> +#define MC_EMEM_ARB_TIMING_FAW					0xa8
> +#define MC_EMEM_ARB_TIMING_RRD					0xac
> +#define MC_EMEM_ARB_TIMING_RAP2PRE				0xb0
> +#define MC_EMEM_ARB_TIMING_WAP2PRE				0xb4
> +#define MC_EMEM_ARB_TIMING_R2R					0xb8
> +#define MC_EMEM_ARB_TIMING_W2W					0xbc
> +#define MC_EMEM_ARB_TIMING_R2W					0xc0
> +#define MC_EMEM_ARB_TIMING_W2R					0xc4
> +#define MC_EMEM_ARB_MISC2					0xc8
> +#define MC_EMEM_ARB_DA_TURNS					0xd0
> +#define MC_EMEM_ARB_DA_COVERS					0xd4
> +#define MC_EMEM_ARB_MISC0					0xd8
> +#define MC_EMEM_ARB_MISC0_EMC_SAME_FREQ				BIT(27)
> +#define MC_EMEM_ARB_MISC1					0xdc
> +#define MC_EMEM_ARB_RING1_THROTTLE				0xe0
> +#define MC_LATENCY_ALLOWANCE_AVPC_0				0x2e4
> +#define MC_LATENCY_ALLOWANCE_HC_0				0x310
> +#define MC_LATENCY_ALLOWANCE_HC_1				0x314
> +#define MC_LATENCY_ALLOWANCE_MPCORE_0				0x320
> +#define MC_LATENCY_ALLOWANCE_NVENC_0				0x328
> +#define MC_LATENCY_ALLOWANCE_PPCS_0				0x344
> +#define MC_LATENCY_ALLOWANCE_PPCS_1				0x348
> +#define MC_LATENCY_ALLOWANCE_ISP2_0				0x370
> +#define MC_LATENCY_ALLOWANCE_ISP2_1				0x374
> +#define MC_LATENCY_ALLOWANCE_XUSB_0				0x37c
> +#define MC_LATENCY_ALLOWANCE_XUSB_1				0x380
> +#define MC_LATENCY_ALLOWANCE_TSEC_0				0x390
> +#define MC_LATENCY_ALLOWANCE_VIC_0				0x394
> +#define MC_LATENCY_ALLOWANCE_VI2_0				0x398
> +#define MC_LATENCY_ALLOWANCE_GPU_0				0x3ac
> +#define MC_LATENCY_ALLOWANCE_SDMMCA_0				0x3b8
> +#define MC_LATENCY_ALLOWANCE_SDMMCAA_0				0x3bc
> +#define MC_LATENCY_ALLOWANCE_SDMMC_0				0x3c0
> +#define MC_LATENCY_ALLOWANCE_SDMMCAB_0				0x3c4
> +#define MC_LATENCY_ALLOWANCE_GPU2_0				0x3e8
> +#define MC_LATENCY_ALLOWANCE_NVDEC_0				0x3d8
> +#define MC_MLL_MPCORER_PTSA_RATE				0x44c
> +#define MC_FTOP_PTSA_RATE					0x50c
> +#define MC_EMEM_ARB_TIMING_RFCPB				0x6c0
> +#define MC_EMEM_ARB_TIMING_CCDMW				0x6c4
> +#define MC_EMEM_ARB_REFPB_HP_CTRL				0x6f0
> +#define MC_EMEM_ARB_REFPB_BANK_CTRL				0x6f4
> +#define MC_PTSA_GRANT_DECREMENT					0x960
> +#define MC_EMEM_ARB_DHYST_CTRL					0xbcc
> +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0			0xbd0
> +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1			0xbd4
> +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2			0xbd8
> +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3			0xbdc
> +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4			0xbe0
> +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5			0xbe4
> +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6			0xbe8
> +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7			0xbec
> +
> +#define CLK_RST_CONTROLLER_CLK_SOURCE_EMC			0x19c
> +#define EMC_CLK_EMC_2X_CLK_SRC_SHIFT				29
> +#define EMC_CLK_EMC_2X_CLK_SRC_MASK				\
> +	(0x7 << EMC_CLK_EMC_2X_CLK_SRC_SHIFT)
> +#define	EMC_CLK_MC_EMC_SAME_FREQ				BIT(16)
> +#define EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT			0
> +#define EMC_CLK_EMC_2X_CLK_DIVISOR_MASK				\
> +	(0xff << EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT)
> +
> +#define EMC_CFG							0xc
> +#define EMC_RC							0x2c
> +#define EMC_RFC							0x30
> +#define EMC_RAS							0x34
> +#define EMC_RP							0x38
> +#define EMC_R2W							0x3c
> +#define EMC_W2R							0x40
> +#define EMC_R2P							0x44
> +#define EMC_W2P							0x48
> +#define EMC_RD_RCD						0x4c
> +#define EMC_WR_RCD						0x50
> +#define EMC_RRD							0x54
> +#define EMC_REXT						0x58
> +#define EMC_WDV							0x5c
> +#define EMC_QUSE						0x60
> +#define EMC_QRST						0x64
> +#define EMC_QSAFE						0x68
> +#define EMC_RDV							0x6c
> +#define EMC_REFRESH						0x70
> +#define EMC_BURST_REFRESH_NUM					0x74
> +#define EMC_PDEX2WR						0x78
> +#define EMC_PDEX2RD						0x7c
> +#define EMC_PCHG2PDEN						0x80
> +#define EMC_ACT2PDEN						0x84
> +#define EMC_AR2PDEN						0x88
> +#define EMC_RW2PDEN						0x8c
> +#define EMC_TXSR						0x90
> +#define EMC_TCKE						0x94
> +#define EMC_TFAW						0x98
> +#define EMC_TRPAB						0x9c
> +#define EMC_TCLKSTABLE						0xa0
> +#define EMC_TCLKSTOP						0xa4
> +#define EMC_TREFBW						0xa8
> +#define EMC_TPPD						0xac
> +#define EMC_ODT_WRITE						0xb0
> +#define EMC_PDEX2MRR						0xb4
> +#define EMC_WEXT						0xb8
> +#define EMC_RFC_SLR						0xc0
> +#define EMC_MRS_WAIT_CNT2					0xc4
> +#define EMC_MRS_WAIT_CNT					0xc8
> +#define EMC_FBIO_SPARE						0x100
> +#define EMC_FBIO_CFG5						0x104
> +#define EMC_FBIO_CFG5_DRAM_TYPE_SHIFT				0
> +#define EMC_FBIO_CFG5_DRAM_TYPE_MASK				\
> +	(0x3 <<	EMC_FBIO_CFG5_DRAM_TYPE_SHIFT)
> +#define EMC_PDEX2CKE						0x118
> +#define EMC_CKE2PDEN						0x11c
> +#define EMC_R2R							0x144
> +#define EMC_EINPUT						0x14c
> +#define EMC_EINPUT_DURATION					0x150
> +#define EMC_PUTERM_EXTRA					0x154
> +#define EMC_TCKESR						0x158
> +#define EMC_TPD							0x15c
> +#define EMC_CFG_DIG_DLL						0x2bc
> +#define EMC_CFG_DIG_DLL_PERIOD					0x2c0
> +#define EMC_RDV_MASK						0x2cc
> +#define EMC_WDV_MASK						0x2d0
> +#define EMC_RDV_EARLY_MASK					0x2d4
> +#define EMC_RDV_EARLY						0x2d8
> +#define EMC_ZCAL_INTERVAL					0x2e0
> +#define EMC_ZCAL_WAIT_CNT					0x2e4
> +#define EMC_FDPD_CTRL_DQ					0x310
> +#define EMC_FDPD_CTRL_CMD					0x314
> +#define EMC_PMACRO_CMD_BRICK_CTRL_FDPD				0x318
> +#define EMC_PMACRO_DATA_BRICK_CTRL_FDPD				0x31c
> +#define EMC_PMACRO_BRICK_CTRL_RFU1				0x330
> +#define EMC_PMACRO_BRICK_CTRL_RFU2				0x334
> +#define EMC_TR_TIMING_0						0x3b4
> +#define EMC_TR_CTRL_1						0x3bc
> +#define EMC_TR_RDV						0x3c4
> +#define EMC_PRE_REFRESH_REQ_CNT					0x3dc
> +#define EMC_DYN_SELF_REF_CONTROL				0x3e0
> +#define EMC_TXSRDLL						0x3e4
> +#define EMC_TR_QPOP						0x3f4
> +#define EMC_TR_RDV_MASK						0x3f8
> +#define EMC_TR_QSAFE						0x3fc
> +#define EMC_TR_QRST						0x400
> +#define EMC_TR_DVFS						0x460
> +#define EMC_AUTO_CAL_CHANNEL					0x464
> +#define EMC_IBDLY						0x468
> +#define EMC_OBDLY						0x46c
> +#define EMC_TXDSRVTTGEN						0x480
> +#define EMC_WE_DURATION						0x48c
> +#define EMC_WS_DURATION						0x490
> +#define EMC_WEV							0x494
> +#define EMC_WSV							0x498
> +#define EMC_CFG_3						0x49c
> +#define EMC_MRW6						0x4a4
> +#define EMC_MRW7						0x4a8
> +#define EMC_MRW8						0x4ac
> +#define EMC_MRW10						0x4b4
> +#define EMC_MRW11						0x4b8
> +#define EMC_MRW12						0x4bc
> +#define EMC_MRW13						0x4c0
> +#define EMC_MRW14						0x4c4
> +#define EMC_MRW15						0x4d0
> +#define EMC_WDV_CHK						0x4e0
> +#define EMC_CFG_PIPE_2						0x554
> +#define EMC_CFG_PIPE_1						0x55c
> +#define EMC_CFG_PIPE						0x560
> +#define EMC_QPOP						0x564
> +#define EMC_QUSE_WIDTH						0x568
> +#define EMC_PUTERM_WIDTH					0x56c
> +#define EMC_REFCTRL2						0x580
> +#define EMC_FBIO_CFG7						0x584
> +#define EMC_DATA_BRLSHFT_0					0x588
> +#define EMC_DATA_BRLSHFT_1					0x58c
> +#define EMC_RFCPB						0x590
> +#define EMC_DQS_BRLSHFT_0					0x594
> +#define EMC_DQS_BRLSHFT_1					0x598
> +#define EMC_CMD_BRLSHFT_0					0x59c
> +#define EMC_CMD_BRLSHFT_1					0x5a0
> +#define EMC_CMD_BRLSHFT_2					0x5a4
> +#define EMC_CMD_BRLSHFT_3					0x5a8
> +#define EMC_QUSE_BRLSHFT_0					0x5ac
> +#define EMC_QUSE_BRLSHFT_1					0x5b8
> +#define EMC_QUSE_BRLSHFT_2					0x5bc
> +#define EMC_CCDMW						0x5c0
> +#define EMC_QUSE_BRLSHFT_3					0x5c4
> +#define EMC_DLL_CFG_0						0x5e4
> +#define EMC_DLL_CFG_1						0x5e8
> +#define EMC_CONFIG_SAMPLE_DELAY					0x5f0
> +#define EMC_PMACRO_QUSE_DDLL_RANK0_0				0x600
> +#define EMC_PMACRO_QUSE_DDLL_RANK0_1				0x604
> +#define EMC_PMACRO_QUSE_DDLL_RANK0_2				0x608
> +#define EMC_PMACRO_QUSE_DDLL_RANK0_3				0x60c
> +#define EMC_PMACRO_QUSE_DDLL_RANK0_4				0x610
> +#define EMC_PMACRO_QUSE_DDLL_RANK0_5				0x614
> +#define EMC_PMACRO_QUSE_DDLL_RANK1_0				0x620
> +#define EMC_PMACRO_QUSE_DDLL_RANK1_1				0x624
> +#define EMC_PMACRO_QUSE_DDLL_RANK1_2				0x628
> +#define EMC_PMACRO_QUSE_DDLL_RANK1_3				0x62c
> +#define EMC_PMACRO_QUSE_DDLL_RANK1_4				0x630
> +#define EMC_PMACRO_QUSE_DDLL_RANK1_5				0x634
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0			0x640
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1			0x644
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2			0x648
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3			0x64c
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4			0x650
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5			0x654
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0			0x660
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1			0x664
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2			0x668
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3			0x66c
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4			0x670
> +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5			0x674
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0			0x680
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1			0x684
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2			0x688
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3			0x68c
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4			0x690
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5			0x694
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0			0x6a0
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1			0x6a4
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2			0x6a8
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3			0x6ac
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4			0x6b0
> +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5			0x6b4
> +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0			0x6c0
> +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1			0x6c4
> +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2			0x6c8
> +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3			0x6cc
> +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0			0x6e0
> +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1			0x6e4
> +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2			0x6e8
> +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3			0x6ec
> +#define EMC_PMACRO_TX_PWRD_0					0x720
> +#define EMC_PMACRO_TX_PWRD_1					0x724
> +#define EMC_PMACRO_TX_PWRD_2					0x728
> +#define EMC_PMACRO_TX_PWRD_3					0x72c
> +#define EMC_PMACRO_TX_PWRD_4					0x730
> +#define EMC_PMACRO_TX_PWRD_5					0x734
> +#define EMC_PMACRO_TX_SEL_CLK_SRC_0				0x740
> +#define EMC_PMACRO_TX_SEL_CLK_SRC_1				0x744
> +#define EMC_PMACRO_TX_SEL_CLK_SRC_3				0x74c
> +#define EMC_PMACRO_TX_SEL_CLK_SRC_2				0x748
> +#define EMC_PMACRO_TX_SEL_CLK_SRC_4				0x750
> +#define EMC_PMACRO_TX_SEL_CLK_SRC_5				0x754
> +#define EMC_PMACRO_DDLL_BYPASS					0x760
> +#define EMC_PMACRO_DDLL_PWRD_0					0x770
> +#define EMC_PMACRO_DDLL_PWRD_1					0x774
> +#define EMC_PMACRO_DDLL_PWRD_2					0x778
> +#define EMC_PMACRO_CMD_CTRL_0					0x780
> +#define EMC_PMACRO_CMD_CTRL_1					0x784
> +#define EMC_PMACRO_CMD_CTRL_2					0x788
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0		0x800
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1		0x804
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2		0x808
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_3		0x80c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0		0x810
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1		0x814
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2		0x818
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_3		0x81c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0		0x820
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1		0x824
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2		0x828
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_3		0x82c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0		0x830
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1		0x834
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2		0x838
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_3		0x83c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0		0x840
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1		0x844
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2		0x848
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_3		0x84c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0		0x850
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1		0x854
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2		0x858
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_3		0x85c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0		0x860
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1		0x864
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2		0x868
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_3		0x86c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0		0x870
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1		0x874
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2		0x878
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_3		0x87c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0		0x880
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1		0x884
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2		0x888
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_3		0x88c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0		0x890
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1		0x894
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2		0x898
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_3		0x89c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0		0x8a0
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1		0x8a4
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2		0x8a8
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_3		0x8ac
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0		0x8b0
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1		0x8b4
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2		0x8b8
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_3		0x8bc
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0		0x900
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1		0x904
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2		0x908
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_3		0x90c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0		0x910
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1		0x914
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2		0x918
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_3		0x91c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0		0x920
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1		0x924
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2		0x928
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_3		0x92c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0		0x930
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1		0x934
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2		0x938
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_3		0x93c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0		0x940
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1		0x944
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2		0x948
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_3		0x94c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0		0x950
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1		0x954
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2		0x958
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_3		0x95c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0		0x960
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1		0x964
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2		0x968
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_3		0x96c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0		0x970
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1		0x974
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2		0x978
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_3		0x97c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_0		0x980
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_1		0x984
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_2		0x988
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_3		0x98c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_0		0x990
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_1		0x994
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_2		0x998
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_3		0x99c
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_0		0x9a0
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_1		0x9a4
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_2		0x9a8
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_3		0x9ac
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_0		0x9b0
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_1		0x9b4
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_2		0x9b8
> +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_3		0x9bc
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0		0xa00
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1		0xa04
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2		0xa08
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0		0xa10
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1		0xa14
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2		0xa18
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0		0xa20
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1		0xa24
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2		0xa28
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0		0xa30
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1		0xa34
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2		0xa38
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0		0xa40
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1		0xa44
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2		0xa48
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0		0xa50
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1		0xa54
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2		0xa58
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0		0xa60
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1		0xa64
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2		0xa68
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0		0xa70
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1		0xa74
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2		0xa78
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0		0xb00
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1		0xb04
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2		0xb08
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0		0xb10
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1		0xb14
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2		0xb18
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0		0xb20
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1		0xb24
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2		0xb28
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0		0xb30
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1		0xb34
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2		0xb38
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0		0xb40
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1		0xb44
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2		0xb48
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0		0xb50
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1		0xb54
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2		0xb58
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0		0xb60
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1		0xb64
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2		0xb68
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0		0xb70
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1		0xb74
> +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2		0xb78
> +#define EMC_PMACRO_IB_VREF_DQ_0					0xbe0
> +#define EMC_PMACRO_IB_VREF_DQ_1					0xbe4
> +#define EMC_PMACRO_IB_VREF_DQS_0				0xbf0
> +#define EMC_PMACRO_IB_VREF_DQS_1				0xbf4
> +#define EMC_PMACRO_DDLL_LONG_CMD_0				0xc00
> +#define EMC_PMACRO_DDLL_LONG_CMD_1				0xc04
> +#define EMC_PMACRO_DDLL_LONG_CMD_2				0xc08
> +#define EMC_PMACRO_DDLL_LONG_CMD_3				0xc0c
> +#define EMC_PMACRO_DDLL_LONG_CMD_4				0xc10
> +#define EMC_PMACRO_DDLL_LONG_CMD_5				0xc14
> +#define EMC_PMACRO_DDLL_SHORT_CMD_0				0xc20
> +#define EMC_PMACRO_DDLL_SHORT_CMD_1				0xc24
> +#define EMC_PMACRO_DDLL_SHORT_CMD_2				0xc28
> +#define EMC_PMACRO_VTTGEN_CTRL_0				0xc34
> +#define EMC_PMACRO_VTTGEN_CTRL_1				0xc38
> +#define EMC_PMACRO_BG_BIAS_CTRL_0				0xc3c
> +#define EMC_PMACRO_PAD_CFG_CTRL					0xc40
> +#define EMC_PMACRO_ZCTRL					0xc44
> +#define EMC_PMACRO_CMD_PAD_RX_CTRL				0xc50
> +#define EMC_PMACRO_DATA_PAD_RX_CTRL				0xc54
> +#define EMC_PMACRO_CMD_RX_TERM_MODE				0xc58
> +#define EMC_PMACRO_DATA_RX_TERM_MODE				0xc5c
> +#define EMC_PMACRO_CMD_PAD_TX_CTRL				0xc60
> +#define EMC_PMACRO_DATA_PAD_TX_CTRL				0xc64
> +#define EMC_PMACRO_COMMON_PAD_TX_CTRL				0xc68
> +#define EMC_PMACRO_AUTOCAL_CFG_COMMON				0xc78
> +#define EMC_PMACRO_VTTGEN_CTRL_2				0xcf0
> +#define EMC_PMACRO_IB_RXRT					0xcf4
> +#define EMC_TRAINING_CTRL					0xe04
> +#define EMC_TRAINING_QUSE_CORS_CTRL				0xe0c
> +#define EMC_TRAINING_QUSE_FINE_CTRL				0xe10
> +#define EMC_TRAINING_QUSE_CTRL_MISC				0xe14
> +#define EMC_TRAINING_WRITE_FINE_CTRL				0xe18
> +#define EMC_TRAINING_WRITE_CTRL_MISC				0xe1c
> +#define EMC_TRAINING_WRITE_VREF_CTRL				0xe20
> +#define EMC_TRAINING_READ_FINE_CTRL				0xe24
> +#define EMC_TRAINING_READ_CTRL_MISC				0xe28
> +#define EMC_TRAINING_READ_VREF_CTRL				0xe2c
> +#define EMC_TRAINING_CA_FINE_CTRL				0xe30
> +#define EMC_TRAINING_CA_CTRL_MISC				0xe34
> +#define EMC_TRAINING_CA_CTRL_MISC1				0xe38
> +#define EMC_TRAINING_CA_VREF_CTRL				0xe3c
> +#define EMC_TRAINING_SETTLE					0xe44
> +#define EMC_TRAINING_MPC					0xe5c
> +#define EMC_TRAINING_VREF_SETTLE				0xe6c
> +#define EMC_TRAINING_QUSE_VREF_CTRL				0xed0
> +#define EMC_TRAINING_OPT_DQS_IB_VREF_RANK0			0xed4
> +#define EMC_TRAINING_OPT_DQS_IB_VREF_RANK1			0xed8
> +
> +#define EMC_COPY_TABLE_PARAM_PERIODIC_FIELDS			BIT(0)
> +#define EMC_COPY_TABLE_PARAM_TRIM_REGS				BIT(1)
> +
> +enum {
> +	REG_MC,
> +	REG_EMC,
> +	REG_EMC0,
> +	REG_EMC1,
> +};
> +
> +#define BURST_REGS_PER_CH_LIST						\
> +{									\
> +	DEFINE_REG(REG_EMC0, EMC_MRW10),				\
> +	DEFINE_REG(REG_EMC1, EMC_MRW10),				\
> +	DEFINE_REG(REG_EMC0, EMC_MRW11),				\
> +	DEFINE_REG(REG_EMC1, EMC_MRW11),				\
> +	DEFINE_REG(REG_EMC0, EMC_MRW12),				\
> +	DEFINE_REG(REG_EMC1, EMC_MRW12),				\
> +	DEFINE_REG(REG_EMC0, EMC_MRW13),				\
> +	DEFINE_REG(REG_EMC1, EMC_MRW13),				\
> +}
> +
> +#define BURST_REGS_LIST							\
> +{									\
> +	DEFINE_REG(REG_EMC, EMC_RC),					\
> +	DEFINE_REG(REG_EMC, EMC_RFC),					\
> +	DEFINE_REG(REG_EMC, EMC_RFCPB),					\
> +	DEFINE_REG(REG_EMC, EMC_REFCTRL2),				\
> +	DEFINE_REG(REG_EMC, EMC_RFC_SLR),				\
> +	DEFINE_REG(REG_EMC, EMC_RAS),					\
> +	DEFINE_REG(REG_EMC, EMC_RP),					\
> +	DEFINE_REG(REG_EMC, EMC_R2W),					\
> +	DEFINE_REG(REG_EMC, EMC_W2R),					\
> +	DEFINE_REG(REG_EMC, EMC_R2P),					\
> +	DEFINE_REG(REG_EMC, EMC_W2P),					\
> +	DEFINE_REG(REG_EMC, EMC_R2R),					\
> +	DEFINE_REG(REG_EMC, EMC_TPPD),					\
> +	DEFINE_REG(REG_EMC, EMC_CCDMW),					\
> +	DEFINE_REG(REG_EMC, EMC_RD_RCD),				\
> +	DEFINE_REG(REG_EMC, EMC_WR_RCD),				\
> +	DEFINE_REG(REG_EMC, EMC_RRD),					\
> +	DEFINE_REG(REG_EMC, EMC_REXT),					\
> +	DEFINE_REG(REG_EMC, EMC_WEXT),					\
> +	DEFINE_REG(REG_EMC, EMC_WDV_CHK),				\
> +	DEFINE_REG(REG_EMC, EMC_WDV),					\
> +	DEFINE_REG(REG_EMC, EMC_WSV),					\
> +	DEFINE_REG(REG_EMC, EMC_WEV),					\
> +	DEFINE_REG(REG_EMC, EMC_WDV_MASK),				\
> +	DEFINE_REG(REG_EMC, EMC_WS_DURATION),				\
> +	DEFINE_REG(REG_EMC, EMC_WE_DURATION),				\
> +	DEFINE_REG(REG_EMC, EMC_QUSE),					\
> +	DEFINE_REG(REG_EMC, EMC_QUSE_WIDTH),				\
> +	DEFINE_REG(REG_EMC, EMC_IBDLY),					\
> +	DEFINE_REG(REG_EMC, EMC_OBDLY),					\
> +	DEFINE_REG(REG_EMC, EMC_EINPUT),				\
> +	DEFINE_REG(REG_EMC, EMC_MRW6),					\
> +	DEFINE_REG(REG_EMC, EMC_EINPUT_DURATION),			\
> +	DEFINE_REG(REG_EMC, EMC_PUTERM_EXTRA),				\
> +	DEFINE_REG(REG_EMC, EMC_PUTERM_WIDTH),				\
> +	DEFINE_REG(REG_EMC, EMC_QRST),					\
> +	DEFINE_REG(REG_EMC, EMC_QSAFE),					\
> +	DEFINE_REG(REG_EMC, EMC_RDV),					\
> +	DEFINE_REG(REG_EMC, EMC_RDV_MASK),				\
> +	DEFINE_REG(REG_EMC, EMC_RDV_EARLY),				\
> +	DEFINE_REG(REG_EMC, EMC_RDV_EARLY_MASK),			\
> +	DEFINE_REG(REG_EMC, EMC_REFRESH),				\
> +	DEFINE_REG(REG_EMC, EMC_BURST_REFRESH_NUM),			\
> +	DEFINE_REG(REG_EMC, EMC_PRE_REFRESH_REQ_CNT),			\
> +	DEFINE_REG(REG_EMC, EMC_PDEX2WR),				\
> +	DEFINE_REG(REG_EMC, EMC_PDEX2RD),				\
> +	DEFINE_REG(REG_EMC, EMC_PCHG2PDEN),				\
> +	DEFINE_REG(REG_EMC, EMC_ACT2PDEN),				\
> +	DEFINE_REG(REG_EMC, EMC_AR2PDEN),				\
> +	DEFINE_REG(REG_EMC, EMC_RW2PDEN),				\
> +	DEFINE_REG(REG_EMC, EMC_CKE2PDEN),				\
> +	DEFINE_REG(REG_EMC, EMC_PDEX2CKE),				\
> +	DEFINE_REG(REG_EMC, EMC_PDEX2MRR),				\
> +	DEFINE_REG(REG_EMC, EMC_TXSR),					\
> +	DEFINE_REG(REG_EMC, EMC_TXSRDLL),				\
> +	DEFINE_REG(REG_EMC, EMC_TCKE),					\
> +	DEFINE_REG(REG_EMC, EMC_TCKESR),				\
> +	DEFINE_REG(REG_EMC, EMC_TPD),					\
> +	DEFINE_REG(REG_EMC, EMC_TFAW),					\
> +	DEFINE_REG(REG_EMC, EMC_TRPAB),					\
> +	DEFINE_REG(REG_EMC, EMC_TCLKSTABLE),				\
> +	DEFINE_REG(REG_EMC, EMC_TCLKSTOP),				\
> +	DEFINE_REG(REG_EMC, EMC_MRW7),					\
> +	DEFINE_REG(REG_EMC, EMC_TREFBW),				\
> +	DEFINE_REG(REG_EMC, EMC_ODT_WRITE),				\
> +	DEFINE_REG(REG_EMC, EMC_FBIO_CFG5),				\
> +	DEFINE_REG(REG_EMC, EMC_FBIO_CFG7),				\
> +	DEFINE_REG(REG_EMC, EMC_CFG_DIG_DLL),				\
> +	DEFINE_REG(REG_EMC, EMC_CFG_DIG_DLL_PERIOD),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_RXRT),			\
> +	DEFINE_REG(REG_EMC, EMC_CFG_PIPE_1),				\
> +	DEFINE_REG(REG_EMC, EMC_CFG_PIPE_2),				\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_4),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_5),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_4),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_5),		\
> +	DEFINE_REG(REG_EMC, EMC_MRW8),					\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_LONG_CMD_0),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_LONG_CMD_1),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_LONG_CMD_2),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_LONG_CMD_3),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_LONG_CMD_4),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_SHORT_CMD_0),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_SHORT_CMD_1),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_SHORT_CMD_2),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_3),	\
> +	DEFINE_REG(REG_EMC, EMC_TXDSRVTTGEN),				\
> +	DEFINE_REG(REG_EMC, EMC_FDPD_CTRL_DQ),				\
> +	DEFINE_REG(REG_EMC, EMC_FDPD_CTRL_CMD),				\
> +	DEFINE_REG(REG_EMC, EMC_FBIO_SPARE),				\
> +	DEFINE_REG(REG_EMC, EMC_ZCAL_INTERVAL),				\
> +	DEFINE_REG(REG_EMC, EMC_ZCAL_WAIT_CNT),				\
> +	DEFINE_REG(REG_EMC, EMC_MRS_WAIT_CNT),				\
> +	DEFINE_REG(REG_EMC, EMC_MRS_WAIT_CNT2),				\
> +	DEFINE_REG(REG_EMC, EMC_AUTO_CAL_CHANNEL),			\
> +	DEFINE_REG(REG_EMC, EMC_DLL_CFG_0),				\
> +	DEFINE_REG(REG_EMC, EMC_DLL_CFG_1),				\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_AUTOCAL_CFG_COMMON),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_ZCTRL),				\
> +	DEFINE_REG(REG_EMC, EMC_CFG),					\
> +	DEFINE_REG(REG_EMC, EMC_CFG_PIPE),				\
> +	DEFINE_REG(REG_EMC, EMC_DYN_SELF_REF_CONTROL),			\
> +	DEFINE_REG(REG_EMC, EMC_QPOP),					\
> +	DEFINE_REG(REG_EMC, EMC_DQS_BRLSHFT_0),				\
> +	DEFINE_REG(REG_EMC, EMC_DQS_BRLSHFT_1),				\
> +	DEFINE_REG(REG_EMC, EMC_CMD_BRLSHFT_2),				\
> +	DEFINE_REG(REG_EMC, EMC_CMD_BRLSHFT_3),				\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_PAD_CFG_CTRL),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DATA_PAD_RX_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_PAD_RX_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DATA_RX_TERM_MODE),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_RX_TERM_MODE),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_PAD_TX_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DATA_PAD_TX_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_COMMON_PAD_TX_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_VTTGEN_CTRL_0),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_VTTGEN_CTRL_1),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_VTTGEN_CTRL_2),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_BRICK_CTRL_RFU1),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_BRICK_CTRL_FDPD),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_BRICK_CTRL_RFU2),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DATA_BRICK_CTRL_FDPD),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_BG_BIAS_CTRL_0),			\
> +	DEFINE_REG(REG_EMC, EMC_CFG_3),					\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_0),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_1),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_2),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_3),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_4),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_PWRD_5),			\
> +	DEFINE_REG(REG_EMC, EMC_CONFIG_SAMPLE_DELAY),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_0),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_1),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_2),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_3),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_4),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_TX_SEL_CLK_SRC_5),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_BYPASS),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_PWRD_0),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_PWRD_1),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_DDLL_PWRD_2),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_CTRL_0),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_CTRL_1),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_CMD_CTRL_2),			\
> +	DEFINE_REG(REG_EMC, EMC_TR_TIMING_0),				\
> +	DEFINE_REG(REG_EMC, EMC_TR_DVFS),				\
> +	DEFINE_REG(REG_EMC, EMC_TR_CTRL_1),				\
> +	DEFINE_REG(REG_EMC, EMC_TR_RDV),				\
> +	DEFINE_REG(REG_EMC, EMC_TR_QPOP),				\
> +	DEFINE_REG(REG_EMC, EMC_TR_RDV_MASK),				\
> +	DEFINE_REG(REG_EMC, EMC_MRW14),					\
> +	DEFINE_REG(REG_EMC, EMC_TR_QSAFE),				\
> +	DEFINE_REG(REG_EMC, EMC_TR_QRST),				\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_CTRL),				\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_SETTLE),			\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_VREF_SETTLE),			\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_CA_FINE_CTRL),			\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_CA_CTRL_MISC),			\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_CA_CTRL_MISC1),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_CA_VREF_CTRL),			\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_QUSE_CORS_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_QUSE_FINE_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_QUSE_CTRL_MISC),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_QUSE_VREF_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_READ_FINE_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_READ_CTRL_MISC),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_READ_VREF_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_WRITE_FINE_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_WRITE_CTRL_MISC),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_WRITE_VREF_CTRL),		\
> +	DEFINE_REG(REG_EMC, EMC_TRAINING_MPC),				\
> +	DEFINE_REG(REG_EMC, EMC_MRW15),					\
> +}
> +
> +#define TRIM_REGS_PER_CH_LIST						\
> +{									\
> +	DEFINE_REG(REG_EMC0, EMC_CMD_BRLSHFT_0),			\
> +	DEFINE_REG(REG_EMC1, EMC_CMD_BRLSHFT_1),			\
> +	DEFINE_REG(REG_EMC0, EMC_DATA_BRLSHFT_0),			\
> +	DEFINE_REG(REG_EMC1, EMC_DATA_BRLSHFT_0),			\
> +	DEFINE_REG(REG_EMC0, EMC_DATA_BRLSHFT_1),			\
> +	DEFINE_REG(REG_EMC1, EMC_DATA_BRLSHFT_1),			\
> +	DEFINE_REG(REG_EMC0, EMC_QUSE_BRLSHFT_0),			\
> +	DEFINE_REG(REG_EMC1, EMC_QUSE_BRLSHFT_1),			\
> +	DEFINE_REG(REG_EMC0, EMC_QUSE_BRLSHFT_2),			\
> +	DEFINE_REG(REG_EMC1, EMC_QUSE_BRLSHFT_3),			\
> +}
> +
> +#define TRIM_REGS_LIST							\
> +{									\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_VREF_DQS_0),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_VREF_DQS_1),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_VREF_DQ_0),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_IB_VREF_DQ_1),			\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2),	\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_0),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_1),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_2),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK0_3),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_0),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_1),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_2),		\
> +	DEFINE_REG(REG_EMC, EMC_PMACRO_QUSE_DDLL_RANK1_3),		\
> +}
> +
> +#define BURST_MC_REGS_LIST						\
> +{									\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_CFG),				\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_OUTSTANDING_REQ),		\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_REFPB_HP_CTRL),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_REFPB_BANK_CTRL),		\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RCD),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RP),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RC),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RAS),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_FAW),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RRD),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RAP2PRE),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_WAP2PRE),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_R2R),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_W2W),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_R2W),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_CCDMW),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_W2R),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_TIMING_RFCPB),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DA_TURNS),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DA_COVERS),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_MISC0),				\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_MISC1),				\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_MISC2),				\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_RING1_THROTTLE),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_CTRL),			\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0),		\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1),		\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2),		\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3),		\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4),		\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5),		\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6),		\
> +	DEFINE_REG(REG_MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7),		\
> +}
> +
> +#define BURST_UP_DOWN_REGS_LIST						\
> +{									\
> +	DEFINE_REG(REG_MC, MC_MLL_MPCORER_PTSA_RATE),			\
> +	DEFINE_REG(REG_MC, MC_FTOP_PTSA_RATE),				\
> +	DEFINE_REG(REG_MC, MC_PTSA_GRANT_DECREMENT),			\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_XUSB_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_XUSB_1),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_TSEC_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_SDMMCA_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_SDMMCAA_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_SDMMC_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_SDMMCAB_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_PPCS_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_PPCS_1),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_MPCORE_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_HC_0),			\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_HC_1),			\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_AVPC_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_GPU_0),			\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_GPU2_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_NVENC_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_NVDEC_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_VIC_0),			\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_VI2_0),			\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_ISP2_0),		\
> +	DEFINE_REG(REG_MC, MC_LATENCY_ALLOWANCE_ISP2_1),		\
> +}
> +
> +#define VREF_REGS_PER_CH_LIST						\
> +{									\
> +	DEFINE_REG(REG_EMC0, EMC_TRAINING_OPT_DQS_IB_VREF_RANK0),	\
> +	DEFINE_REG(REG_EMC1, EMC_TRAINING_OPT_DQS_IB_VREF_RANK0),	\
> +	DEFINE_REG(REG_EMC0, EMC_TRAINING_OPT_DQS_IB_VREF_RANK1),	\
> +	DEFINE_REG(REG_EMC1, EMC_TRAINING_OPT_DQS_IB_VREF_RANK1),	\
> +}
> +
> +#define DEFINE_REG(type, reg)	reg##_INDEX
> +enum BURST_REGS_LIST;
> +enum TRIM_REGS_LIST;
> +enum BURST_MC_REGS_LIST;
> +enum BURST_UP_DOWN_REGS_LIST;
> +#undef DEFINE_REG
> +
> +#define DEFINE_REG(type, reg)	type##_##reg##_INDEX
> +enum BURST_REGS_PER_CH_LIST;
> +enum TRIM_REGS_PER_CH_LIST;
> +enum VREF_REGS_PER_CH_LIST;
> +#undef DEFINE_REG
> +
> +enum {
> +	DRAM_TYPE_DDR3   = 0,
> +	DRAM_TYPE_LPDDR4 = 1,
> +	DRAM_TYPE_LPDDR2 = 2,
> +	DRAM_TYPE_DDR2 = 3,
> +};
> +
> +struct emc_table {
> +	u32 rev;
> +	char dvfs_ver[60];
> +	u32 rate;
> +	u32 min_volt;
> +	u32 gpu_min_volt;
> +	char clock_src[32];
> +	u32 clk_src_emc;
> +	u32 needs_training;
> +	u32 training_parttern;
> +	u32 trained;
> +
> +	u32 periodic_training;
> +	u32 trained_dram_clktree_c0d0u0;
> +	u32 trained_dram_clktree_c0d0u1;
> +	u32 trained_dram_clktree_c0d1u0;
> +	u32 trained_dram_clktree_c0d1u1;
> +	u32 trained_dram_clktree_c1d0u0;
> +	u32 trained_dram_clktree_c1d0u1;
> +	u32 trained_dram_clktree_c1d1u0;
> +	u32 trained_dram_clktree_c1d1u1;
> +	u32 current_dram_clktree_c0d0u0;
> +	u32 current_dram_clktree_c0d0u1;
> +	u32 current_dram_clktree_c0d1u0;
> +	u32 current_dram_clktree_c0d1u1;
> +	u32 current_dram_clktree_c1d0u0;
> +	u32 current_dram_clktree_c1d0u1;
> +	u32 current_dram_clktree_c1d1u0;
> +	u32 current_dram_clktree_c1d1u1;
> +	u32 run_clocks;
> +	u32 tree_margin;
> +
> +	u32 num_burst;
> +	u32 num_burst_per_ch;
> +	u32 num_trim;
> +	u32 num_trim_per_ch;
> +	u32 num_mc_regs;
> +	u32 num_up_down;
> +	u32 vref_num;
> +	u32 training_mod_num;
> +	u32 dram_timing_num;
> +
> +	u32  ptfv_list[12];
> +
> +	u32 burst_regs[221];
> +	u32 burst_reg_per_ch[8];
> +	u32 shadow_regs_ca_train[221];
> +	u32 shadow_regs_quse_train[221];
> +	u32 shadow_regs_rdwr_train[221];
> +
> +	u32 trim_regs[138];
> +	u32 trim_perch_regs[10];
> +
> +	u32 vref_perch_regs[4];
> +
> +	u32 dram_timings[5];
> +	u32 training_mod_regs[20];
> +	u32 save_restore_mod_regs[12];
> +	u32 burst_mc_regs[33];
> +	u32 la_scale_regs[24];
> +
> +	u32 min_mrs_wait;
> +	u32 emc_mrw;
> +	u32 emc_mrw2;
> +	u32 emc_mrw3;
> +	u32 emc_mrw4;
> +	u32 emc_mrw9;
> +	u32 emc_mrs;
> +	u32 emc_emrs;
> +	u32 emc_emrs2;
> +	u32 emc_auto_cal_config;
> +	u32 emc_auto_cal_config2;
> +	u32 emc_auto_cal_config3;
> +	u32 emc_auto_cal_config4;
> +	u32 emc_auto_cal_config5;
> +	u32 emc_auto_cal_config6;
> +	u32 emc_auto_cal_config7;
> +	u32 emc_auto_cal_config8;
> +	u32 emc_cfg_2;
> +	u32 emc_sel_dpd_ctrl;
> +	u32 emc_fdpd_ctrl_cmd_no_ramp;
> +	u32 dll_clk_src;
> +	u32 clk_out_enb_x_0_clk_enb_emc_dll;
> +	u32 latency;
> +};
> +
> +struct tegra_emc {
> +	struct clk_hw hw;
> +	struct clk *emc_clk;
> +	struct device *dev;
> +
> +	struct tegra_mc *mc;
> +
> +	void __iomem *emc_base;
> +	void __iomem *emc0_base;
> +	void __iomem *emc1_base;
> +
> +	struct emc_table *current_timing;
> +	struct emc_table *next_timing;
> +	struct emc_table start_timing;
> +
> +	struct emc_table *emc_table;
> +	struct emc_table *emc_table_normal;
> +	struct emc_table *emc_table_derated;
> +
> +	unsigned int emc_table_size;
> +
> +	int dram_dev_num;
> +	u32 dram_type;
> +	u32 ram_code;
> +	u32 clk_setting;
> +};
> +#define to_emc(_hw) container_of(_hw, struct tegra_emc, hw)
> +
> +struct supported_sequence {
> +	u8     table_rev;
> +	void (*set_clock)(struct tegra_emc *emc, u32 clksrc);
> +	u32  (*periodic_compensation)(struct tegra_emc *emc);
> +	char  *seq_rev;
> +};
> +
> +int tegra_emc_dt_parse_pdata(struct platform_device *pdev,
> +			     struct emc_table **tables,
> +			     struct emc_table **derated_tables,
> +			     int *num_entries);
> +
> +#endif
> diff --git a/drivers/memory/tegra/tegra210-emc.c b/drivers/memory/tegra/tegra210-emc.c
> new file mode 100644
> index 000000000000..0c20bcd0e6de
> --- /dev/null
> +++ b/drivers/memory/tegra/tegra210-emc.c
> @@ -0,0 +1,886 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2015-2019, NVIDIA CORPORATION.  All rights reserved.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clk/tegra.h>
> +#include <linux/clk-provider.h>
> +#include <linux/debugfs.h>
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/of_address.h>
> +#include <linux/of_platform.h>
> +#include <soc/tegra/fuse.h>
> +#include <soc/tegra/mc.h>
> +
> +#include "mc.h"
> +#include "tegra210-emc-reg.h"
> +
> +#define TEGRA_EMC_TABLE_MAX_SIZE		16
> +#define TEGRA210_EMC_SUSPEND_RATE		204000000
> +
> +enum TEGRA_EMC_SOURCE {
> +	TEGRA_EMC_SRC_PLLM,
> +	TEGRA_EMC_SRC_PLLC,
> +	TEGRA_EMC_SRC_PLLP,
> +	TEGRA_EMC_SRC_CLKM,
> +	TEGRA_EMC_SRC_PLLM_UD,
> +	TEGRA_EMC_SRC_PLLMB_UD,
> +	TEGRA_EMC_SRC_PLLMB,
> +	TEGRA_EMC_SRC_PLLP_UD,
> +	TEGRA_EMC_SRC_COUNT,
> +};
> +
> +struct emc_sel {
> +	struct clk *input;
> +	u32 value;
> +	unsigned long input_rate;
> +
> +	struct clk *input_b;
> +	u32 value_b;
> +	unsigned long input_rate_b;
> +};
> +
> +struct emc_stats {
> +	u64 time_at_clock[TEGRA_EMC_TABLE_MAX_SIZE];
> +	int last_sel;
> +	u64 last_update;
> +	u64 clkchange_count;
> +	spinlock_t spinlock;
> +};
> +
> +static struct emc_sel *emc_clk_sel;
> +static struct clk *emc_src[TEGRA_EMC_SRC_COUNT];
> +static const char *emc_src_names[TEGRA_EMC_SRC_COUNT] = {
> +	[TEGRA_EMC_SRC_PLLM] = "pll_m",
> +	[TEGRA_EMC_SRC_PLLC] = "pll_c",
> +	[TEGRA_EMC_SRC_PLLP] = "pll_p",
> +	[TEGRA_EMC_SRC_CLKM] = "clk_m",
> +	[TEGRA_EMC_SRC_PLLM_UD] = "pll_m_ud",
> +	[TEGRA_EMC_SRC_PLLMB_UD] = "pll_mb_ud",
> +	[TEGRA_EMC_SRC_PLLMB] = "pll_mb",
> +	[TEGRA_EMC_SRC_PLLP_UD] = "pll_p_ud",
> +};
> +static struct emc_stats emc_stats;
> +static struct supported_sequence supported_seqs[] = {
> +	{
> +		0,
> +		NULL,
> +		NULL,
> +		NULL
> +	}
> +};
> +static struct supported_sequence *seq;
> +static struct tegra_emc *tegra_emc;
> +static DEFINE_SPINLOCK(emc_access_lock);
> +static ktime_t clkchange_time;
> +static int clkchange_delay = 100;
> +
> +static void emc_train(struct timer_list *tmr);
> +DEFINE_TIMER(emc_training_timer, emc_train);
> +static u32 timer_period_training = 100;
> +
> +#define DEFINE_REG(type, reg) (reg)
> +u32 burst_regs_per_ch_off[] = BURST_REGS_PER_CH_LIST;
> +u32 burst_regs_off[] = BURST_REGS_LIST;
> +u32 burst_mc_regs_off[] = BURST_MC_REGS_LIST;
> +u32 la_scale_regs_off[] = BURST_UP_DOWN_REGS_LIST;
> +u32 trim_regs_per_ch_off[] = TRIM_REGS_PER_CH_LIST;
> +u32 trim_regs_off[] = TRIM_REGS_LIST;
> +u32 vref_regs_per_ch_off[] = VREF_REGS_PER_CH_LIST;
> +#undef DEFINE_REG
> +
> +#define DEFINE_REG(type, reg) (type)
> +u32 burst_regs_per_ch_type[] = BURST_REGS_PER_CH_LIST;
> +u32 trim_regs_per_ch_type[] = TRIM_REGS_PER_CH_LIST;
> +u32 vref_regs_per_ch_type[] = VREF_REGS_PER_CH_LIST;
> +#undef DEFINE_REG
> +
> +#ifdef CONFIG_PM_SLEEP
> +static bool emc_suspend;
> +static unsigned long emc_resume_rate;
> +#endif
> +
> +inline u32 emc_readl(struct tegra_emc *emc, unsigned long offset)
> +{
> +	return readl(emc->emc_base + offset);
> +}
> +
> +inline u32 emc_readl_per_ch(struct tegra_emc *emc, int type,
> +			    unsigned long offset)
> +{
> +	u32 val = 0;
> +
> +	switch (type) {
> +	case REG_EMC:
> +	case REG_EMC0:
> +		val = readl(emc->emc_base + offset);
> +		break;
> +	case REG_EMC1:
> +		val = readl(emc->emc1_base + offset);
> +		break;
> +	}
> +
> +	return val;
> +}
> +
> +static inline u32 emc_src_val(u32 val)
> +{
> +	return (val & EMC_CLK_EMC_2X_CLK_SRC_MASK) >>
> +		EMC_CLK_EMC_2X_CLK_SRC_SHIFT;
> +}
> +
> +static inline u32 emc_div_val(u32 val)
> +{
> +	return (val & EMC_CLK_EMC_2X_CLK_DIVISOR_MASK) >>
> +		EMC_CLK_EMC_2X_CLK_DIVISOR_SHIFT;
> +}
> +
> +static void emc_train(struct timer_list *tmr)
> +{
> +	unsigned long flags;
> +	struct tegra_emc *emc = tegra_emc;

Looks like you could embed timer into "struct tegra_emc" and then the global variable won't be needed.

> +
> +	if (!emc->current_timing)
> +		return;
> +
> +	spin_lock_irqsave(&emc_access_lock, flags);
> +	if (seq->periodic_compensation)
> +		seq->periodic_compensation(emc);
> +	spin_unlock_irqrestore(&emc_access_lock, flags);
> +
> +	mod_timer(&emc_training_timer,
> +		  jiffies + msecs_to_jiffies(timer_period_training));
> +}
> +
> +static void emc_training_timer_start(void)
> +{
> +	mod_timer(&emc_training_timer,
> +		  jiffies + msecs_to_jiffies(timer_period_training));
> +}
> +
> +static void emc_training_timer_stop(void)
> +{
> +	del_timer(&emc_training_timer);
> +}
> +
> +static void emc_set_clock(struct tegra_emc *emc, u32 clksrc)
> +{
> +	seq->set_clock(emc, clksrc);
> +
> +	if (emc->next_timing->periodic_training)
> +		emc_training_timer_start();
> +	else
> +		emc_training_timer_stop();
> +}
> +
> +static inline void emc_get_timing(struct tegra_emc *emc,
> +				  struct emc_table *timing)
> +{
> +	int i, div;
> +	u32 val;
> +	unsigned long rate;
> +
> +	for (i = 0; i < timing->num_burst; i++) {
> +		if (burst_regs_off[i])
> +			timing->burst_regs[i] = emc_readl(emc,
> +							  burst_regs_off[i]);
> +		else
> +			timing->burst_regs[i] = 0;
> +	}
> +
> +	for (i = 0; i < timing->num_burst_per_ch; i++)
> +		timing->burst_reg_per_ch[i] = emc_readl_per_ch(emc,
> +			burst_regs_per_ch_type[i], burst_regs_per_ch_off[i]);
> +
> +	for (i = 0; i < timing->num_trim; i++)
> +		timing->trim_regs[i] = emc_readl(emc, trim_regs_off[i]);
> +
> +	for (i = 0; i < timing->num_trim_per_ch; i++)
> +		timing->trim_perch_regs[i] = emc_readl_per_ch(emc,
> +			trim_regs_per_ch_type[i], trim_regs_per_ch_off[i]);
> +
> +	for (i = 0; i < timing->vref_num; i++)
> +		timing->vref_perch_regs[i] = emc_readl_per_ch(emc,
> +			vref_regs_per_ch_type[i], vref_regs_per_ch_off[i]);
> +
> +	for (i = 0; i < timing->num_mc_regs; i++)
> +		timing->burst_mc_regs[i] = mc_readl(emc->mc,
> +						    burst_mc_regs_off[i]);
> +
> +	for (i = 0; i < timing->num_up_down; i++)
> +		timing->la_scale_regs[i] = mc_readl(emc->mc,
> +						    la_scale_regs_off[i]);
> +
> +	val = tegra210_clk_emc_get_setting();
> +	rate = clk_get_rate(emc_src[emc_src_val(val)]);
> +	div = emc_div_val(val);
> +	div += 2;
> +	rate *= 2;
> +	rate += div - 1;
> +	do_div(rate, div);
> +	timing->rate = rate / 1000;
> +}
> +
> +static void __emc_copy_table_params(struct emc_table *src,
> +				    struct emc_table *dst, int flags)
> +{
> +	int i;
> +
> +	if (flags & EMC_COPY_TABLE_PARAM_PERIODIC_FIELDS) {
> +		dst->trained_dram_clktree_c0d0u0 =
> +			src->trained_dram_clktree_c0d0u0;
> +		dst->trained_dram_clktree_c0d0u1 =
> +			src->trained_dram_clktree_c0d0u1;
> +		dst->trained_dram_clktree_c0d1u0 =
> +			src->trained_dram_clktree_c0d1u0;
> +		dst->trained_dram_clktree_c0d1u1 =
> +			src->trained_dram_clktree_c0d1u1;
> +		dst->trained_dram_clktree_c1d0u0 =
> +			src->trained_dram_clktree_c1d0u0;
> +		dst->trained_dram_clktree_c1d0u1 =
> +			src->trained_dram_clktree_c1d0u1;
> +		dst->trained_dram_clktree_c1d1u0 =
> +			src->trained_dram_clktree_c1d1u0;
> +		dst->trained_dram_clktree_c1d1u1 =
> +			src->trained_dram_clktree_c1d1u1;
> +		dst->current_dram_clktree_c0d0u0 =
> +			src->current_dram_clktree_c0d0u0;
> +		dst->current_dram_clktree_c0d0u1 =
> +			src->current_dram_clktree_c0d0u1;
> +		dst->current_dram_clktree_c0d1u0 =
> +			src->current_dram_clktree_c0d1u0;
> +		dst->current_dram_clktree_c0d1u1 =
> +			src->current_dram_clktree_c0d1u1;
> +		dst->current_dram_clktree_c1d0u0 =
> +			src->current_dram_clktree_c1d0u0;
> +		dst->current_dram_clktree_c1d0u1 =
> +			src->current_dram_clktree_c1d0u1;
> +		dst->current_dram_clktree_c1d1u0 =
> +			src->current_dram_clktree_c1d1u0;
> +		dst->current_dram_clktree_c1d1u1 =
> +			src->current_dram_clktree_c1d1u1;
> +	}
> +
> +	if (flags & EMC_COPY_TABLE_PARAM_TRIM_REGS) {
> +		for (i = 0; i < src->num_trim_per_ch; i++)
> +			dst->trim_perch_regs[i] = src->trim_perch_regs[i];
> +
> +		for (i = 0; i < src->num_trim; i++)
> +			dst->trim_regs[i] = src->trim_regs[i];
> +
> +		for (i = 0; i < src->num_burst_per_ch; i++)
> +			dst->burst_reg_per_ch[i] = src->burst_reg_per_ch[i];
> +
> +		dst->trained = src->trained;
> +	}
> +}
> +
> +static void emc_copy_table_params(struct emc_table *src,
> +				  struct emc_table *dst,
> +				  int table_size,
> +				  int flags)
> +{
> +	int i;
> +
> +	for (i = 0; i < table_size; i++)
> +		__emc_copy_table_params(&src[i], &dst[i], flags);
> +}
> +
> +static void emc_last_stats_update(int last_sel)
> +{
> +	unsigned long flags;
> +	u64 cur_jiffies = get_jiffies_64();
> +
> +	spin_lock_irqsave(&emc_stats.spinlock, flags);
> +
> +	if (emc_stats.last_sel < TEGRA_EMC_TABLE_MAX_SIZE)
> +		emc_stats.time_at_clock[emc_stats.last_sel] =
> +			emc_stats.time_at_clock[emc_stats.last_sel]
> +			+ (cur_jiffies - emc_stats.last_update);
> +
> +	emc_stats.last_update = cur_jiffies;
> +
> +	if (last_sel < TEGRA_EMC_TABLE_MAX_SIZE) {
> +		emc_stats.clkchange_count++;
> +		emc_stats.last_sel = last_sel;
> +	}
> +
> +	spin_unlock_irqrestore(&emc_stats.spinlock, flags);
> +}
> +
> +static int emc_table_lookup(struct tegra_emc *emc, unsigned long rate)
> +{
> +	int i;
> +
> +	for (i = 0; i < emc->emc_table_size; i++) {
> +		if (emc_clk_sel[i].input == NULL)
> +			continue;
> +
> +		if (emc->emc_table[i].rate == rate)
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static struct clk *emc_predict_parent(struct tegra_emc *emc,
> +				      unsigned long rate)
> +{
> +	struct clk *old_parent, *new_parent;
> +	unsigned long parent_rate;
> +	int idx;
> +
> +	idx = emc_table_lookup(emc, rate / 1000);
> +	if (idx < 0)
> +		return ERR_PTR(-EINVAL);
> +
> +	parent_rate = emc_clk_sel[idx].input_rate * 1000;
> +	new_parent = emc_clk_sel[idx].input;
> +	old_parent = clk_get_parent(emc->emc_clk);
> +
> +	if (parent_rate == clk_get_rate(old_parent))
> +		return old_parent;
> +
> +	if (clk_is_match(new_parent, old_parent))
> +		new_parent = emc_clk_sel[idx].input_b;
> +
> +	if (parent_rate != clk_get_rate(new_parent))
> +		clk_set_rate(new_parent, parent_rate);
> +
> +	return new_parent;
> +}
> +
> +static int emc_set_rate(struct tegra_emc *emc, unsigned long rate)
> +{
> +	int i;
> +	unsigned long flags;
> +	s64 last_change_delay;
> +	struct clk *parent;
> +
> +	if (emc_suspend)
> +		rate = TEGRA210_EMC_SUSPEND_RATE;
> +
> +	if (rate == emc->current_timing->rate)
> +		return 0;
> +
> +	i = emc_table_lookup(emc, rate / 1000);
> +
> +	if (i < 0)
> +		return i;
> +
> +	if (rate > 204000000 && !emc->emc_table[i].trained)
> +		return -EINVAL;
> +
> +	parent = emc_predict_parent(emc, rate);
> +	if (clk_is_match(parent, emc_clk_sel[i].input))
> +		emc->clk_setting = emc_clk_sel[i].value;
> +	else
> +		emc->clk_setting = emc_clk_sel[i].value_b;
> +
> +	emc->next_timing = &emc->emc_table[i];
> +	last_change_delay = ktime_us_delta(ktime_get(), clkchange_time);
> +	if ((last_change_delay >= 0) && (last_change_delay < clkchange_delay))
> +		udelay(clkchange_delay - (int)last_change_delay);
> +
> +	spin_lock_irqsave(&emc_access_lock, flags);
> +	emc_set_clock(emc, emc->clk_setting);
> +	clkchange_time = ktime_get();
> +	emc->current_timing = &emc->emc_table[i];
> +	spin_unlock_irqrestore(&emc_access_lock, flags);
> +
> +	emc_last_stats_update(i);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_DEBUG_FS
> +static int emc_stats_show(struct seq_file *s, void *data)
> +{
> +	int i;
> +	struct tegra_emc *emc = (struct tegra_emc *)s->private;
> +
> +	if (!emc->emc_table_size || !seq)
> +		return 0;
> +
> +	emc_last_stats_update(TEGRA_EMC_TABLE_MAX_SIZE);
> +
> +	seq_printf(s, "%-10s %-10s\n", "rate kHz", "time");
> +	for (i = 0; i < emc->emc_table_size; i++) {
> +		if (emc_clk_sel[i].input == NULL)
> +			continue;
> +
> +		seq_printf(s, "%-10u %-10llu\n",
> +			   emc->emc_table[i].rate,
> +			   jiffies_64_to_clock_t(
> +			   emc_stats.time_at_clock[i]));
> +	}
> +	seq_printf(s, "%-15s %llu\n", "transitions:",
> +		   emc_stats.clkchange_count);
> +	seq_printf(s, "%-15s %llu\n", "time-stamp:",
> +		   jiffies_64_to_clock_t(emc_stats.last_update));
> +
> +	return 0;
> +}
> +
> +static int emc_stats_open(struct inode *inode, struct file *file)
> +{
> +	return single_open(file, emc_stats_show, inode->i_private);
> +}
> +
> +static const struct file_operations emc_stats_fops = {
> +	.open		= emc_stats_open,
> +	.read		= seq_read,
> +	.llseek		= seq_lseek,
> +	.release	= single_release,
> +};
> +
> +static int debug_emc_get_rate(void *data, u64 *val)
> +{
> +	struct clk *c = data;
> +
> +	*val = clk_get_rate(c);
> +
> +	return 0;
> +}
> +
> +static int debug_emc_set_rate(void *data, u64 val)
> +{
> +	struct clk *c = data;
> +
> +	return clk_set_rate(c, val);
> +}
> +DEFINE_SIMPLE_ATTRIBUTE(emc_rate_fops, debug_emc_get_rate,
> +			debug_emc_set_rate, "%llu\n");
> +
> +static int tegra_emc_debug_init(struct tegra_emc *emc)
> +{
> +	struct dentry *emc_debugfs_root;
> +
> +	emc_debugfs_root = debugfs_create_dir("tegra_emc", NULL);
> +	if (!emc_debugfs_root)
> +		return -ENOMEM;
> +
> +	if (!debugfs_create_file("stats", 0444, emc_debugfs_root, emc,
> +				 &emc_stats_fops))
> +		goto err_out;
> +
> +	if (!debugfs_create_file("rate", 0644, emc_debugfs_root, emc->emc_clk,
> +				 &emc_rate_fops))
> +		goto err_out;
> +
> +	return 0;
> +
> +err_out:
> +	debugfs_remove_recursive(emc_debugfs_root);
> +	return -ENOMEM;
> +}
> +#endif /* CONFIG_DEBUG_FS */

Every downstream EMC driver has this debugfs, but is it useful for upstream? I think it should be generalized and made re-usable by all of the EMC drivers if it has a real value, otherwise it could be dropped.

> +static u8 clk_emc_get_parent(struct clk_hw *hw)
> +{
> +	struct tegra_emc *emc = to_emc(hw);
> +
> +	if (!emc->clk_setting)
> +		emc->clk_setting = tegra210_clk_emc_get_setting();
> +
> +	return emc_src_val(emc->clk_setting);
> +}
> +
> +static unsigned long clk_emc_recalc_rate(struct clk_hw *hw,
> +					 unsigned long parent_rate)
> +{
> +	struct tegra_emc *emc = to_emc(hw);
> +
> +	if (!emc->emc_table_size || !seq) {
> +		u32 emc_setting = tegra210_clk_emc_get_setting();
> +
> +		return clk_get_rate(emc_src[emc_src_val(emc_setting)]);
> +	}
> +
> +	return emc->current_timing->rate * 1000;
> +}
> +
> +static long clk_emc_round_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long *prate)
> +{
> +	struct tegra_emc *emc = to_emc(hw);
> +	int i;
> +
> +	if (!emc->emc_table_size || !seq) {
> +		u32 emc_setting = tegra210_clk_emc_get_setting();
> +
> +		return clk_get_rate(emc_src[emc_src_val(emc_setting)]);
> +	}
> +
> +	if (emc_suspend)
> +		return TEGRA210_EMC_SUSPEND_RATE;
> +
> +	rate /= 1000;
> +
> +	for (i = 0; i < emc->emc_table_size; i++) {
> +		if (emc->emc_table[i].rate >= rate)
> +			return emc->emc_table[i].rate * 1000;
> +	}
> +
> +	return emc->emc_table[i - 1].rate * 1000;
> +}
> +
> +static int clk_emc_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long parent_rate)
> +{
> +	struct tegra_emc *emc = to_emc(hw);
> +	struct clk *old_parent, *new_parent;
> +	int ret = -EINVAL;
> +
> +	if (!emc->emc_table_size || !seq)
> +		return ret;
> +
> +	if (emc_suspend)
> +		rate = TEGRA210_EMC_SUSPEND_RATE;
> +
> +	old_parent = clk_get_parent(hw->clk);
> +	new_parent = emc_predict_parent(emc, rate);
> +	if (IS_ERR(new_parent))
> +		goto out;
> +
> +	if (!clk_is_match(new_parent, old_parent))
> +		clk_prepare_enable(new_parent);
> +
> +	ret = emc_set_rate(emc, rate);
> +	if (ret) {
> +		if (new_parent != old_parent)
> +			clk_disable_unprepare(new_parent);
> +		goto out;
> +	}
> +
> +	if (!clk_is_match(new_parent, old_parent)) {
> +		clk_hw_reparent(hw, __clk_get_hw(new_parent));
> +		clk_disable_unprepare(old_parent);
> +	}
> +
> +out:
> +	return ret;
> +}
> +
> +static const struct clk_ops tegra_clk_emc_ops = {
> +	.get_parent = clk_emc_get_parent,
> +	.recalc_rate = clk_emc_recalc_rate,
> +	.round_rate = clk_emc_round_rate,
> +	.set_rate = clk_emc_set_rate,
> +};

Couldn't the "best parent" be selected using the determine_rate() callback and then the rate and parent be applied using set_rate_and_parent(), replacing the set_rate()? It looks like you're re-implementing something that CLK framework already has support for.

> +
> +static int find_matching_input(struct emc_table *table, struct emc_sel *sel)
> +{
> +	u32 div_value;
> +	u32 src_value;
> +	unsigned long input_rate = 0;
> +	struct clk *input_clk;
> +
> +	div_value = emc_div_val(table->clk_src_emc);
> +	src_value = emc_src_val(table->clk_src_emc);
> +
> +	if (div_value & 0x1) {
> +		pr_warn("Tegra EMC: invalid odd divider for EMC rate %u\n",
> +			table->rate);
> +		return -EINVAL;
> +	}
> +
> +	if (!(table->clk_src_emc & EMC_CLK_MC_EMC_SAME_FREQ) !=
> +	    !(MC_EMEM_ARB_MISC0_EMC_SAME_FREQ &
> +	    table->burst_regs[MC_EMEM_ARB_MISC0_INDEX])) {
> +		pr_warn("Tegra EMC: ambiguous EMC to MC ratio for rate %u\n",
> +			table->rate);
> +		return -EINVAL;
> +	}
> +
> +	input_clk = emc_src[src_value];
> +	if (input_clk == emc_src[TEGRA_EMC_SRC_PLLM]
> +		|| input_clk == emc_src[TEGRA_EMC_SRC_PLLM_UD]) {
> +		input_rate = table->rate * (1 + div_value / 2);
> +	} else {
> +		input_rate = clk_get_rate(input_clk) / 1000;
> +		if (input_rate != (table->rate * (1 + div_value / 2))) {
> +			pr_warn("Tegra EMC: rate %u doesn't match input\n",
> +				table->rate);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	sel->input = input_clk;
> +	sel->input_rate = input_rate;
> +	sel->value = table->clk_src_emc;
> +	sel->input_b = input_clk;
> +	sel->input_rate_b = input_rate;
> +	sel->value_b = table->clk_src_emc;
> +
> +	if (input_clk == emc_src[TEGRA_EMC_SRC_PLLM]) {
> +		sel->input_b = emc_src[TEGRA_EMC_SRC_PLLMB];
> +		sel->value_b = table->clk_src_emc &
> +			       ~EMC_CLK_EMC_2X_CLK_SRC_MASK;
> +		sel->value_b |= TEGRA_EMC_SRC_PLLMB <<
> +				EMC_CLK_EMC_2X_CLK_SRC_SHIFT;
> +	}
> +
> +	if (input_clk == emc_src[TEGRA_EMC_SRC_PLLM_UD]) {
> +		sel->input_b = emc_src[TEGRA_EMC_SRC_PLLMB_UD];
> +		sel->value_b = table->clk_src_emc &
> +			       ~EMC_CLK_EMC_2X_CLK_SRC_MASK;
> +		sel->value_b |= TEGRA_EMC_SRC_PLLMB_UD <<
> +				EMC_CLK_EMC_2X_CLK_SRC_SHIFT;
> +	}
> +
> +	return 0;
> +}
> +
> +static int tegra210_emc_probe(struct platform_device *pdev)
> +{
> +	int i, div;
> +	unsigned long table_rate;
> +	unsigned long current_rate;
> +	struct device_node *np;
> +	struct platform_device *mc;
> +	struct tegra_emc *emc;
> +	struct clk_init_data init;
> +	struct clk *clk;
> +	struct resource *r;
> +	u32 emc_setting;
> +
> +	emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL);
> +	if (!emc)
> +		return -ENOMEM;
> +
> +	np = of_parse_phandle(pdev->dev.of_node, "nvidia,memory-controller", 0);
> +	if (!np) {
> +		dev_err(&pdev->dev, "could not get memory controller\n");
> +		return -ENOENT;
> +	}
> +
> +	mc = of_find_device_by_node(np);
> +	of_node_put(np);
> +	if (!mc)
> +		return -ENOENT;
> +
> +	emc->mc = platform_get_drvdata(mc);
> +	if (!emc->mc)
> +		return -EPROBE_DEFER;
> +
> +	emc->ram_code = tegra_read_ram_code();
> +	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	emc->emc_base = devm_ioremap_resource(&pdev->dev, r);
> +	r = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +	emc->emc0_base = devm_ioremap_resource(&pdev->dev, r);
> +	r = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> +	emc->emc1_base = devm_ioremap_resource(&pdev->dev, r);
> +
> +	for (i = 0; i < TEGRA_EMC_SRC_COUNT; i++) {
> +		emc_src[i] = devm_clk_get(&pdev->dev,
> +						emc_src_names[i]);
> +		if (IS_ERR(emc_src[i])) {
> +			dev_err(&pdev->dev, "Can not find EMC source clock\n");
> +			return -ENODATA;
> +		}
> +	}
> +
> +	/* Init EMC rate statistic data */
> +	emc_stats.clkchange_count = 0;
> +	spin_lock_init(&emc_stats.spinlock);
> +	emc_stats.last_update = get_jiffies_64();
> +	emc_stats.last_sel = TEGRA_EMC_TABLE_MAX_SIZE;
> +
> +	emc->dram_type = (emc_readl(emc, EMC_FBIO_CFG5) &
> +			  EMC_FBIO_CFG5_DRAM_TYPE_MASK) >>
> +			  EMC_FBIO_CFG5_DRAM_TYPE_SHIFT;
> +	if (emc->dram_type != DRAM_TYPE_DDR3 &&
> +	    emc->dram_type != DRAM_TYPE_LPDDR2 &&
> +	    emc->dram_type != DRAM_TYPE_LPDDR4) {
> +		dev_err(&pdev->dev, "DRAM not supported\n");
> +		return -ENODATA;
> +	}
> +
> +	emc->dram_dev_num = tegra_mc_get_emem_device_count(emc->mc);
> +
> +	tegra_emc_dt_parse_pdata(pdev, &emc->emc_table_normal,
> +				 &emc->emc_table_derated,
> +				 &emc->emc_table_size);
> +	if (!emc->emc_table_size ||
> +	    emc->emc_table_size > TEGRA_EMC_TABLE_MAX_SIZE) {
> +		dev_err(&pdev->dev, "Invalid table size %d\n",
> +			emc->emc_table_size);
> +		goto emc_clk_register;

Why do you want to continue and not to error out?

> +	}
> +	emc->emc_table = emc->emc_table_normal;
> +
> +	/*
> +	 * Copy trained trimmers from the normal table to the derated
> +	 * table for LP4. Bootloader trains only the normal table.
> +	 * Trimmers are the same for derated and normal tables.
> +	 */
> +	if (emc->emc_table_derated && emc->dram_type == DRAM_TYPE_LPDDR4)
> +		emc_copy_table_params(emc->emc_table_normal,
> +				      emc->emc_table_derated,
> +				      emc->emc_table_size,
> +				      EMC_COPY_TABLE_PARAM_PERIODIC_FIELDS |
> +				      EMC_COPY_TABLE_PARAM_TRIM_REGS);
> +
> +	seq = supported_seqs;
> +	while (seq->table_rev) {
> +		if (seq->table_rev == emc->emc_table[0].rev)
> +			break;
> +		seq++;
> +	}
> +	if (!seq->set_clock) {
> +		seq = NULL;
> +		dev_err(&pdev->dev, "Invalid EMC sequence for table Rev. %d\n",
> +			emc->emc_table[0].rev);
> +		goto emc_clk_register;

Same as the above.

> +	}
> +
> +	emc_clk_sel = devm_kcalloc(&pdev->dev,
> +				   emc->emc_table_size,
> +				   sizeof(struct emc_sel),
> +				   GFP_KERNEL);
> +	if (!emc_clk_sel) {
> +		dev_err(&pdev->dev, "Memory allocation failed\n");
> +		return -ENOMEM;
> +	}
> +
> +	/* calculate the rate from source clock */
> +	emc_setting = tegra210_clk_emc_get_setting();
> +	current_rate = clk_get_rate(emc_src[emc_src_val(emc_setting)]);
> +	div = emc_div_val(emc_setting);
> +	div += 2;
> +	current_rate *= 2;
> +	current_rate += div - 1;
> +	do_div(current_rate, div);
> +	current_rate /=  1000;

The same is done in emc_get_timing(), hence it probably worth to factor out this hunk into a function.

> +
> +	for (i = 0; i < emc->emc_table_size; i++) {
> +		table_rate = emc->emc_table[i].rate;
> +		if (!table_rate)
> +			continue;
> +
> +		if (i && ((table_rate <= emc->emc_table[i-1].rate) ||
> +		   (emc->emc_table[i].min_volt <
> +		    emc->emc_table[i-1].min_volt)))
> +			continue;
> +
> +		if (emc->emc_table[i].rev != emc->emc_table[0].rev)
> +			continue;
> +
> +		if (find_matching_input(&emc->emc_table[i], &emc_clk_sel[i]))
> +			continue;
> +
> +		if (table_rate == current_rate)
> +			emc_stats.last_sel = i;
> +	}
> +
> +	dev_info(&pdev->dev, "validated EMC DFS table\n");

It is not possible to fail the validation, hence remove this message?

> +	/* Update the start_timing base on the settings from firmware */
> +	emc->start_timing.num_burst = emc->emc_table[0].num_burst;
> +	emc->start_timing.num_burst_per_ch =
> +		emc->emc_table[0].num_burst_per_ch;
> +	emc->start_timing.num_trim = emc->emc_table[0].num_trim;
> +	emc->start_timing.num_trim_per_ch =
> +		emc->emc_table[0].num_trim_per_ch;
> +	emc->start_timing.num_mc_regs = emc->emc_table[0].num_mc_regs;
> +	emc->start_timing.num_up_down = emc->emc_table[0].num_up_down;
> +	emc->start_timing.vref_num = emc->emc_table[0].vref_num;
> +
> +	emc_get_timing(emc, &emc->start_timing);
> +	emc->current_timing = &emc->start_timing;
> +	emc->clk_setting = emc_setting;
> +
> +emc_clk_register:
> +	init.name = "emc";
> +	init.ops = &tegra_clk_emc_ops;
> +	init.flags = CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE;
> +	init.parent_names = emc_src_names;
> +	init.num_parents = ARRAY_SIZE(emc_src_names);
> +	emc->hw.init = &init;
> +
> +	clk = clk_register(&pdev->dev, &emc->hw);
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);
> +	emc->emc_clk = clk;
> +	emc->dev = &pdev->dev;
> +	tegra_emc = emc;
> +	dev_set_drvdata(emc->dev, emc);
> +
> +	if (emc->emc_table_size && seq) {
> +		for (i = 0; i < emc->emc_table_size; i++) {
> +			table_rate = emc->emc_table[i].rate * 1000;
> +			if (clk_set_rate(clk, table_rate))
> +				dev_info(&pdev->dev,
> +					 "rate: %lu validation fail\n",
> +					 table_rate);

dev_err()? Do you really want to continue in a error case? Is it realistic that clk_set_rate() may suddenly fail, maybe remove this "validation" if not..

> +
> +			dev_info(&pdev->dev, "rate: %lu validation success\n",
> +				 table_rate);
> +		}
> +	}
> +
> +	if (IS_ENABLED(CONFIG_DEBUG_FS))
> +		tegra_emc_debug_init(emc);

Apparently compilation will fail if CONFIG_DEBUG_FS is disabled because tegra_emc_debug_init() will be undefined. Seems you should remove #ifdef CONFIG_DEBUG_FS and just allow tegra_emc_debug_init() to fail when CONFIG_DEBUG_FS=n and hence the condition "if (IS_ENABLED(CONFIG_DEBUG_FS))" won't be needed too.

> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int tegra210_emc_suspend(struct device *dev)
> +{
> +	struct tegra_emc *emc = dev_get_drvdata(dev);
> +
> +	if (!IS_ERR(emc->emc_clk)) {
> +		emc_suspend = true;
> +		emc_resume_rate = clk_get_rate(emc->emc_clk);
> +		clk_set_rate(emc->emc_clk, TEGRA210_EMC_SUSPEND_RATE);
> +
> +		pr_debug("%s at rate %lu\n", __func__,
> +			 clk_get_rate(emc->emc_clk));
> +	}
> +
> +	return 0;
> +}
> +
> +static int tegra210_emc_resume(struct device *dev)
> +{
> +	struct tegra_emc *emc = dev_get_drvdata(dev);
> +
> +	if (!IS_ERR(emc->emc_clk)) {

When emc->emc_clk may become a PTR_ERR?

> +		emc_suspend = false;
> +		clk_set_rate(emc->emc_clk, emc_resume_rate);
> +
> +		pr_debug("%s at rate %lu\n", __func__,
> +			 clk_get_rate(emc->emc_clk));
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops tegra210_emc_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(tegra210_emc_suspend, tegra210_emc_resume)
> +};
> +#endif
> +
> +static const struct of_device_id tegra210_emc_of_match[] = {
> +	{ .compatible = "nvidia,tegra210-emc", },
> +	{ },
> +};
> +
> +static struct platform_driver tegra210_emc_driver = {
> +	.driver	= {
> +		.name = "tegra210-emc",
> +		.of_match_table = tegra210_emc_of_match,
> +		.pm = &tegra210_emc_pm_ops,

Compilation will fail with CONFIG_PM_SLEEP=n, just remove the "#ifdef CONFIG_PM_SLEEP".

.suppress_bind_attrs = true,

> +	},
> +	.probe = tegra210_emc_probe,
> +};
> +
> +static int __init tegra210_emc_init(void)
> +{
> +	return platform_driver_register(&tegra210_emc_driver);
> +}
> +subsys_initcall(tegra210_emc_init);
> 


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-03-25  7:45 ` [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings Joseph Lo
  2019-03-31  6:41   ` Rob Herring
  2019-04-01 12:12   ` Dmitry Osipenko
@ 2019-04-04  9:17   ` Dmitry Osipenko
  2019-04-04  9:30     ` Dmitry Osipenko
  2019-04-08  8:49     ` Joseph Lo
  2 siblings, 2 replies; 28+ messages in thread
From: Dmitry Osipenko @ 2019-04-04  9:17 UTC (permalink / raw)
  To: Joseph Lo, Thierry Reding, Peter De Schrijver, Jonathan Hunter,
	Rob Herring, Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree

25.03.2019 10:45, Joseph Lo пишет:
> Add the binding document for the external memory controller (EMC) which
> communicates with external LPDDR4 devices. It includes the bindings of
> the EMC node and the EMC table of different rates.
> 
> To support high rates for LPDDR4, the EMC table must be trained before
> it can be used for runtime clock switching. It has been done by firmware
> and merged to the table that Linux kernel uses. For backward
> compatibility with the devices that had been launched on the market, like
> Shield and Jetson platforms, the bindings in the EMC table should remain
> the same. So the firmware can recognize them and merge the trained EMC
> table for the kernel.
> 
> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
> 
> Signed-off-by: Joseph Lo <josephl@nvidia.com>
> ---
>  .../nvidia,tegra210-emc.txt                   | 605 ++++++++++++++++++
>  1 file changed, 605 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
> 
> diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
> new file mode 100644
> index 000000000000..1f6b6df6d37b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
> @@ -0,0 +1,605 @@
> +NVIDIA Tegra210 SoC EMC (external memory controller)
> +====================================================
> +
> +Required properties :
> +- compatible : should be "nvidia,tegra21-emc", "nvidia,tegra210-emc".
> +- reg : physical base address and length of the controller's registers.
> +- clocks : phandles of the possible source clocks
> +- clock-names : names of the possible source clocks
> +- #address-cells : should be 1
> +- #size-cells : should be 0
> +- nvidia,memory-controller : phandle of the memory controller.
> +- nvidia,use-ram-code : boolean, indicates whether we should use RAM_CODE in
> +		        the register to find matching emc-table nodes
> +

The "interrupts" property is missing. You could use the CLK handshake event to wait for the clock rate change completion instead of polling the register if you didn't rely on the downstream binding, see T20 driver for the example. BTW, I'm wondering if you're going to push other downstream bindings to upstream.. apparently EMC won't be the only binding that that could diverge from the upstream and then it's not obvious whether the locked-down variant of T210 is supportable by upstream at all.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-04-04  9:17   ` Dmitry Osipenko
@ 2019-04-04  9:30     ` Dmitry Osipenko
  2019-04-08  8:49     ` Joseph Lo
  1 sibling, 0 replies; 28+ messages in thread
From: Dmitry Osipenko @ 2019-04-04  9:30 UTC (permalink / raw)
  To: Joseph Lo, Thierry Reding, Peter De Schrijver, Jonathan Hunter,
	Rob Herring, Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree

04.04.2019 12:17, Dmitry Osipenko пишет:
> 25.03.2019 10:45, Joseph Lo пишет:
>> Add the binding document for the external memory controller (EMC) which
>> communicates with external LPDDR4 devices. It includes the bindings of
>> the EMC node and the EMC table of different rates.
>>
>> To support high rates for LPDDR4, the EMC table must be trained before
>> it can be used for runtime clock switching. It has been done by firmware
>> and merged to the table that Linux kernel uses. For backward
>> compatibility with the devices that had been launched on the market, like
>> Shield and Jetson platforms, the bindings in the EMC table should remain
>> the same. So the firmware can recognize them and merge the trained EMC
>> table for the kernel.
>>
>> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
>>
>> Signed-off-by: Joseph Lo <josephl@nvidia.com>
>> ---
>>  .../nvidia,tegra210-emc.txt                   | 605 ++++++++++++++++++
>>  1 file changed, 605 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>>
>> diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>> new file mode 100644
>> index 000000000000..1f6b6df6d37b
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>> @@ -0,0 +1,605 @@
>> +NVIDIA Tegra210 SoC EMC (external memory controller)
>> +====================================================
>> +
>> +Required properties :
>> +- compatible : should be "nvidia,tegra21-emc", "nvidia,tegra210-emc".
>> +- reg : physical base address and length of the controller's registers.
>> +- clocks : phandles of the possible source clocks
>> +- clock-names : names of the possible source clocks
>> +- #address-cells : should be 1
>> +- #size-cells : should be 0
>> +- nvidia,memory-controller : phandle of the memory controller.
>> +- nvidia,use-ram-code : boolean, indicates whether we should use RAM_CODE in
>> +		        the register to find matching emc-table nodes
>> +
> 
> The "interrupts" property is missing. You could use the CLK handshake event to wait for the clock rate change completion instead of polling the register if you didn't rely on the downstream binding, see T20 driver for the example. BTW, I'm wondering if you're going to push other downstream bindings to upstream.. apparently EMC won't be the only binding that that could diverge from the upstream and then it's not obvious whether the locked-down variant of T210 is supportable by upstream at all.
> 

One solution could be to use a multi-stage bootloader, where the last stage is an opensource part that will take the downstream binding and convert it into a proper one.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 2/8] clk: tegra: clock changes for emc scaling support on Tegra210
  2019-04-03  9:22   ` Thierry Reding
@ 2019-04-08  7:52     ` Joseph Lo
  2019-04-08  9:15     ` Peter De Schrijver
  1 sibling, 0 replies; 28+ messages in thread
From: Joseph Lo @ 2019-04-08  7:52 UTC (permalink / raw)
  To: Thierry Reding
  Cc: Peter De Schrijver, Jonathan Hunter, Rob Herring, Stephen Boyd,
	linux-arm-kernel, linux-tegra, linux-clk, devicetree

On 4/3/19 5:22 PM, Thierry Reding wrote:
> On Mon, Mar 25, 2019 at 03:45:17PM +0800, Joseph Lo wrote:
>> 1) Introduce low jitter paths for pllp and pll_mb used by the EMC driver.
>> 2) Remove the old emc_mux clock and don't use the common EMC clock
>>     definition. This will be replaced by a new clock defined in the EMC
>>     driver.
>> 3) Export functions to allow accessing the CAR register required for EMC
>>     clock scaling. These functions will be used to access the CAR register
>>     as part of the scaling sequence.
> 
> The fact that you can enumerate 3 logical changes made by this commit
> indicates that it should be split up into smaller patches.

Okay, will do.

> 
>> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
>>
>> Signed-off-by: Joseph Lo <josephl@nvidia.com>
>> ---
>>   drivers/clk/tegra/clk-tegra210.c         | 112 +++++++++++++++++++----
>>   include/dt-bindings/clock/tegra210-car.h |   4 +-
>>   include/linux/clk/tegra.h                |   5 +
>>   3 files changed, 103 insertions(+), 18 deletions(-)
>>
>> diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c
>> index 7545af763d7a..e17b5279ea69 100644
>> --- a/drivers/clk/tegra/clk-tegra210.c
>> +++ b/drivers/clk/tegra/clk-tegra210.c
>> @@ -47,6 +47,7 @@
>>   #define CLK_SOURCE_LA 0x1f8
>>   #define CLK_SOURCE_SDMMC2 0x154
>>   #define CLK_SOURCE_SDMMC4 0x164
>> +#define CLK_SOURCE_EMC_DLL 0x664
>>   
>>   #define PLLC_BASE 0x80
>>   #define PLLC_OUT 0x84
>> @@ -234,6 +235,10 @@
>>   #define RST_DFLL_DVCO 0x2f4
>>   #define DVFS_DFLL_RESET_SHIFT 0
>>   
>> +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET	0x284
>> +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR	0x288
>> +#define CLK_OUT_ENB_X_CLK_ENB_EMC_DLL		BIT(14)
>> +
>>   #define CLK_RST_CONTROLLER_RST_DEV_Y_SET 0x2a8
>>   #define CLK_RST_CONTROLLER_RST_DEV_Y_CLR 0x2ac
>>   
>> @@ -319,12 +324,6 @@ static unsigned long tegra210_input_freq[] = {
>>   	[8] = 12000000,
>>   };
>>   
>> -static const char *mux_pllmcp_clkm[] = {
>> -	"pll_m", "pll_c", "pll_p", "clk_m", "pll_m_ud", "pll_mb", "pll_mb",
>> -	"pll_p",
>> -};
>> -#define mux_pllmcp_clkm_idx NULL
>> -
>>   #define PLL_ENABLE			(1 << 30)
>>   
>>   #define PLLCX_MISC1_IDDQ		(1 << 27)
>> @@ -2310,7 +2309,7 @@ static struct tegra_clk tegra210_clks[tegra_clk_max] __initdata = {
>>   	[tegra_clk_i2c2] = { .dt_id = TEGRA210_CLK_I2C2, .present = true },
>>   	[tegra_clk_uartc_8] = { .dt_id = TEGRA210_CLK_UARTC, .present = true },
>>   	[tegra_clk_mipi_cal] = { .dt_id = TEGRA210_CLK_MIPI_CAL, .present = true },
>> -	[tegra_clk_emc] = { .dt_id = TEGRA210_CLK_EMC, .present = true },
>> +	[tegra_clk_emc] = { .dt_id = TEGRA210_CLK_EMC, .present = false },
>>   	[tegra_clk_usb2] = { .dt_id = TEGRA210_CLK_USB2, .present = true },
>>   	[tegra_clk_bsev] = { .dt_id = TEGRA210_CLK_BSEV, .present = true },
>>   	[tegra_clk_uartd_8] = { .dt_id = TEGRA210_CLK_UARTD, .present = true },
>> @@ -2921,6 +2920,82 @@ static int tegra210_init_pllu(void)
>>   	return 0;
>>   }
>>   
>> +void tegra210_clk_emc_dll_enable(bool flag)
>> +{
>> +	unsigned long flags = 0;
>> +	u32 offset = flag ? CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET :
>> +		     CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR;
>> +
>> +	spin_lock_irqsave(&emc_lock, flags);
>> +
>> +	writel_relaxed(CLK_OUT_ENB_X_CLK_ENB_EMC_DLL, clk_base + offset);
>> +	readl(clk_base + offset);
>> +
>> +	spin_unlock_irqrestore(&emc_lock, flags);
>> +}
>> +EXPORT_SYMBOL_GPL(tegra210_clk_emc_dll_enable);
>> +
>> +void tegra210_clk_emc_dll_update_setting(u32 emc_dll_src_value)
> 
> Do we really want to pass the whole register value through this
> function? The register has three fields, so perhaps it's safer to pass
> the fields individually? Or perhaps we only need to modify a subset of
> the fields and can reduce the number of parameters we pass? Letting a
> different driver pass any arbitrary value here takes away any means of
> checking for validity.

The emc table has a property for this register. So the scaling sequence 
will update the register with the value in the table.

> 
>> +{
>> +	unsigned long flags = 0;
>> +
>> +	spin_lock_irqsave(&emc_lock, flags);
>> +
>> +	writel_relaxed(emc_dll_src_value, clk_base + CLK_SOURCE_EMC_DLL);
>> +	readl(clk_base + CLK_SOURCE_EMC_DLL);
> 
> Could we not just use a writel() here and do away with the flushing
> readl()?
Will try that.

> 
> Also, it doesn't look like that spinlock actually protects anything.
> You're just writing a value. If anyone else is holding that lock they
> will either overwrite our value after we release the lock, or we
> overwrite their value when they release the lock.

Yeah, agreed. Will remove.

Thanks,
Joseph

> 
>> +
>> +	spin_unlock_irqrestore(&emc_lock, flags);
>> +}
>> +EXPORT_SYMBOL_GPL(tegra210_clk_emc_dll_update_setting);
>> +
>> +void tegra210_clk_emc_update_setting(u32 emc_src_value)
>> +{
>> +	unsigned long flags = 0;
>> +
>> +	spin_lock_irqsave(&emc_lock, flags);
>> +
>> +	writel_relaxed(emc_src_value, clk_base + CLK_SOURCE_EMC);
>> +	readl(clk_base + CLK_SOURCE_EMC);
>> +
>> +	spin_unlock_irqrestore(&emc_lock, flags);
>> +}
>> +EXPORT_SYMBOL_GPL(tegra210_clk_emc_update_setting);
> 
> Same comments as above.
> 
>> +
>> +u32 tegra210_clk_emc_get_setting(void)
>> +{
>> +	unsigned long flags = 0;
>> +	u32 val;
>> +
>> +	spin_lock_irqsave(&emc_lock, flags);
>> +
>> +	val = readl_relaxed(clk_base + CLK_SOURCE_EMC);
>> +
>> +	spin_unlock_irqrestore(&emc_lock, flags);
> 
> Similar to the above, the spinlock doesn't protect anything here.
> 
> Thierry
> 

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-04-04  9:17   ` Dmitry Osipenko
  2019-04-04  9:30     ` Dmitry Osipenko
@ 2019-04-08  8:49     ` Joseph Lo
  1 sibling, 0 replies; 28+ messages in thread
From: Joseph Lo @ 2019-04-08  8:49 UTC (permalink / raw)
  To: Dmitry Osipenko, Thierry Reding, Peter De Schrijver,
	Jonathan Hunter, Rob Herring, Stephen Boyd
  Cc: linux-arm-kernel, linux-tegra, linux-clk, devicetree

On 4/4/19 5:17 PM, Dmitry Osipenko wrote:
> 25.03.2019 10:45, Joseph Lo пишет:
>> Add the binding document for the external memory controller (EMC) which
>> communicates with external LPDDR4 devices. It includes the bindings of
>> the EMC node and the EMC table of different rates.
>>
>> To support high rates for LPDDR4, the EMC table must be trained before
>> it can be used for runtime clock switching. It has been done by firmware
>> and merged to the table that Linux kernel uses. For backward
>> compatibility with the devices that had been launched on the market, like
>> Shield and Jetson platforms, the bindings in the EMC table should remain
>> the same. So the firmware can recognize them and merge the trained EMC
>> table for the kernel.
>>
>> Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
>>
>> Signed-off-by: Joseph Lo <josephl@nvidia.com>
>> ---
>>   .../nvidia,tegra210-emc.txt                   | 605 ++++++++++++++++++
>>   1 file changed, 605 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>>
>> diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>> new file mode 100644
>> index 000000000000..1f6b6df6d37b
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra210-emc.txt
>> @@ -0,0 +1,605 @@
>> +NVIDIA Tegra210 SoC EMC (external memory controller)
>> +====================================================
>> +
>> +Required properties :
>> +- compatible : should be "nvidia,tegra21-emc", "nvidia,tegra210-emc".
>> +- reg : physical base address and length of the controller's registers.
>> +- clocks : phandles of the possible source clocks
>> +- clock-names : names of the possible source clocks
>> +- #address-cells : should be 1
>> +- #size-cells : should be 0
>> +- nvidia,memory-controller : phandle of the memory controller.
>> +- nvidia,use-ram-code : boolean, indicates whether we should use RAM_CODE in
>> +		        the register to find matching emc-table nodes
>> +
> 
> The "interrupts" property is missing. You could use the CLK handshake event to wait for the clock rate change completion instead of polling the register if you didn't rely on the downstream binding, see T20 driver for the example. BTW, I'm wondering if you're going to push other downstream bindings to upstream.. apparently EMC won't be the only binding that that could diverge from the upstream and then it's not obvious whether the locked-down variant of T210 is supportable by upstream at all.
> 
Will add "interrupts" property. And yes by default, we don't use that 
for Tegra210. Because it's in the middle of the scaling sequence for 
checking the clock source change complete.

Thanks,
Joseph

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 2/8] clk: tegra: clock changes for emc scaling support on Tegra210
  2019-04-03  9:22   ` Thierry Reding
  2019-04-08  7:52     ` Joseph Lo
@ 2019-04-08  9:15     ` Peter De Schrijver
  1 sibling, 0 replies; 28+ messages in thread
From: Peter De Schrijver @ 2019-04-08  9:15 UTC (permalink / raw)
  To: Thierry Reding
  Cc: Joseph Lo, Jonathan Hunter, Rob Herring, Stephen Boyd,
	linux-arm-kernel, linux-tegra, linux-clk, devicetree

On Wed, Apr 03, 2019 at 11:22:50AM +0200, Thierry Reding wrote:
> On Mon, Mar 25, 2019 at 03:45:17PM +0800, Joseph Lo wrote:
> > 1) Introduce low jitter paths for pllp and pll_mb used by the EMC driver.
> > 2) Remove the old emc_mux clock and don't use the common EMC clock
> >    definition. This will be replaced by a new clock defined in the EMC
> >    driver.
> > 3) Export functions to allow accessing the CAR register required for EMC
> >    clock scaling. These functions will be used to access the CAR register
> >    as part of the scaling sequence.
> 
> The fact that you can enumerate 3 logical changes made by this commit
> indicates that it should be split up into smaller patches.
> 
> > Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
> > 
> > Signed-off-by: Joseph Lo <josephl@nvidia.com>
> > ---
> >  drivers/clk/tegra/clk-tegra210.c         | 112 +++++++++++++++++++----
> >  include/dt-bindings/clock/tegra210-car.h |   4 +-
> >  include/linux/clk/tegra.h                |   5 +
> >  3 files changed, 103 insertions(+), 18 deletions(-)
> > 
> > diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c
> > index 7545af763d7a..e17b5279ea69 100644
> > --- a/drivers/clk/tegra/clk-tegra210.c
> > +++ b/drivers/clk/tegra/clk-tegra210.c
> > @@ -47,6 +47,7 @@
> >  #define CLK_SOURCE_LA 0x1f8
> >  #define CLK_SOURCE_SDMMC2 0x154
> >  #define CLK_SOURCE_SDMMC4 0x164
> > +#define CLK_SOURCE_EMC_DLL 0x664
> >  
> >  #define PLLC_BASE 0x80
> >  #define PLLC_OUT 0x84
> > @@ -234,6 +235,10 @@
> >  #define RST_DFLL_DVCO 0x2f4
> >  #define DVFS_DFLL_RESET_SHIFT 0
> >  
> > +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET	0x284
> > +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR	0x288
> > +#define CLK_OUT_ENB_X_CLK_ENB_EMC_DLL		BIT(14)
> > +
> >  #define CLK_RST_CONTROLLER_RST_DEV_Y_SET 0x2a8
> >  #define CLK_RST_CONTROLLER_RST_DEV_Y_CLR 0x2ac
> >  
> > @@ -319,12 +324,6 @@ static unsigned long tegra210_input_freq[] = {
> >  	[8] = 12000000,
> >  };
> >  
> > -static const char *mux_pllmcp_clkm[] = {
> > -	"pll_m", "pll_c", "pll_p", "clk_m", "pll_m_ud", "pll_mb", "pll_mb",
> > -	"pll_p",
> > -};
> > -#define mux_pllmcp_clkm_idx NULL
> > -
> >  #define PLL_ENABLE			(1 << 30)
> >  
> >  #define PLLCX_MISC1_IDDQ		(1 << 27)
> > @@ -2310,7 +2309,7 @@ static struct tegra_clk tegra210_clks[tegra_clk_max] __initdata = {
> >  	[tegra_clk_i2c2] = { .dt_id = TEGRA210_CLK_I2C2, .present = true },
> >  	[tegra_clk_uartc_8] = { .dt_id = TEGRA210_CLK_UARTC, .present = true },
> >  	[tegra_clk_mipi_cal] = { .dt_id = TEGRA210_CLK_MIPI_CAL, .present = true },
> > -	[tegra_clk_emc] = { .dt_id = TEGRA210_CLK_EMC, .present = true },
> > +	[tegra_clk_emc] = { .dt_id = TEGRA210_CLK_EMC, .present = false },
> >  	[tegra_clk_usb2] = { .dt_id = TEGRA210_CLK_USB2, .present = true },
> >  	[tegra_clk_bsev] = { .dt_id = TEGRA210_CLK_BSEV, .present = true },
> >  	[tegra_clk_uartd_8] = { .dt_id = TEGRA210_CLK_UARTD, .present = true },
> > @@ -2921,6 +2920,82 @@ static int tegra210_init_pllu(void)
> >  	return 0;
> >  }
> >  
> > +void tegra210_clk_emc_dll_enable(bool flag)
> > +{
> > +	unsigned long flags = 0;
> > +	u32 offset = flag ? CLK_RST_CONTROLLER_CLK_OUT_ENB_X_SET :
> > +		     CLK_RST_CONTROLLER_CLK_OUT_ENB_X_CLR;
> > +
> > +	spin_lock_irqsave(&emc_lock, flags);
> > +
> > +	writel_relaxed(CLK_OUT_ENB_X_CLK_ENB_EMC_DLL, clk_base + offset);
> > +	readl(clk_base + offset);
> > +
> > +	spin_unlock_irqrestore(&emc_lock, flags);
> > +}
> > +EXPORT_SYMBOL_GPL(tegra210_clk_emc_dll_enable);
> > +
> > +void tegra210_clk_emc_dll_update_setting(u32 emc_dll_src_value)
> 
> Do we really want to pass the whole register value through this
> function? The register has three fields, so perhaps it's safer to pass
> the fields individually? Or perhaps we only need to modify a subset of
> the fields and can reduce the number of parameters we pass? Letting a
> different driver pass any arbitrary value here takes away any means of
> checking for validity.
> 

The EMC scaling driver needs to modify all 3 fields. If the DDLL_CLK_SEL
field would be fixed, this function could be left out and CCF be used
instead.

Peter.

^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 3/8] memory: tegra: Add Tegra210 EMC clock driver
  2019-04-03 11:34   ` Thierry Reding
@ 2019-04-08  9:25     ` Peter De Schrijver
  0 siblings, 0 replies; 28+ messages in thread
From: Peter De Schrijver @ 2019-04-08  9:25 UTC (permalink / raw)
  To: Thierry Reding
  Cc: Joseph Lo, devicetree, Stephen Boyd, Jonathan Hunter,
	Rob Herring, linux-tegra, linux-clk, linux-arm-kernel

On Wed, Apr 03, 2019 at 01:34:26PM +0200, Thierry Reding wrote:
> On Mon, Mar 25, 2019 at 03:45:18PM +0800, Joseph Lo wrote:
> > This is the initial patch for Tegra210 EMC clock driver, which doesn't
> > include the support code and detail sequence for clock scaling yet.
> > 
> > The driver is designed to support LPDDR4 SDRAMs. Because of the LPDDR4
> > devices need to do initial time training before it can be used, the
> > firmware will help to do that at early boot stage. The trained table for
> > the rates that we will support in the kernel will be merged to the
> > kernel DTB. So the driver can get the trained table for clock scaling
> > support.
> > 
> > For the higher rate support (above 800MHz), the periodic training is
> > needed for the timing compensation. So basically, two methodologies for
> > clock scaling support, one is following the clock changing sequence to
> > update the EMC table to EMC registers and another is if the rate needs
> > periodic training, then we will start a timer to do that periodically
> > until it leaves the rate that doesn't need that.
> > 
> > Based on the work of Peter De Schrijver <pdeschrijver@nvidia.com>.
> > 
> > Signed-off-by: Joseph Lo <josephl@nvidia.com>
> > ---
> >  drivers/memory/tegra/Kconfig             |   10 +
> >  drivers/memory/tegra/Makefile            |    1 +
> >  drivers/memory/tegra/tegra210-dt-parse.c |  340 +++++++
> >  drivers/memory/tegra/tegra210-emc-reg.h  | 1083 ++++++++++++++++++++++
> >  drivers/memory/tegra/tegra210-emc.c      |  886 ++++++++++++++++++
> >  5 files changed, 2320 insertions(+)
> >  create mode 100644 drivers/memory/tegra/tegra210-dt-parse.c
> >  create mode 100644 drivers/memory/tegra/tegra210-emc-reg.h
> >  create mode 100644 drivers/memory/tegra/tegra210-emc.c
> > 
> > diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig
> > index 34e0b70f5c5f..614e9b370183 100644
> > --- a/drivers/memory/tegra/Kconfig
> > +++ b/drivers/memory/tegra/Kconfig
> > @@ -25,3 +25,13 @@ config TEGRA124_EMC
> >  	  Tegra124 chips. The EMC controls the external DRAM on the board.
> >  	  This driver is required to change memory timings / clock rate for
> >  	  external memory.
> > +
> > +config TEGRA210_EMC
> > +	bool "NVIDIA Tegra210 External Memory Controller driver"
> > +	default y
> > +	depends on TEGRA_MC && ARCH_TEGRA_210_SOC
> > +	help
> > +	  This driver is for the External Memory Controller (EMC) found on
> > +	  Tegra210 chips. The EMC controls the external DRAM on the board.
> > +	  This driver is required to change memory timings / clock rate for
> > +	  external memory.
> > diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile
> > index 3971a6b7c487..36a835620bbd 100644
> > --- a/drivers/memory/tegra/Makefile
> > +++ b/drivers/memory/tegra/Makefile
> > @@ -12,4 +12,5 @@ obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
> >  
> >  obj-$(CONFIG_TEGRA20_EMC)  += tegra20-emc.o
> >  obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o
> > +obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o tegra210-dt-parse.o
> >  obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o
> > diff --git a/drivers/memory/tegra/tegra210-dt-parse.c b/drivers/memory/tegra/tegra210-dt-parse.c
> > new file mode 100644
> > index 000000000000..6a3a3a28ac64
> > --- /dev/null
> > +++ b/drivers/memory/tegra/tegra210-dt-parse.c
> > @@ -0,0 +1,340 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2013-2019, NVIDIA CORPORATION.  All rights reserved.
> > + */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/err.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <soc/tegra/fuse.h>
> > +
> > +#include "tegra210-emc-reg.h"
> > +
> > +static struct device_node *tegra_emc_ramcode_devnode(
> > +	struct device_node *np)
> 
> This is weirdly wrapped. Typically if it doesn't all fit on one line
> you'd break after the return type, like so:
> 
>     static struct device_node *tegra_emc_ramcode_devnode(struct device_node *np)
> 
> That said, the above does seem to fit on a single line, so there'n no
> reason to wrap at all. You could still try to make it a little shorter
> by using the _node suffix instead of _devnode.
> 

..

> 
> Should this be an array? Seems like that could make it easier to write
> the tables to these registers later on.
> 
> > +
> > +	struct emc_table *current_timing;
> > +	struct emc_table *next_timing;
> > +	struct emc_table start_timing;
> 
> Why is start_timing not a pointer? It looks to me like that's basically
> a copy of emc_table[0], so why not just point it at that?
> 

No. Apparently it is possible the EMC registers are configured by the
bootloader differently than anything mentioned in the table. Given that
the switching sequence need some of the current register values, we need
to read those from the hardware and we cannot just point to an existing
table entry.

Peter.


^ permalink raw reply	[flat|nested] 28+ messages in thread

* Re: [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings
  2019-04-03  4:26       ` Rob Herring
@ 2019-04-10  2:41         ` Joseph Lo
  0 siblings, 0 replies; 28+ messages in thread
From: Joseph Lo @ 2019-04-10  2:41 UTC (permalink / raw)
  To: Rob Herring
  Cc: devicetree, Stephen Boyd, Peter De Schrijver, Jonathan Hunter,
	Thierry Reding, linux-tegra, linux-clk,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE

On 4/3/19 12:26 PM, Rob Herring wrote:
> On Mon, Apr 1, 2019 at 2:58 AM Joseph Lo <josephl@nvidia.com> wrote:
>>
>> On 3/31/19 2:41 PM, Rob Herring wrote:
>>> On Mon, Mar 25, 2019 at 03:45:16PM +0800, Joseph Lo wrote:
>>>> Add the binding document for the external memory controller (EMC) which
>>>> communicates with external LPDDR4 devices. It includes the bindings of
>>>> the EMC node and the EMC table of different rates.
>>>>
>>>> To support high rates for LPDDR4, the EMC table must be trained before
>>>> it can be used for runtime clock switching. It has been done by firmware
>>>> and merged to the table that Linux kernel uses. For backward
>>>> compatibility with the devices that had been launched on the market, like
>>>> Shield and Jetson platforms, the bindings in the EMC table should remain
>>>> the same. So the firmware can recognize them and merge the trained EMC
>>>> table for the kernel.
>>
>> Hi Rob,
>>
>> Thanks for reviewing.
>>
>>>
>>> Overall seems pretty bloated. How much of this really varies by board
>>> vs. just being a dump of all the register values to stuff?
>>
>> Most of them are register values. And by different SDRAM devices that
>> could be used on the same platform (use ram code to identify them), the
>> value could be different.
>>
>>>
>>> Primarily, I'm leary of getting a similar binding for every vendor's DDR
>>> setup.
>>>
>>> Some mostly trivial comments follow.
>>
>> Really sorry about that. I understand these basic rules for DT bindings,
>> but the case here is that these un-reviewed bindings have been used in
>> the firmware on the shipped products. To support the same with the
>> upstream kernel and consider the firmware blob may not be updated, we
>> have no choice to just use the same bindings in the upstream kernel.
>>
>> How can we deal with this case?
> 
> Simply, we do not accept bindings as-is. If we did, then there would
> be no point in documenting and reviewing bindings. NVidia chose this
> path and now gets to live with it.

Hi Rob,

Thanks for letting us know the criterion. We will follow up and continue 
the reviewing process of the binding. Please help us to review.

And we will fix the driver and firmware to use the reviewed bindings 
later. Need to settle the binding first.

Thanks,
Joseph


^ permalink raw reply	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2019-04-10  2:41 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-03-25  7:45 [PATCH 0/8] Add EMC scaling support for Tegra210 Joseph Lo
2019-03-25  7:45 ` [PATCH 1/8] dt-bindings: memory: tegra: Add Tegra210 EMC bindings Joseph Lo
2019-03-31  6:41   ` Rob Herring
2019-04-01  7:57     ` Joseph Lo
2019-04-03  4:26       ` Rob Herring
2019-04-10  2:41         ` Joseph Lo
2019-04-01 12:12   ` Dmitry Osipenko
2019-04-02  2:26     ` Joseph Lo
2019-04-02 10:21       ` Dmitry Osipenko
2019-04-04  9:17   ` Dmitry Osipenko
2019-04-04  9:30     ` Dmitry Osipenko
2019-04-08  8:49     ` Joseph Lo
2019-03-25  7:45 ` [PATCH 2/8] clk: tegra: clock changes for emc scaling support on Tegra210 Joseph Lo
2019-04-03  9:22   ` Thierry Reding
2019-04-08  7:52     ` Joseph Lo
2019-04-08  9:15     ` Peter De Schrijver
2019-03-25  7:45 ` [PATCH 3/8] memory: tegra: Add Tegra210 EMC clock driver Joseph Lo
2019-04-03 11:34   ` Thierry Reding
2019-04-08  9:25     ` Peter De Schrijver
2019-04-03 11:55   ` Dmitry Osipenko
2019-03-25  7:45 ` [PATCH 4/8] memory: tegra: add EMC scaling support code for Tegra210 Joseph Lo
2019-04-02 11:39   ` Dmitry Osipenko
2019-04-02 14:53     ` Joseph Lo
2019-03-25  7:45 ` [PATCH 5/8] memory: tegra: Add EMC scaling sequence " Joseph Lo
2019-04-02 11:36   ` Dmitry Osipenko
2019-04-02 14:49     ` Joseph Lo
2019-03-25  7:45 ` [PATCH 6/8] arm64: tegra: Add external memory controller node " Joseph Lo
2019-03-29 14:41 ` [PATCH 0/8] Add EMC scaling support " Peter De Schrijver

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).