* [PATCH v10 13/15] memory: tegra: Ensure timing control debug features are disabled
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
Timing control debug features should be disabled at a boot time, but you
never now and hence it's better to disable them explicitly because some of
those features are crucial for the driver to do a proper thing.
Acked-by: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/memory/tegra/mc.c | 3 +++
drivers/memory/tegra/mc.h | 2 ++
2 files changed, 5 insertions(+)
diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c
index 43819e8df95c..1bad7f238881 100644
--- a/drivers/memory/tegra/mc.c
+++ b/drivers/memory/tegra/mc.c
@@ -657,6 +657,9 @@ static int tegra_mc_probe(struct platform_device *pdev)
} else
#endif
{
+ /* ensure that debug features are disabled */
+ mc_writel(mc, 0x00000000, MC_TIMING_CONTROL_DBG);
+
err = tegra_mc_setup_latency_allowance(mc);
if (err < 0) {
dev_err(&pdev->dev,
diff --git a/drivers/memory/tegra/mc.h b/drivers/memory/tegra/mc.h
index 410efc4d7e7b..cd52628c2b96 100644
--- a/drivers/memory/tegra/mc.h
+++ b/drivers/memory/tegra/mc.h
@@ -30,6 +30,8 @@
#define MC_EMEM_ARB_OVERRIDE 0xe8
#define MC_EMEM_ARB_OVERRIDE_EACK_MASK 0x3
+#define MC_TIMING_CONTROL_DBG 0xf8
+
#define MC_TIMING_CONTROL 0xfc
#define MC_TIMING_UPDATE BIT(0)
--
2.22.0
^ permalink raw reply related
* [PATCH v10 12/15] memory: tegra: Introduce Tegra30 EMC driver
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
Introduce driver for the External Memory Controller (EMC) found on Tegra30
chips, it controls the external DRAM on the board. The purpose of this
driver is to program memory timing for external memory on the EMC clock
rate change.
Acked-by: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/memory/tegra/Kconfig | 10 +
drivers/memory/tegra/Makefile | 1 +
drivers/memory/tegra/mc.c | 9 +-
drivers/memory/tegra/mc.h | 30 +-
drivers/memory/tegra/tegra30-emc.c | 1232 ++++++++++++++++++++++++++++
drivers/memory/tegra/tegra30.c | 42 +
include/soc/tegra/mc.h | 2 +-
7 files changed, 1311 insertions(+), 15 deletions(-)
create mode 100644 drivers/memory/tegra/tegra30-emc.c
diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig
index 4680124ddcab..fbfbaada61a2 100644
--- a/drivers/memory/tegra/Kconfig
+++ b/drivers/memory/tegra/Kconfig
@@ -17,6 +17,16 @@ config TEGRA20_EMC
This driver is required to change memory timings / clock rate for
external memory.
+config TEGRA30_EMC
+ bool "NVIDIA Tegra30 External Memory Controller driver"
+ default y
+ depends on TEGRA_MC && ARCH_TEGRA_3x_SOC
+ help
+ This driver is for the External Memory Controller (EMC) found on
+ Tegra30 chips. The EMC controls the external DRAM on the board.
+ This driver is required to change memory timings / clock rate for
+ external memory.
+
config TEGRA124_EMC
bool "NVIDIA Tegra124 External Memory Controller driver"
default y
diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile
index 3971a6b7c487..3d23c4261104 100644
--- a/drivers/memory/tegra/Makefile
+++ b/drivers/memory/tegra/Makefile
@@ -11,5 +11,6 @@ tegra-mc-$(CONFIG_ARCH_TEGRA_210_SOC) += tegra210.o
obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
obj-$(CONFIG_TEGRA20_EMC) += tegra20-emc.o
+obj-$(CONFIG_TEGRA30_EMC) += tegra30-emc.o
obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o
obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o
diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c
index 3d8d322511c5..43819e8df95c 100644
--- a/drivers/memory/tegra/mc.c
+++ b/drivers/memory/tegra/mc.c
@@ -48,9 +48,6 @@
#define MC_EMEM_ADR_CFG 0x54
#define MC_EMEM_ADR_CFG_EMEM_NUMDEV BIT(0)
-#define MC_TIMING_CONTROL 0xfc
-#define MC_TIMING_UPDATE BIT(0)
-
static const struct of_device_id tegra_mc_of_match[] = {
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
{ .compatible = "nvidia,tegra20-mc-gart", .data = &tegra20_mc_soc },
@@ -307,7 +304,7 @@ static int tegra_mc_setup_latency_allowance(struct tegra_mc *mc)
return 0;
}
-void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate)
+int tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate)
{
unsigned int i;
struct tegra_mc_timing *timing = NULL;
@@ -322,11 +319,13 @@ void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate)
if (!timing) {
dev_err(mc->dev, "no memory timing registered for rate %lu\n",
rate);
- return;
+ return -EINVAL;
}
for (i = 0; i < mc->soc->num_emem_regs; ++i)
mc_writel(mc, timing->emem_data[i], mc->soc->emem_regs[i]);
+
+ return 0;
}
unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc)
diff --git a/drivers/memory/tegra/mc.h b/drivers/memory/tegra/mc.h
index f9353494b708..410efc4d7e7b 100644
--- a/drivers/memory/tegra/mc.h
+++ b/drivers/memory/tegra/mc.h
@@ -6,20 +6,32 @@
#ifndef MEMORY_TEGRA_MC_H
#define MEMORY_TEGRA_MC_H
+#include <linux/bits.h>
#include <linux/io.h>
#include <linux/types.h>
#include <soc/tegra/mc.h>
-#define MC_INT_DECERR_MTS (1 << 16)
-#define MC_INT_SECERR_SEC (1 << 13)
-#define MC_INT_DECERR_VPR (1 << 12)
-#define MC_INT_INVALID_APB_ASID_UPDATE (1 << 11)
-#define MC_INT_INVALID_SMMU_PAGE (1 << 10)
-#define MC_INT_ARBITRATION_EMEM (1 << 9)
-#define MC_INT_SECURITY_VIOLATION (1 << 8)
-#define MC_INT_INVALID_GART_PAGE (1 << 7)
-#define MC_INT_DECERR_EMEM (1 << 6)
+#define MC_INT_DECERR_MTS BIT(16)
+#define MC_INT_SECERR_SEC BIT(13)
+#define MC_INT_DECERR_VPR BIT(12)
+#define MC_INT_INVALID_APB_ASID_UPDATE BIT(11)
+#define MC_INT_INVALID_SMMU_PAGE BIT(10)
+#define MC_INT_ARBITRATION_EMEM BIT(9)
+#define MC_INT_SECURITY_VIOLATION BIT(8)
+#define MC_INT_INVALID_GART_PAGE BIT(7)
+#define MC_INT_DECERR_EMEM BIT(6)
+
+#define MC_EMEM_ARB_OUTSTANDING_REQ 0x94
+#define MC_EMEM_ARB_OUTSTANDING_REQ_MAX_MASK 0x1ff
+#define MC_EMEM_ARB_OUTSTANDING_REQ_HOLDOFF_OVERRIDE BIT(30)
+#define MC_EMEM_ARB_OUTSTANDING_REQ_LIMIT_ENABLE BIT(31)
+
+#define MC_EMEM_ARB_OVERRIDE 0xe8
+#define MC_EMEM_ARB_OVERRIDE_EACK_MASK 0x3
+
+#define MC_TIMING_CONTROL 0xfc
+#define MC_TIMING_UPDATE BIT(0)
static inline u32 mc_readl(struct tegra_mc *mc, unsigned long offset)
{
diff --git a/drivers/memory/tegra/tegra30-emc.c b/drivers/memory/tegra/tegra30-emc.c
new file mode 100644
index 000000000000..6929980bf907
--- /dev/null
+++ b/drivers/memory/tegra/tegra30-emc.c
@@ -0,0 +1,1232 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tegra30 External Memory Controller driver
+ *
+ * Based on downstream driver from NVIDIA and tegra124-emc.c
+ * Copyright (C) 2011-2014 NVIDIA Corporation
+ *
+ * Author: Dmitry Osipenko <digetx@gmail.com>
+ * Copyright (C) 2019 GRATE-DRIVER project
+ */
+
+#include <linux/clk.h>
+#include <linux/clk/tegra.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/sort.h>
+#include <linux/types.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "mc.h"
+
+#define EMC_INTSTATUS 0x000
+#define EMC_INTMASK 0x004
+#define EMC_DBG 0x008
+#define EMC_CFG 0x00c
+#define EMC_REFCTRL 0x020
+#define EMC_TIMING_CONTROL 0x028
+#define EMC_RC 0x02c
+#define EMC_RFC 0x030
+#define EMC_RAS 0x034
+#define EMC_RP 0x038
+#define EMC_R2W 0x03c
+#define EMC_W2R 0x040
+#define EMC_R2P 0x044
+#define EMC_W2P 0x048
+#define EMC_RD_RCD 0x04c
+#define EMC_WR_RCD 0x050
+#define EMC_RRD 0x054
+#define EMC_REXT 0x058
+#define EMC_WDV 0x05c
+#define EMC_QUSE 0x060
+#define EMC_QRST 0x064
+#define EMC_QSAFE 0x068
+#define EMC_RDV 0x06c
+#define EMC_REFRESH 0x070
+#define EMC_BURST_REFRESH_NUM 0x074
+#define EMC_PDEX2WR 0x078
+#define EMC_PDEX2RD 0x07c
+#define EMC_PCHG2PDEN 0x080
+#define EMC_ACT2PDEN 0x084
+#define EMC_AR2PDEN 0x088
+#define EMC_RW2PDEN 0x08c
+#define EMC_TXSR 0x090
+#define EMC_TCKE 0x094
+#define EMC_TFAW 0x098
+#define EMC_TRPAB 0x09c
+#define EMC_TCLKSTABLE 0x0a0
+#define EMC_TCLKSTOP 0x0a4
+#define EMC_TREFBW 0x0a8
+#define EMC_QUSE_EXTRA 0x0ac
+#define EMC_ODT_WRITE 0x0b0
+#define EMC_ODT_READ 0x0b4
+#define EMC_WEXT 0x0b8
+#define EMC_CTT 0x0bc
+#define EMC_MRS_WAIT_CNT 0x0c8
+#define EMC_MRS 0x0cc
+#define EMC_EMRS 0x0d0
+#define EMC_SELF_REF 0x0e0
+#define EMC_MRW 0x0e8
+#define EMC_XM2DQSPADCTRL3 0x0f8
+#define EMC_FBIO_SPARE 0x100
+#define EMC_FBIO_CFG5 0x104
+#define EMC_FBIO_CFG6 0x114
+#define EMC_CFG_RSV 0x120
+#define EMC_AUTO_CAL_CONFIG 0x2a4
+#define EMC_AUTO_CAL_INTERVAL 0x2a8
+#define EMC_AUTO_CAL_STATUS 0x2ac
+#define EMC_STATUS 0x2b4
+#define EMC_CFG_2 0x2b8
+#define EMC_CFG_DIG_DLL 0x2bc
+#define EMC_CFG_DIG_DLL_PERIOD 0x2c0
+#define EMC_CTT_DURATION 0x2d8
+#define EMC_CTT_TERM_CTRL 0x2dc
+#define EMC_ZCAL_INTERVAL 0x2e0
+#define EMC_ZCAL_WAIT_CNT 0x2e4
+#define EMC_ZQ_CAL 0x2ec
+#define EMC_XM2CMDPADCTRL 0x2f0
+#define EMC_XM2DQSPADCTRL2 0x2fc
+#define EMC_XM2DQPADCTRL2 0x304
+#define EMC_XM2CLKPADCTRL 0x308
+#define EMC_XM2COMPPADCTRL 0x30c
+#define EMC_XM2VTTGENPADCTRL 0x310
+#define EMC_XM2VTTGENPADCTRL2 0x314
+#define EMC_XM2QUSEPADCTRL 0x318
+#define EMC_DLL_XFORM_DQS0 0x328
+#define EMC_DLL_XFORM_DQS1 0x32c
+#define EMC_DLL_XFORM_DQS2 0x330
+#define EMC_DLL_XFORM_DQS3 0x334
+#define EMC_DLL_XFORM_DQS4 0x338
+#define EMC_DLL_XFORM_DQS5 0x33c
+#define EMC_DLL_XFORM_DQS6 0x340
+#define EMC_DLL_XFORM_DQS7 0x344
+#define EMC_DLL_XFORM_QUSE0 0x348
+#define EMC_DLL_XFORM_QUSE1 0x34c
+#define EMC_DLL_XFORM_QUSE2 0x350
+#define EMC_DLL_XFORM_QUSE3 0x354
+#define EMC_DLL_XFORM_QUSE4 0x358
+#define EMC_DLL_XFORM_QUSE5 0x35c
+#define EMC_DLL_XFORM_QUSE6 0x360
+#define EMC_DLL_XFORM_QUSE7 0x364
+#define EMC_DLL_XFORM_DQ0 0x368
+#define EMC_DLL_XFORM_DQ1 0x36c
+#define EMC_DLL_XFORM_DQ2 0x370
+#define EMC_DLL_XFORM_DQ3 0x374
+#define EMC_DLI_TRIM_TXDQS0 0x3a8
+#define EMC_DLI_TRIM_TXDQS1 0x3ac
+#define EMC_DLI_TRIM_TXDQS2 0x3b0
+#define EMC_DLI_TRIM_TXDQS3 0x3b4
+#define EMC_DLI_TRIM_TXDQS4 0x3b8
+#define EMC_DLI_TRIM_TXDQS5 0x3bc
+#define EMC_DLI_TRIM_TXDQS6 0x3c0
+#define EMC_DLI_TRIM_TXDQS7 0x3c4
+#define EMC_STALL_THEN_EXE_BEFORE_CLKCHANGE 0x3c8
+#define EMC_STALL_THEN_EXE_AFTER_CLKCHANGE 0x3cc
+#define EMC_UNSTALL_RW_AFTER_CLKCHANGE 0x3d0
+#define EMC_SEL_DPD_CTRL 0x3d8
+#define EMC_PRE_REFRESH_REQ_CNT 0x3dc
+#define EMC_DYN_SELF_REF_CONTROL 0x3e0
+#define EMC_TXSRDLL 0x3e4
+
+#define EMC_STATUS_TIMING_UPDATE_STALLED BIT(23)
+
+#define EMC_MODE_SET_DLL_RESET BIT(8)
+#define EMC_MODE_SET_LONG_CNT BIT(26)
+
+#define EMC_SELF_REF_CMD_ENABLED BIT(0)
+
+#define DRAM_DEV_SEL_ALL (0 << 30)
+#define DRAM_DEV_SEL_0 (2 << 30)
+#define DRAM_DEV_SEL_1 (1 << 30)
+#define DRAM_BROADCAST(num) \
+ ((num) > 1 ? DRAM_DEV_SEL_ALL : DRAM_DEV_SEL_0)
+
+#define EMC_ZQ_CAL_CMD BIT(0)
+#define EMC_ZQ_CAL_LONG BIT(4)
+#define EMC_ZQ_CAL_LONG_CMD_DEV0 \
+ (DRAM_DEV_SEL_0 | EMC_ZQ_CAL_LONG | EMC_ZQ_CAL_CMD)
+#define EMC_ZQ_CAL_LONG_CMD_DEV1 \
+ (DRAM_DEV_SEL_1 | EMC_ZQ_CAL_LONG | EMC_ZQ_CAL_CMD)
+
+#define EMC_DBG_READ_MUX_ASSEMBLY BIT(0)
+#define EMC_DBG_WRITE_MUX_ACTIVE BIT(1)
+#define EMC_DBG_FORCE_UPDATE BIT(2)
+#define EMC_DBG_CFG_PRIORITY BIT(24)
+
+#define EMC_CFG5_QUSE_MODE_SHIFT 13
+#define EMC_CFG5_QUSE_MODE_MASK (7 << EMC_CFG5_QUSE_MODE_SHIFT)
+
+#define EMC_CFG5_QUSE_MODE_INTERNAL_LPBK 2
+#define EMC_CFG5_QUSE_MODE_PULSE_INTERN 3
+
+#define EMC_SEL_DPD_CTRL_QUSE_DPD_ENABLE BIT(9)
+
+#define EMC_XM2COMPPADCTRL_VREF_CAL_ENABLE BIT(10)
+
+#define EMC_XM2QUSEPADCTRL_IVREF_ENABLE BIT(4)
+
+#define EMC_XM2DQSPADCTRL2_VREF_ENABLE BIT(5)
+#define EMC_XM2DQSPADCTRL3_VREF_ENABLE BIT(5)
+
+#define EMC_AUTO_CAL_STATUS_ACTIVE BIT(31)
+
+#define EMC_FBIO_CFG5_DRAM_TYPE_MASK 0x3
+
+#define EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK 0x3ff
+#define EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT 16
+#define EMC_MRS_WAIT_CNT_LONG_WAIT_MASK \
+ (0x3ff << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT)
+
+#define EMC_REFCTRL_DEV_SEL_MASK 0x3
+#define EMC_REFCTRL_ENABLE BIT(31)
+#define EMC_REFCTRL_ENABLE_ALL(num) \
+ (((num) > 1 ? 0 : 2) | EMC_REFCTRL_ENABLE)
+#define EMC_REFCTRL_DISABLE_ALL(num) ((num) > 1 ? 0 : 2)
+
+#define EMC_CFG_PERIODIC_QRST BIT(21)
+#define EMC_CFG_DYN_SREF_ENABLE BIT(28)
+
+#define EMC_CLKCHANGE_REQ_ENABLE BIT(0)
+#define EMC_CLKCHANGE_PD_ENABLE BIT(1)
+#define EMC_CLKCHANGE_SR_ENABLE BIT(2)
+
+#define EMC_TIMING_UPDATE BIT(0)
+
+#define EMC_REFRESH_OVERFLOW_INT BIT(3)
+#define EMC_CLKCHANGE_COMPLETE_INT BIT(4)
+
+enum emc_dram_type {
+ DRAM_TYPE_DDR3,
+ DRAM_TYPE_DDR1,
+ DRAM_TYPE_LPDDR2,
+ DRAM_TYPE_DDR2,
+};
+
+enum emc_dll_change {
+ DLL_CHANGE_NONE,
+ DLL_CHANGE_ON,
+ DLL_CHANGE_OFF
+};
+
+static const u16 emc_timing_registers[] = {
+ [0] = EMC_RC,
+ [1] = EMC_RFC,
+ [2] = EMC_RAS,
+ [3] = EMC_RP,
+ [4] = EMC_R2W,
+ [5] = EMC_W2R,
+ [6] = EMC_R2P,
+ [7] = EMC_W2P,
+ [8] = EMC_RD_RCD,
+ [9] = EMC_WR_RCD,
+ [10] = EMC_RRD,
+ [11] = EMC_REXT,
+ [12] = EMC_WEXT,
+ [13] = EMC_WDV,
+ [14] = EMC_QUSE,
+ [15] = EMC_QRST,
+ [16] = EMC_QSAFE,
+ [17] = EMC_RDV,
+ [18] = EMC_REFRESH,
+ [19] = EMC_BURST_REFRESH_NUM,
+ [20] = EMC_PRE_REFRESH_REQ_CNT,
+ [21] = EMC_PDEX2WR,
+ [22] = EMC_PDEX2RD,
+ [23] = EMC_PCHG2PDEN,
+ [24] = EMC_ACT2PDEN,
+ [25] = EMC_AR2PDEN,
+ [26] = EMC_RW2PDEN,
+ [27] = EMC_TXSR,
+ [28] = EMC_TXSRDLL,
+ [29] = EMC_TCKE,
+ [30] = EMC_TFAW,
+ [31] = EMC_TRPAB,
+ [32] = EMC_TCLKSTABLE,
+ [33] = EMC_TCLKSTOP,
+ [34] = EMC_TREFBW,
+ [35] = EMC_QUSE_EXTRA,
+ [36] = EMC_FBIO_CFG6,
+ [37] = EMC_ODT_WRITE,
+ [38] = EMC_ODT_READ,
+ [39] = EMC_FBIO_CFG5,
+ [40] = EMC_CFG_DIG_DLL,
+ [41] = EMC_CFG_DIG_DLL_PERIOD,
+ [42] = EMC_DLL_XFORM_DQS0,
+ [43] = EMC_DLL_XFORM_DQS1,
+ [44] = EMC_DLL_XFORM_DQS2,
+ [45] = EMC_DLL_XFORM_DQS3,
+ [46] = EMC_DLL_XFORM_DQS4,
+ [47] = EMC_DLL_XFORM_DQS5,
+ [48] = EMC_DLL_XFORM_DQS6,
+ [49] = EMC_DLL_XFORM_DQS7,
+ [50] = EMC_DLL_XFORM_QUSE0,
+ [51] = EMC_DLL_XFORM_QUSE1,
+ [52] = EMC_DLL_XFORM_QUSE2,
+ [53] = EMC_DLL_XFORM_QUSE3,
+ [54] = EMC_DLL_XFORM_QUSE4,
+ [55] = EMC_DLL_XFORM_QUSE5,
+ [56] = EMC_DLL_XFORM_QUSE6,
+ [57] = EMC_DLL_XFORM_QUSE7,
+ [58] = EMC_DLI_TRIM_TXDQS0,
+ [59] = EMC_DLI_TRIM_TXDQS1,
+ [60] = EMC_DLI_TRIM_TXDQS2,
+ [61] = EMC_DLI_TRIM_TXDQS3,
+ [62] = EMC_DLI_TRIM_TXDQS4,
+ [63] = EMC_DLI_TRIM_TXDQS5,
+ [64] = EMC_DLI_TRIM_TXDQS6,
+ [65] = EMC_DLI_TRIM_TXDQS7,
+ [66] = EMC_DLL_XFORM_DQ0,
+ [67] = EMC_DLL_XFORM_DQ1,
+ [68] = EMC_DLL_XFORM_DQ2,
+ [69] = EMC_DLL_XFORM_DQ3,
+ [70] = EMC_XM2CMDPADCTRL,
+ [71] = EMC_XM2DQSPADCTRL2,
+ [72] = EMC_XM2DQPADCTRL2,
+ [73] = EMC_XM2CLKPADCTRL,
+ [74] = EMC_XM2COMPPADCTRL,
+ [75] = EMC_XM2VTTGENPADCTRL,
+ [76] = EMC_XM2VTTGENPADCTRL2,
+ [77] = EMC_XM2QUSEPADCTRL,
+ [78] = EMC_XM2DQSPADCTRL3,
+ [79] = EMC_CTT_TERM_CTRL,
+ [80] = EMC_ZCAL_INTERVAL,
+ [81] = EMC_ZCAL_WAIT_CNT,
+ [82] = EMC_MRS_WAIT_CNT,
+ [83] = EMC_AUTO_CAL_CONFIG,
+ [84] = EMC_CTT,
+ [85] = EMC_CTT_DURATION,
+ [86] = EMC_DYN_SELF_REF_CONTROL,
+ [87] = EMC_FBIO_SPARE,
+ [88] = EMC_CFG_RSV,
+};
+
+struct emc_timing {
+ unsigned long rate;
+
+ u32 data[ARRAY_SIZE(emc_timing_registers)];
+
+ u32 emc_auto_cal_interval;
+ u32 emc_mode_1;
+ u32 emc_mode_2;
+ u32 emc_mode_reset;
+ u32 emc_zcal_cnt_long;
+ bool emc_cfg_periodic_qrst;
+ bool emc_cfg_dyn_self_ref;
+};
+
+struct tegra_emc {
+ struct device *dev;
+ struct tegra_mc *mc;
+ struct completion clk_handshake_complete;
+ struct notifier_block clk_nb;
+ struct clk *clk;
+ void __iomem *regs;
+ unsigned int irq;
+
+ struct emc_timing *timings;
+ unsigned int num_timings;
+
+ u32 mc_override;
+ u32 emc_cfg;
+
+ u32 emc_mode_1;
+ u32 emc_mode_2;
+ u32 emc_mode_reset;
+
+ bool vref_cal_toggle : 1;
+ bool zcal_long : 1;
+ bool dll_on : 1;
+ bool prepared : 1;
+ bool bad_state : 1;
+};
+
+static irqreturn_t tegra_emc_isr(int irq, void *data)
+{
+ struct tegra_emc *emc = data;
+ u32 intmask = EMC_REFRESH_OVERFLOW_INT | EMC_CLKCHANGE_COMPLETE_INT;
+ u32 status;
+
+ status = readl_relaxed(emc->regs + EMC_INTSTATUS) & intmask;
+ if (!status)
+ return IRQ_NONE;
+
+ /* notify about EMC-CAR handshake completion */
+ if (status & EMC_CLKCHANGE_COMPLETE_INT)
+ complete(&emc->clk_handshake_complete);
+
+ /* notify about HW problem */
+ if (status & EMC_REFRESH_OVERFLOW_INT)
+ dev_err_ratelimited(emc->dev,
+ "refresh request overflow timeout\n");
+
+ /* clear interrupts */
+ writel_relaxed(status, emc->regs + EMC_INTSTATUS);
+
+ return IRQ_HANDLED;
+}
+
+static struct emc_timing *emc_find_timing(struct tegra_emc *emc,
+ unsigned long rate)
+{
+ struct emc_timing *timing = NULL;
+ unsigned int i;
+
+ for (i = 0; i < emc->num_timings; i++) {
+ if (emc->timings[i].rate >= rate) {
+ timing = &emc->timings[i];
+ break;
+ }
+ }
+
+ if (!timing) {
+ dev_err(emc->dev, "no timing for rate %lu\n", rate);
+ return NULL;
+ }
+
+ return timing;
+}
+
+static bool emc_dqs_preset(struct tegra_emc *emc, struct emc_timing *timing,
+ bool *schmitt_to_vref)
+{
+ bool preset = false;
+ u32 val;
+
+ if (timing->data[71] & EMC_XM2DQSPADCTRL2_VREF_ENABLE) {
+ val = readl_relaxed(emc->regs + EMC_XM2DQSPADCTRL2);
+
+ if (!(val & EMC_XM2DQSPADCTRL2_VREF_ENABLE)) {
+ val |= EMC_XM2DQSPADCTRL2_VREF_ENABLE;
+ writel_relaxed(val, emc->regs + EMC_XM2DQSPADCTRL2);
+
+ preset = true;
+ }
+ }
+
+ if (timing->data[78] & EMC_XM2DQSPADCTRL3_VREF_ENABLE) {
+ val = readl_relaxed(emc->regs + EMC_XM2DQSPADCTRL3);
+
+ if (!(val & EMC_XM2DQSPADCTRL3_VREF_ENABLE)) {
+ val |= EMC_XM2DQSPADCTRL3_VREF_ENABLE;
+ writel_relaxed(val, emc->regs + EMC_XM2DQSPADCTRL3);
+
+ preset = true;
+ }
+ }
+
+ if (timing->data[77] & EMC_XM2QUSEPADCTRL_IVREF_ENABLE) {
+ val = readl_relaxed(emc->regs + EMC_XM2QUSEPADCTRL);
+
+ if (!(val & EMC_XM2QUSEPADCTRL_IVREF_ENABLE)) {
+ val |= EMC_XM2QUSEPADCTRL_IVREF_ENABLE;
+ writel_relaxed(val, emc->regs + EMC_XM2QUSEPADCTRL);
+
+ *schmitt_to_vref = true;
+ preset = true;
+ }
+ }
+
+ return preset;
+}
+
+static int emc_seq_update_timing(struct tegra_emc *emc)
+{
+ u32 val;
+ int err;
+
+ writel_relaxed(EMC_TIMING_UPDATE, emc->regs + EMC_TIMING_CONTROL);
+
+ err = readl_relaxed_poll_timeout_atomic(emc->regs + EMC_STATUS, val,
+ !(val & EMC_STATUS_TIMING_UPDATE_STALLED),
+ 1, 200);
+ if (err) {
+ dev_err(emc->dev, "failed to update timing: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int emc_prepare_mc_clk_cfg(struct tegra_emc *emc, unsigned long rate)
+{
+ struct tegra_mc *mc = emc->mc;
+ unsigned int misc0_index = 16;
+ unsigned int i;
+ bool same;
+
+ for (i = 0; i < mc->num_timings; i++) {
+ if (mc->timings[i].rate != rate)
+ continue;
+
+ if (mc->timings[i].emem_data[misc0_index] & BIT(27))
+ same = true;
+ else
+ same = false;
+
+ return tegra20_clk_prepare_emc_mc_same_freq(emc->clk, same);
+ }
+
+ return -EINVAL;
+}
+
+static int emc_prepare_timing_change(struct tegra_emc *emc, unsigned long rate)
+{
+ struct emc_timing *timing = emc_find_timing(emc, rate);
+ enum emc_dll_change dll_change;
+ enum emc_dram_type dram_type;
+ bool schmitt_to_vref = false;
+ unsigned int pre_wait = 0;
+ bool qrst_used = false;
+ unsigned int dram_num;
+ unsigned int i;
+ u32 fbio_cfg5;
+ u32 emc_dbg;
+ u32 val;
+ int err;
+
+ if (!timing || emc->bad_state)
+ return -EINVAL;
+
+ dev_dbg(emc->dev, "%s: using timing rate %lu for requested rate %lu\n",
+ __func__, timing->rate, rate);
+
+ emc->bad_state = true;
+
+ err = emc_prepare_mc_clk_cfg(emc, rate);
+ if (err) {
+ dev_err(emc->dev, "mc clock preparation failed: %d\n", err);
+ return err;
+ }
+
+ emc->vref_cal_toggle = false;
+ emc->mc_override = mc_readl(emc->mc, MC_EMEM_ARB_OVERRIDE);
+ emc->emc_cfg = readl_relaxed(emc->regs + EMC_CFG);
+ emc_dbg = readl_relaxed(emc->regs + EMC_DBG);
+
+ if (emc->dll_on == !!(timing->emc_mode_1 & 0x1))
+ dll_change = DLL_CHANGE_NONE;
+ else if (timing->emc_mode_1 & 0x1)
+ dll_change = DLL_CHANGE_ON;
+ else
+ dll_change = DLL_CHANGE_OFF;
+
+ emc->dll_on = !!(timing->emc_mode_1 & 0x1);
+
+ if (timing->data[80] && !readl_relaxed(emc->regs + EMC_ZCAL_INTERVAL))
+ emc->zcal_long = true;
+ else
+ emc->zcal_long = false;
+
+ fbio_cfg5 = readl_relaxed(emc->regs + EMC_FBIO_CFG5);
+ dram_type = fbio_cfg5 & EMC_FBIO_CFG5_DRAM_TYPE_MASK;
+
+ dram_num = tegra_mc_get_emem_device_count(emc->mc);
+
+ /* disable dynamic self-refresh */
+ if (emc->emc_cfg & EMC_CFG_DYN_SREF_ENABLE) {
+ emc->emc_cfg &= ~EMC_CFG_DYN_SREF_ENABLE;
+ writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG);
+
+ pre_wait = 5;
+ }
+
+ /* update MC arbiter settings */
+ val = mc_readl(emc->mc, MC_EMEM_ARB_OUTSTANDING_REQ);
+ if (!(val & MC_EMEM_ARB_OUTSTANDING_REQ_HOLDOFF_OVERRIDE) ||
+ ((val & MC_EMEM_ARB_OUTSTANDING_REQ_MAX_MASK) > 0x50)) {
+
+ val = MC_EMEM_ARB_OUTSTANDING_REQ_LIMIT_ENABLE |
+ MC_EMEM_ARB_OUTSTANDING_REQ_HOLDOFF_OVERRIDE | 0x50;
+ mc_writel(emc->mc, val, MC_EMEM_ARB_OUTSTANDING_REQ);
+ mc_writel(emc->mc, MC_TIMING_UPDATE, MC_TIMING_CONTROL);
+ }
+
+ if (emc->mc_override & MC_EMEM_ARB_OVERRIDE_EACK_MASK)
+ mc_writel(emc->mc,
+ emc->mc_override & ~MC_EMEM_ARB_OVERRIDE_EACK_MASK,
+ MC_EMEM_ARB_OVERRIDE);
+
+ /* check DQ/DQS VREF delay */
+ if (emc_dqs_preset(emc, timing, &schmitt_to_vref)) {
+ if (pre_wait < 3)
+ pre_wait = 3;
+ }
+
+ if (pre_wait) {
+ err = emc_seq_update_timing(emc);
+ if (err)
+ return err;
+
+ udelay(pre_wait);
+ }
+
+ /* disable auto-calibration if VREF mode is switching */
+ if (timing->emc_auto_cal_interval) {
+ val = readl_relaxed(emc->regs + EMC_XM2COMPPADCTRL);
+ val ^= timing->data[74];
+
+ if (val & EMC_XM2COMPPADCTRL_VREF_CAL_ENABLE) {
+ writel_relaxed(0, emc->regs + EMC_AUTO_CAL_INTERVAL);
+
+ err = readl_relaxed_poll_timeout_atomic(
+ emc->regs + EMC_AUTO_CAL_STATUS, val,
+ !(val & EMC_AUTO_CAL_STATUS_ACTIVE), 1, 300);
+ if (err) {
+ dev_err(emc->dev,
+ "failed to disable auto-cal: %d\n",
+ err);
+ return err;
+ }
+
+ emc->vref_cal_toggle = true;
+ }
+ }
+
+ /* program shadow registers */
+ for (i = 0; i < ARRAY_SIZE(timing->data); i++) {
+ /* EMC_XM2CLKPADCTRL should be programmed separately */
+ if (i != 73)
+ writel_relaxed(timing->data[i],
+ emc->regs + emc_timing_registers[i]);
+ }
+
+ err = tegra_mc_write_emem_configuration(emc->mc, timing->rate);
+ if (err)
+ return err;
+
+ /* DDR3: predict MRS long wait count */
+ if (dram_type == DRAM_TYPE_DDR3 && dll_change == DLL_CHANGE_ON) {
+ u32 cnt = 512;
+
+ if (emc->zcal_long)
+ cnt -= dram_num * 256;
+
+ val = timing->data[82] & EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK;
+ if (cnt < val)
+ cnt = val;
+
+ val = timing->data[82] & ~EMC_MRS_WAIT_CNT_LONG_WAIT_MASK;
+ val |= (cnt << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT) &
+ EMC_MRS_WAIT_CNT_LONG_WAIT_MASK;
+
+ writel_relaxed(val, emc->regs + EMC_MRS_WAIT_CNT);
+ }
+
+ /* disable interrupt since read access is prohibited after stalling */
+ disable_irq(emc->irq);
+
+ /* this read also completes the writes */
+ val = readl_relaxed(emc->regs + EMC_SEL_DPD_CTRL);
+
+ if (!(val & EMC_SEL_DPD_CTRL_QUSE_DPD_ENABLE) && schmitt_to_vref) {
+ u32 cur_mode, new_mode;
+
+ cur_mode = fbio_cfg5 & EMC_CFG5_QUSE_MODE_MASK;
+ cur_mode >>= EMC_CFG5_QUSE_MODE_SHIFT;
+
+ new_mode = timing->data[39] & EMC_CFG5_QUSE_MODE_MASK;
+ new_mode >>= EMC_CFG5_QUSE_MODE_SHIFT;
+
+ if ((cur_mode != EMC_CFG5_QUSE_MODE_PULSE_INTERN &&
+ cur_mode != EMC_CFG5_QUSE_MODE_INTERNAL_LPBK) ||
+ (new_mode != EMC_CFG5_QUSE_MODE_PULSE_INTERN &&
+ new_mode != EMC_CFG5_QUSE_MODE_INTERNAL_LPBK))
+ qrst_used = true;
+ }
+
+ /* flow control marker 1 */
+ writel_relaxed(0x1, emc->regs + EMC_STALL_THEN_EXE_BEFORE_CLKCHANGE);
+
+ /* enable periodic reset */
+ if (qrst_used) {
+ writel_relaxed(emc_dbg | EMC_DBG_WRITE_MUX_ACTIVE,
+ emc->regs + EMC_DBG);
+ writel_relaxed(emc->emc_cfg | EMC_CFG_PERIODIC_QRST,
+ emc->regs + EMC_CFG);
+ writel_relaxed(emc_dbg, emc->regs + EMC_DBG);
+ }
+
+ /* disable auto-refresh to save time after clock change */
+ writel_relaxed(EMC_REFCTRL_DISABLE_ALL(dram_num),
+ emc->regs + EMC_REFCTRL);
+
+ /* turn off DLL and enter self-refresh on DDR3 */
+ if (dram_type == DRAM_TYPE_DDR3) {
+ if (dll_change == DLL_CHANGE_OFF)
+ writel_relaxed(timing->emc_mode_1,
+ emc->regs + EMC_EMRS);
+
+ writel_relaxed(DRAM_BROADCAST(dram_num) |
+ EMC_SELF_REF_CMD_ENABLED,
+ emc->regs + EMC_SELF_REF);
+ }
+
+ /* flow control marker 2 */
+ writel_relaxed(0x1, emc->regs + EMC_STALL_THEN_EXE_AFTER_CLKCHANGE);
+
+ /* enable write-active MUX, update unshadowed pad control */
+ writel_relaxed(emc_dbg | EMC_DBG_WRITE_MUX_ACTIVE, emc->regs + EMC_DBG);
+ writel_relaxed(timing->data[73], emc->regs + EMC_XM2CLKPADCTRL);
+
+ /* restore periodic QRST and disable write-active MUX */
+ val = !!(emc->emc_cfg & EMC_CFG_PERIODIC_QRST);
+ if (qrst_used || timing->emc_cfg_periodic_qrst != val) {
+ if (timing->emc_cfg_periodic_qrst)
+ emc->emc_cfg |= EMC_CFG_PERIODIC_QRST;
+ else
+ emc->emc_cfg &= ~EMC_CFG_PERIODIC_QRST;
+
+ writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG);
+ }
+ writel_relaxed(emc_dbg, emc->regs + EMC_DBG);
+
+ /* exit self-refresh on DDR3 */
+ if (dram_type == DRAM_TYPE_DDR3)
+ writel_relaxed(DRAM_BROADCAST(dram_num),
+ emc->regs + EMC_SELF_REF);
+
+ /* set DRAM-mode registers */
+ if (dram_type == DRAM_TYPE_DDR3) {
+ if (timing->emc_mode_1 != emc->emc_mode_1)
+ writel_relaxed(timing->emc_mode_1,
+ emc->regs + EMC_EMRS);
+
+ if (timing->emc_mode_2 != emc->emc_mode_2)
+ writel_relaxed(timing->emc_mode_2,
+ emc->regs + EMC_EMRS);
+
+ if (timing->emc_mode_reset != emc->emc_mode_reset ||
+ dll_change == DLL_CHANGE_ON) {
+ val = timing->emc_mode_reset;
+ if (dll_change == DLL_CHANGE_ON) {
+ val |= EMC_MODE_SET_DLL_RESET;
+ val |= EMC_MODE_SET_LONG_CNT;
+ } else {
+ val &= ~EMC_MODE_SET_DLL_RESET;
+ }
+ writel_relaxed(val, emc->regs + EMC_MRS);
+ }
+ } else {
+ if (timing->emc_mode_2 != emc->emc_mode_2)
+ writel_relaxed(timing->emc_mode_2,
+ emc->regs + EMC_MRW);
+
+ if (timing->emc_mode_1 != emc->emc_mode_1)
+ writel_relaxed(timing->emc_mode_1,
+ emc->regs + EMC_MRW);
+ }
+
+ emc->emc_mode_1 = timing->emc_mode_1;
+ emc->emc_mode_2 = timing->emc_mode_2;
+ emc->emc_mode_reset = timing->emc_mode_reset;
+
+ /* issue ZCAL command if turning ZCAL on */
+ if (emc->zcal_long) {
+ writel_relaxed(EMC_ZQ_CAL_LONG_CMD_DEV0,
+ emc->regs + EMC_ZQ_CAL);
+
+ if (dram_num > 1)
+ writel_relaxed(EMC_ZQ_CAL_LONG_CMD_DEV1,
+ emc->regs + EMC_ZQ_CAL);
+ }
+
+ /* re-enable auto-refresh */
+ writel_relaxed(EMC_REFCTRL_ENABLE_ALL(dram_num),
+ emc->regs + EMC_REFCTRL);
+
+ /* flow control marker 3 */
+ writel_relaxed(0x1, emc->regs + EMC_UNSTALL_RW_AFTER_CLKCHANGE);
+
+ reinit_completion(&emc->clk_handshake_complete);
+
+ /* interrupt can be re-enabled now */
+ enable_irq(emc->irq);
+
+ emc->bad_state = false;
+ emc->prepared = true;
+
+ return 0;
+}
+
+static int emc_complete_timing_change(struct tegra_emc *emc,
+ unsigned long rate)
+{
+ struct emc_timing *timing = emc_find_timing(emc, rate);
+ unsigned long timeout;
+ int ret;
+
+ timeout = wait_for_completion_timeout(&emc->clk_handshake_complete,
+ msecs_to_jiffies(100));
+ if (timeout == 0) {
+ dev_err(emc->dev, "emc-car handshake failed\n");
+ emc->bad_state = true;
+ return -EIO;
+ }
+
+ /* restore auto-calibration */
+ if (emc->vref_cal_toggle)
+ writel_relaxed(timing->emc_auto_cal_interval,
+ emc->regs + EMC_AUTO_CAL_INTERVAL);
+
+ /* restore dynamic self-refresh */
+ if (timing->emc_cfg_dyn_self_ref) {
+ emc->emc_cfg |= EMC_CFG_DYN_SREF_ENABLE;
+ writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG);
+ }
+
+ /* set number of clocks to wait after each ZQ command */
+ if (emc->zcal_long)
+ writel_relaxed(timing->emc_zcal_cnt_long,
+ emc->regs + EMC_ZCAL_WAIT_CNT);
+
+ udelay(2);
+ /* update restored timing */
+ ret = emc_seq_update_timing(emc);
+ if (ret)
+ emc->bad_state = true;
+
+ /* restore early ACK */
+ mc_writel(emc->mc, emc->mc_override, MC_EMEM_ARB_OVERRIDE);
+
+ emc->prepared = false;
+
+ return ret;
+}
+
+static int emc_unprepare_timing_change(struct tegra_emc *emc,
+ unsigned long rate)
+{
+ if (emc->prepared && !emc->bad_state) {
+ /* shouldn't ever happen in practice */
+ dev_err(emc->dev, "timing configuration can't be reverted\n");
+ emc->bad_state = true;
+ }
+
+ return 0;
+}
+
+static int emc_clk_change_notify(struct notifier_block *nb,
+ unsigned long msg, void *data)
+{
+ struct tegra_emc *emc = container_of(nb, struct tegra_emc, clk_nb);
+ struct clk_notifier_data *cnd = data;
+ int err;
+
+ switch (msg) {
+ case PRE_RATE_CHANGE:
+ err = emc_prepare_timing_change(emc, cnd->new_rate);
+ break;
+
+ case ABORT_RATE_CHANGE:
+ err = emc_unprepare_timing_change(emc, cnd->old_rate);
+ break;
+
+ case POST_RATE_CHANGE:
+ err = emc_complete_timing_change(emc, cnd->new_rate);
+ break;
+
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return notifier_from_errno(err);
+}
+
+static int load_one_timing_from_dt(struct tegra_emc *emc,
+ struct emc_timing *timing,
+ struct device_node *node)
+{
+ u32 value;
+ int err;
+
+ err = of_property_read_u32(node, "clock-frequency", &value);
+ if (err) {
+ dev_err(emc->dev, "timing %pOF: failed to read rate: %d\n",
+ node, err);
+ return err;
+ }
+
+ timing->rate = value;
+
+ err = of_property_read_u32_array(node, "nvidia,emc-configuration",
+ timing->data,
+ ARRAY_SIZE(emc_timing_registers));
+ if (err) {
+ dev_err(emc->dev,
+ "timing %pOF: failed to read emc timing data: %d\n",
+ node, err);
+ return err;
+ }
+
+#define EMC_READ_BOOL(prop, dtprop) \
+ timing->prop = of_property_read_bool(node, dtprop);
+
+#define EMC_READ_U32(prop, dtprop) \
+ err = of_property_read_u32(node, dtprop, &timing->prop); \
+ if (err) { \
+ dev_err(emc->dev, \
+ "timing %pOFn: failed to read " #prop ": %d\n", \
+ node, err); \
+ return err; \
+ }
+
+ EMC_READ_U32(emc_auto_cal_interval, "nvidia,emc-auto-cal-interval")
+ EMC_READ_U32(emc_mode_1, "nvidia,emc-mode-1")
+ EMC_READ_U32(emc_mode_2, "nvidia,emc-mode-2")
+ EMC_READ_U32(emc_mode_reset, "nvidia,emc-mode-reset")
+ EMC_READ_U32(emc_zcal_cnt_long, "nvidia,emc-zcal-cnt-long")
+ EMC_READ_BOOL(emc_cfg_dyn_self_ref, "nvidia,emc-cfg-dyn-self-ref")
+ EMC_READ_BOOL(emc_cfg_periodic_qrst, "nvidia,emc-cfg-periodic-qrst")
+
+#undef EMC_READ_U32
+#undef EMC_READ_BOOL
+
+ dev_dbg(emc->dev, "%s: %pOF: rate %lu\n", __func__, node, timing->rate);
+
+ return 0;
+}
+
+static int cmp_timings(const void *_a, const void *_b)
+{
+ const struct emc_timing *a = _a;
+ const struct emc_timing *b = _b;
+
+ if (a->rate < b->rate)
+ return -1;
+
+ if (a->rate > b->rate)
+ return 1;
+
+ return 0;
+}
+
+static int emc_check_mc_timings(struct tegra_emc *emc)
+{
+ struct tegra_mc *mc = emc->mc;
+ unsigned int i;
+
+ if (emc->num_timings != mc->num_timings) {
+ dev_err(emc->dev, "emc/mc timings number mismatch: %u %u\n",
+ emc->num_timings, mc->num_timings);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < mc->num_timings; i++) {
+ if (emc->timings[i].rate != mc->timings[i].rate) {
+ dev_err(emc->dev,
+ "emc/mc timing rate mismatch: %lu %lu\n",
+ emc->timings[i].rate, mc->timings[i].rate);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int emc_load_timings_from_dt(struct tegra_emc *emc,
+ struct device_node *node)
+{
+ struct device_node *child;
+ struct emc_timing *timing;
+ int child_count;
+ int err;
+
+ child_count = of_get_child_count(node);
+ if (!child_count) {
+ dev_err(emc->dev, "no memory timings in: %pOF\n", node);
+ return -EINVAL;
+ }
+
+ emc->timings = devm_kcalloc(emc->dev, child_count, sizeof(*timing),
+ GFP_KERNEL);
+ if (!emc->timings)
+ return -ENOMEM;
+
+ emc->num_timings = child_count;
+ timing = emc->timings;
+
+ for_each_child_of_node(node, child) {
+ err = load_one_timing_from_dt(emc, timing++, child);
+ if (err) {
+ of_node_put(child);
+ return err;
+ }
+ }
+
+ sort(emc->timings, emc->num_timings, sizeof(*timing), cmp_timings,
+ NULL);
+
+ err = emc_check_mc_timings(emc);
+ if (err)
+ return err;
+
+ dev_info(emc->dev,
+ "got %u timings for RAM code %u (min %luMHz max %luMHz)\n",
+ emc->num_timings,
+ tegra_read_ram_code(),
+ emc->timings[0].rate / 1000000,
+ emc->timings[emc->num_timings - 1].rate / 1000000);
+
+ return 0;
+}
+
+static struct device_node *emc_find_node_by_ram_code(struct device *dev)
+{
+ struct device_node *np;
+ u32 value, ram_code;
+ int err;
+
+ ram_code = tegra_read_ram_code();
+
+ for_each_child_of_node(dev->of_node, np) {
+ err = of_property_read_u32(np, "nvidia,ram-code", &value);
+ if (err || value != ram_code)
+ continue;
+
+ return np;
+ }
+
+ dev_err(dev, "no memory timings for RAM code %u found in device-tree\n",
+ ram_code);
+
+ return NULL;
+}
+
+static int emc_setup_hw(struct tegra_emc *emc)
+{
+ u32 intmask = EMC_REFRESH_OVERFLOW_INT | EMC_CLKCHANGE_COMPLETE_INT;
+ u32 fbio_cfg5, emc_cfg, emc_dbg;
+ enum emc_dram_type dram_type;
+
+ fbio_cfg5 = readl_relaxed(emc->regs + EMC_FBIO_CFG5);
+ dram_type = fbio_cfg5 & EMC_FBIO_CFG5_DRAM_TYPE_MASK;
+
+ emc_cfg = readl_relaxed(emc->regs + EMC_CFG_2);
+
+ /* enable EMC and CAR to handshake on PLL divider/source changes */
+ emc_cfg |= EMC_CLKCHANGE_REQ_ENABLE;
+
+ /* configure clock change mode accordingly to DRAM type */
+ switch (dram_type) {
+ case DRAM_TYPE_LPDDR2:
+ emc_cfg |= EMC_CLKCHANGE_PD_ENABLE;
+ emc_cfg &= ~EMC_CLKCHANGE_SR_ENABLE;
+ break;
+
+ default:
+ emc_cfg &= ~EMC_CLKCHANGE_SR_ENABLE;
+ emc_cfg &= ~EMC_CLKCHANGE_PD_ENABLE;
+ break;
+ }
+
+ writel_relaxed(emc_cfg, emc->regs + EMC_CFG_2);
+
+ /* initialize interrupt */
+ writel_relaxed(intmask, emc->regs + EMC_INTMASK);
+ writel_relaxed(0xffffffff, emc->regs + EMC_INTSTATUS);
+
+ /* ensure that unwanted debug features are disabled */
+ emc_dbg = readl_relaxed(emc->regs + EMC_DBG);
+ emc_dbg |= EMC_DBG_CFG_PRIORITY;
+ emc_dbg &= ~EMC_DBG_READ_MUX_ASSEMBLY;
+ emc_dbg &= ~EMC_DBG_WRITE_MUX_ACTIVE;
+ emc_dbg &= ~EMC_DBG_FORCE_UPDATE;
+ writel_relaxed(emc_dbg, emc->regs + EMC_DBG);
+
+ return 0;
+}
+
+static long emc_round_rate(unsigned long rate,
+ unsigned long min_rate,
+ unsigned long max_rate,
+ void *arg)
+{
+ struct emc_timing *timing = NULL;
+ struct tegra_emc *emc = arg;
+ unsigned int i;
+
+ min_rate = min(min_rate, emc->timings[emc->num_timings - 1].rate);
+
+ for (i = 0; i < emc->num_timings; i++) {
+ if (emc->timings[i].rate < rate && i != emc->num_timings - 1)
+ continue;
+
+ if (emc->timings[i].rate > max_rate) {
+ i = max(i, 1u) - 1;
+
+ if (emc->timings[i].rate < min_rate)
+ break;
+ }
+
+ if (emc->timings[i].rate < min_rate)
+ continue;
+
+ timing = &emc->timings[i];
+ break;
+ }
+
+ if (!timing) {
+ dev_err(emc->dev, "no timing for rate %lu min %lu max %lu\n",
+ rate, min_rate, max_rate);
+ return -EINVAL;
+ }
+
+ return timing->rate;
+}
+
+static int tegra_emc_probe(struct platform_device *pdev)
+{
+ struct platform_device *mc;
+ struct device_node *np;
+ struct tegra_emc *emc;
+ int err;
+
+ if (of_get_child_count(pdev->dev.of_node) == 0) {
+ dev_info(&pdev->dev,
+ "device-tree node doesn't have memory timings\n");
+ return 0;
+ }
+
+ np = of_parse_phandle(pdev->dev.of_node, "nvidia,memory-controller", 0);
+ if (!np) {
+ dev_err(&pdev->dev, "could not get memory controller node\n");
+ return -ENOENT;
+ }
+
+ mc = of_find_device_by_node(np);
+ of_node_put(np);
+ if (!mc)
+ return -ENOENT;
+
+ np = emc_find_node_by_ram_code(&pdev->dev);
+ if (!np)
+ return -EINVAL;
+
+ emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL);
+ if (!emc) {
+ of_node_put(np);
+ return -ENOMEM;
+ }
+
+ emc->mc = platform_get_drvdata(mc);
+ if (!emc->mc)
+ return -EPROBE_DEFER;
+
+ init_completion(&emc->clk_handshake_complete);
+ emc->clk_nb.notifier_call = emc_clk_change_notify;
+ emc->dev = &pdev->dev;
+
+ err = emc_load_timings_from_dt(emc, np);
+ of_node_put(np);
+ if (err)
+ return err;
+
+ emc->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(emc->regs))
+ return PTR_ERR(emc->regs);
+
+ err = emc_setup_hw(emc);
+ if (err)
+ return err;
+
+ err = platform_get_irq(pdev, 0);
+ if (err < 0) {
+ dev_err(&pdev->dev, "interrupt not specified: %d\n", err);
+ return err;
+ }
+ emc->irq = err;
+
+ err = devm_request_irq(&pdev->dev, emc->irq, tegra_emc_isr, 0,
+ dev_name(&pdev->dev), emc);
+ if (err) {
+ dev_err(&pdev->dev, "failed to request irq: %d\n", err);
+ return err;
+ }
+
+ tegra20_clk_set_emc_round_callback(emc_round_rate, emc);
+
+ emc->clk = devm_clk_get(&pdev->dev, "emc");
+ if (IS_ERR(emc->clk)) {
+ err = PTR_ERR(emc->clk);
+ dev_err(&pdev->dev, "failed to get emc clock: %d\n", err);
+ goto unset_cb;
+ }
+
+ err = clk_notifier_register(emc->clk, &emc->clk_nb);
+ if (err) {
+ dev_err(&pdev->dev, "failed to register clk notifier: %d\n",
+ err);
+ goto unset_cb;
+ }
+
+ platform_set_drvdata(pdev, emc);
+
+ return 0;
+
+unset_cb:
+ tegra20_clk_set_emc_round_callback(NULL, NULL);
+
+ return err;
+}
+
+static int tegra_emc_suspend(struct device *dev)
+{
+ struct tegra_emc *emc = dev_get_drvdata(dev);
+
+ /*
+ * Suspending in a bad state will hang machine. The "prepared" var
+ * shall be always false here unless it's a kernel bug that caused
+ * suspending in a wrong order.
+ */
+ if (WARN_ON(emc->prepared) || emc->bad_state)
+ return -EINVAL;
+
+ emc->bad_state = true;
+
+ return 0;
+}
+
+static int tegra_emc_resume(struct device *dev)
+{
+ struct tegra_emc *emc = dev_get_drvdata(dev);
+
+ emc_setup_hw(emc);
+ emc->bad_state = false;
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra_emc_pm_ops = {
+ .suspend = tegra_emc_suspend,
+ .resume = tegra_emc_resume,
+};
+
+static const struct of_device_id tegra_emc_of_match[] = {
+ { .compatible = "nvidia,tegra30-emc", },
+ {},
+};
+
+static struct platform_driver tegra_emc_driver = {
+ .probe = tegra_emc_probe,
+ .driver = {
+ .name = "tegra30-emc",
+ .of_match_table = tegra_emc_of_match,
+ .pm = &tegra_emc_pm_ops,
+ .suppress_bind_attrs = true,
+ },
+};
+
+static int __init tegra_emc_init(void)
+{
+ return platform_driver_register(&tegra_emc_driver);
+}
+subsys_initcall(tegra_emc_init);
diff --git a/drivers/memory/tegra/tegra30.c b/drivers/memory/tegra/tegra30.c
index 14788fc2f9e8..b1226d3f067f 100644
--- a/drivers/memory/tegra/tegra30.c
+++ b/drivers/memory/tegra/tegra30.c
@@ -10,6 +10,46 @@
#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_DA_TURNS 0xd0
+#define MC_EMEM_ARB_DA_COVERS 0xd4
+#define MC_EMEM_ARB_MISC0 0xd8
+#define MC_EMEM_ARB_RING1_THROTTLE 0xe0
+
+static const unsigned long tegra30_mc_emem_regs[] = {
+ MC_EMEM_ARB_CFG,
+ MC_EMEM_ARB_OUTSTANDING_REQ,
+ 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_W2R,
+ MC_EMEM_ARB_DA_TURNS,
+ MC_EMEM_ARB_DA_COVERS,
+ MC_EMEM_ARB_MISC0,
+ MC_EMEM_ARB_RING1_THROTTLE,
+};
+
static const struct tegra_mc_client tegra30_mc_clients[] = {
{
.id = 0x00,
@@ -994,6 +1034,8 @@ const struct tegra_mc_soc tegra30_mc_soc = {
.atom_size = 16,
.client_id_mask = 0x7f,
.smmu = &tegra30_smmu_soc,
+ .emem_regs = tegra30_mc_emem_regs,
+ .num_emem_regs = ARRAY_SIZE(tegra30_mc_emem_regs),
.intmask = MC_INT_INVALID_SMMU_PAGE | MC_INT_SECURITY_VIOLATION |
MC_INT_DECERR_EMEM,
.reset_ops = &tegra_mc_reset_ops_common,
diff --git a/include/soc/tegra/mc.h b/include/soc/tegra/mc.h
index 16e2c2fb5f6c..1238e35653d1 100644
--- a/include/soc/tegra/mc.h
+++ b/include/soc/tegra/mc.h
@@ -181,7 +181,7 @@ struct tegra_mc {
spinlock_t lock;
};
-void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate);
+int tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate);
unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc);
#endif /* __SOC_TEGRA_MC_H__ */
--
2.22.0
^ permalink raw reply related
* [PATCH v10 11/15] dt-bindings: memory: Add binding for NVIDIA Tegra30 External Memory Controller
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
Add device-tree binding for NVIDIA Tegra30 External Memory Controller.
The binding is based on the Tegra124 EMC binding since hardware is
similar, although there are couple significant differences.
Note that the memory timing description is given in a platform-specific
form because there is no detailed information on how to convert a
typical-common DDR timing into the register values. The timing format is
borrowed from downstream kernel, hence there is no hurdle in regards to
upstreaming of memory timings for the boards.
Acked-by: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
.../nvidia,tegra30-emc.yaml | 336 ++++++++++++++++++
1 file changed, 336 insertions(+)
create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-emc.yaml
diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-emc.yaml b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-emc.yaml
new file mode 100644
index 000000000000..02c70c776414
--- /dev/null
+++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-emc.yaml
@@ -0,0 +1,336 @@
+# SPDX-License-Identifier: (GPL-2.0)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/memory-controllers/nvidia,tegra30-emc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVIDIA Tegra30 SoC External Memory Controller
+
+maintainers:
+ - Dmitry Osipenko <digetx@gmail.com>
+ - Jon Hunter <jonathanh@nvidia.com>
+ - Thierry Reding <thierry.reding@gmail.com>
+
+description: |
+ The EMC interfaces with the off-chip SDRAM to service the request stream
+ sent from Memory Controller. The EMC also has various performance-affecting
+ settings beyond the obvious SDRAM configuration parameters and initialization
+ settings. Tegra30 EMC supports multiple JEDEC standard protocols: LPDDR2,
+ LPDDR3, and DDR3.
+
+properties:
+ compatible:
+ const: nvidia,tegra30-emc
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ nvidia,memory-controller:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle of the Memory Controller node.
+
+patternProperties:
+ "^emc-timings-[0-9]+$":
+ type: object
+ properties:
+ nvidia,ram-code:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Value of RAM_CODE this timing set is used for.
+
+ patternProperties:
+ "^timing-[0-9]+$":
+ type: object
+ properties:
+ clock-frequency:
+ description:
+ Memory clock rate in Hz.
+ minimum: 1000000
+ maximum: 900000000
+
+ nvidia,emc-auto-cal-interval:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Pad calibration interval in microseconds.
+ minimum: 0
+ maximum: 2097151
+
+ nvidia,emc-mode-1:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Mode Register 1.
+
+ nvidia,emc-mode-2:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Mode Register 2.
+
+ nvidia,emc-mode-reset:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Mode Register 0.
+
+ nvidia,emc-zcal-cnt-long:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Number of EMC clocks to wait before issuing any commands after
+ sending ZCAL_MRW_CMD.
+ minimum: 0
+ maximum: 1023
+
+ nvidia,emc-cfg-dyn-self-ref:
+ type: boolean
+ description:
+ Dynamic self-refresh enabled.
+
+ nvidia,emc-cfg-periodic-qrst:
+ type: boolean
+ description:
+ FBIO "read" FIFO periodic resetting enabled.
+
+ nvidia,emc-configuration:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description:
+ EMC timing characterization data. These are the registers
+ (see section "18.13.2 EMC Registers" in the TRM) whose values
+ need to be specified, according to the board documentation.
+ items:
+ - description: EMC_RC
+ - description: EMC_RFC
+ - description: EMC_RAS
+ - description: EMC_RP
+ - description: EMC_R2W
+ - description: EMC_W2R
+ - description: EMC_R2P
+ - description: EMC_W2P
+ - description: EMC_RD_RCD
+ - description: EMC_WR_RCD
+ - description: EMC_RRD
+ - description: EMC_REXT
+ - description: EMC_WEXT
+ - description: EMC_WDV
+ - description: EMC_QUSE
+ - description: EMC_QRST
+ - description: EMC_QSAFE
+ - description: EMC_RDV
+ - description: EMC_REFRESH
+ - description: EMC_BURST_REFRESH_NUM
+ - description: EMC_PRE_REFRESH_REQ_CNT
+ - description: EMC_PDEX2WR
+ - description: EMC_PDEX2RD
+ - description: EMC_PCHG2PDEN
+ - description: EMC_ACT2PDEN
+ - description: EMC_AR2PDEN
+ - description: EMC_RW2PDEN
+ - description: EMC_TXSR
+ - description: EMC_TXSRDLL
+ - description: EMC_TCKE
+ - description: EMC_TFAW
+ - description: EMC_TRPAB
+ - description: EMC_TCLKSTABLE
+ - description: EMC_TCLKSTOP
+ - description: EMC_TREFBW
+ - description: EMC_QUSE_EXTRA
+ - description: EMC_FBIO_CFG6
+ - description: EMC_ODT_WRITE
+ - description: EMC_ODT_READ
+ - description: EMC_FBIO_CFG5
+ - description: EMC_CFG_DIG_DLL
+ - description: EMC_CFG_DIG_DLL_PERIOD
+ - description: EMC_DLL_XFORM_DQS0
+ - description: EMC_DLL_XFORM_DQS1
+ - description: EMC_DLL_XFORM_DQS2
+ - description: EMC_DLL_XFORM_DQS3
+ - description: EMC_DLL_XFORM_DQS4
+ - description: EMC_DLL_XFORM_DQS5
+ - description: EMC_DLL_XFORM_DQS6
+ - description: EMC_DLL_XFORM_DQS7
+ - description: EMC_DLL_XFORM_QUSE0
+ - description: EMC_DLL_XFORM_QUSE1
+ - description: EMC_DLL_XFORM_QUSE2
+ - description: EMC_DLL_XFORM_QUSE3
+ - description: EMC_DLL_XFORM_QUSE4
+ - description: EMC_DLL_XFORM_QUSE5
+ - description: EMC_DLL_XFORM_QUSE6
+ - description: EMC_DLL_XFORM_QUSE7
+ - description: EMC_DLI_TRIM_TXDQS0
+ - description: EMC_DLI_TRIM_TXDQS1
+ - description: EMC_DLI_TRIM_TXDQS2
+ - description: EMC_DLI_TRIM_TXDQS3
+ - description: EMC_DLI_TRIM_TXDQS4
+ - description: EMC_DLI_TRIM_TXDQS5
+ - description: EMC_DLI_TRIM_TXDQS6
+ - description: EMC_DLI_TRIM_TXDQS7
+ - description: EMC_DLL_XFORM_DQ0
+ - description: EMC_DLL_XFORM_DQ1
+ - description: EMC_DLL_XFORM_DQ2
+ - description: EMC_DLL_XFORM_DQ3
+ - description: EMC_XM2CMDPADCTRL
+ - description: EMC_XM2DQSPADCTRL2
+ - description: EMC_XM2DQPADCTRL2
+ - description: EMC_XM2CLKPADCTRL
+ - description: EMC_XM2COMPPADCTRL
+ - description: EMC_XM2VTTGENPADCTRL
+ - description: EMC_XM2VTTGENPADCTRL2
+ - description: EMC_XM2QUSEPADCTRL
+ - description: EMC_XM2DQSPADCTRL3
+ - description: EMC_CTT_TERM_CTRL
+ - description: EMC_ZCAL_INTERVAL
+ - description: EMC_ZCAL_WAIT_CNT
+ - description: EMC_MRS_WAIT_CNT
+ - description: EMC_AUTO_CAL_CONFIG
+ - description: EMC_CTT
+ - description: EMC_CTT_DURATION
+ - description: EMC_DYN_SELF_REF_CONTROL
+ - description: EMC_FBIO_SPARE
+ - description: EMC_CFG_RSV
+
+ required:
+ - clock-frequency
+ - nvidia,emc-auto-cal-interval
+ - nvidia,emc-mode-1
+ - nvidia,emc-mode-2
+ - nvidia,emc-mode-reset
+ - nvidia,emc-zcal-cnt-long
+ - nvidia,emc-configuration
+
+ additionalProperties: false
+
+ required:
+ - nvidia,ram-code
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - nvidia,memory-controller
+
+additionalProperties: false
+
+examples:
+ - |
+ external-memory-controller@7000f400 {
+ compatible = "nvidia,tegra30-emc";
+ reg = <0x7000f400 0x400>;
+ interrupts = <0 78 4>;
+ clocks = <&tegra_car 57>;
+
+ nvidia,memory-controller = <&mc>;
+
+ emc-timings-1 {
+ nvidia,ram-code = <1>;
+
+ timing-667000000 {
+ clock-frequency = <667000000>;
+
+ nvidia,emc-auto-cal-interval = <0x001fffff>;
+ nvidia,emc-mode-1 = <0x80100002>;
+ nvidia,emc-mode-2 = <0x80200018>;
+ nvidia,emc-mode-reset = <0x80000b71>;
+ nvidia,emc-zcal-cnt-long = <0x00000040>;
+ nvidia,emc-cfg-periodic-qrst;
+
+ nvidia,emc-configuration = <
+ 0x00000020 /* EMC_RC */
+ 0x0000006a /* EMC_RFC */
+ 0x00000017 /* EMC_RAS */
+ 0x00000007 /* EMC_RP */
+ 0x00000005 /* EMC_R2W */
+ 0x0000000c /* EMC_W2R */
+ 0x00000003 /* EMC_R2P */
+ 0x00000011 /* EMC_W2P */
+ 0x00000007 /* EMC_RD_RCD */
+ 0x00000007 /* EMC_WR_RCD */
+ 0x00000002 /* EMC_RRD */
+ 0x00000001 /* EMC_REXT */
+ 0x00000000 /* EMC_WEXT */
+ 0x00000007 /* EMC_WDV */
+ 0x0000000a /* EMC_QUSE */
+ 0x00000009 /* EMC_QRST */
+ 0x0000000b /* EMC_QSAFE */
+ 0x00000011 /* EMC_RDV */
+ 0x00001412 /* EMC_REFRESH */
+ 0x00000000 /* EMC_BURST_REFRESH_NUM */
+ 0x00000504 /* EMC_PRE_REFRESH_REQ_CNT */
+ 0x00000002 /* EMC_PDEX2WR */
+ 0x0000000e /* EMC_PDEX2RD */
+ 0x00000001 /* EMC_PCHG2PDEN */
+ 0x00000000 /* EMC_ACT2PDEN */
+ 0x0000000c /* EMC_AR2PDEN */
+ 0x00000016 /* EMC_RW2PDEN */
+ 0x00000072 /* EMC_TXSR */
+ 0x00000200 /* EMC_TXSRDLL */
+ 0x00000005 /* EMC_TCKE */
+ 0x00000015 /* EMC_TFAW */
+ 0x00000000 /* EMC_TRPAB */
+ 0x00000006 /* EMC_TCLKSTABLE */
+ 0x00000007 /* EMC_TCLKSTOP */
+ 0x00001453 /* EMC_TREFBW */
+ 0x0000000b /* EMC_QUSE_EXTRA */
+ 0x00000006 /* EMC_FBIO_CFG6 */
+ 0x00000000 /* EMC_ODT_WRITE */
+ 0x00000000 /* EMC_ODT_READ */
+ 0x00005088 /* EMC_FBIO_CFG5 */
+ 0xf00b0191 /* EMC_CFG_DIG_DLL */
+ 0x00008000 /* EMC_CFG_DIG_DLL_PERIOD */
+ 0x00000008 /* EMC_DLL_XFORM_DQS0 */
+ 0x00000008 /* EMC_DLL_XFORM_DQS1 */
+ 0x00000008 /* EMC_DLL_XFORM_DQS2 */
+ 0x00000008 /* EMC_DLL_XFORM_DQS3 */
+ 0x0000000a /* EMC_DLL_XFORM_DQS4 */
+ 0x0000000a /* EMC_DLL_XFORM_DQS5 */
+ 0x0000000a /* EMC_DLL_XFORM_DQS6 */
+ 0x0000000a /* EMC_DLL_XFORM_DQS7 */
+ 0x00018000 /* EMC_DLL_XFORM_QUSE0 */
+ 0x00018000 /* EMC_DLL_XFORM_QUSE1 */
+ 0x00018000 /* EMC_DLL_XFORM_QUSE2 */
+ 0x00018000 /* EMC_DLL_XFORM_QUSE3 */
+ 0x00000000 /* EMC_DLL_XFORM_QUSE4 */
+ 0x00000000 /* EMC_DLL_XFORM_QUSE5 */
+ 0x00000000 /* EMC_DLL_XFORM_QUSE6 */
+ 0x00000000 /* EMC_DLL_XFORM_QUSE7 */
+ 0x00000000 /* EMC_DLI_TRIM_TXDQS0 */
+ 0x00000000 /* EMC_DLI_TRIM_TXDQS1 */
+ 0x00000000 /* EMC_DLI_TRIM_TXDQS2 */
+ 0x00000000 /* EMC_DLI_TRIM_TXDQS3 */
+ 0x00000000 /* EMC_DLI_TRIM_TXDQS4 */
+ 0x00000000 /* EMC_DLI_TRIM_TXDQS5 */
+ 0x00000000 /* EMC_DLI_TRIM_TXDQS6 */
+ 0x00000000 /* EMC_DLI_TRIM_TXDQS7 */
+ 0x0000000a /* EMC_DLL_XFORM_DQ0 */
+ 0x0000000a /* EMC_DLL_XFORM_DQ1 */
+ 0x0000000a /* EMC_DLL_XFORM_DQ2 */
+ 0x0000000a /* EMC_DLL_XFORM_DQ3 */
+ 0x000002a0 /* EMC_XM2CMDPADCTRL */
+ 0x0800013d /* EMC_XM2DQSPADCTRL2 */
+ 0x22220000 /* EMC_XM2DQPADCTRL2 */
+ 0x77fff884 /* EMC_XM2CLKPADCTRL */
+ 0x01f1f501 /* EMC_XM2COMPPADCTRL */
+ 0x07077404 /* EMC_XM2VTTGENPADCTRL */
+ 0x54000000 /* EMC_XM2VTTGENPADCTRL2 */
+ 0x080001e8 /* EMC_XM2QUSEPADCTRL */
+ 0x0c000021 /* EMC_XM2DQSPADCTRL3 */
+ 0x00000802 /* EMC_CTT_TERM_CTRL */
+ 0x00020000 /* EMC_ZCAL_INTERVAL */
+ 0x00000100 /* EMC_ZCAL_WAIT_CNT */
+ 0x0155000c /* EMC_MRS_WAIT_CNT */
+ 0xa0f10000 /* EMC_AUTO_CAL_CONFIG */
+ 0x00000000 /* EMC_CTT */
+ 0x00000000 /* EMC_CTT_DURATION */
+ 0x800028a5 /* EMC_DYN_SELF_REF_CONTROL */
+ 0xe8000000 /* EMC_FBIO_SPARE */
+ 0xff00ff49 /* EMC_CFG_RSV */
+ >;
+ };
+ };
+ };
--
2.22.0
^ permalink raw reply related
* [PATCH v10 10/15] dt-bindings: memory: Add binding for NVIDIA Tegra30 Memory Controller
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
Add binding for the NVIDIA Tegra30 SoC Memory Controller.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
.../memory-controllers/nvidia,tegra30-mc.yaml | 167 ++++++++++++++++++
1 file changed, 167 insertions(+)
create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-mc.yaml
diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-mc.yaml b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-mc.yaml
new file mode 100644
index 000000000000..84fd57bcf0dc
--- /dev/null
+++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-mc.yaml
@@ -0,0 +1,167 @@
+# SPDX-License-Identifier: (GPL-2.0)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/memory-controllers/nvidia,tegra30-mc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVIDIA Tegra30 SoC Memory Controller
+
+maintainers:
+ - Dmitry Osipenko <digetx@gmail.com>
+ - Jon Hunter <jonathanh@nvidia.com>
+ - Thierry Reding <thierry.reding@gmail.com>
+
+description: |
+ Tegra30 Memory Controller architecturally consists of the following parts:
+
+ Arbitration Domains, which can handle a single request or response per
+ clock from a group of clients. Typically, a system has a single Arbitration
+ Domain, but an implementation may divide the client space into multiple
+ Arbitration Domains to increase the effective system bandwidth.
+
+ Protocol Arbiter, which manage a related pool of memory devices. A system
+ may have a single Protocol Arbiter or multiple Protocol Arbiters.
+
+ Memory Crossbar, which routes request and responses between Arbitration
+ Domains and Protocol Arbiters. In the simplest version of the system, the
+ Memory Crossbar is just a pass through between a single Arbitration Domain
+ and a single Protocol Arbiter.
+
+ Global Resources, which include things like configuration registers which
+ are shared across the Memory Subsystem.
+
+ The Tegra30 Memory Controller handles memory requests from internal clients
+ and arbitrates among them to allocate memory bandwidth for DDR3L and LPDDR2
+ SDRAMs.
+
+properties:
+ compatible:
+ const: nvidia,tegra30-mc
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ items:
+ - const: mc
+
+ interrupts:
+ maxItems: 1
+
+ "#reset-cells":
+ const: 1
+
+ "#iommu-cells":
+ const: 1
+
+patternProperties:
+ "^emc-timings-[0-9]+$":
+ type: object
+ properties:
+ nvidia,ram-code:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Value of RAM_CODE this timing set is used for.
+
+ patternProperties:
+ "^timing-[0-9]+$":
+ type: object
+ properties:
+ clock-frequency:
+ description:
+ Memory clock rate in Hz.
+ minimum: 1000000
+ maximum: 900000000
+
+ nvidia,emem-configuration:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description: |
+ Values to be written to the EMEM register block. See section
+ "18.13.1 MC Registers" in the TRM.
+ items:
+ - description: MC_EMEM_ARB_CFG
+ - description: MC_EMEM_ARB_OUTSTANDING_REQ
+ - description: MC_EMEM_ARB_TIMING_RCD
+ - description: MC_EMEM_ARB_TIMING_RP
+ - description: MC_EMEM_ARB_TIMING_RC
+ - description: MC_EMEM_ARB_TIMING_RAS
+ - description: MC_EMEM_ARB_TIMING_FAW
+ - description: MC_EMEM_ARB_TIMING_RRD
+ - description: MC_EMEM_ARB_TIMING_RAP2PRE
+ - description: MC_EMEM_ARB_TIMING_WAP2PRE
+ - description: MC_EMEM_ARB_TIMING_R2R
+ - description: MC_EMEM_ARB_TIMING_W2W
+ - description: MC_EMEM_ARB_TIMING_R2W
+ - description: MC_EMEM_ARB_TIMING_W2R
+ - description: MC_EMEM_ARB_DA_TURNS
+ - description: MC_EMEM_ARB_DA_COVERS
+ - description: MC_EMEM_ARB_MISC0
+ - description: MC_EMEM_ARB_RING1_THROTTLE
+
+ required:
+ - clock-frequency
+ - nvidia,emem-configuration
+
+ additionalProperties: false
+
+ required:
+ - nvidia,ram-code
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+ - "#reset-cells"
+ - "#iommu-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ memory-controller@7000f000 {
+ compatible = "nvidia,tegra30-mc";
+ reg = <0x7000f000 0x400>;
+ clocks = <&tegra_car 32>;
+ clock-names = "mc";
+
+ interrupts = <0 77 4>;
+
+ #iommu-cells = <1>;
+ #reset-cells = <1>;
+
+ emc-timings-1 {
+ nvidia,ram-code = <1>;
+
+ timing-667000000 {
+ clock-frequency = <667000000>;
+
+ nvidia,emem-configuration = <
+ 0x0000000a /* MC_EMEM_ARB_CFG */
+ 0xc0000079 /* MC_EMEM_ARB_OUTSTANDING_REQ */
+ 0x00000003 /* MC_EMEM_ARB_TIMING_RCD */
+ 0x00000004 /* MC_EMEM_ARB_TIMING_RP */
+ 0x00000010 /* MC_EMEM_ARB_TIMING_RC */
+ 0x0000000b /* MC_EMEM_ARB_TIMING_RAS */
+ 0x0000000a /* MC_EMEM_ARB_TIMING_FAW */
+ 0x00000001 /* MC_EMEM_ARB_TIMING_RRD */
+ 0x00000003 /* MC_EMEM_ARB_TIMING_RAP2PRE */
+ 0x0000000b /* MC_EMEM_ARB_TIMING_WAP2PRE */
+ 0x00000002 /* MC_EMEM_ARB_TIMING_R2R */
+ 0x00000002 /* MC_EMEM_ARB_TIMING_W2W */
+ 0x00000004 /* MC_EMEM_ARB_TIMING_R2W */
+ 0x00000008 /* MC_EMEM_ARB_TIMING_W2R */
+ 0x08040202 /* MC_EMEM_ARB_DA_TURNS */
+ 0x00130b10 /* MC_EMEM_ARB_DA_COVERS */
+ 0x70ea1f11 /* MC_EMEM_ARB_MISC0 */
+ 0x001f0000 /* MC_EMEM_ARB_RING1_THROTTLE */
+ >;
+ };
+ };
+ };
--
2.22.0
^ permalink raw reply related
* [PATCH v10 09/15] dt-bindings: memory: tegra30: Convert to Tegra124 YAML
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
The Tegra30 binding will actually differ from the Tegra124 a tad, in
particular the EMEM configuration description. Hence rename the binding
to Tegra124 during of the conversion to YAML.
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
.../nvidia,tegra124-mc.yaml | 152 ++++++++++++++++++
.../memory-controllers/nvidia,tegra30-mc.txt | 123 --------------
2 files changed, 152 insertions(+), 123 deletions(-)
create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra124-mc.yaml
delete mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-mc.txt
diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra124-mc.yaml b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra124-mc.yaml
new file mode 100644
index 000000000000..30d9fb193d7f
--- /dev/null
+++ b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra124-mc.yaml
@@ -0,0 +1,152 @@
+# SPDX-License-Identifier: (GPL-2.0)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/memory-controllers/nvidia,tegra124-mc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVIDIA Tegra124 SoC Memory Controller
+
+maintainers:
+ - Jon Hunter <jonathanh@nvidia.com>
+ - Thierry Reding <thierry.reding@gmail.com>
+
+description: |
+ Tegra124 SoC features a hybrid 2x32-bit / 1x64-bit memory controller.
+ These are interleaved to provide high performance with the load shared across
+ two memory channels. The Tegra124 Memory Controller handles memory requests
+ from internal clients and arbitrates among them to allocate memory bandwidth
+ for DDR3L and LPDDR3 SDRAMs.
+
+properties:
+ compatible:
+ const: nvidia,tegra124-mc
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ items:
+ - const: mc
+
+ interrupts:
+ maxItems: 1
+
+ "#reset-cells":
+ const: 1
+
+ "#iommu-cells":
+ const: 1
+
+patternProperties:
+ "^emc-timings-[0-9]+$":
+ type: object
+ properties:
+ nvidia,ram-code:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Value of RAM_CODE this timing set is used for.
+
+ patternProperties:
+ "^timing-[0-9]+$":
+ type: object
+ properties:
+ clock-frequency:
+ description:
+ Memory clock rate in Hz.
+ minimum: 1000000
+ maximum: 1066000000
+
+ nvidia,emem-configuration:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description: |
+ Values to be written to the EMEM register block. See section
+ "15.6.1 MC Registers" in the TRM.
+ items:
+ - description: MC_EMEM_ARB_CFG
+ - description: MC_EMEM_ARB_OUTSTANDING_REQ
+ - description: MC_EMEM_ARB_TIMING_RCD
+ - description: MC_EMEM_ARB_TIMING_RP
+ - description: MC_EMEM_ARB_TIMING_RC
+ - description: MC_EMEM_ARB_TIMING_RAS
+ - description: MC_EMEM_ARB_TIMING_FAW
+ - description: MC_EMEM_ARB_TIMING_RRD
+ - description: MC_EMEM_ARB_TIMING_RAP2PRE
+ - description: MC_EMEM_ARB_TIMING_WAP2PRE
+ - description: MC_EMEM_ARB_TIMING_R2R
+ - description: MC_EMEM_ARB_TIMING_W2W
+ - description: MC_EMEM_ARB_TIMING_R2W
+ - description: MC_EMEM_ARB_TIMING_W2R
+ - description: MC_EMEM_ARB_DA_TURNS
+ - description: MC_EMEM_ARB_DA_COVERS
+ - description: MC_EMEM_ARB_MISC0
+ - description: MC_EMEM_ARB_MISC1
+ - description: MC_EMEM_ARB_RING1_THROTTLE
+
+ required:
+ - clock-frequency
+ - nvidia,emem-configuration
+
+ additionalProperties: false
+
+ required:
+ - nvidia,ram-code
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+ - "#reset-cells"
+ - "#iommu-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ memory-controller@70019000 {
+ compatible = "nvidia,tegra124-mc";
+ reg = <0x0 0x70019000 0x0 0x1000>;
+ clocks = <&tegra_car 32>;
+ clock-names = "mc";
+
+ interrupts = <0 77 4>;
+
+ #iommu-cells = <1>;
+ #reset-cells = <1>;
+
+ emc-timings-3 {
+ nvidia,ram-code = <3>;
+
+ timing-12750000 {
+ clock-frequency = <12750000>;
+
+ nvidia,emem-configuration = <
+ 0x40040001 /* MC_EMEM_ARB_CFG */
+ 0x8000000a /* MC_EMEM_ARB_OUTSTANDING_REQ */
+ 0x00000001 /* MC_EMEM_ARB_TIMING_RCD */
+ 0x00000001 /* MC_EMEM_ARB_TIMING_RP */
+ 0x00000002 /* MC_EMEM_ARB_TIMING_RC */
+ 0x00000000 /* MC_EMEM_ARB_TIMING_RAS */
+ 0x00000002 /* MC_EMEM_ARB_TIMING_FAW */
+ 0x00000001 /* MC_EMEM_ARB_TIMING_RRD */
+ 0x00000002 /* MC_EMEM_ARB_TIMING_RAP2PRE */
+ 0x00000008 /* MC_EMEM_ARB_TIMING_WAP2PRE */
+ 0x00000003 /* MC_EMEM_ARB_TIMING_R2R */
+ 0x00000002 /* MC_EMEM_ARB_TIMING_W2W */
+ 0x00000003 /* MC_EMEM_ARB_TIMING_R2W */
+ 0x00000006 /* MC_EMEM_ARB_TIMING_W2R */
+ 0x06030203 /* MC_EMEM_ARB_DA_TURNS */
+ 0x000a0402 /* MC_EMEM_ARB_DA_COVERS */
+ 0x77e30303 /* MC_EMEM_ARB_MISC0 */
+ 0x70000f03 /* MC_EMEM_ARB_MISC1 */
+ 0x001f0000 /* MC_EMEM_ARB_RING1_THROTTLE */
+ >;
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-mc.txt b/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-mc.txt
deleted file mode 100644
index a878b5908a4d..000000000000
--- a/Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-mc.txt
+++ /dev/null
@@ -1,123 +0,0 @@
-NVIDIA Tegra Memory Controller device tree bindings
-===================================================
-
-memory-controller node
-----------------------
-
-Required properties:
-- compatible: Should be "nvidia,tegra<chip>-mc"
-- reg: Physical base address and length of the controller's registers.
-- clocks: Must contain an entry for each entry in clock-names.
- See ../clocks/clock-bindings.txt for details.
-- clock-names: Must include the following entries:
- - mc: the module's clock input
-- interrupts: The interrupt outputs from the controller.
-- #reset-cells : Should be 1. This cell represents memory client module ID.
- The assignments may be found in header file <dt-bindings/memory/tegra30-mc.h>
- or in the TRM documentation.
-
-Required properties for Tegra30, Tegra114, Tegra124, Tegra132 and Tegra210:
-- #iommu-cells: Should be 1. The single cell of the IOMMU specifier defines
- the SWGROUP of the master.
-
-This device implements an IOMMU that complies with the generic IOMMU binding.
-See ../iommu/iommu.txt for details.
-
-emc-timings subnode
--------------------
-
-The node should contain a "emc-timings" subnode for each supported RAM type (see field RAM_CODE in
-register PMC_STRAPPING_OPT_A).
-
-Required properties for "emc-timings" nodes :
-- nvidia,ram-code : Should contain the value of RAM_CODE this timing set is used for.
-
-timing subnode
---------------
-
-Each "emc-timings" node should contain a subnode for every supported EMC clock rate.
-
-Required properties for timing nodes :
-- clock-frequency : Should contain the memory clock rate in Hz.
-- nvidia,emem-configuration : Values to be written to the EMEM register block. For the Tegra124 SoC
-(see section "15.6.1 MC Registers" in the TRM), these are the registers whose values need to be
-specified, according to the board documentation:
-
- MC_EMEM_ARB_CFG
- MC_EMEM_ARB_OUTSTANDING_REQ
- 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_W2R
- MC_EMEM_ARB_DA_TURNS
- MC_EMEM_ARB_DA_COVERS
- MC_EMEM_ARB_MISC0
- MC_EMEM_ARB_MISC1
- MC_EMEM_ARB_RING1_THROTTLE
-
-Example SoC include file:
-
-/ {
- mc: memory-controller@70019000 {
- compatible = "nvidia,tegra124-mc";
- reg = <0x0 0x70019000 0x0 0x1000>;
- clocks = <&tegra_car TEGRA124_CLK_MC>;
- clock-names = "mc";
-
- interrupts = <GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH>;
-
- #iommu-cells = <1>;
- #reset-cells = <1>;
- };
-
- sdhci@700b0000 {
- compatible = "nvidia,tegra124-sdhci";
- ...
- iommus = <&mc TEGRA_SWGROUP_SDMMC1A>;
- resets = <&mc TEGRA124_MC_RESET_SDMMC1>;
- };
-};
-
-Example board file:
-
-/ {
- memory-controller@70019000 {
- emc-timings-3 {
- nvidia,ram-code = <3>;
-
- timing-12750000 {
- clock-frequency = <12750000>;
-
- nvidia,emem-configuration = <
- 0x40040001 /* MC_EMEM_ARB_CFG */
- 0x8000000a /* MC_EMEM_ARB_OUTSTANDING_REQ */
- 0x00000001 /* MC_EMEM_ARB_TIMING_RCD */
- 0x00000001 /* MC_EMEM_ARB_TIMING_RP */
- 0x00000002 /* MC_EMEM_ARB_TIMING_RC */
- 0x00000000 /* MC_EMEM_ARB_TIMING_RAS */
- 0x00000002 /* MC_EMEM_ARB_TIMING_FAW */
- 0x00000001 /* MC_EMEM_ARB_TIMING_RRD */
- 0x00000002 /* MC_EMEM_ARB_TIMING_RAP2PRE */
- 0x00000008 /* MC_EMEM_ARB_TIMING_WAP2PRE */
- 0x00000003 /* MC_EMEM_ARB_TIMING_R2R */
- 0x00000002 /* MC_EMEM_ARB_TIMING_W2W */
- 0x00000003 /* MC_EMEM_ARB_TIMING_R2W */
- 0x00000006 /* MC_EMEM_ARB_TIMING_W2R */
- 0x06030203 /* MC_EMEM_ARB_DA_TURNS */
- 0x000a0402 /* MC_EMEM_ARB_DA_COVERS */
- 0x77e30303 /* MC_EMEM_ARB_MISC0 */
- 0x70000f03 /* MC_EMEM_ARB_MISC1 */
- 0x001f0000 /* MC_EMEM_ARB_RING1_THROTTLE */
- >;
- };
- };
- };
-};
--
2.22.0
^ permalink raw reply related
* [PATCH v10 08/15] memory: tegra20-emc: wait_for_completion_timeout() doesn't return error
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
The "interruptible" variant may error out, the "uninterruptible" not.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/memory/tegra/tegra20-emc.c | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c
index da75efc632c7..1b23b1c34476 100644
--- a/drivers/memory/tegra/tegra20-emc.c
+++ b/drivers/memory/tegra/tegra20-emc.c
@@ -224,7 +224,7 @@ static int emc_prepare_timing_change(struct tegra_emc *emc, unsigned long rate)
static int emc_complete_timing_change(struct tegra_emc *emc, bool flush)
{
- long timeout;
+ unsigned long timeout;
dev_dbg(emc->dev, "%s: flush %d\n", __func__, flush);
@@ -240,10 +240,6 @@ static int emc_complete_timing_change(struct tegra_emc *emc, bool flush)
if (timeout == 0) {
dev_err(emc->dev, "EMC-CAR handshake failed\n");
return -EIO;
- } else if (timeout < 0) {
- dev_err(emc->dev, "failed to wait for EMC-CAR handshake: %ld\n",
- timeout);
- return timeout;
}
return 0;
--
2.22.0
^ permalink raw reply related
* [PATCH v10 07/15] memory: tegra20-emc: Increase handshake timeout
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
Turned out that it could take over a millisecond under some circumstances,
like running on a very low CPU/memory frequency. TRM says that handshake
happens when there is a "safe" moment, but not explains exactly what that
moment is. Apparently at least memory should be idling and thus the low
frequency should be a reasonable cause for a longer handshake delay.
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/memory/tegra/tegra20-emc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c
index 25a6aad6a7a9..da75efc632c7 100644
--- a/drivers/memory/tegra/tegra20-emc.c
+++ b/drivers/memory/tegra/tegra20-emc.c
@@ -236,7 +236,7 @@ static int emc_complete_timing_change(struct tegra_emc *emc, bool flush)
}
timeout = wait_for_completion_timeout(&emc->clk_handshake_complete,
- usecs_to_jiffies(100));
+ msecs_to_jiffies(100));
if (timeout == 0) {
dev_err(emc->dev, "EMC-CAR handshake failed\n");
return -EIO;
--
2.22.0
^ permalink raw reply related
* [PATCH v10 06/15] memory: tegra20-emc: Print a brief info message about the timings
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
During boot print how many memory timings got the driver and what's the
RAM code. This is a very useful information when something is wrong with
boards memory timing.
Suggested-by: Marc Dietrich <marvin24@gmx.de>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/memory/tegra/tegra20-emc.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c
index 85c24f285fd4..25a6aad6a7a9 100644
--- a/drivers/memory/tegra/tegra20-emc.c
+++ b/drivers/memory/tegra/tegra20-emc.c
@@ -368,6 +368,13 @@ static int tegra_emc_load_timings_from_dt(struct tegra_emc *emc,
sort(emc->timings, emc->num_timings, sizeof(*timing), cmp_timings,
NULL);
+ dev_info(emc->dev,
+ "got %u timings for RAM code %u (min %luMHz max %luMHz)\n",
+ emc->num_timings,
+ tegra_read_ram_code(),
+ emc->timings[0].rate / 1000000,
+ emc->timings[emc->num_timings - 1].rate / 1000000);
+
return 0;
}
--
2.22.0
^ permalink raw reply related
* [PATCH v10 05/15] memory: tegra20-emc: Pre-configure debug register
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
The driver expects certain debug features to be disabled in order to
work properly. Let's disable them explicitly for consistency and to not
rely on a boot state.
Acked-by: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/memory/tegra/tegra20-emc.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c
index 1ce351dd5461..85c24f285fd4 100644
--- a/drivers/memory/tegra/tegra20-emc.c
+++ b/drivers/memory/tegra/tegra20-emc.c
@@ -22,6 +22,7 @@
#define EMC_INTSTATUS 0x000
#define EMC_INTMASK 0x004
+#define EMC_DBG 0x008
#define EMC_TIMING_CONTROL 0x028
#define EMC_RC 0x02c
#define EMC_RFC 0x030
@@ -80,6 +81,12 @@
#define EMC_REFRESH_OVERFLOW_INT BIT(3)
#define EMC_CLKCHANGE_COMPLETE_INT BIT(4)
+#define EMC_DBG_READ_MUX_ASSEMBLY BIT(0)
+#define EMC_DBG_WRITE_MUX_ACTIVE BIT(1)
+#define EMC_DBG_FORCE_UPDATE BIT(2)
+#define EMC_DBG_READ_DQM_CTRL BIT(9)
+#define EMC_DBG_CFG_PRIORITY BIT(24)
+
static const u16 emc_timing_registers[] = {
EMC_RC,
EMC_RFC,
@@ -396,7 +403,7 @@ tegra_emc_find_node_by_ram_code(struct device *dev)
static int emc_setup_hw(struct tegra_emc *emc)
{
u32 intmask = EMC_REFRESH_OVERFLOW_INT | EMC_CLKCHANGE_COMPLETE_INT;
- u32 emc_cfg;
+ u32 emc_cfg, emc_dbg;
emc_cfg = readl_relaxed(emc->regs + EMC_CFG_2);
@@ -419,6 +426,14 @@ static int emc_setup_hw(struct tegra_emc *emc)
writel_relaxed(intmask, emc->regs + EMC_INTMASK);
writel_relaxed(intmask, emc->regs + EMC_INTSTATUS);
+ /* ensure that unwanted debug features are disabled */
+ emc_dbg = readl_relaxed(emc->regs + EMC_DBG);
+ emc_dbg |= EMC_DBG_CFG_PRIORITY;
+ emc_dbg &= ~EMC_DBG_READ_MUX_ASSEMBLY;
+ emc_dbg &= ~EMC_DBG_WRITE_MUX_ACTIVE;
+ emc_dbg &= ~EMC_DBG_FORCE_UPDATE;
+ writel_relaxed(emc_dbg, emc->regs + EMC_DBG);
+
return 0;
}
--
2.22.0
^ permalink raw reply related
* [PATCH v10 04/15] memory: tegra20-emc: Include io.h instead of iopoll.h
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
The register polling code was gone, but the included header change was
missed. Fix it up for consistency.
Acked-by: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/memory/tegra/tegra20-emc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c
index b519f02b0ee9..1ce351dd5461 100644
--- a/drivers/memory/tegra/tegra20-emc.c
+++ b/drivers/memory/tegra/tegra20-emc.c
@@ -10,7 +10,7 @@
#include <linux/completion.h>
#include <linux/err.h>
#include <linux/interrupt.h>
-#include <linux/iopoll.h>
+#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
--
2.22.0
^ permalink raw reply related
* [PATCH v10 03/15] memory: tegra20-emc: Adapt for clock driver changes
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
Now Terga20 and Tegra30 EMC drivers should provide clock-rounding
functionality using the new Tegra-CLK driver API.
Acked-by: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/memory/tegra/tegra20-emc.c | 50 ++++++++++++++++++++++++++++--
1 file changed, 48 insertions(+), 2 deletions(-)
diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c
index da8fa592b071..b519f02b0ee9 100644
--- a/drivers/memory/tegra/tegra20-emc.c
+++ b/drivers/memory/tegra/tegra20-emc.c
@@ -6,6 +6,7 @@
*/
#include <linux/clk.h>
+#include <linux/clk/tegra.h>
#include <linux/completion.h>
#include <linux/err.h>
#include <linux/interrupt.h>
@@ -421,6 +422,44 @@ static int emc_setup_hw(struct tegra_emc *emc)
return 0;
}
+static long emc_round_rate(unsigned long rate,
+ unsigned long min_rate,
+ unsigned long max_rate,
+ void *arg)
+{
+ struct emc_timing *timing = NULL;
+ struct tegra_emc *emc = arg;
+ unsigned int i;
+
+ min_rate = min(min_rate, emc->timings[emc->num_timings - 1].rate);
+
+ for (i = 0; i < emc->num_timings; i++) {
+ if (emc->timings[i].rate < rate && i != emc->num_timings - 1)
+ continue;
+
+ if (emc->timings[i].rate > max_rate) {
+ i = max(i, 1u) - 1;
+
+ if (emc->timings[i].rate < min_rate)
+ break;
+ }
+
+ if (emc->timings[i].rate < min_rate)
+ continue;
+
+ timing = &emc->timings[i];
+ break;
+ }
+
+ if (!timing) {
+ dev_err(emc->dev, "no timing for rate %lu min %lu max %lu\n",
+ rate, min_rate, max_rate);
+ return -EINVAL;
+ }
+
+ return timing->rate;
+}
+
static int tegra_emc_probe(struct platform_device *pdev)
{
struct device_node *np;
@@ -477,21 +516,28 @@ static int tegra_emc_probe(struct platform_device *pdev)
return err;
}
+ tegra20_clk_set_emc_round_callback(emc_round_rate, emc);
+
emc->clk = devm_clk_get(&pdev->dev, "emc");
if (IS_ERR(emc->clk)) {
err = PTR_ERR(emc->clk);
dev_err(&pdev->dev, "failed to get emc clock: %d\n", err);
- return err;
+ goto unset_cb;
}
err = clk_notifier_register(emc->clk, &emc->clk_nb);
if (err) {
dev_err(&pdev->dev, "failed to register clk notifier: %d\n",
err);
- return err;
+ goto unset_cb;
}
return 0;
+
+unset_cb:
+ tegra20_clk_set_emc_round_callback(NULL, NULL);
+
+ return err;
}
static const struct of_device_id tegra_emc_of_match[] = {
--
2.22.0
^ permalink raw reply related
* [PATCH v10 02/15] memory: tegra20-emc: Drop setting EMC rate to max on probe
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
The memory frequency scaling will be managed by tegra20-devfreq driver
and PM QoS once all the prerequisite patches will get upstreamed.
The parent clock is now managed by the clock driver and we also should
assume that PLLM rate can't be changed on some devices (Galaxy Tab 10.1
for example). Altogether there is no point in touching of clock's rate
from the EMC driver.
Acked-by: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/memory/tegra/tegra20-emc.c | 78 +-----------------------------
1 file changed, 1 insertion(+), 77 deletions(-)
diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c
index 9ee5bef49e47..da8fa592b071 100644
--- a/drivers/memory/tegra/tegra20-emc.c
+++ b/drivers/memory/tegra/tegra20-emc.c
@@ -137,9 +137,6 @@ struct tegra_emc {
struct device *dev;
struct completion clk_handshake_complete;
struct notifier_block clk_nb;
- struct clk *backup_clk;
- struct clk *emc_mux;
- struct clk *pll_m;
struct clk *clk;
void __iomem *regs;
@@ -424,41 +421,6 @@ static int emc_setup_hw(struct tegra_emc *emc)
return 0;
}
-static int emc_init(struct tegra_emc *emc, unsigned long rate)
-{
- int err;
-
- err = clk_set_parent(emc->emc_mux, emc->backup_clk);
- if (err) {
- dev_err(emc->dev,
- "failed to reparent to backup source: %d\n", err);
- return err;
- }
-
- err = clk_set_rate(emc->pll_m, rate);
- if (err) {
- dev_err(emc->dev,
- "failed to change pll_m rate: %d\n", err);
- return err;
- }
-
- err = clk_set_parent(emc->emc_mux, emc->pll_m);
- if (err) {
- dev_err(emc->dev,
- "failed to reparent to pll_m: %d\n", err);
- return err;
- }
-
- err = clk_set_rate(emc->clk, rate);
- if (err) {
- dev_err(emc->dev,
- "failed to change emc rate: %d\n", err);
- return err;
- }
-
- return 0;
-}
-
static int tegra_emc_probe(struct platform_device *pdev)
{
struct device_node *np;
@@ -522,52 +484,14 @@ static int tegra_emc_probe(struct platform_device *pdev)
return err;
}
- emc->pll_m = clk_get_sys(NULL, "pll_m");
- if (IS_ERR(emc->pll_m)) {
- err = PTR_ERR(emc->pll_m);
- dev_err(&pdev->dev, "failed to get pll_m clock: %d\n", err);
- return err;
- }
-
- emc->backup_clk = clk_get_sys(NULL, "pll_p");
- if (IS_ERR(emc->backup_clk)) {
- err = PTR_ERR(emc->backup_clk);
- dev_err(&pdev->dev, "failed to get pll_p clock: %d\n", err);
- goto put_pll_m;
- }
-
- emc->emc_mux = clk_get_parent(emc->clk);
- if (IS_ERR(emc->emc_mux)) {
- err = PTR_ERR(emc->emc_mux);
- dev_err(&pdev->dev, "failed to get emc_mux clock: %d\n", err);
- goto put_backup;
- }
-
err = clk_notifier_register(emc->clk, &emc->clk_nb);
if (err) {
dev_err(&pdev->dev, "failed to register clk notifier: %d\n",
err);
- goto put_backup;
- }
-
- /* set DRAM clock rate to maximum */
- err = emc_init(emc, emc->timings[emc->num_timings - 1].rate);
- if (err) {
- dev_err(&pdev->dev, "failed to initialize EMC clock rate: %d\n",
- err);
- goto unreg_notifier;
+ return err;
}
return 0;
-
-unreg_notifier:
- clk_notifier_unregister(emc->clk, &emc->clk_nb);
-put_backup:
- clk_put(emc->backup_clk);
-put_pll_m:
- clk_put(emc->pll_m);
-
- return err;
}
static const struct of_device_id tegra_emc_of_match[] = {
--
2.22.0
^ permalink raw reply related
* [PATCH v10 01/15] clk: tegra20/30: Add custom EMC clock implementation
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
In-Reply-To: <20190811210043.20122-1-digetx@gmail.com>
A proper External Memory Controller clock rounding and parent selection
functionality is required by the EMC drivers, it is not available using
the generic clock implementation because only the Memory Controller driver
is aware of what clock rates are actually available for a particular
device. EMC drivers will have to register a Tegra-specific CLK-API
callback which will perform rounding of a requested rate. EMC clock users
won't be able to request EMC clock by getting -EPROBE_DEFER until EMC
driver is probed and the callback is set up.
The functionality is somewhat similar to the clk-emc.c which serves
Tegra124+ SoCs. The later HW generations support more parent clock sources
and the HW configuration / integration with the EMC drivers differs a tad
from the older gens, hence it's not really worth to try to squash
everything into a single source file.
Acked-by: Peter De Schrijver <pdeschrijver@nvidia.com>
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
---
drivers/clk/tegra/Makefile | 2 +
drivers/clk/tegra/clk-tegra20-emc.c | 293 ++++++++++++++++++++++++++++
drivers/clk/tegra/clk-tegra20.c | 55 ++----
drivers/clk/tegra/clk-tegra30.c | 38 ++--
drivers/clk/tegra/clk.h | 3 +
include/linux/clk/tegra.h | 11 ++
6 files changed, 350 insertions(+), 52 deletions(-)
create mode 100644 drivers/clk/tegra/clk-tegra20-emc.c
diff --git a/drivers/clk/tegra/Makefile b/drivers/clk/tegra/Makefile
index 4812e45c2214..df966ca06788 100644
--- a/drivers/clk/tegra/Makefile
+++ b/drivers/clk/tegra/Makefile
@@ -17,7 +17,9 @@ obj-y += clk-tegra-fixed.o
obj-y += clk-tegra-super-gen4.o
obj-$(CONFIG_TEGRA_CLK_EMC) += clk-emc.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += clk-tegra20.o
+obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += clk-tegra20-emc.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += clk-tegra30.o
+obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += clk-tegra20-emc.o
obj-$(CONFIG_ARCH_TEGRA_114_SOC) += clk-tegra114.o
obj-$(CONFIG_ARCH_TEGRA_124_SOC) += clk-tegra124.o
obj-$(CONFIG_TEGRA_CLK_DFLL) += clk-tegra124-dfll-fcpu.o
diff --git a/drivers/clk/tegra/clk-tegra20-emc.c b/drivers/clk/tegra/clk-tegra20-emc.c
new file mode 100644
index 000000000000..03bf0009a33c
--- /dev/null
+++ b/drivers/clk/tegra/clk-tegra20-emc.c
@@ -0,0 +1,293 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Based on drivers/clk/tegra/clk-emc.c
+ * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author: Dmitry Osipenko <digetx@gmail.com>
+ * Copyright (C) 2019 GRATE-DRIVER project
+ */
+
+#define pr_fmt(fmt) "tegra-emc-clk: " fmt
+
+#include <linux/bits.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/tegra.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include "clk.h"
+
+#define CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK GENMASK(7, 0)
+#define CLK_SOURCE_EMC_2X_CLK_SRC_MASK GENMASK(31, 30)
+#define CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT 30
+
+#define MC_EMC_SAME_FREQ BIT(16)
+#define USE_PLLM_UD BIT(29)
+
+#define EMC_SRC_PLL_M 0
+#define EMC_SRC_PLL_C 1
+#define EMC_SRC_PLL_P 2
+#define EMC_SRC_CLK_M 3
+
+static const char * const emc_parent_clk_names[] = {
+ "pll_m", "pll_c", "pll_p", "clk_m",
+};
+
+struct tegra_clk_emc {
+ struct clk_hw hw;
+ void __iomem *reg;
+ bool mc_same_freq;
+ bool want_low_jitter;
+
+ tegra20_clk_emc_round_cb *round_cb;
+ void *cb_arg;
+};
+
+static inline struct tegra_clk_emc *to_tegra_clk_emc(struct clk_hw *hw)
+{
+ return container_of(hw, struct tegra_clk_emc, hw);
+}
+
+static unsigned long emc_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
+ u32 val, div;
+
+ val = readl_relaxed(emc->reg);
+ div = val & CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK;
+
+ return DIV_ROUND_UP(parent_rate * 2, div + 2);
+}
+
+static u8 emc_get_parent(struct clk_hw *hw)
+{
+ struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
+
+ return readl_relaxed(emc->reg) >> CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT;
+}
+
+static int emc_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
+ u32 val, div;
+
+ val = readl_relaxed(emc->reg);
+ val &= ~CLK_SOURCE_EMC_2X_CLK_SRC_MASK;
+ val |= index << CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT;
+
+ div = val & CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK;
+
+ if (index == EMC_SRC_PLL_M && div == 0 && emc->want_low_jitter)
+ val |= USE_PLLM_UD;
+ else
+ val &= ~USE_PLLM_UD;
+
+ if (emc->mc_same_freq)
+ val |= MC_EMC_SAME_FREQ;
+ else
+ val &= ~MC_EMC_SAME_FREQ;
+
+ writel_relaxed(val, emc->reg);
+
+ fence_udelay(1, emc->reg);
+
+ return 0;
+}
+
+static int emc_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
+ unsigned int index;
+ u32 val, div;
+
+ div = div_frac_get(rate, parent_rate, 8, 1, 0);
+
+ val = readl_relaxed(emc->reg);
+ val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK;
+ val |= div;
+
+ index = val >> CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT;
+
+ if (index == EMC_SRC_PLL_M && div == 0 && emc->want_low_jitter)
+ val |= USE_PLLM_UD;
+ else
+ val &= ~USE_PLLM_UD;
+
+ if (emc->mc_same_freq)
+ val |= MC_EMC_SAME_FREQ;
+ else
+ val &= ~MC_EMC_SAME_FREQ;
+
+ writel_relaxed(val, emc->reg);
+
+ fence_udelay(1, emc->reg);
+
+ return 0;
+}
+
+static int emc_set_rate_and_parent(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate,
+ u8 index)
+{
+ struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
+ u32 val, div;
+
+ div = div_frac_get(rate, parent_rate, 8, 1, 0);
+
+ val = readl_relaxed(emc->reg);
+
+ val &= ~CLK_SOURCE_EMC_2X_CLK_SRC_MASK;
+ val |= index << CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT;
+
+ val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK;
+ val |= div;
+
+ if (index == EMC_SRC_PLL_M && div == 0 && emc->want_low_jitter)
+ val |= USE_PLLM_UD;
+ else
+ val &= ~USE_PLLM_UD;
+
+ if (emc->mc_same_freq)
+ val |= MC_EMC_SAME_FREQ;
+ else
+ val &= ~MC_EMC_SAME_FREQ;
+
+ writel_relaxed(val, emc->reg);
+
+ fence_udelay(1, emc->reg);
+
+ return 0;
+}
+
+static int emc_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
+{
+ struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
+ struct clk_hw *parent_hw;
+ unsigned long divided_rate;
+ unsigned long parent_rate;
+ unsigned int i;
+ long emc_rate;
+ int div;
+
+ emc_rate = emc->round_cb(req->rate, req->min_rate, req->max_rate,
+ emc->cb_arg);
+ if (emc_rate < 0)
+ return emc_rate;
+
+ for (i = 0; i < ARRAY_SIZE(emc_parent_clk_names); i++) {
+ parent_hw = clk_hw_get_parent_by_index(hw, i);
+
+ if (req->best_parent_hw == parent_hw)
+ parent_rate = req->best_parent_rate;
+ else
+ parent_rate = clk_hw_get_rate(parent_hw);
+
+ if (emc_rate > parent_rate)
+ continue;
+
+ div = div_frac_get(emc_rate, parent_rate, 8, 1, 0);
+ divided_rate = DIV_ROUND_UP(parent_rate * 2, div + 2);
+
+ if (divided_rate != emc_rate)
+ continue;
+
+ req->best_parent_rate = parent_rate;
+ req->best_parent_hw = parent_hw;
+ req->rate = emc_rate;
+ break;
+ }
+
+ if (i == ARRAY_SIZE(emc_parent_clk_names)) {
+ pr_err_once("can't find parent for rate %lu emc_rate %lu\n",
+ req->rate, emc_rate);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct clk_ops tegra_clk_emc_ops = {
+ .recalc_rate = emc_recalc_rate,
+ .get_parent = emc_get_parent,
+ .set_parent = emc_set_parent,
+ .set_rate = emc_set_rate,
+ .set_rate_and_parent = emc_set_rate_and_parent,
+ .determine_rate = emc_determine_rate,
+};
+
+void tegra20_clk_set_emc_round_callback(tegra20_clk_emc_round_cb *round_cb,
+ void *cb_arg)
+{
+ struct clk *clk = __clk_lookup("emc");
+ struct tegra_clk_emc *emc;
+ struct clk_hw *hw;
+
+ if (clk) {
+ hw = __clk_get_hw(clk);
+ emc = to_tegra_clk_emc(hw);
+
+ emc->round_cb = round_cb;
+ emc->cb_arg = cb_arg;
+ }
+}
+
+bool tegra20_clk_emc_driver_available(struct clk_hw *emc_hw)
+{
+ return to_tegra_clk_emc(emc_hw)->round_cb != NULL;
+}
+
+struct clk *tegra20_clk_register_emc(void __iomem *ioaddr, bool low_jitter)
+{
+ struct tegra_clk_emc *emc;
+ struct clk_init_data init;
+ struct clk *clk;
+
+ emc = kzalloc(sizeof(*emc), GFP_KERNEL);
+ if (!emc)
+ return NULL;
+
+ /*
+ * EMC stands for External Memory Controller.
+ *
+ * We don't want EMC clock to be disabled ever by gating its
+ * parent and whatnot because system is busted immediately in that
+ * case, hence the clock is marked as critical.
+ */
+ init.name = "emc";
+ init.ops = &tegra_clk_emc_ops;
+ init.flags = CLK_IS_CRITICAL;
+ init.parent_names = emc_parent_clk_names;
+ init.num_parents = ARRAY_SIZE(emc_parent_clk_names);
+
+ emc->reg = ioaddr;
+ emc->hw.init = &init;
+ emc->want_low_jitter = low_jitter;
+
+ clk = clk_register(NULL, &emc->hw);
+ if (IS_ERR(clk)) {
+ kfree(emc);
+ return NULL;
+ }
+
+ return clk;
+}
+
+int tegra20_clk_prepare_emc_mc_same_freq(struct clk *emc_clk, bool same)
+{
+ struct tegra_clk_emc *emc;
+ struct clk_hw *hw;
+
+ if (!emc_clk)
+ return -EINVAL;
+
+ hw = __clk_get_hw(emc_clk);
+ emc = to_tegra_clk_emc(hw);
+ emc->mc_same_freq = same;
+
+ return 0;
+}
diff --git a/drivers/clk/tegra/clk-tegra20.c b/drivers/clk/tegra/clk-tegra20.c
index bcd871134f45..cceefbd67a3b 100644
--- a/drivers/clk/tegra/clk-tegra20.c
+++ b/drivers/clk/tegra/clk-tegra20.c
@@ -130,8 +130,6 @@ static struct cpu_clk_suspend_context {
static void __iomem *clk_base;
static void __iomem *pmc_base;
-static DEFINE_SPINLOCK(emc_lock);
-
#define TEGRA_INIT_DATA_MUX(_name, _parents, _offset, \
_clk_num, _gate_flags, _clk_id) \
TEGRA_INIT_DATA(_name, NULL, NULL, _parents, _offset, \
@@ -760,7 +758,6 @@ static const char *pwm_parents[] = { "pll_p", "pll_c", "audio", "clk_m",
static const char *mux_pllpcm_clkm[] = { "pll_p", "pll_c", "pll_m", "clk_m" };
static const char *mux_pllpdc_clkm[] = { "pll_p", "pll_d_out0", "pll_c",
"clk_m" };
-static const char *mux_pllmcp_clkm[] = { "pll_m", "pll_c", "pll_p", "clk_m" };
static struct tegra_periph_init_data tegra_periph_clk_list[] = {
TEGRA_INIT_DATA_MUX("i2s1", i2s1_parents, CLK_SOURCE_I2S1, 11, TEGRA_PERIPH_ON_APB, TEGRA20_CLK_I2S1),
@@ -787,41 +784,6 @@ static struct tegra_periph_init_data tegra_periph_nodiv_clk_list[] = {
TEGRA_INIT_DATA_NODIV("disp2", mux_pllpdc_clkm, CLK_SOURCE_DISP2, 30, 2, 26, 0, TEGRA20_CLK_DISP2),
};
-static void __init tegra20_emc_clk_init(void)
-{
- const u32 use_pllm_ud = BIT(29);
- struct clk *clk;
- u32 emc_reg;
-
- clk = clk_register_mux(NULL, "emc_mux", mux_pllmcp_clkm,
- ARRAY_SIZE(mux_pllmcp_clkm),
- CLK_SET_RATE_NO_REPARENT,
- clk_base + CLK_SOURCE_EMC,
- 30, 2, 0, &emc_lock);
-
- clk = tegra_clk_register_mc("mc", "emc_mux", clk_base + CLK_SOURCE_EMC,
- &emc_lock);
- clks[TEGRA20_CLK_MC] = clk;
-
- /* un-divided pll_m_out0 is currently unsupported */
- emc_reg = readl_relaxed(clk_base + CLK_SOURCE_EMC);
- if (emc_reg & use_pllm_ud) {
- pr_err("%s: un-divided PllM_out0 used as clock source\n",
- __func__);
- return;
- }
-
- /*
- * Note that 'emc_mux' source and 'emc' rate shouldn't be changed at
- * the same time due to a HW bug, this won't happen because we're
- * defining 'emc_mux' and 'emc' as distinct clocks.
- */
- clk = tegra_clk_register_divider("emc", "emc_mux",
- clk_base + CLK_SOURCE_EMC, CLK_IS_CRITICAL,
- TEGRA_DIVIDER_INT, 0, 8, 1, &emc_lock);
- clks[TEGRA20_CLK_EMC] = clk;
-}
-
static void __init tegra20_periph_clk_init(void)
{
struct tegra_periph_init_data *data;
@@ -835,7 +797,13 @@ static void __init tegra20_periph_clk_init(void)
clks[TEGRA20_CLK_AC97] = clk;
/* emc */
- tegra20_emc_clk_init();
+ clk = tegra20_clk_register_emc(clk_base + CLK_SOURCE_EMC, false);
+
+ clks[TEGRA20_CLK_EMC] = clk;
+
+ clk = tegra_clk_register_mc("mc", "emc", clk_base + CLK_SOURCE_EMC,
+ NULL);
+ clks[TEGRA20_CLK_MC] = clk;
/* dsi */
clk = tegra_clk_register_periph_gate("dsi", "pll_d", 0, clk_base, 0,
@@ -1115,6 +1083,8 @@ static struct clk *tegra20_clk_src_onecell_get(struct of_phandle_args *clkspec,
if (IS_ERR(clk))
return clk;
+ hw = __clk_get_hw(clk);
+
/*
* Tegra20 CDEV1 and CDEV2 clocks are a bit special case, their parent
* clock is created by the pinctrl driver. It is possible for clk user
@@ -1124,13 +1094,16 @@ static struct clk *tegra20_clk_src_onecell_get(struct of_phandle_args *clkspec,
*/
if (clkspec->args[0] == TEGRA20_CLK_CDEV1 ||
clkspec->args[0] == TEGRA20_CLK_CDEV2) {
- hw = __clk_get_hw(clk);
-
parent_hw = clk_hw_get_parent(hw);
if (!parent_hw)
return ERR_PTR(-EPROBE_DEFER);
}
+ if (clkspec->args[0] == TEGRA20_CLK_EMC) {
+ if (!tegra20_clk_emc_driver_available(hw))
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
return clk;
}
diff --git a/drivers/clk/tegra/clk-tegra30.c b/drivers/clk/tegra/clk-tegra30.c
index 7b4c6a488527..95b0e4a16dd5 100644
--- a/drivers/clk/tegra/clk-tegra30.c
+++ b/drivers/clk/tegra/clk-tegra30.c
@@ -151,7 +151,6 @@ static unsigned long input_freq;
static DEFINE_SPINLOCK(cml_lock);
static DEFINE_SPINLOCK(pll_d_lock);
-static DEFINE_SPINLOCK(emc_lock);
#define TEGRA_INIT_DATA_MUX(_name, _parents, _offset, \
_clk_num, _gate_flags, _clk_id) \
@@ -808,7 +807,7 @@ static struct tegra_clk tegra30_clks[tegra_clk_max] __initdata = {
[tegra_clk_pll_a] = { .dt_id = TEGRA30_CLK_PLL_A, .present = true },
[tegra_clk_pll_a_out0] = { .dt_id = TEGRA30_CLK_PLL_A_OUT0, .present = true },
[tegra_clk_cec] = { .dt_id = TEGRA30_CLK_CEC, .present = true },
- [tegra_clk_emc] = { .dt_id = TEGRA30_CLK_EMC, .present = true },
+ [tegra_clk_emc] = { .dt_id = TEGRA30_CLK_EMC, .present = false },
};
static const char *pll_e_parents[] = { "pll_ref", "pll_p" };
@@ -995,7 +994,6 @@ static void __init tegra30_super_clk_init(void)
static const char *mux_pllacp_clkm[] = { "pll_a_out0", "unused", "pll_p",
"clk_m" };
static const char *mux_pllpcm_clkm[] = { "pll_p", "pll_c", "pll_m", "clk_m" };
-static const char *mux_pllmcp_clkm[] = { "pll_m", "pll_c", "pll_p", "clk_m" };
static const char *spdif_out_parents[] = { "pll_a_out0", "spdif_2x", "pll_p",
"clk_m" };
static const char *mux_pllmcpa[] = { "pll_m", "pll_c", "pll_p", "pll_a_out0" };
@@ -1044,14 +1042,12 @@ static void __init tegra30_periph_clk_init(void)
clks[TEGRA30_CLK_AFI] = clk;
/* emc */
- clk = clk_register_mux(NULL, "emc_mux", mux_pllmcp_clkm,
- ARRAY_SIZE(mux_pllmcp_clkm),
- CLK_SET_RATE_NO_REPARENT,
- clk_base + CLK_SOURCE_EMC,
- 30, 2, 0, &emc_lock);
+ clk = tegra20_clk_register_emc(clk_base + CLK_SOURCE_EMC, true);
+
+ clks[TEGRA30_CLK_EMC] = clk;
- clk = tegra_clk_register_mc("mc", "emc_mux", clk_base + CLK_SOURCE_EMC,
- &emc_lock);
+ clk = tegra_clk_register_mc("mc", "emc", clk_base + CLK_SOURCE_EMC,
+ NULL);
clks[TEGRA30_CLK_MC] = clk;
/* cml0 */
@@ -1302,6 +1298,26 @@ static struct tegra_audio_clk_info tegra30_audio_plls[] = {
{ "pll_a", &pll_a_params, tegra_clk_pll_a, "pll_p_out1" },
};
+static struct clk *tegra30_clk_src_onecell_get(struct of_phandle_args *clkspec,
+ void *data)
+{
+ struct clk_hw *hw;
+ struct clk *clk;
+
+ clk = of_clk_src_onecell_get(clkspec, data);
+ if (IS_ERR(clk))
+ return clk;
+
+ hw = __clk_get_hw(clk);
+
+ if (clkspec->args[0] == TEGRA30_CLK_EMC) {
+ if (!tegra20_clk_emc_driver_available(hw))
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ return clk;
+}
+
static void __init tegra30_clock_init(struct device_node *np)
{
struct device_node *node;
@@ -1345,7 +1361,7 @@ static void __init tegra30_clock_init(struct device_node *np)
tegra_init_dup_clks(tegra_clk_duplicates, clks, TEGRA30_CLK_CLK_MAX);
- tegra_add_of_provider(np, of_clk_src_onecell_get);
+ tegra_add_of_provider(np, tegra30_clk_src_onecell_get);
tegra_register_devclks(devclks, ARRAY_SIZE(devclks));
tegra_clk_apply_init_table = tegra30_clock_apply_init_table;
diff --git a/drivers/clk/tegra/clk.h b/drivers/clk/tegra/clk.h
index a4fbf55930aa..f81c10654aa9 100644
--- a/drivers/clk/tegra/clk.h
+++ b/drivers/clk/tegra/clk.h
@@ -842,4 +842,7 @@ int div_frac_get(unsigned long rate, unsigned parent_rate, u8 width,
udelay(delay); \
} while (0)
+bool tegra20_clk_emc_driver_available(struct clk_hw *emc_hw);
+struct clk *tegra20_clk_register_emc(void __iomem *ioaddr, bool low_jitter);
+
#endif /* TEGRA_CLK_H */
diff --git a/include/linux/clk/tegra.h b/include/linux/clk/tegra.h
index cf0f2cb5e109..2b1b35240074 100644
--- a/include/linux/clk/tegra.h
+++ b/include/linux/clk/tegra.h
@@ -132,4 +132,15 @@ 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);
+struct clk;
+
+typedef long (tegra20_clk_emc_round_cb)(unsigned long rate,
+ unsigned long min_rate,
+ unsigned long max_rate,
+ void *arg);
+
+void tegra20_clk_set_emc_round_callback(tegra20_clk_emc_round_cb *round_cb,
+ void *cb_arg);
+int tegra20_clk_prepare_emc_mc_same_freq(struct clk *emc_clk, bool same);
+
#endif /* __LINUX_CLK_TEGRA_H_ */
--
2.22.0
^ permalink raw reply related
* [PATCH v10 00/15] memory: tegra: Introduce Tegra30 EMC driver
From: Dmitry Osipenko @ 2019-08-11 21:00 UTC (permalink / raw)
To: Rob Herring, Michael Turquette, Joseph Lo, Thierry Reding,
Jonathan Hunter, Peter De Schrijver, Prashant Gaikwad,
Stephen Boyd
Cc: devicetree, linux-clk, linux-tegra, linux-kernel
Hello,
This series introduces driver for the External Memory Controller (EMC)
found on Tegra30 chips, it controls the external DRAM on the board. The
purpose of this driver is to program memory timing for external memory on
the EMC clock rate change. The driver was tested using the ACTMON devfreq
driver that performs memory frequency scaling based on memory-usage load.
Changelog:
v10: - Addressed review comments that were made by Rob Herring to v9 by
dropping unnecessary reg descriptions, specifying valid ranges and
using boolean type where appropriate in the device-tree patches.
v9: - Fixed memory corruption bug that was uncovered after introducing
some extra optimizations to the devfreq driver that allows CPU
to stay longer in the LP2 cpuidle state. The corruption is caused by
a very late AUTO-REFRESH re-enabling due to a possible schedule on
waiting for clk-change completion, the re-enabling is now a part of
"EMC exec-after-clkchange" hardware sequence.
- Added "type: object" to T124 MC YAML, that was missed in v8 by accident.
v8: - Added two new patches:
memory: tegra20-emc: Increase handshake timeout
memory: tegra20-emc: wait_for_completion_timeout() doesn't return error
Turned out that memory-clk handshake may take much more time under
some circumstances. The second patch is a minor cleanup. The same
changes are also applied to the Terga30 EMC driver addition-patch.
The pattern-properties of YAML bindings gained "type: object", for
consistency.
v7: - Addressed review comments that were made by Rob Herring to v6 by
removing old Terga30 Memory Controller binding once it's converted
to YAML, by using explicit patterns for the sub-nodes and specifying
min/max clock rates in the YAML.
- Two patches that were added in v6 are removed from the series:
clk: tegra20: emc: Add tegra20_clk_emc_on_pllp()
ARM: tegra30: cpuidle: Don't enter LP2 on CPU0 when EMC runs off PLLP
Because the problem with the PLLP is resolved now, turned out it was
a bug in the CPU-suspend code.
- The "Introduce Tegra30 EMC driver" patch got a fix for the "Same Freq"
bit typo, it's a bit 27 and not 16.
v6: - Tegra124 Memory Controller binding factored out into standalone
binding because it requires to specify MC_EMEM_ARB_MISC1 for EMEM
programming, which is not required for Tegra30. This makes the
upstream MC registers specification to match downstream exactly,
easing porting of boards memory timings configuration to upstream.
- Tegra30/124 Memory Controller binding converted to YAML.
- Tegra30 External Memory Controller binding now is in YAML format.
- Added workaround for hanging during LP2 when EMC runs off PLLP on
Tegra30 in this new patches:
clk: tegra20: emc: Add tegra20_clk_emc_on_pllp()
ARM: tegra30: cpuidle: Don't enter LP2 on CPU0 when EMC runs off PLLP
- Added info message to the Tegra20/30 EMC drivers, telling about
RAM code and a number of available timings:
memory: tegra20-emc: Print a brief info message about the timings
v5: - Addressed review comments that were made by Thierry Reding to v4 by
adding appropriate copyrights to the source code headers and making
Tegra30 EMC driver to use common Tegra20 CLK API directly instead
of having a dummy-proxy functions specifically for Tegra30.
- Addressed review comments that were made by Stephen Boyd to v4 by
rewording commit message of the "Add custom EMC clock implementation"
patch and adding clarifying comment (to that patch as well) which
tells why EMC is a critical clock.
- Added suspend-resume to Tegra30 EMC driver to error out if EMC driver
is in a "bad state" as it will likely cause a hang on entering suspend.
- Dropped patch "tegra20-emc: Replace clk_get_sys with devm_clk_get"
because the replaced clocks are actually should be removed altogether
in the "Drop setting EMC rate to max on probe" patch and that was
missed by an accident.
- Added "tegra20-emc: Pre-configure debug register" patch which ensures
that inappropriate HW debug features are disabled at a probe time.
The same change is also made in the "Introduce Tegra30 EMC driver"
patch.
- Added ACKs to the patches from Peter De Schrijver that he gave to v4
since all of the v5 changes are actually very minor.
v4: - Addressed review comments that were made by Peter De Schrijver to v3
by adding fence_udelay() after writes in the "Add custom EMC clock
implementation" patch.
- Added two new minor patches:
memory: tegra: Ensure timing control debug features are disabled
memory: tegra: Consolidate registers definition into one place
The first one is needed to ensure that EMC driver will work
properly regardless of hardware configuration left after boot.
The second patch is just a minor code cleanup.
- The "Introduce Tegra30 EMC driver" got also few very minor changes.
Now every possible error case is handled, nothing is ignored.
The EMC_DBG register is explicitly initialized during probe to be
on the safe side.
v3: - Addressed review comments that were made by Stephen Boyd to v2 by
adding explicit typing for the callback variable, by including
"clk-provider.h" directly in the code and by dropping __clk_lookup
usage where possible.
- Added more patches into this series:
memory: tegra20-emc: Drop setting EMC rate to max on probe
memory: tegra20-emc: Adapt for clock driver changes
memory: tegra20-emc: Include io.h instead of iopoll.h
memory: tegra20-emc: Replace clk_get_sys with devm_clk_get
Initially I was going to include these patches into other patchset,
but changed my mind after rearranging things a tad. The "Adapt for
clock driver changes" patch is directly related to the clock changes
done in the first patch of this series, the rest are minor cleanups
that are fine to include here as well.
- Added some more words to the commit message of "Add binding for NVIDIA
Tegra30 External Memory Controller" patch, clarifying why common DDR
timing device-tree form isn't suitable for Tegra30.
- The Tegra30 EMC driver now explicitly selects the registers access
mode (EMC_DBG mux), not relying on the setting left from bootloader.
v2: - Added support for changing MC clock diver configuration based on
Memory Controller (MC) configuration which is part of the memory
timing.
- Merged the "Add custom EMC clock implementation" patch into this
series because the "Introduce Tegra30 EMC driver" patch directly
depends on it. Please note that Tegra20 EMC driver will need to be
adapted for the clock changes as well, I'll send out the Tegra20
patches after this series will be applied because of some other
dependencies (devfreq) and because the temporary breakage won't
be critical (driver will just error out on probe).
- EMC driver now performs MC configuration validation by checking
that the number of MC / EMC timings matches and that the timings
rate is the same.
- EMC driver now supports timings that want to change the MC clock
configuration.
- Other minor prettifying changes of the code.
Dmitry Osipenko (15):
clk: tegra20/30: Add custom EMC clock implementation
memory: tegra20-emc: Drop setting EMC rate to max on probe
memory: tegra20-emc: Adapt for clock driver changes
memory: tegra20-emc: Include io.h instead of iopoll.h
memory: tegra20-emc: Pre-configure debug register
memory: tegra20-emc: Print a brief info message about the timings
memory: tegra20-emc: Increase handshake timeout
memory: tegra20-emc: wait_for_completion_timeout() doesn't return
error
dt-bindings: memory: tegra30: Convert to Tegra124 YAML
dt-bindings: memory: Add binding for NVIDIA Tegra30 Memory Controller
dt-bindings: memory: Add binding for NVIDIA Tegra30 External Memory
Controller
memory: tegra: Introduce Tegra30 EMC driver
memory: tegra: Ensure timing control debug features are disabled
memory: tegra: Consolidate registers definition into common header
ARM: dts: tegra30: Add External Memory Controller node
.../nvidia,tegra124-mc.yaml | 152 ++
.../nvidia,tegra30-emc.yaml | 336 +++++
.../memory-controllers/nvidia,tegra30-mc.txt | 123 --
.../memory-controllers/nvidia,tegra30-mc.yaml | 167 +++
arch/arm/boot/dts/tegra30.dtsi | 9 +
drivers/clk/tegra/Makefile | 2 +
drivers/clk/tegra/clk-tegra20-emc.c | 293 ++++
drivers/clk/tegra/clk-tegra20.c | 55 +-
drivers/clk/tegra/clk-tegra30.c | 38 +-
drivers/clk/tegra/clk.h | 3 +
drivers/memory/tegra/Kconfig | 10 +
drivers/memory/tegra/Makefile | 1 +
drivers/memory/tegra/mc.c | 42 +-
drivers/memory/tegra/mc.h | 74 +-
drivers/memory/tegra/tegra124.c | 20 -
drivers/memory/tegra/tegra20-emc.c | 134 +-
drivers/memory/tegra/tegra30-emc.c | 1232 +++++++++++++++++
drivers/memory/tegra/tegra30.c | 23 +
include/linux/clk/tegra.h | 11 +
include/soc/tegra/mc.h | 2 +-
20 files changed, 2414 insertions(+), 313 deletions(-)
create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra124-mc.yaml
create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-emc.yaml
delete mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-mc.txt
create mode 100644 Documentation/devicetree/bindings/memory-controllers/nvidia,tegra30-mc.yaml
create mode 100644 drivers/clk/tegra/clk-tegra20-emc.c
create mode 100644 drivers/memory/tegra/tegra30-emc.c
--
2.22.0
^ permalink raw reply
* [PATCH v5 3/3] arm64: defconfig: Enable Sun4i SPDIF module
From: Clément Péron @ 2019-08-11 20:31 UTC (permalink / raw)
To: Maxime Ripard, Rob Herring
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Clément Péron
In-Reply-To: <20190811203144.5999-1-peron.clem-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Allwinner A64 and H6 use the Sun4i SPDIF driver.
Enable this to allow a proper support.
Signed-off-by: Clément Péron <peron.clem-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 0e58ef02880c..b0638849c14c 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -558,6 +558,7 @@ CONFIG_SND_SOC_ROCKCHIP_RT5645=m
CONFIG_SND_SOC_RK3399_GRU_SOUND=m
CONFIG_SND_SOC_SAMSUNG=y
CONFIG_SND_SOC_RCAR=m
+CONFIG_SND_SUN4I_SPDIF=m
CONFIG_SND_SOC_AK4613=m
CONFIG_SND_SOC_ES7134=m
CONFIG_SND_SOC_ES7241=m
--
2.20.1
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To view this discussion on the web, visit https://groups.google.com/d/msgid/linux-sunxi/20190811203144.5999-4-peron.clem%40gmail.com.
^ permalink raw reply related
* [PATCH v5 2/3] arm64: dts: allwinner: h6: Enable SPDIF for Beelink GS1
From: Clément Péron @ 2019-08-11 20:31 UTC (permalink / raw)
To: Maxime Ripard, Rob Herring
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Clément Péron
In-Reply-To: <20190811203144.5999-1-peron.clem-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Beelink GS1 board has a SPDIF out connector, so enable it in
the device-tree.
Signed-off-by: Clément Péron <peron.clem-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
arch/arm64/boot/dts/allwinner/sun50i-h6-beelink-gs1.dts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6-beelink-gs1.dts b/arch/arm64/boot/dts/allwinner/sun50i-h6-beelink-gs1.dts
index 0dc33c90dd60..76a95ad33dc5 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6-beelink-gs1.dts
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6-beelink-gs1.dts
@@ -243,6 +243,10 @@
vcc-pm-supply = <®_aldo1>;
};
+&spdif {
+ status = "okay";
+};
+
&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart0_ph_pins>;
--
2.20.1
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To view this discussion on the web, visit https://groups.google.com/d/msgid/linux-sunxi/20190811203144.5999-3-peron.clem%40gmail.com.
^ permalink raw reply related
* [PATCH v5 1/3] arm64: dts: allwinner: Add SPDIF node for Allwinner H6
From: Clément Péron @ 2019-08-11 20:31 UTC (permalink / raw)
To: Maxime Ripard, Rob Herring
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Clément Péron
In-Reply-To: <20190811203144.5999-1-peron.clem-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
The Allwinner H6 has a SPDIF controller called OWA (One Wire Audio).
Only one pinmuxing is available so set it as default.
Signed-off-by: Clément Péron <peron.clem-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 38 ++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
index 7628a7c83096..677eb374678d 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi
@@ -83,6 +83,24 @@
method = "smc";
};
+ sound-spdif {
+ compatible = "simple-audio-card";
+ simple-audio-card,name = "sun50i-h6-spdif";
+
+ simple-audio-card,cpu {
+ sound-dai = <&spdif>;
+ };
+
+ simple-audio-card,codec {
+ sound-dai = <&spdif_out>;
+ };
+ };
+
+ spdif_out: spdif-out {
+ #sound-dai-cells = <0>;
+ compatible = "linux,spdif-dit";
+ };
+
timer {
compatible = "arm,armv8-timer";
interrupts = <GIC_PPI 13
@@ -282,6 +300,11 @@
bias-pull-up;
};
+ spdif_tx_pin: spdif-tx-pin {
+ pins = "PH7";
+ function = "spdif";
+ };
+
uart0_ph_pins: uart0-ph-pins {
pins = "PH0", "PH1";
function = "uart0";
@@ -411,6 +434,21 @@
};
};
+ spdif: spdif@5093000 {
+ #sound-dai-cells = <0>;
+ compatible = "allwinner,sun50i-h6-spdif";
+ reg = <0x05093000 0x400>;
+ interrupts = <GIC_SPI 21 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_BUS_SPDIF>, <&ccu CLK_SPDIF>;
+ clock-names = "apb", "spdif";
+ resets = <&ccu RST_BUS_SPDIF>;
+ dmas = <&dma 2>;
+ dma-names = "tx";
+ pinctrl-names = "default";
+ pinctrl-0 = <&spdif_tx_pin>;
+ status = "disabled";
+ };
+
usb2otg: usb@5100000 {
compatible = "allwinner,sun50i-h6-musb",
"allwinner,sun8i-a33-musb";
--
2.20.1
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To view this discussion on the web, visit https://groups.google.com/d/msgid/linux-sunxi/20190811203144.5999-2-peron.clem%40gmail.com.
^ permalink raw reply related
* [PATCH v5 0/3] Allwinner H6 SPDIF support
From: Clément Péron @ 2019-08-11 20:31 UTC (permalink / raw)
To: Maxime Ripard, Rob Herring
Cc: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw, Clément Péron
Allwinner H6 SoC has a SPDIF controller called One Wire Audio (OWA) which
is different from the previous H3 generation and not compatible.
Difference are an increase of fifo sizes, some memory mapping are different
and there is now the possibility to output the master clock on a pin.
Actually all these features are unused and only a bit for flushing the TX
fifo is required.
Changes since v4:
- rename audio card name to sun50i-h6-spdif
- drop patches already merged
Changes since v3:
- rename reg_fctl_ftx to val_fctl_ftx
- rebase this series on sound-next
- fix dt-bindings due to change in sound-next
- change node name sound_spdif to sound-spdif
Changes since v2:
- Split quirks and H6 support patch
- Add specific section for quirks comment
Changes since v1:
- Remove H3 compatible
- Add TX fifo bit flush quirks
- Add H6 bindings in SPDIF driver
Clément Péron (3):
arm64: dts: allwinner: Add SPDIF node for Allwinner H6
arm64: dts: allwinner: h6: Enable SPDIF for Beelink GS1
arm64: defconfig: Enable Sun4i SPDIF module
.../dts/allwinner/sun50i-h6-beelink-gs1.dts | 4 ++
arch/arm64/boot/dts/allwinner/sun50i-h6.dtsi | 38 +++++++++++++++++++
arch/arm64/configs/defconfig | 1 +
3 files changed, 43 insertions(+)
--
2.20.1
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
To view this discussion on the web, visit https://groups.google.com/d/msgid/linux-sunxi/20190811203144.5999-1-peron.clem%40gmail.com.
^ permalink raw reply
* Re: [PATCH v8 13/21] clk: tegra210: Use fence_udelay during PLLU init
From: Sowjanya Komatineni @ 2019-08-11 19:16 UTC (permalink / raw)
To: Dmitry Osipenko, thierry.reding, jonathanh, tglx, jason,
marc.zyngier, linus.walleij, stefan, mark.rutland
Cc: pdeschrijver, pgaikwad, sboyd, linux-clk, linux-gpio, jckuo,
josephl, talho, linux-tegra, linux-kernel, mperttunen, spatra,
robh+dt, devicetree, rjw, viresh.kumar, linux-pm
In-Reply-To: <1d09a2c5-4973-340f-fdfc-d4e665c8b55d@gmail.com>
On 8/11/19 11:02 AM, Dmitry Osipenko wrote:
> 09.08.2019 2:46, Sowjanya Komatineni пишет:
>> This patch uses fence_udelay rather than udelay during PLLU
>> initialization to ensure writes to clock registers happens before
>> waiting for specified delay.
>>
>> Acked-by: Thierry Reding <treding@nvidia.com>
>> Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
>> ---
>> drivers/clk/tegra/clk-tegra210.c | 8 ++++----
>> 1 file changed, 4 insertions(+), 4 deletions(-)
>>
>> diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c
>> index 4721ee030d1c..998bf60b219a 100644
>> --- a/drivers/clk/tegra/clk-tegra210.c
>> +++ b/drivers/clk/tegra/clk-tegra210.c
>> @@ -2841,7 +2841,7 @@ static int tegra210_enable_pllu(void)
>> reg = readl_relaxed(clk_base + pllu.params->ext_misc_reg[0]);
>> reg &= ~BIT(pllu.params->iddq_bit_idx);
>> writel_relaxed(reg, clk_base + pllu.params->ext_misc_reg[0]);
>> - udelay(5);
>> + fence_udelay(5, clk_base);
>>
>> reg = readl_relaxed(clk_base + PLLU_BASE);
>> reg &= ~GENMASK(20, 0);
>> @@ -2849,7 +2849,7 @@ static int tegra210_enable_pllu(void)
>> reg |= fentry->n << 8;
>> reg |= fentry->p << 16;
>> writel(reg, clk_base + PLLU_BASE);
>> - udelay(1);
>> + fence_udelay(1, clk_base);
>> reg |= PLL_ENABLE;
>> writel(reg, clk_base + PLLU_BASE);
>>
>> @@ -2895,12 +2895,12 @@ static int tegra210_init_pllu(void)
>> reg = readl_relaxed(clk_base + XUSB_PLL_CFG0);
>> reg &= ~XUSB_PLL_CFG0_PLLU_LOCK_DLY_MASK;
>> writel_relaxed(reg, clk_base + XUSB_PLL_CFG0);
>> - udelay(1);
>> + fence_udelay(1, clk_base);
>>
>> reg = readl_relaxed(clk_base + PLLU_HW_PWRDN_CFG0);
>> reg |= PLLU_HW_PWRDN_CFG0_SEQ_ENABLE;
>> writel_relaxed(reg, clk_base + PLLU_HW_PWRDN_CFG0);
>> - udelay(1);
>> + fence_udelay(1, clk_base);
>>
>> reg = readl_relaxed(clk_base + PLLU_BASE);
>> reg &= ~PLLU_BASE_CLKENABLE_USB;
>>
> The clk_base corresponds to the RESET controller's part of Clock-and-Reset hardware, is it
> okay to read-back the RST register and not the clock for the fencing?
Yes as both reset and clocks are all in same CAR
^ permalink raw reply
* Re: [PATCH v8 14/21] clk: tegra210: Add suspend and resume support
From: Sowjanya Komatineni @ 2019-08-11 19:15 UTC (permalink / raw)
To: Dmitry Osipenko, thierry.reding, jonathanh, tglx, jason,
marc.zyngier, linus.walleij, stefan, mark.rutland
Cc: pdeschrijver, pgaikwad, sboyd, linux-clk, linux-gpio, jckuo,
josephl, talho, linux-tegra, linux-kernel, mperttunen, spatra,
robh+dt, devicetree, rjw, viresh.kumar, linux-pm
In-Reply-To: <4397de5d-772d-2b04-5f87-b2988f6c96c8@gmail.com>
On 8/11/19 10:39 AM, Dmitry Osipenko wrote:
> 09.08.2019 21:40, Sowjanya Komatineni пишет:
>> On 8/9/19 11:18 AM, Dmitry Osipenko wrote:
>>> 09.08.2019 19:19, Sowjanya Komatineni пишет:
>>>> On 8/9/19 6:56 AM, Dmitry Osipenko wrote:
>>>>> 09.08.2019 2:46, Sowjanya Komatineni пишет:
>>>>>> This patch adds support for clk: tegra210: suspend-resume.
>>>>>>
>>>>>> All the CAR controller settings are lost on suspend when core
>>>>>> power goes off.
>>>>>>
>>>>>> This patch has implementation for saving and restoring all PLLs
>>>>>> and clocks context during system suspend and resume to have the
>>>>>> clocks back to same state for normal operation.
>>>>>>
>>>>>> Clock driver suspend and resume are registered as syscore_ops as clocks
>>>>>> restore need to happen before the other drivers resume to have all their
>>>>>> clocks back to the same state as before suspend.
>>>>>>
>>>>>> Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
>>>>>> ---
>>>>>> drivers/clk/tegra/clk-tegra210.c | 103 +++++++++++++++++++++++++++++++++++++--
>>>>>> drivers/clk/tegra/clk.c | 64 ++++++++++++++++++++++++
>>>>>> drivers/clk/tegra/clk.h | 3 ++
>>>>>> 3 files changed, 166 insertions(+), 4 deletions(-)
>>>>>>
>>>>>> diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c
>>>>>> index 998bf60b219a..8dd6f4f4debb 100644
>>>>>> --- a/drivers/clk/tegra/clk-tegra210.c
>>>>>> +++ b/drivers/clk/tegra/clk-tegra210.c
>>>>>> @@ -9,13 +9,13 @@
>>>>>> #include <linux/clkdev.h>
>>>>>> #include <linux/of.h>
>>>>>> #include <linux/of_address.h>
>>>>>> +#include <linux/syscore_ops.h>
>>>>>> #include <linux/delay.h>
>>>>>> #include <linux/export.h>
>>>>>> #include <linux/mutex.h>
>>>>>> #include <linux/clk/tegra.h>
>>>>>> #include <dt-bindings/clock/tegra210-car.h>
>>>>>> #include <dt-bindings/reset/tegra210-car.h>
>>>>>> -#include <linux/iopoll.h>
>>>>>> #include <linux/sizes.h>
>>>>>> #include <soc/tegra/pmc.h>
>>>>>> @@ -220,11 +220,15 @@
>>>>>> #define CLK_M_DIVISOR_SHIFT 2
>>>>>> #define CLK_M_DIVISOR_MASK 0x3
>>>>>> +#define CLK_MASK_ARM 0x44
>>>>>> +#define MISC_CLK_ENB 0x48
>>>>>> +
>>>>>> #define RST_DFLL_DVCO 0x2f4
>>>>>> #define DVFS_DFLL_RESET_SHIFT 0
>>>>>> #define CLK_RST_CONTROLLER_RST_DEV_Y_SET 0x2a8
>>>>>> #define CLK_RST_CONTROLLER_RST_DEV_Y_CLR 0x2ac
>>>>>> +#define CPU_SOFTRST_CTRL 0x380
>>>>>> #define LVL2_CLK_GATE_OVRA 0xf8
>>>>>> #define LVL2_CLK_GATE_OVRC 0x3a0
>>>>>> @@ -2825,6 +2829,7 @@ static int tegra210_enable_pllu(void)
>>>>>> struct tegra_clk_pll_freq_table *fentry;
>>>>>> struct tegra_clk_pll pllu;
>>>>>> u32 reg;
>>>>>> + int ret;
>>>>>> for (fentry = pll_u_freq_table; fentry->input_rate; fentry++) {
>>>>>> if (fentry->input_rate == pll_ref_freq)
>>>>>> @@ -2853,9 +2858,14 @@ static int tegra210_enable_pllu(void)
>>>>>> reg |= PLL_ENABLE;
>>>>>> writel(reg, clk_base + PLLU_BASE);
>>>>>> - readl_relaxed_poll_timeout_atomic(clk_base + PLLU_BASE, reg,
>>>>>> - reg & PLL_BASE_LOCK, 2, 1000);
>>>>>> - if (!(reg & PLL_BASE_LOCK)) {
>>>>>> + /*
>>>>>> + * During clocks resume, same PLLU init and enable sequence get
>>>>>> + * executed. So, readx_poll_timeout_atomic can't be used here as it
>>>>>> + * uses ktime_get() and timekeeping resume doesn't happen by that
>>>>>> + * time. So, using tegra210_wait_for_mask for PLL LOCK.
>>>>>> + */
>>>>>> + ret = tegra210_wait_for_mask(&pllu, PLLU_BASE, PLL_BASE_LOCK);
>>>>>> + if (ret) {
>>>>>> pr_err("Timed out waiting for PLL_U to lock\n");
>>>>>> return -ETIMEDOUT;
>>>>>> }
>>>>>> @@ -3288,6 +3298,84 @@ static void tegra210_disable_cpu_clock(u32 cpu)
>>>>>> }
>>>>>> #ifdef CONFIG_PM_SLEEP
>>>>>> +/*
>>>>>> + * This array lists mask values for each peripheral clk bank
>>>>>> + * to mask out reserved bits during the clocks state restore
>>>>>> + * on SC7 resume to prevent accidental writes to these reserved
>>>>>> + * bits.
>>>>>> + */
>>>>>> +static u32 periph_clk_rsvd_mask[TEGRA210_CAR_BANK_COUNT] = {
>>>>> Should be more natural to have a "valid_mask" instead of "rsvd_mask".
>>>>>
>>>>> What's actually wrong with touching of the reserved bits? They must be NO-OP.. or the
>>>>> reserved bits are actually some kind of "secret" bits? If those bits have some use-case
>>>>> outside of Silicon HW (like FPGA simulation), then this doesn't matter for upstream and you
>>>>> have to keep the workaround locally in the downstream kernel or whatever.
>>>> Will rename as valid_mask.
>>>>
>>>> some bits in these registers are undefined and is not good to write to these bits as they
>>>> can cause pslverr.
>>> Okay, it should be explained in the comment.
>>>
>>> Is it possible to disable trapping of changing the undefined bits?
>> No its internal to design
> Okay.
>
> Also, what about to move the valid_mask into struct tegra_clk_periph_regs?
No, we cannot move to tegra_clk_periph_regs as its in tegra/clk.c and is
common for all tegra.
Reserved bits are different on tegra chips so should come from Tegra
chip specific clock driver like
clk-tegra210 for Tegra210.
>>>>>> + 0x23282006,
>>>>>> + 0x782e0c18,
>>>>>> + 0x0c012c05,
>>>>>> + 0x003e7304,
>>>>>> + 0x86c04800,
>>>>>> + 0xc0199000,
>>>>>> + 0x03e03800,
>>>>>> +};
>>>>>> +
>>>>>> +#define car_readl(_base, _off) readl_relaxed(clk_base + (_base) + ((_off) * 4))
>>>>>> +#define car_writel(_val, _base, _off) \
>>>>>> + writel_relaxed(_val, clk_base + (_base) + ((_off) * 4))
>>>>>> +
>>>>>> +static u32 spare_reg_ctx, misc_clk_enb_ctx, clk_msk_arm_ctx;
>>>>>> +static u32 cpu_softrst_ctx[3];
>>>>>> +
>>>>>> +static int tegra210_clk_suspend(void)
>>>>>> +{
>>>>>> + unsigned int i;
>>>>>> +
>>>>>> + clk_save_context();
>>>>>> +
>>>>>> + /*
>>>>>> + * Save the bootloader configured clock registers SPARE_REG0,
>>>>>> + * MISC_CLK_ENB, CLK_MASK_ARM, CPU_SOFTRST_CTRL.
>>>>>> + */
>>>>>> + spare_reg_ctx = readl_relaxed(clk_base + SPARE_REG0);
>>>>>> + misc_clk_enb_ctx = readl_relaxed(clk_base + MISC_CLK_ENB);
>>>>>> + clk_msk_arm_ctx = readl_relaxed(clk_base + CLK_MASK_ARM);
>>>>>> +
>>>>>> + for (i = 0; i < ARRAY_SIZE(cpu_softrst_ctx); i++)
>>>>>> + cpu_softrst_ctx[i] = car_readl(CPU_SOFTRST_CTRL, i);
>>>>>> +
>>>>>> + tegra_clk_periph_suspend();
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static void tegra210_clk_resume(void)
>>>>>> +{
>>>>>> + unsigned int i;
>>>>>> +
>>>>>> + tegra_clk_osc_resume(clk_base);
>>>>>> +
>>>>>> + /*
>>>>>> + * Restore the bootloader configured clock registers SPARE_REG0,
>>>>>> + * MISC_CLK_ENB, CLK_MASK_ARM, CPU_SOFTRST_CTRL from saved context.
>>>>>> + */
>>>>>> + writel_relaxed(spare_reg_ctx, clk_base + SPARE_REG0);
>>>>>> + writel_relaxed(misc_clk_enb_ctx, clk_base + MISC_CLK_ENB);
>>>>>> + writel_relaxed(clk_msk_arm_ctx, clk_base + CLK_MASK_ARM);
>>>>>> +
>>>>>> + for (i = 0; i < ARRAY_SIZE(cpu_softrst_ctx); i++)
>>>>>> + car_writel(cpu_softrst_ctx[i], CPU_SOFTRST_CTRL, i);
>>>>>> +
>>>>>> + fence_udelay(5, clk_base);
>>>>>> +
>>>>>> + /* enable all the clocks before changing the clock sources */
>>>>>> + tegra_clk_periph_force_on(periph_clk_rsvd_mask);
>>>>> Why clocks need to be enabled before changing the sources?
>>>> To prevent glitchless frequency switch, Tegra clock programming recommended sequence is to
>>>> change MUX control or divisor or both with the clocks running.
>>> This should be explained in the comment.
>>>
>>>> Actual state of clocks before suspend are restored later after all PLL's and peripheral
>>>> clocks are restored.
>>>>
>>>>>> + /* wait for all writes to happen to have all the clocks enabled */
>>>>>> + wmb();
>>>>> fence_udelay() has exactly the same barrier at the very beginning of readl(), no need to
>>>>> duplicate it here.
>>> Actually, readl does the rmb() and it should be a more correct variant of fencing because it
>>> actually ensures that the write reached hardware. I suppose that something like fence_udelay
>>> should be used for the pinctrl as well.
>>>
>>>>>> + fence_udelay(2, clk_base);
>>>>>> +
>>>>>> + /* restore PLLs and all peripheral clock rates */
>>>>>> + tegra210_init_pllu();
>>>>> Why USB PLL need to be restored at first?
>>>> USB PLL restore is independent to all other clocks restore. So this can be done either
>>>> before clk_restore_context or even after.
>>> Then why not to implement restore_context for PLLU?
>> pllu is registered as fixed_rate clock and we using clk core clk_register_fixed_rate which
>> uses clk_fixed_rate_ops from the same generic clk-fixed-rate driver.
>>
>> Also pllu init happens in the same clk-tegra210, so invoking it during resume which is the
>> same sequence needed during resume as well.
> Okay.
>
^ permalink raw reply
* Re: [PATCH v2 3/5] counter: new TI eQEP driver
From: David Lechner @ 2019-08-11 19:15 UTC (permalink / raw)
To: Jonathan Cameron, linux-omap
Cc: linux-iio, Rob Herring, Mark Rutland, Benoît Cousson,
Tony Lindgren, William Breathitt Gray, Thierry Reding, devicetree,
linux-kernel, linux-pwm
In-Reply-To: <20190811102345.1af5c23c@archlinux>
On 8/11/19 4:23 AM, Jonathan Cameron wrote:
> On Wed, 7 Aug 2019 14:40:21 -0500
> David Lechner <david@lechnology.com> wrote:
>> +
>> + pm_runtime_enable(dev);
>> + pm_runtime_get(dev);
> I'm a little confused on the flow here.
>
> pm_runtime_enable turns on runtime pm in general.
>
> pm_runtime_get basically calls runtime_resume to ensrue the
> device has power.
>
>> +
>> + return devm_counter_register(dev, &priv->counter);
>
> This registers the userspace interfaces and exposes the device.
>
>> +}
>> +
>> +static int ti_eqep_remove(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> +
>> + pm_runtime_put(dev),
>> + pm_runtime_disable(dev);
>
> pm_runtime_put notifies the system that the device is idle
> (and hence potentially turns it off).
>
> Not good if the counter is still registered.
>
> I'm assuming the presence of runtime pm at all is to interact
> with a parent driver and hence stop that turning off if this
> driver is probed? That's probably fine, but add a few comments
> to make it clear that this driver itself doesn't really do
> runtime pm at all.
>
To be honest, despite reading the runtime PM docs more than once,
I still don't feel like I have a good grasp on how it is supposed
to work. I just know that we get a crash if it is omitted because
the IP block is not powered on.
I will fix the ordering in _remove() and add a comment in v3.
^ permalink raw reply
* Re: [PATCH v3 4/4] arm64: dts/sdm845: Enable FW implemented safe sequence handler on MTP
From: Bjorn Andersson @ 2019-08-11 19:01 UTC (permalink / raw)
To: Vivek Gautam
Cc: open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
linux-arm-msm, Will Deacon, open list, robh+dt, David Brown,
list@263.net:IOMMU DRIVERS <iommu@lists.linux-foundation.org>, Joerg Roedel <joro@8bytes.org>,,
Andy Gross, Robin Murphy
In-Reply-To: <CAFp+6iHGrXAJ2Y1ewxaePGYEcbnprjScUnGyR61qvOv03HVZhQ@mail.gmail.com>
On Sun 11 Aug 09:08 PDT 2019, Vivek Gautam wrote:
> On Tue, Aug 6, 2019 at 3:56 AM Bjorn Andersson
> <bjorn.andersson@linaro.org> wrote:
> >
> > On Wed 12 Jun 00:15 PDT 2019, Vivek Gautam wrote:
> >
> > > Indicate on MTP SDM845 that firmware implements handler to
> > > TLB invalidate erratum SCM call where SAFE sequence is toggled
> > > to achieve optimum performance on real-time clients, such as
> > > display and camera.
> > >
> > > Signed-off-by: Vivek Gautam <vivek.gautam@codeaurora.org>
> > > ---
> > > arch/arm64/boot/dts/qcom/sdm845.dtsi | 1 +
> > > 1 file changed, 1 insertion(+)
> > >
> > > diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi
> > > index 78ec373a2b18..6a73d9744a71 100644
> > > --- a/arch/arm64/boot/dts/qcom/sdm845.dtsi
> > > +++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi
> > > @@ -2368,6 +2368,7 @@
> > > compatible = "qcom,sdm845-smmu-500", "arm,mmu-500";
> > > reg = <0 0x15000000 0 0x80000>;
> > > #iommu-cells = <2>;
> > > + qcom,smmu-500-fw-impl-safe-errata;
> >
> > Looked back at this series and started to wonder if there there is a
> > case where this should not be set? I mean we're after all adding this to
> > the top 845 dtsi...
>
> My bad.
> This is not valid in case of cheza. Cheza firmware doesn't implement
> the safe errata handling hook.
> On cheza we just have the liberty of accessing the secure registers
> through scm calls - this is what
> we were doing in earlier patch series handling this errata.
> So, a property like this should go to mtp board's dts file.
>
It would have been nice if the common case was just selected by default,
but afaict no safe workaround is needed on Cheza? You mention here that
it should (could?) use the scm write based approach instead, would an
introduction of that come with another flag?
PS. In it's current form it's correct that this should be moved to the
device dts files - all but one of them...
Regards,
Bjorn
^ permalink raw reply
* Re: [PATCH v8 04/21] clk: tegra: pllout: Save and restore pllout context
From: Dmitry Osipenko @ 2019-08-11 18:04 UTC (permalink / raw)
To: Sowjanya Komatineni, thierry.reding, jonathanh, tglx, jason,
marc.zyngier, linus.walleij, stefan, mark.rutland
Cc: pdeschrijver, pgaikwad, sboyd, linux-clk, linux-gpio, jckuo,
josephl, talho, linux-tegra, linux-kernel, mperttunen, spatra,
robh+dt, devicetree, rjw, viresh.kumar, linux-pm
In-Reply-To: <1565308020-31952-5-git-send-email-skomatineni@nvidia.com>
09.08.2019 2:46, Sowjanya Komatineni пишет:
> This patch implements save and restore of pllout context.
>
> During system suspend, core power goes off and looses the settings
> of the Tegra CAR controller registers.
>
> So during suspend entry the state of pllout is saved and on resume
> it is restored back to have pllout in same state as before suspend.
>
> pllout rate is saved and restore in clock divider so it will be at
> same rate as before suspend when pllout state is restored.
>
> Acked-by: Thierry Reding <treding@nvidia.com>
> Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
> ---
> drivers/clk/tegra/clk-pll-out.c | 9 +++++++++
> drivers/clk/tegra/clk-tegra210.c | 3 ++-
> drivers/clk/tegra/clk.h | 6 ++++++
> 3 files changed, 17 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/clk/tegra/clk-pll-out.c b/drivers/clk/tegra/clk-pll-out.c
> index 35f2bf00e1e6..d8bf89a81e6d 100644
> --- a/drivers/clk/tegra/clk-pll-out.c
> +++ b/drivers/clk/tegra/clk-pll-out.c
> @@ -69,10 +69,19 @@ static void clk_pll_out_disable(struct clk_hw *hw)
> spin_unlock_irqrestore(pll_out->lock, flags);
> }
>
> +static void tegra_clk_pll_out_restore_context(struct clk_hw *hw)
> +{
> + if (!__clk_get_enable_count(hw->clk))
> + clk_pll_out_disable(hw);
> + else
> + clk_pll_out_enable(hw);
> +}
> +
> const struct clk_ops tegra_clk_pll_out_ops = {
> .is_enabled = clk_pll_out_is_enabled,
> .enable = clk_pll_out_enable,
> .disable = clk_pll_out_disable,
> + .restore_context = tegra_clk_pll_out_restore_context,
> };
>
> struct clk *tegra_clk_register_pll_out(const char *name,
> diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c
> index df172d5772d7..4721ee030d1c 100644
> --- a/drivers/clk/tegra/clk-tegra210.c
> +++ b/drivers/clk/tegra/clk-tegra210.c
> @@ -3200,7 +3200,8 @@ static void __init tegra210_pll_init(void __iomem *clk_base,
> 8, 8, 1, NULL);
> clk = tegra_clk_register_pll_out("pll_re_out1", "pll_re_out1_div",
> clk_base + PLLRE_OUT1, 1, 0,
> - CLK_SET_RATE_PARENT, 0, NULL);
> + CLK_SET_RATE_PARENT, TEGRA_PLLRE_OUT,
> + NULL);
> clks[TEGRA210_CLK_PLL_RE_OUT1] = clk;
>
> /* PLLE */
> diff --git a/drivers/clk/tegra/clk.h b/drivers/clk/tegra/clk.h
> index 905bf1096558..a464524fbc90 100644
> --- a/drivers/clk/tegra/clk.h
> +++ b/drivers/clk/tegra/clk.h
> @@ -437,6 +437,10 @@ struct clk *tegra_clk_register_pllu_tegra210(const char *name,
> * @rst_bit_idx: bit to reset PLL divider
> * @lock: register lock
> * @flags: hardware-specific flags
> + *
> + * Flags:
> + * TEGRA_PLLRE_OUT - This flag indicates that it is PLLRE_OUT and is used to
> + * identify PLLRE_OUT during clk_pll_out save and restore.
> */
> struct tegra_clk_pll_out {
> struct clk_hw hw;
> @@ -447,6 +451,8 @@ struct tegra_clk_pll_out {
> u8 flags;
> };
>
> +#define TEGRA_PLLRE_OUT BIT(0)
> +
> #define to_clk_pll_out(_hw) container_of(_hw, struct tegra_clk_pll_out, hw)
>
> extern const struct clk_ops tegra_clk_pll_out_ops;
>
Looks like the TEGRA_PLLRE_OUT flag is unused.
^ permalink raw reply
* Re: [PATCH v8 13/21] clk: tegra210: Use fence_udelay during PLLU init
From: Dmitry Osipenko @ 2019-08-11 18:02 UTC (permalink / raw)
To: Sowjanya Komatineni, thierry.reding, jonathanh, tglx, jason,
marc.zyngier, linus.walleij, stefan, mark.rutland
Cc: pdeschrijver, pgaikwad, sboyd, linux-clk, linux-gpio, jckuo,
josephl, talho, linux-tegra, linux-kernel, mperttunen, spatra,
robh+dt, devicetree, rjw, viresh.kumar, linux-pm
In-Reply-To: <1565308020-31952-14-git-send-email-skomatineni@nvidia.com>
09.08.2019 2:46, Sowjanya Komatineni пишет:
> This patch uses fence_udelay rather than udelay during PLLU
> initialization to ensure writes to clock registers happens before
> waiting for specified delay.
>
> Acked-by: Thierry Reding <treding@nvidia.com>
> Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
> ---
> drivers/clk/tegra/clk-tegra210.c | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/clk/tegra/clk-tegra210.c b/drivers/clk/tegra/clk-tegra210.c
> index 4721ee030d1c..998bf60b219a 100644
> --- a/drivers/clk/tegra/clk-tegra210.c
> +++ b/drivers/clk/tegra/clk-tegra210.c
> @@ -2841,7 +2841,7 @@ static int tegra210_enable_pllu(void)
> reg = readl_relaxed(clk_base + pllu.params->ext_misc_reg[0]);
> reg &= ~BIT(pllu.params->iddq_bit_idx);
> writel_relaxed(reg, clk_base + pllu.params->ext_misc_reg[0]);
> - udelay(5);
> + fence_udelay(5, clk_base);
>
> reg = readl_relaxed(clk_base + PLLU_BASE);
> reg &= ~GENMASK(20, 0);
> @@ -2849,7 +2849,7 @@ static int tegra210_enable_pllu(void)
> reg |= fentry->n << 8;
> reg |= fentry->p << 16;
> writel(reg, clk_base + PLLU_BASE);
> - udelay(1);
> + fence_udelay(1, clk_base);
> reg |= PLL_ENABLE;
> writel(reg, clk_base + PLLU_BASE);
>
> @@ -2895,12 +2895,12 @@ static int tegra210_init_pllu(void)
> reg = readl_relaxed(clk_base + XUSB_PLL_CFG0);
> reg &= ~XUSB_PLL_CFG0_PLLU_LOCK_DLY_MASK;
> writel_relaxed(reg, clk_base + XUSB_PLL_CFG0);
> - udelay(1);
> + fence_udelay(1, clk_base);
>
> reg = readl_relaxed(clk_base + PLLU_HW_PWRDN_CFG0);
> reg |= PLLU_HW_PWRDN_CFG0_SEQ_ENABLE;
> writel_relaxed(reg, clk_base + PLLU_HW_PWRDN_CFG0);
> - udelay(1);
> + fence_udelay(1, clk_base);
>
> reg = readl_relaxed(clk_base + PLLU_BASE);
> reg &= ~PLLU_BASE_CLKENABLE_USB;
>
The clk_base corresponds to the RESET controller's part of Clock-and-Reset hardware, is it
okay to read-back the RST register and not the clock for the fencing?
^ permalink raw reply
* Re: [PATCH v8 15/21] soc/tegra: pmc: Allow to support more tegras wake
From: Dmitry Osipenko @ 2019-08-11 17:52 UTC (permalink / raw)
To: Sowjanya Komatineni, thierry.reding, jonathanh, tglx, jason,
marc.zyngier, linus.walleij, stefan, mark.rutland
Cc: pdeschrijver, pgaikwad, sboyd, linux-clk, linux-gpio, jckuo,
josephl, talho, linux-tegra, linux-kernel, mperttunen, spatra,
robh+dt, devicetree, rjw, viresh.kumar, linux-pm
In-Reply-To: <1565308020-31952-16-git-send-email-skomatineni@nvidia.com>
09.08.2019 2:46, Sowjanya Komatineni пишет:
> This patch allows to create separate irq_set_wake and irq_set_type
> implementations for different tegra designs PMC that has different
> wake models which require difference wake registers and different
> programming sequence.
>
> AOWAKE model support is available for Tegra186 and Tegra194 only
> and it resides within PMC and supports tiered wake architecture.
>
> Tegra210 and prior tegra designs uses PMC directly to receive wake
> events and coordinate the wake sequence.
>
> Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
> ---
> drivers/soc/tegra/pmc.c | 12 ++++++++----
> 1 file changed, 8 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
> index 9f9c1c677cf4..91c84d0e66ae 100644
> --- a/drivers/soc/tegra/pmc.c
> +++ b/drivers/soc/tegra/pmc.c
> @@ -226,6 +226,8 @@ struct tegra_pmc_soc {
> void (*setup_irq_polarity)(struct tegra_pmc *pmc,
> struct device_node *np,
> bool invert);
> + int (*irq_set_wake)(struct irq_data *data, unsigned int on);
> + int (*irq_set_type)(struct irq_data *data, unsigned int type);
>
> const char * const *reset_sources;
> unsigned int num_reset_sources;
> @@ -1920,7 +1922,7 @@ static const struct irq_domain_ops tegra_pmc_irq_domain_ops = {
> .alloc = tegra_pmc_irq_alloc,
> };
>
> -static int tegra_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
> +static int tegra186_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
> {
> struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
> unsigned int offset, bit;
> @@ -1952,7 +1954,7 @@ static int tegra_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
> return 0;
> }
>
> -static int tegra_pmc_irq_set_type(struct irq_data *data, unsigned int type)
> +static int tegra186_pmc_irq_set_type(struct irq_data *data, unsigned int type)
> {
> struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
> u32 value;
> @@ -2006,8 +2008,8 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc)
> pmc->irq.irq_unmask = irq_chip_unmask_parent;
> pmc->irq.irq_eoi = irq_chip_eoi_parent;
> pmc->irq.irq_set_affinity = irq_chip_set_affinity_parent;
> - pmc->irq.irq_set_type = tegra_pmc_irq_set_type;
> - pmc->irq.irq_set_wake = tegra_pmc_irq_set_wake;
> + pmc->irq.irq_set_type = pmc->soc->irq_set_type;
> + pmc->irq.irq_set_wake = pmc->soc->irq_set_wake;
>
> pmc->domain = irq_domain_add_hierarchy(parent, 0, 96, pmc->dev->of_node,
> &tegra_pmc_irq_domain_ops, pmc);
> @@ -2680,6 +2682,8 @@ static const struct tegra_pmc_soc tegra186_pmc_soc = {
> .regs = &tegra186_pmc_regs,
> .init = NULL,
> .setup_irq_polarity = tegra186_pmc_setup_irq_polarity,
> + .irq_set_wake = tegra186_pmc_irq_set_wake,
> + .irq_set_type = tegra186_pmc_irq_set_type,
> .reset_sources = tegra186_reset_sources,
> .num_reset_sources = ARRAY_SIZE(tegra186_reset_sources),
> .reset_levels = tegra186_reset_levels,
>
Reviewed-by: Dmitry Osipenko <digetx@gmail.com>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox