diff options
author | Arnd Bergmann <arnd@arndb.de> | 2021-10-21 22:34:20 +0300 |
---|---|---|
committer | Arnd Bergmann <arnd@arndb.de> | 2021-10-21 22:34:21 +0300 |
commit | bccb5d53e259c56a2c7277441f2aa54cacc4267e (patch) | |
tree | f152cb50cd4b646a4f1dc7702f7d69a01c174697 /drivers/memory/tegra | |
parent | c13d33985defbc172388e6a6e0772904d43347fb (diff) | |
parent | 14b43c20c283de36131da0cb44f3170b9ffa7630 (diff) | |
download | linux-bccb5d53e259c56a2c7277441f2aa54cacc4267e.tar.xz |
Merge tag 'memory-controller-drv-5.16-2' of git://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl into arm/drivers
Memory controller drivers for v5.16, part two
1. Convert LPDDR2 bindings to dtschema and extend them with new
properties.
2. Tegra 20 EMC: support matching timings by LPDDR2 configuration from
devicetree.
* tag 'memory-controller-drv-5.16-2' of git://git.kernel.org/pub/scm/linux/kernel/git/krzk/linux-mem-ctrl:
memory: tegra20-emc: Add runtime dependency on devfreq governor module
memory: tegra20-emc: Support matching timings by LPDDR2 configuration
memory: Add LPDDR2-info helpers
dt-bindings: memory: tegra20: emc: Document new LPDDR2 sub-node
dt-bindings: Add vendor prefix for Elpida Memory
dt-bindings: memory: lpddr2: Document Elpida B8132B2PB-6D-F
dt-bindings: memory: lpddr2: Add revision-id properties
dt-bindings: memory: lpddr2: Convert to schema
dt-bindings: Relocate DDR bindings
Link: https://lore.kernel.org/r/20211021093002.118192-1-krzysztof.kozlowski@canonical.com
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Diffstat (limited to 'drivers/memory/tegra')
-rw-r--r-- | drivers/memory/tegra/Kconfig | 1 | ||||
-rw-r--r-- | drivers/memory/tegra/tegra20-emc.c | 200 |
2 files changed, 187 insertions, 14 deletions
diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig index f9bae36c03a3..7951764b4efe 100644 --- a/drivers/memory/tegra/Kconfig +++ b/drivers/memory/tegra/Kconfig @@ -16,6 +16,7 @@ config TEGRA20_EMC depends on ARCH_TEGRA_2x_SOC || COMPILE_TEST select DEVFREQ_GOV_SIMPLE_ONDEMAND select PM_DEVFREQ + select DDR help This driver is for the External Memory Controller (EMC) found on Tegra20 chips. The EMC controls the external DRAM on the board. diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c index c3462dbc8c22..497b6edbf3ca 100644 --- a/drivers/memory/tegra/tegra20-emc.c +++ b/drivers/memory/tegra/tegra20-emc.c @@ -5,6 +5,7 @@ * Author: Dmitry Osipenko <digetx@gmail.com> */ +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/clk/tegra.h> #include <linux/debugfs.h> @@ -27,11 +28,15 @@ #include <soc/tegra/common.h> #include <soc/tegra/fuse.h> +#include "../jedec_ddr.h" +#include "../of_memory.h" + #include "mc.h" #define EMC_INTSTATUS 0x000 #define EMC_INTMASK 0x004 #define EMC_DBG 0x008 +#define EMC_ADR_CFG_0 0x010 #define EMC_TIMING_CONTROL 0x028 #define EMC_RC 0x02c #define EMC_RFC 0x030 @@ -68,6 +73,7 @@ #define EMC_QUSE_EXTRA 0x0ac #define EMC_ODT_WRITE 0x0b0 #define EMC_ODT_READ 0x0b4 +#define EMC_MRR 0x0ec #define EMC_FBIO_CFG5 0x104 #define EMC_FBIO_CFG6 0x114 #define EMC_STAT_CONTROL 0x160 @@ -94,6 +100,7 @@ #define EMC_REFRESH_OVERFLOW_INT BIT(3) #define EMC_CLKCHANGE_COMPLETE_INT BIT(4) +#define EMC_MRR_DIVLD_INT BIT(5) #define EMC_DBG_READ_MUX_ASSEMBLY BIT(0) #define EMC_DBG_WRITE_MUX_ACTIVE BIT(1) @@ -102,11 +109,25 @@ #define EMC_DBG_CFG_PRIORITY BIT(24) #define EMC_FBIO_CFG5_DRAM_WIDTH_X16 BIT(4) +#define EMC_FBIO_CFG5_DRAM_TYPE GENMASK(1, 0) + +#define EMC_MRR_DEV_SELECTN GENMASK(31, 30) +#define EMC_MRR_MRR_MA GENMASK(23, 16) +#define EMC_MRR_MRR_DATA GENMASK(15, 0) + +#define EMC_ADR_CFG_0_EMEM_NUMDEV GENMASK(25, 24) #define EMC_PWR_GATHER_CLEAR (1 << 8) #define EMC_PWR_GATHER_DISABLE (2 << 8) #define EMC_PWR_GATHER_ENABLE (3 << 8) +enum emc_dram_type { + DRAM_TYPE_RESERVED, + DRAM_TYPE_DDR1, + DRAM_TYPE_LPDDR2, + DRAM_TYPE_DDR2, +}; + static const u16 emc_timing_registers[] = { EMC_RC, EMC_RFC, @@ -201,6 +222,14 @@ struct tegra_emc { struct mutex rate_lock; struct devfreq_simple_ondemand_data ondemand_data; + + /* memory chip identity information */ + union lpddr2_basic_config4 basic_conf4; + unsigned int manufacturer_id; + unsigned int revision_id1; + unsigned int revision_id2; + + bool mrr_error; }; static irqreturn_t tegra_emc_isr(int irq, void *data) @@ -397,15 +426,19 @@ static int tegra_emc_load_timings_from_dt(struct tegra_emc *emc, if (!emc->timings) return -ENOMEM; - emc->num_timings = child_count; timing = emc->timings; for_each_child_of_node(node, child) { + if (of_node_name_eq(child, "lpddr2")) + continue; + err = load_one_timing_from_dt(emc, timing++, child); if (err) { of_node_put(child); return err; } + + emc->num_timings++; } sort(emc->timings, emc->num_timings, sizeof(*timing), cmp_timings, @@ -422,12 +455,18 @@ static int tegra_emc_load_timings_from_dt(struct tegra_emc *emc, } static struct device_node * -tegra_emc_find_node_by_ram_code(struct device *dev) +tegra_emc_find_node_by_ram_code(struct tegra_emc *emc) { + struct device *dev = emc->dev; struct device_node *np; u32 value, ram_code; int err; + if (emc->mrr_error) { + dev_warn(dev, "memory timings skipped due to MRR error\n"); + return NULL; + } + if (of_get_child_count(dev->of_node) == 0) { dev_info_once(dev, "device-tree doesn't have memory timings\n"); return NULL; @@ -442,8 +481,49 @@ tegra_emc_find_node_by_ram_code(struct device *dev) np = of_find_node_by_name(np, "emc-tables")) { err = of_property_read_u32(np, "nvidia,ram-code", &value); if (err || value != ram_code) { - of_node_put(np); - continue; + struct device_node *lpddr2_np; + bool cfg_mismatches = false; + + lpddr2_np = of_find_node_by_name(np, "lpddr2"); + if (lpddr2_np) { + const struct lpddr2_info *info; + + info = of_lpddr2_get_info(lpddr2_np, dev); + if (info) { + if (info->manufacturer_id >= 0 && + info->manufacturer_id != emc->manufacturer_id) + cfg_mismatches = true; + + if (info->revision_id1 >= 0 && + info->revision_id1 != emc->revision_id1) + cfg_mismatches = true; + + if (info->revision_id2 >= 0 && + info->revision_id2 != emc->revision_id2) + cfg_mismatches = true; + + if (info->density != emc->basic_conf4.density) + cfg_mismatches = true; + + if (info->io_width != emc->basic_conf4.io_width) + cfg_mismatches = true; + + if (info->arch_type != emc->basic_conf4.arch_type) + cfg_mismatches = true; + } else { + dev_err(dev, "failed to parse %pOF\n", lpddr2_np); + cfg_mismatches = true; + } + + of_node_put(lpddr2_np); + } else { + cfg_mismatches = true; + } + + if (cfg_mismatches) { + of_node_put(np); + continue; + } } return np; @@ -455,10 +535,72 @@ tegra_emc_find_node_by_ram_code(struct device *dev) return NULL; } +static int emc_read_lpddr_mode_register(struct tegra_emc *emc, + unsigned int emem_dev, + unsigned int register_addr, + unsigned int *register_data) +{ + u32 memory_dev = emem_dev + 1; + u32 val, mr_mask = 0xff; + int err; + + /* clear data-valid interrupt status */ + writel_relaxed(EMC_MRR_DIVLD_INT, emc->regs + EMC_INTSTATUS); + + /* issue mode register read request */ + val = FIELD_PREP(EMC_MRR_DEV_SELECTN, memory_dev); + val |= FIELD_PREP(EMC_MRR_MRR_MA, register_addr); + + writel_relaxed(val, emc->regs + EMC_MRR); + + /* wait for the LPDDR2 data-valid interrupt */ + err = readl_relaxed_poll_timeout_atomic(emc->regs + EMC_INTSTATUS, val, + val & EMC_MRR_DIVLD_INT, + 1, 100); + if (err) { + dev_err(emc->dev, "mode register %u read failed: %d\n", + register_addr, err); + emc->mrr_error = true; + return err; + } + + /* read out mode register data */ + val = readl_relaxed(emc->regs + EMC_MRR); + *register_data = FIELD_GET(EMC_MRR_MRR_DATA, val) & mr_mask; + + return 0; +} + +static void emc_read_lpddr_sdram_info(struct tegra_emc *emc, + unsigned int emem_dev, + bool print_out) +{ + /* these registers are standard for all LPDDR JEDEC memory chips */ + emc_read_lpddr_mode_register(emc, emem_dev, 5, &emc->manufacturer_id); + emc_read_lpddr_mode_register(emc, emem_dev, 6, &emc->revision_id1); + emc_read_lpddr_mode_register(emc, emem_dev, 7, &emc->revision_id2); + emc_read_lpddr_mode_register(emc, emem_dev, 8, &emc->basic_conf4.value); + + if (!print_out) + return; + + dev_info(emc->dev, "SDRAM[dev%u]: manufacturer: 0x%x (%s) rev1: 0x%x rev2: 0x%x prefetch: S%u density: %uMbit iowidth: %ubit\n", + emem_dev, emc->manufacturer_id, + lpddr2_jedec_manufacturer(emc->manufacturer_id), + emc->revision_id1, emc->revision_id2, + 4 >> emc->basic_conf4.arch_type, + 64 << emc->basic_conf4.density, + 32 >> emc->basic_conf4.io_width); +} + static int emc_setup_hw(struct tegra_emc *emc) { + u32 emc_cfg, emc_dbg, emc_fbio, emc_adr_cfg; u32 intmask = EMC_REFRESH_OVERFLOW_INT; - u32 emc_cfg, emc_dbg, emc_fbio; + static bool print_sdram_info_once; + enum emc_dram_type dram_type; + const char *dram_type_str; + unsigned int emem_numdev; emc_cfg = readl_relaxed(emc->regs + EMC_CFG_2); @@ -496,7 +638,36 @@ static int emc_setup_hw(struct tegra_emc *emc) else emc->dram_bus_width = 32; - dev_info_once(emc->dev, "%ubit DRAM bus\n", emc->dram_bus_width); + dram_type = FIELD_GET(EMC_FBIO_CFG5_DRAM_TYPE, emc_fbio); + + switch (dram_type) { + case DRAM_TYPE_RESERVED: + dram_type_str = "INVALID"; + break; + case DRAM_TYPE_DDR1: + dram_type_str = "DDR1"; + break; + case DRAM_TYPE_LPDDR2: + dram_type_str = "LPDDR2"; + break; + case DRAM_TYPE_DDR2: + dram_type_str = "DDR2"; + break; + } + + emc_adr_cfg = readl_relaxed(emc->regs + EMC_ADR_CFG_0); + emem_numdev = FIELD_GET(EMC_ADR_CFG_0_EMEM_NUMDEV, emc_adr_cfg) + 1; + + dev_info_once(emc->dev, "%ubit DRAM bus, %u %s %s attached\n", + emc->dram_bus_width, emem_numdev, dram_type_str, + emem_numdev == 2 ? "devices" : "device"); + + if (dram_type == DRAM_TYPE_LPDDR2) { + while (emem_numdev--) + emc_read_lpddr_sdram_info(emc, emem_numdev, + !print_sdram_info_once); + print_sdram_info_once = true; + } return 0; } @@ -1049,14 +1220,6 @@ static int tegra_emc_probe(struct platform_device *pdev) emc->clk_nb.notifier_call = tegra_emc_clk_change_notify; emc->dev = &pdev->dev; - np = tegra_emc_find_node_by_ram_code(&pdev->dev); - if (np) { - err = tegra_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); @@ -1065,6 +1228,14 @@ static int tegra_emc_probe(struct platform_device *pdev) if (err) return err; + np = tegra_emc_find_node_by_ram_code(emc); + if (np) { + err = tegra_emc_load_timings_from_dt(emc, np); + of_node_put(np); + if (err) + return err; + } + err = devm_request_irq(&pdev->dev, irq, tegra_emc_isr, 0, dev_name(&pdev->dev), emc); if (err) { @@ -1117,4 +1288,5 @@ module_platform_driver(tegra_emc_driver); MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>"); MODULE_DESCRIPTION("NVIDIA Tegra20 EMC driver"); +MODULE_SOFTDEP("pre: governor_simpleondemand"); MODULE_LICENSE("GPL v2"); |