diff options
Diffstat (limited to 'drivers/memory')
37 files changed, 17836 insertions, 0 deletions
diff --git a/drivers/memory/Kconfig b/drivers/memory/Kconfig new file mode 100644 index 000000000..63389f075 --- /dev/null +++ b/drivers/memory/Kconfig @@ -0,0 +1,151 @@ +# +# Memory devices +# + +menuconfig MEMORY + bool "Memory Controller drivers" + +if MEMORY + +config ARM_PL172_MPMC + tristate "ARM PL172 MPMC driver" + depends on ARM_AMBA && OF + help + This selects the ARM PrimeCell PL172 MultiPort Memory Controller. + If you have an embedded system with an AMBA bus and a PL172 + controller, say Y or M here. + +config ATMEL_SDRAMC + bool "Atmel (Multi-port DDR-)SDRAM Controller" + default y + depends on ARCH_AT91 && OF + help + This driver is for Atmel SDRAM Controller or Atmel Multi-port + DDR-SDRAM Controller available on Atmel AT91SAM9 and SAMA5 SoCs. + Starting with the at91sam9g45, this controller supports SDR, DDR and + LP-DDR memories. + +config ATMEL_EBI + bool "Atmel EBI driver" + default y + depends on ARCH_AT91 && OF + select MFD_SYSCON + select MFD_ATMEL_SMC + help + Driver for Atmel EBI controller. + Used to configure the EBI (external bus interface) when the device- + tree is used. This bus supports NANDs, external ethernet controller, + SRAMs, ATA devices, etc. + +config TI_AEMIF + tristate "Texas Instruments AEMIF driver" + depends on (ARCH_DAVINCI || ARCH_KEYSTONE) && OF + help + This driver is for the AEMIF module available in Texas Instruments + SoCs. AEMIF stands for Asynchronous External Memory Interface and + is intended to provide a glue-less interface to a variety of + asynchronuous memory devices like ASRAM, NOR and NAND memory. A total + of 256M bytes of any of these memories can be accessed at a given + time via four chip selects with 64M byte access per chip select. + +config TI_EMIF + tristate "Texas Instruments EMIF driver" + depends on ARCH_OMAP2PLUS + select DDR + help + This driver is for the EMIF module available in Texas Instruments + SoCs. EMIF is an SDRAM controller that, based on its revision, + supports one or more of DDR2, DDR3, and LPDDR2 SDRAM protocols. + This driver takes care of only LPDDR2 memories presently. The + functions of the driver includes re-configuring AC timing + parameters and other settings during frequency, voltage and + temperature changes + +config OMAP_GPMC + bool + select GPIOLIB + help + This driver is for the General Purpose Memory Controller (GPMC) + present on Texas Instruments SoCs (e.g. OMAP2+). GPMC allows + interfacing to a variety of asynchronous as well as synchronous + memory drives like NOR, NAND, OneNAND, SRAM. + +config OMAP_GPMC_DEBUG + bool "Enable GPMC debug output and skip reset of GPMC during init" + depends on OMAP_GPMC + help + Enables verbose debugging mostly to decode the bootloader provided + timings. To preserve the bootloader provided timings, the reset + of GPMC is skipped during init. Enable this during development to + configure devices connected to the GPMC bus. + + NOTE: In addition to matching the register setup with the bootloader + you also need to match the GPMC FCLK frequency used by the + bootloader or else the GPMC timings won't be identical with the + bootloader timings. + +config TI_EMIF_SRAM + tristate "Texas Instruments EMIF SRAM driver" + depends on (SOC_AM33XX || SOC_AM43XX) && SRAM + help + This driver is for the EMIF module available on Texas Instruments + AM33XX and AM43XX SoCs and is required for PM. Certain parts of + the EMIF PM code must run from on-chip SRAM late in the suspend + sequence so this driver provides several relocatable PM functions + for the SoC PM code to use. + +config MVEBU_DEVBUS + bool "Marvell EBU Device Bus Controller" + default y + depends on PLAT_ORION && OF + help + This driver is for the Device Bus controller available in some + Marvell EBU SoCs such as Discovery (mv78xx0), Orion (88f5xxx) and + Armada 370 and Armada XP. This controller allows to handle flash + devices such as NOR, NAND, SRAM, and FPGA. + +config FSL_CORENET_CF + tristate "Freescale CoreNet Error Reporting" + depends on FSL_SOC_BOOKE + help + Say Y for reporting of errors from the Freescale CoreNet + Coherency Fabric. Errors reported include accesses to + physical addresses that mapped by no local access window + (LAW) or an invalid LAW, as well as bad cache state that + represents a coherency violation. + +config FSL_IFC + bool + depends on FSL_SOC || ARCH_LAYERSCAPE || SOC_LS1021A || COMPILE_TEST + depends on HAS_IOMEM + +config JZ4780_NEMC + bool "Ingenic JZ4780 SoC NEMC driver" + default y + depends on MACH_JZ4780 || COMPILE_TEST + depends on HAS_IOMEM && OF + help + This driver is for the NAND/External Memory Controller (NEMC) in + the Ingenic JZ4780. This controller is used to handle external + memory devices such as NAND and SRAM. + +config MTK_SMI + bool + depends on ARCH_MEDIATEK || COMPILE_TEST + help + This driver is for the Memory Controller module in MediaTek SoCs, + mainly help enable/disable iommu and control the power domain and + clocks for each local arbiter. + +config DA8XX_DDRCTL + bool "Texas Instruments da8xx DDR2/mDDR driver" + depends on ARCH_DAVINCI_DA8XX + help + This driver is for the DDR2/mDDR Memory Controller present on + Texas Instruments da8xx SoCs. It's used to tweak various memory + controller configuration options. + +source "drivers/memory/samsung/Kconfig" +source "drivers/memory/tegra/Kconfig" + +endif diff --git a/drivers/memory/Makefile b/drivers/memory/Makefile new file mode 100644 index 000000000..a01ab3e22 --- /dev/null +++ b/drivers/memory/Makefile @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for memory devices +# + +ifeq ($(CONFIG_DDR),y) +obj-$(CONFIG_OF) += of_memory.o +endif +obj-$(CONFIG_ARM_PL172_MPMC) += pl172.o +obj-$(CONFIG_ATMEL_SDRAMC) += atmel-sdramc.o +obj-$(CONFIG_ATMEL_EBI) += atmel-ebi.o +obj-$(CONFIG_ARCH_BRCMSTB) += brcmstb_dpfe.o +obj-$(CONFIG_TI_AEMIF) += ti-aemif.o +obj-$(CONFIG_TI_EMIF) += emif.o +obj-$(CONFIG_OMAP_GPMC) += omap-gpmc.o +obj-$(CONFIG_FSL_CORENET_CF) += fsl-corenet-cf.o +obj-$(CONFIG_FSL_IFC) += fsl_ifc.o +obj-$(CONFIG_MVEBU_DEVBUS) += mvebu-devbus.o +obj-$(CONFIG_JZ4780_NEMC) += jz4780-nemc.o +obj-$(CONFIG_MTK_SMI) += mtk-smi.o +obj-$(CONFIG_DA8XX_DDRCTL) += da8xx-ddrctl.o + +obj-$(CONFIG_SAMSUNG_MC) += samsung/ +obj-$(CONFIG_TEGRA_MC) += tegra/ +obj-$(CONFIG_TI_EMIF_SRAM) += ti-emif-sram.o +ti-emif-sram-objs := ti-emif-pm.o ti-emif-sram-pm.o + +AFLAGS_ti-emif-sram-pm.o :=-Wa,-march=armv7-a + +include drivers/memory/Makefile.asm-offsets + +drivers/memory/ti-emif-sram-pm.o: include/generated/ti-emif-asm-offsets.h diff --git a/drivers/memory/Makefile.asm-offsets b/drivers/memory/Makefile.asm-offsets new file mode 100644 index 000000000..843ff60cc --- /dev/null +++ b/drivers/memory/Makefile.asm-offsets @@ -0,0 +1,5 @@ +drivers/memory/emif-asm-offsets.s: drivers/memory/emif-asm-offsets.c + $(call if_changed_dep,cc_s_c) + +include/generated/ti-emif-asm-offsets.h: drivers/memory/emif-asm-offsets.s FORCE + $(call filechk,offsets,__TI_EMIF_ASM_OFFSETS_H__) diff --git a/drivers/memory/atmel-ebi.c b/drivers/memory/atmel-ebi.c new file mode 100644 index 000000000..8e7b5a1d2 --- /dev/null +++ b/drivers/memory/atmel-ebi.c @@ -0,0 +1,627 @@ +/* + * EBI driver for Atmel chips + * inspired by the fsl weim bus driver + * + * Copyright (C) 2013 Jean-Jacques Hiblot <jjhiblot@traphandler.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/atmel-matrix.h> +#include <linux/mfd/syscon/atmel-smc.h> +#include <linux/init.h> +#include <linux/of_device.h> +#include <linux/regmap.h> + +struct atmel_ebi_dev_config { + int cs; + struct atmel_smc_cs_conf smcconf; +}; + +struct atmel_ebi; + +struct atmel_ebi_dev { + struct list_head node; + struct atmel_ebi *ebi; + u32 mode; + int numcs; + struct atmel_ebi_dev_config configs[]; +}; + +struct atmel_ebi_caps { + unsigned int available_cs; + unsigned int ebi_csa_offs; + void (*get_config)(struct atmel_ebi_dev *ebid, + struct atmel_ebi_dev_config *conf); + int (*xlate_config)(struct atmel_ebi_dev *ebid, + struct device_node *configs_np, + struct atmel_ebi_dev_config *conf); + void (*apply_config)(struct atmel_ebi_dev *ebid, + struct atmel_ebi_dev_config *conf); +}; + +struct atmel_ebi { + struct clk *clk; + struct regmap *matrix; + struct { + struct regmap *regmap; + struct clk *clk; + const struct atmel_hsmc_reg_layout *layout; + } smc; + + struct device *dev; + const struct atmel_ebi_caps *caps; + struct list_head devs; +}; + +struct atmel_smc_timing_xlate { + const char *name; + int (*converter)(struct atmel_smc_cs_conf *conf, + unsigned int shift, unsigned int nycles); + unsigned int shift; +}; + +#define ATMEL_SMC_SETUP_XLATE(nm, pos) \ + { .name = nm, .converter = atmel_smc_cs_conf_set_setup, .shift = pos} + +#define ATMEL_SMC_PULSE_XLATE(nm, pos) \ + { .name = nm, .converter = atmel_smc_cs_conf_set_pulse, .shift = pos} + +#define ATMEL_SMC_CYCLE_XLATE(nm, pos) \ + { .name = nm, .converter = atmel_smc_cs_conf_set_cycle, .shift = pos} + +static void at91sam9_ebi_get_config(struct atmel_ebi_dev *ebid, + struct atmel_ebi_dev_config *conf) +{ + atmel_smc_cs_conf_get(ebid->ebi->smc.regmap, conf->cs, + &conf->smcconf); +} + +static void sama5_ebi_get_config(struct atmel_ebi_dev *ebid, + struct atmel_ebi_dev_config *conf) +{ + atmel_hsmc_cs_conf_get(ebid->ebi->smc.regmap, ebid->ebi->smc.layout, + conf->cs, &conf->smcconf); +} + +static const struct atmel_smc_timing_xlate timings_xlate_table[] = { + ATMEL_SMC_SETUP_XLATE("atmel,smc-ncs-rd-setup-ns", + ATMEL_SMC_NCS_RD_SHIFT), + ATMEL_SMC_SETUP_XLATE("atmel,smc-ncs-wr-setup-ns", + ATMEL_SMC_NCS_WR_SHIFT), + ATMEL_SMC_SETUP_XLATE("atmel,smc-nrd-setup-ns", ATMEL_SMC_NRD_SHIFT), + ATMEL_SMC_SETUP_XLATE("atmel,smc-nwe-setup-ns", ATMEL_SMC_NWE_SHIFT), + ATMEL_SMC_PULSE_XLATE("atmel,smc-ncs-rd-pulse-ns", + ATMEL_SMC_NCS_RD_SHIFT), + ATMEL_SMC_PULSE_XLATE("atmel,smc-ncs-wr-pulse-ns", + ATMEL_SMC_NCS_WR_SHIFT), + ATMEL_SMC_PULSE_XLATE("atmel,smc-nrd-pulse-ns", ATMEL_SMC_NRD_SHIFT), + ATMEL_SMC_PULSE_XLATE("atmel,smc-nwe-pulse-ns", ATMEL_SMC_NWE_SHIFT), + ATMEL_SMC_CYCLE_XLATE("atmel,smc-nrd-cycle-ns", ATMEL_SMC_NRD_SHIFT), + ATMEL_SMC_CYCLE_XLATE("atmel,smc-nwe-cycle-ns", ATMEL_SMC_NWE_SHIFT), +}; + +static int atmel_ebi_xslate_smc_timings(struct atmel_ebi_dev *ebid, + struct device_node *np, + struct atmel_smc_cs_conf *smcconf) +{ + unsigned int clk_rate = clk_get_rate(ebid->ebi->clk); + unsigned int clk_period_ns = NSEC_PER_SEC / clk_rate; + bool required = false; + unsigned int ncycles; + int ret, i; + u32 val; + + ret = of_property_read_u32(np, "atmel,smc-tdf-ns", &val); + if (!ret) { + required = true; + ncycles = DIV_ROUND_UP(val, clk_period_ns); + if (ncycles > ATMEL_SMC_MODE_TDF_MAX) { + ret = -EINVAL; + goto out; + } + + if (ncycles < ATMEL_SMC_MODE_TDF_MIN) + ncycles = ATMEL_SMC_MODE_TDF_MIN; + + smcconf->mode |= ATMEL_SMC_MODE_TDF(ncycles); + } + + for (i = 0; i < ARRAY_SIZE(timings_xlate_table); i++) { + const struct atmel_smc_timing_xlate *xlate; + + xlate = &timings_xlate_table[i]; + + ret = of_property_read_u32(np, xlate->name, &val); + if (ret) { + if (!required) + continue; + else + break; + } + + if (!required) { + ret = -EINVAL; + break; + } + + ncycles = DIV_ROUND_UP(val, clk_period_ns); + ret = xlate->converter(smcconf, xlate->shift, ncycles); + if (ret) + goto out; + } + +out: + if (ret) { + dev_err(ebid->ebi->dev, + "missing or invalid timings definition in %pOF", + np); + return ret; + } + + return required; +} + +static int atmel_ebi_xslate_smc_config(struct atmel_ebi_dev *ebid, + struct device_node *np, + struct atmel_ebi_dev_config *conf) +{ + struct atmel_smc_cs_conf *smcconf = &conf->smcconf; + bool required = false; + const char *tmp_str; + u32 tmp; + int ret; + + ret = of_property_read_u32(np, "atmel,smc-bus-width", &tmp); + if (!ret) { + switch (tmp) { + case 8: + smcconf->mode |= ATMEL_SMC_MODE_DBW_8; + break; + + case 16: + smcconf->mode |= ATMEL_SMC_MODE_DBW_16; + break; + + case 32: + smcconf->mode |= ATMEL_SMC_MODE_DBW_32; + break; + + default: + return -EINVAL; + } + + required = true; + } + + if (of_property_read_bool(np, "atmel,smc-tdf-optimized")) { + smcconf->mode |= ATMEL_SMC_MODE_TDFMODE_OPTIMIZED; + required = true; + } + + tmp_str = NULL; + of_property_read_string(np, "atmel,smc-byte-access-type", &tmp_str); + if (tmp_str && !strcmp(tmp_str, "write")) { + smcconf->mode |= ATMEL_SMC_MODE_BAT_WRITE; + required = true; + } + + tmp_str = NULL; + of_property_read_string(np, "atmel,smc-read-mode", &tmp_str); + if (tmp_str && !strcmp(tmp_str, "nrd")) { + smcconf->mode |= ATMEL_SMC_MODE_READMODE_NRD; + required = true; + } + + tmp_str = NULL; + of_property_read_string(np, "atmel,smc-write-mode", &tmp_str); + if (tmp_str && !strcmp(tmp_str, "nwe")) { + smcconf->mode |= ATMEL_SMC_MODE_WRITEMODE_NWE; + required = true; + } + + tmp_str = NULL; + of_property_read_string(np, "atmel,smc-exnw-mode", &tmp_str); + if (tmp_str) { + if (!strcmp(tmp_str, "frozen")) + smcconf->mode |= ATMEL_SMC_MODE_EXNWMODE_FROZEN; + else if (!strcmp(tmp_str, "ready")) + smcconf->mode |= ATMEL_SMC_MODE_EXNWMODE_READY; + else if (strcmp(tmp_str, "disabled")) + return -EINVAL; + + required = true; + } + + ret = of_property_read_u32(np, "atmel,smc-page-mode", &tmp); + if (!ret) { + switch (tmp) { + case 4: + smcconf->mode |= ATMEL_SMC_MODE_PS_4; + break; + + case 8: + smcconf->mode |= ATMEL_SMC_MODE_PS_8; + break; + + case 16: + smcconf->mode |= ATMEL_SMC_MODE_PS_16; + break; + + case 32: + smcconf->mode |= ATMEL_SMC_MODE_PS_32; + break; + + default: + return -EINVAL; + } + + smcconf->mode |= ATMEL_SMC_MODE_PMEN; + required = true; + } + + ret = atmel_ebi_xslate_smc_timings(ebid, np, &conf->smcconf); + if (ret < 0) + return -EINVAL; + + if ((ret > 0 && !required) || (!ret && required)) { + dev_err(ebid->ebi->dev, "missing atmel,smc- properties in %pOF", + np); + return -EINVAL; + } + + return required; +} + +static void at91sam9_ebi_apply_config(struct atmel_ebi_dev *ebid, + struct atmel_ebi_dev_config *conf) +{ + atmel_smc_cs_conf_apply(ebid->ebi->smc.regmap, conf->cs, + &conf->smcconf); +} + +static void sama5_ebi_apply_config(struct atmel_ebi_dev *ebid, + struct atmel_ebi_dev_config *conf) +{ + atmel_hsmc_cs_conf_apply(ebid->ebi->smc.regmap, ebid->ebi->smc.layout, + conf->cs, &conf->smcconf); +} + +static int atmel_ebi_dev_setup(struct atmel_ebi *ebi, struct device_node *np, + int reg_cells) +{ + const struct atmel_ebi_caps *caps = ebi->caps; + struct atmel_ebi_dev_config conf = { }; + struct device *dev = ebi->dev; + struct atmel_ebi_dev *ebid; + unsigned long cslines = 0; + int ret, numcs = 0, nentries, i; + bool apply = false; + u32 cs; + + nentries = of_property_count_elems_of_size(np, "reg", + reg_cells * sizeof(u32)); + for (i = 0; i < nentries; i++) { + ret = of_property_read_u32_index(np, "reg", i * reg_cells, + &cs); + if (ret) + return ret; + + if (cs >= AT91_MATRIX_EBI_NUM_CS || + !(ebi->caps->available_cs & BIT(cs))) { + dev_err(dev, "invalid reg property in %pOF\n", np); + return -EINVAL; + } + + if (!test_and_set_bit(cs, &cslines)) + numcs++; + } + + if (!numcs) { + dev_err(dev, "invalid reg property in %pOF\n", np); + return -EINVAL; + } + + ebid = devm_kzalloc(ebi->dev, + sizeof(*ebid) + (numcs * sizeof(*ebid->configs)), + GFP_KERNEL); + if (!ebid) + return -ENOMEM; + + ebid->ebi = ebi; + ebid->numcs = numcs; + + ret = caps->xlate_config(ebid, np, &conf); + if (ret < 0) + return ret; + else if (ret) + apply = true; + + i = 0; + for_each_set_bit(cs, &cslines, AT91_MATRIX_EBI_NUM_CS) { + ebid->configs[i].cs = cs; + + if (apply) { + conf.cs = cs; + caps->apply_config(ebid, &conf); + } + + caps->get_config(ebid, &ebid->configs[i]); + + /* + * Attach the EBI device to the generic SMC logic if at least + * one "atmel,smc-" property is present. + */ + if (ebi->caps->ebi_csa_offs && apply) + regmap_update_bits(ebi->matrix, + ebi->caps->ebi_csa_offs, + BIT(cs), 0); + + i++; + } + + list_add_tail(&ebid->node, &ebi->devs); + + return 0; +} + +static const struct atmel_ebi_caps at91sam9260_ebi_caps = { + .available_cs = 0xff, + .ebi_csa_offs = AT91SAM9260_MATRIX_EBICSA, + .get_config = at91sam9_ebi_get_config, + .xlate_config = atmel_ebi_xslate_smc_config, + .apply_config = at91sam9_ebi_apply_config, +}; + +static const struct atmel_ebi_caps at91sam9261_ebi_caps = { + .available_cs = 0xff, + .ebi_csa_offs = AT91SAM9261_MATRIX_EBICSA, + .get_config = at91sam9_ebi_get_config, + .xlate_config = atmel_ebi_xslate_smc_config, + .apply_config = at91sam9_ebi_apply_config, +}; + +static const struct atmel_ebi_caps at91sam9263_ebi0_caps = { + .available_cs = 0x3f, + .ebi_csa_offs = AT91SAM9263_MATRIX_EBI0CSA, + .get_config = at91sam9_ebi_get_config, + .xlate_config = atmel_ebi_xslate_smc_config, + .apply_config = at91sam9_ebi_apply_config, +}; + +static const struct atmel_ebi_caps at91sam9263_ebi1_caps = { + .available_cs = 0x7, + .ebi_csa_offs = AT91SAM9263_MATRIX_EBI1CSA, + .get_config = at91sam9_ebi_get_config, + .xlate_config = atmel_ebi_xslate_smc_config, + .apply_config = at91sam9_ebi_apply_config, +}; + +static const struct atmel_ebi_caps at91sam9rl_ebi_caps = { + .available_cs = 0x3f, + .ebi_csa_offs = AT91SAM9RL_MATRIX_EBICSA, + .get_config = at91sam9_ebi_get_config, + .xlate_config = atmel_ebi_xslate_smc_config, + .apply_config = at91sam9_ebi_apply_config, +}; + +static const struct atmel_ebi_caps at91sam9g45_ebi_caps = { + .available_cs = 0x3f, + .ebi_csa_offs = AT91SAM9G45_MATRIX_EBICSA, + .get_config = at91sam9_ebi_get_config, + .xlate_config = atmel_ebi_xslate_smc_config, + .apply_config = at91sam9_ebi_apply_config, +}; + +static const struct atmel_ebi_caps at91sam9x5_ebi_caps = { + .available_cs = 0x3f, + .ebi_csa_offs = AT91SAM9X5_MATRIX_EBICSA, + .get_config = at91sam9_ebi_get_config, + .xlate_config = atmel_ebi_xslate_smc_config, + .apply_config = at91sam9_ebi_apply_config, +}; + +static const struct atmel_ebi_caps sama5d3_ebi_caps = { + .available_cs = 0xf, + .get_config = sama5_ebi_get_config, + .xlate_config = atmel_ebi_xslate_smc_config, + .apply_config = sama5_ebi_apply_config, +}; + +static const struct of_device_id atmel_ebi_id_table[] = { + { + .compatible = "atmel,at91sam9260-ebi", + .data = &at91sam9260_ebi_caps, + }, + { + .compatible = "atmel,at91sam9261-ebi", + .data = &at91sam9261_ebi_caps, + }, + { + .compatible = "atmel,at91sam9263-ebi0", + .data = &at91sam9263_ebi0_caps, + }, + { + .compatible = "atmel,at91sam9263-ebi1", + .data = &at91sam9263_ebi1_caps, + }, + { + .compatible = "atmel,at91sam9rl-ebi", + .data = &at91sam9rl_ebi_caps, + }, + { + .compatible = "atmel,at91sam9g45-ebi", + .data = &at91sam9g45_ebi_caps, + }, + { + .compatible = "atmel,at91sam9x5-ebi", + .data = &at91sam9x5_ebi_caps, + }, + { + .compatible = "atmel,sama5d3-ebi", + .data = &sama5d3_ebi_caps, + }, + { /* sentinel */ } +}; + +static int atmel_ebi_dev_disable(struct atmel_ebi *ebi, struct device_node *np) +{ + struct device *dev = ebi->dev; + struct property *newprop; + + newprop = devm_kzalloc(dev, sizeof(*newprop), GFP_KERNEL); + if (!newprop) + return -ENOMEM; + + newprop->name = devm_kstrdup(dev, "status", GFP_KERNEL); + if (!newprop->name) + return -ENOMEM; + + newprop->value = devm_kstrdup(dev, "disabled", GFP_KERNEL); + if (!newprop->value) + return -ENOMEM; + + newprop->length = sizeof("disabled"); + + return of_update_property(np, newprop); +} + +static int atmel_ebi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *child, *np = dev->of_node, *smc_np; + const struct of_device_id *match; + struct atmel_ebi *ebi; + int ret, reg_cells; + struct clk *clk; + u32 val; + + match = of_match_device(atmel_ebi_id_table, dev); + if (!match || !match->data) + return -EINVAL; + + ebi = devm_kzalloc(dev, sizeof(*ebi), GFP_KERNEL); + if (!ebi) + return -ENOMEM; + + platform_set_drvdata(pdev, ebi); + + INIT_LIST_HEAD(&ebi->devs); + ebi->caps = match->data; + ebi->dev = dev; + + clk = devm_clk_get(dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ebi->clk = clk; + + smc_np = of_parse_phandle(dev->of_node, "atmel,smc", 0); + + ebi->smc.regmap = syscon_node_to_regmap(smc_np); + if (IS_ERR(ebi->smc.regmap)) { + ret = PTR_ERR(ebi->smc.regmap); + goto put_node; + } + + ebi->smc.layout = atmel_hsmc_get_reg_layout(smc_np); + if (IS_ERR(ebi->smc.layout)) { + ret = PTR_ERR(ebi->smc.layout); + goto put_node; + } + + ebi->smc.clk = of_clk_get(smc_np, 0); + if (IS_ERR(ebi->smc.clk)) { + if (PTR_ERR(ebi->smc.clk) != -ENOENT) { + ret = PTR_ERR(ebi->smc.clk); + goto put_node; + } + + ebi->smc.clk = NULL; + } + of_node_put(smc_np); + ret = clk_prepare_enable(ebi->smc.clk); + if (ret) + return ret; + + /* + * The sama5d3 does not provide an EBICSA register and thus does need + * to access the matrix registers. + */ + if (ebi->caps->ebi_csa_offs) { + ebi->matrix = + syscon_regmap_lookup_by_phandle(np, "atmel,matrix"); + if (IS_ERR(ebi->matrix)) + return PTR_ERR(ebi->matrix); + } + + ret = of_property_read_u32(np, "#address-cells", &val); + if (ret) { + dev_err(dev, "missing #address-cells property\n"); + return ret; + } + + reg_cells = val; + + ret = of_property_read_u32(np, "#size-cells", &val); + if (ret) { + dev_err(dev, "missing #address-cells property\n"); + return ret; + } + + reg_cells += val; + + for_each_available_child_of_node(np, child) { + if (!of_find_property(child, "reg", NULL)) + continue; + + ret = atmel_ebi_dev_setup(ebi, child, reg_cells); + if (ret) { + dev_err(dev, "failed to configure EBI bus for %pOF, disabling the device", + child); + + ret = atmel_ebi_dev_disable(ebi, child); + if (ret) { + of_node_put(child); + return ret; + } + } + } + + return of_platform_populate(np, NULL, NULL, dev); + +put_node: + of_node_put(smc_np); + return ret; +} + +static __maybe_unused int atmel_ebi_resume(struct device *dev) +{ + struct atmel_ebi *ebi = dev_get_drvdata(dev); + struct atmel_ebi_dev *ebid; + + list_for_each_entry(ebid, &ebi->devs, node) { + int i; + + for (i = 0; i < ebid->numcs; i++) + ebid->ebi->caps->apply_config(ebid, &ebid->configs[i]); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(atmel_ebi_pm_ops, NULL, atmel_ebi_resume); + +static struct platform_driver atmel_ebi_driver = { + .driver = { + .name = "atmel-ebi", + .of_match_table = atmel_ebi_id_table, + .pm = &atmel_ebi_pm_ops, + }, +}; +builtin_platform_driver_probe(atmel_ebi_driver, atmel_ebi_probe); diff --git a/drivers/memory/atmel-sdramc.c b/drivers/memory/atmel-sdramc.c new file mode 100644 index 000000000..b418b39af --- /dev/null +++ b/drivers/memory/atmel-sdramc.c @@ -0,0 +1,88 @@ +/* + * Atmel (Multi-port DDR-)SDRAM Controller driver + * + * Author: Alexandre Belloni <alexandre.belloni@free-electrons.com> + * + * Copyright (C) 2014 Atmel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +struct at91_ramc_caps { + bool has_ddrck; + bool has_mpddr_clk; +}; + +static const struct at91_ramc_caps at91rm9200_caps = { }; + +static const struct at91_ramc_caps at91sam9g45_caps = { + .has_ddrck = 1, + .has_mpddr_clk = 0, +}; + +static const struct at91_ramc_caps sama5d3_caps = { + .has_ddrck = 1, + .has_mpddr_clk = 1, +}; + +static const struct of_device_id atmel_ramc_of_match[] = { + { .compatible = "atmel,at91rm9200-sdramc", .data = &at91rm9200_caps, }, + { .compatible = "atmel,at91sam9260-sdramc", .data = &at91rm9200_caps, }, + { .compatible = "atmel,at91sam9g45-ddramc", .data = &at91sam9g45_caps, }, + { .compatible = "atmel,sama5d3-ddramc", .data = &sama5d3_caps, }, + {}, +}; + +static int atmel_ramc_probe(struct platform_device *pdev) +{ + const struct at91_ramc_caps *caps; + struct clk *clk; + + caps = of_device_get_match_data(&pdev->dev); + + if (caps->has_ddrck) { + clk = devm_clk_get(&pdev->dev, "ddrck"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + clk_prepare_enable(clk); + } + + if (caps->has_mpddr_clk) { + clk = devm_clk_get(&pdev->dev, "mpddr"); + if (IS_ERR(clk)) { + pr_err("AT91 RAMC: couldn't get mpddr clock\n"); + return PTR_ERR(clk); + } + clk_prepare_enable(clk); + } + + return 0; +} + +static struct platform_driver atmel_ramc_driver = { + .probe = atmel_ramc_probe, + .driver = { + .name = "atmel-ramc", + .of_match_table = atmel_ramc_of_match, + }, +}; + +builtin_platform_driver(atmel_ramc_driver); diff --git a/drivers/memory/brcmstb_dpfe.c b/drivers/memory/brcmstb_dpfe.c new file mode 100644 index 000000000..04599eccd --- /dev/null +++ b/drivers/memory/brcmstb_dpfe.c @@ -0,0 +1,746 @@ +/* + * DDR PHY Front End (DPFE) driver for Broadcom set top box SoCs + * + * Copyright (c) 2017 Broadcom + * + * Released under the GPLv2 only. + * SPDX-License-Identifier: GPL-2.0 + */ + +/* + * This driver provides access to the DPFE interface of Broadcom STB SoCs. + * The firmware running on the DCPU inside the DDR PHY can provide current + * information about the system's RAM, for instance the DRAM refresh rate. + * This can be used as an indirect indicator for the DRAM's temperature. + * Slower refresh rate means cooler RAM, higher refresh rate means hotter + * RAM. + * + * Throughout the driver, we use readl_relaxed() and writel_relaxed(), which + * already contain the appropriate le32_to_cpu()/cpu_to_le32() calls. + * + * Note regarding the loading of the firmware image: we use be32_to_cpu() + * and le_32_to_cpu(), so we can support the following four cases: + * - LE kernel + LE firmware image (the most common case) + * - LE kernel + BE firmware image + * - BE kernel + LE firmware image + * - BE kernel + BE firmware image + * + * The DPCU always runs in big endian mode. The firwmare image, however, can + * be in either format. Also, communication between host CPU and DCPU is + * always in little endian. + */ + +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> + +#define DRVNAME "brcmstb-dpfe" +#define FIRMWARE_NAME "dpfe.bin" + +/* DCPU register offsets */ +#define REG_DCPU_RESET 0x0 +#define REG_TO_DCPU_MBOX 0x10 +#define REG_TO_HOST_MBOX 0x14 + +/* Macros to process offsets returned by the DCPU */ +#define DRAM_MSG_ADDR_OFFSET 0x0 +#define DRAM_MSG_TYPE_OFFSET 0x1c +#define DRAM_MSG_ADDR_MASK ((1UL << DRAM_MSG_TYPE_OFFSET) - 1) +#define DRAM_MSG_TYPE_MASK ((1UL << \ + (BITS_PER_LONG - DRAM_MSG_TYPE_OFFSET)) - 1) + +/* Message RAM */ +#define DCPU_MSG_RAM_START 0x100 +#define DCPU_MSG_RAM(x) (DCPU_MSG_RAM_START + (x) * sizeof(u32)) + +/* DRAM Info Offsets & Masks */ +#define DRAM_INFO_INTERVAL 0x0 +#define DRAM_INFO_MR4 0x4 +#define DRAM_INFO_ERROR 0x8 +#define DRAM_INFO_MR4_MASK 0xff + +/* DRAM MR4 Offsets & Masks */ +#define DRAM_MR4_REFRESH 0x0 /* Refresh rate */ +#define DRAM_MR4_SR_ABORT 0x3 /* Self Refresh Abort */ +#define DRAM_MR4_PPRE 0x4 /* Post-package repair entry/exit */ +#define DRAM_MR4_TH_OFFS 0x5 /* Thermal Offset; vendor specific */ +#define DRAM_MR4_TUF 0x7 /* Temperature Update Flag */ + +#define DRAM_MR4_REFRESH_MASK 0x7 +#define DRAM_MR4_SR_ABORT_MASK 0x1 +#define DRAM_MR4_PPRE_MASK 0x1 +#define DRAM_MR4_TH_OFFS_MASK 0x3 +#define DRAM_MR4_TUF_MASK 0x1 + +/* DRAM Vendor Offsets & Masks */ +#define DRAM_VENDOR_MR5 0x0 +#define DRAM_VENDOR_MR6 0x4 +#define DRAM_VENDOR_MR7 0x8 +#define DRAM_VENDOR_MR8 0xc +#define DRAM_VENDOR_ERROR 0x10 +#define DRAM_VENDOR_MASK 0xff + +/* Reset register bits & masks */ +#define DCPU_RESET_SHIFT 0x0 +#define DCPU_RESET_MASK 0x1 +#define DCPU_CLK_DISABLE_SHIFT 0x2 + +/* DCPU return codes */ +#define DCPU_RET_ERROR_BIT BIT(31) +#define DCPU_RET_SUCCESS 0x1 +#define DCPU_RET_ERR_HEADER (DCPU_RET_ERROR_BIT | BIT(0)) +#define DCPU_RET_ERR_INVAL (DCPU_RET_ERROR_BIT | BIT(1)) +#define DCPU_RET_ERR_CHKSUM (DCPU_RET_ERROR_BIT | BIT(2)) +#define DCPU_RET_ERR_COMMAND (DCPU_RET_ERROR_BIT | BIT(3)) +/* This error code is not firmware defined and only used in the driver. */ +#define DCPU_RET_ERR_TIMEDOUT (DCPU_RET_ERROR_BIT | BIT(4)) + +/* Firmware magic */ +#define DPFE_BE_MAGIC 0xfe1010fe +#define DPFE_LE_MAGIC 0xfe0101fe + +/* Error codes */ +#define ERR_INVALID_MAGIC -1 +#define ERR_INVALID_SIZE -2 +#define ERR_INVALID_CHKSUM -3 + +/* Message types */ +#define DPFE_MSG_TYPE_COMMAND 1 +#define DPFE_MSG_TYPE_RESPONSE 2 + +#define DELAY_LOOP_MAX 200000 + +enum dpfe_msg_fields { + MSG_HEADER, + MSG_COMMAND, + MSG_ARG_COUNT, + MSG_ARG0, + MSG_CHKSUM, + MSG_FIELD_MAX /* Last entry */ +}; + +enum dpfe_commands { + DPFE_CMD_GET_INFO, + DPFE_CMD_GET_REFRESH, + DPFE_CMD_GET_VENDOR, + DPFE_CMD_MAX /* Last entry */ +}; + +struct dpfe_msg { + u32 header; + u32 command; + u32 arg_count; + u32 arg0; + u32 chksum; /* This is the sum of all other entries. */ +}; + +/* + * Format of the binary firmware file: + * + * entry + * 0 header + * value: 0xfe0101fe <== little endian + * 0xfe1010fe <== big endian + * 1 sequence: + * [31:16] total segments on this build + * [15:0] this segment sequence. + * 2 FW version + * 3 IMEM byte size + * 4 DMEM byte size + * IMEM + * DMEM + * last checksum ==> sum of everything + */ +struct dpfe_firmware_header { + u32 magic; + u32 sequence; + u32 version; + u32 imem_size; + u32 dmem_size; +}; + +/* Things we only need during initialization. */ +struct init_data { + unsigned int dmem_len; + unsigned int imem_len; + unsigned int chksum; + bool is_big_endian; +}; + +/* Things we need for as long as we are active. */ +struct private_data { + void __iomem *regs; + void __iomem *dmem; + void __iomem *imem; + struct device *dev; + struct mutex lock; +}; + +static const char *error_text[] = { + "Success", "Header code incorrect", "Unknown command or argument", + "Incorrect checksum", "Malformed command", "Timed out", +}; + +/* List of supported firmware commands */ +static const u32 dpfe_commands[DPFE_CMD_MAX][MSG_FIELD_MAX] = { + [DPFE_CMD_GET_INFO] = { + [MSG_HEADER] = DPFE_MSG_TYPE_COMMAND, + [MSG_COMMAND] = 1, + [MSG_ARG_COUNT] = 1, + [MSG_ARG0] = 1, + [MSG_CHKSUM] = 4, + }, + [DPFE_CMD_GET_REFRESH] = { + [MSG_HEADER] = DPFE_MSG_TYPE_COMMAND, + [MSG_COMMAND] = 2, + [MSG_ARG_COUNT] = 1, + [MSG_ARG0] = 1, + [MSG_CHKSUM] = 5, + }, + [DPFE_CMD_GET_VENDOR] = { + [MSG_HEADER] = DPFE_MSG_TYPE_COMMAND, + [MSG_COMMAND] = 2, + [MSG_ARG_COUNT] = 1, + [MSG_ARG0] = 2, + [MSG_CHKSUM] = 6, + }, +}; + +static bool is_dcpu_enabled(void __iomem *regs) +{ + u32 val; + + val = readl_relaxed(regs + REG_DCPU_RESET); + + return !(val & DCPU_RESET_MASK); +} + +static void __disable_dcpu(void __iomem *regs) +{ + u32 val; + + if (!is_dcpu_enabled(regs)) + return; + + /* Put DCPU in reset if it's running. */ + val = readl_relaxed(regs + REG_DCPU_RESET); + val |= (1 << DCPU_RESET_SHIFT); + writel_relaxed(val, regs + REG_DCPU_RESET); +} + +static void __enable_dcpu(void __iomem *regs) +{ + u32 val; + + /* Clear mailbox registers. */ + writel_relaxed(0, regs + REG_TO_DCPU_MBOX); + writel_relaxed(0, regs + REG_TO_HOST_MBOX); + + /* Disable DCPU clock gating */ + val = readl_relaxed(regs + REG_DCPU_RESET); + val &= ~(1 << DCPU_CLK_DISABLE_SHIFT); + writel_relaxed(val, regs + REG_DCPU_RESET); + + /* Take DCPU out of reset */ + val = readl_relaxed(regs + REG_DCPU_RESET); + val &= ~(1 << DCPU_RESET_SHIFT); + writel_relaxed(val, regs + REG_DCPU_RESET); +} + +static unsigned int get_msg_chksum(const u32 msg[]) +{ + unsigned int sum = 0; + unsigned int i; + + /* Don't include the last field in the checksum. */ + for (i = 0; i < MSG_FIELD_MAX - 1; i++) + sum += msg[i]; + + return sum; +} + +static void __iomem *get_msg_ptr(struct private_data *priv, u32 response, + char *buf, ssize_t *size) +{ + unsigned int msg_type; + unsigned int offset; + void __iomem *ptr = NULL; + + msg_type = (response >> DRAM_MSG_TYPE_OFFSET) & DRAM_MSG_TYPE_MASK; + offset = (response >> DRAM_MSG_ADDR_OFFSET) & DRAM_MSG_ADDR_MASK; + + /* + * msg_type == 1: the offset is relative to the message RAM + * msg_type == 0: the offset is relative to the data RAM (this is the + * previous way of passing data) + * msg_type is anything else: there's critical hardware problem + */ + switch (msg_type) { + case 1: + ptr = priv->regs + DCPU_MSG_RAM_START + offset; + break; + case 0: + ptr = priv->dmem + offset; + break; + default: + dev_emerg(priv->dev, "invalid message reply from DCPU: %#x\n", + response); + if (buf && size) + *size = sprintf(buf, + "FATAL: communication error with DCPU\n"); + } + + return ptr; +} + +static int __send_command(struct private_data *priv, unsigned int cmd, + u32 result[]) +{ + const u32 *msg = dpfe_commands[cmd]; + void __iomem *regs = priv->regs; + unsigned int i, chksum; + int ret = 0; + u32 resp; + + if (cmd >= DPFE_CMD_MAX) + return -1; + + mutex_lock(&priv->lock); + + /* Write command and arguments to message area */ + for (i = 0; i < MSG_FIELD_MAX; i++) + writel_relaxed(msg[i], regs + DCPU_MSG_RAM(i)); + + /* Tell DCPU there is a command waiting */ + writel_relaxed(1, regs + REG_TO_DCPU_MBOX); + + /* Wait for DCPU to process the command */ + for (i = 0; i < DELAY_LOOP_MAX; i++) { + /* Read response code */ + resp = readl_relaxed(regs + REG_TO_HOST_MBOX); + if (resp > 0) + break; + udelay(5); + } + + if (i == DELAY_LOOP_MAX) { + resp = (DCPU_RET_ERR_TIMEDOUT & ~DCPU_RET_ERROR_BIT); + ret = -ffs(resp); + } else { + /* Read response data */ + for (i = 0; i < MSG_FIELD_MAX; i++) + result[i] = readl_relaxed(regs + DCPU_MSG_RAM(i)); + } + + /* Tell DCPU we are done */ + writel_relaxed(0, regs + REG_TO_HOST_MBOX); + + mutex_unlock(&priv->lock); + + if (ret) + return ret; + + /* Verify response */ + chksum = get_msg_chksum(result); + if (chksum != result[MSG_CHKSUM]) + resp = DCPU_RET_ERR_CHKSUM; + + if (resp != DCPU_RET_SUCCESS) { + resp &= ~DCPU_RET_ERROR_BIT; + ret = -ffs(resp); + } + + return ret; +} + +/* Ensure that the firmware file loaded meets all the requirements. */ +static int __verify_firmware(struct init_data *init, + const struct firmware *fw) +{ + const struct dpfe_firmware_header *header = (void *)fw->data; + unsigned int dmem_size, imem_size, total_size; + bool is_big_endian = false; + const u32 *chksum_ptr; + + if (header->magic == DPFE_BE_MAGIC) + is_big_endian = true; + else if (header->magic != DPFE_LE_MAGIC) + return ERR_INVALID_MAGIC; + + if (is_big_endian) { + dmem_size = be32_to_cpu(header->dmem_size); + imem_size = be32_to_cpu(header->imem_size); + } else { + dmem_size = le32_to_cpu(header->dmem_size); + imem_size = le32_to_cpu(header->imem_size); + } + + /* Data and instruction sections are 32 bit words. */ + if ((dmem_size % sizeof(u32)) != 0 || (imem_size % sizeof(u32)) != 0) + return ERR_INVALID_SIZE; + + /* + * The header + the data section + the instruction section + the + * checksum must be equal to the total firmware size. + */ + total_size = dmem_size + imem_size + sizeof(*header) + + sizeof(*chksum_ptr); + if (total_size != fw->size) + return ERR_INVALID_SIZE; + + /* The checksum comes at the very end. */ + chksum_ptr = (void *)fw->data + sizeof(*header) + dmem_size + imem_size; + + init->is_big_endian = is_big_endian; + init->dmem_len = dmem_size; + init->imem_len = imem_size; + init->chksum = (is_big_endian) + ? be32_to_cpu(*chksum_ptr) : le32_to_cpu(*chksum_ptr); + + return 0; +} + +/* Verify checksum by reading back the firmware from co-processor RAM. */ +static int __verify_fw_checksum(struct init_data *init, + struct private_data *priv, + const struct dpfe_firmware_header *header, + u32 checksum) +{ + u32 magic, sequence, version, sum; + u32 __iomem *dmem = priv->dmem; + u32 __iomem *imem = priv->imem; + unsigned int i; + + if (init->is_big_endian) { + magic = be32_to_cpu(header->magic); + sequence = be32_to_cpu(header->sequence); + version = be32_to_cpu(header->version); + } else { + magic = le32_to_cpu(header->magic); + sequence = le32_to_cpu(header->sequence); + version = le32_to_cpu(header->version); + } + + sum = magic + sequence + version + init->dmem_len + init->imem_len; + + for (i = 0; i < init->dmem_len / sizeof(u32); i++) + sum += readl_relaxed(dmem + i); + + for (i = 0; i < init->imem_len / sizeof(u32); i++) + sum += readl_relaxed(imem + i); + + return (sum == checksum) ? 0 : -1; +} + +static int __write_firmware(u32 __iomem *mem, const u32 *fw, + unsigned int size, bool is_big_endian) +{ + unsigned int i; + + /* Convert size to 32-bit words. */ + size /= sizeof(u32); + + /* It is recommended to clear the firmware area first. */ + for (i = 0; i < size; i++) + writel_relaxed(0, mem + i); + + /* Now copy it. */ + if (is_big_endian) { + for (i = 0; i < size; i++) + writel_relaxed(be32_to_cpu(fw[i]), mem + i); + } else { + for (i = 0; i < size; i++) + writel_relaxed(le32_to_cpu(fw[i]), mem + i); + } + + return 0; +} + +static int brcmstb_dpfe_download_firmware(struct platform_device *pdev, + struct init_data *init) +{ + const struct dpfe_firmware_header *header; + unsigned int dmem_size, imem_size; + struct device *dev = &pdev->dev; + bool is_big_endian = false; + struct private_data *priv; + const struct firmware *fw; + const u32 *dmem, *imem; + const void *fw_blob; + int ret; + + priv = platform_get_drvdata(pdev); + + /* + * Skip downloading the firmware if the DCPU is already running and + * responding to commands. + */ + if (is_dcpu_enabled(priv->regs)) { + u32 response[MSG_FIELD_MAX]; + + ret = __send_command(priv, DPFE_CMD_GET_INFO, response); + if (!ret) + return 0; + } + + ret = request_firmware(&fw, FIRMWARE_NAME, dev); + /* request_firmware() prints its own error messages. */ + if (ret) + return ret; + + ret = __verify_firmware(init, fw); + if (ret) + return -EFAULT; + + __disable_dcpu(priv->regs); + + is_big_endian = init->is_big_endian; + dmem_size = init->dmem_len; + imem_size = init->imem_len; + + /* At the beginning of the firmware blob is a header. */ + header = (struct dpfe_firmware_header *)fw->data; + /* Void pointer to the beginning of the actual firmware. */ + fw_blob = fw->data + sizeof(*header); + /* IMEM comes right after the header. */ + imem = fw_blob; + /* DMEM follows after IMEM. */ + dmem = fw_blob + imem_size; + + ret = __write_firmware(priv->dmem, dmem, dmem_size, is_big_endian); + if (ret) + return ret; + ret = __write_firmware(priv->imem, imem, imem_size, is_big_endian); + if (ret) + return ret; + + ret = __verify_fw_checksum(init, priv, header, init->chksum); + if (ret) + return ret; + + __enable_dcpu(priv->regs); + + return 0; +} + +static ssize_t generic_show(unsigned int command, u32 response[], + struct device *dev, char *buf) +{ + struct private_data *priv; + int ret; + + priv = dev_get_drvdata(dev); + if (!priv) + return sprintf(buf, "ERROR: driver private data not set\n"); + + ret = __send_command(priv, command, response); + if (ret < 0) + return sprintf(buf, "ERROR: %s\n", error_text[-ret]); + + return 0; +} + +static ssize_t show_info(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + u32 response[MSG_FIELD_MAX]; + unsigned int info; + ssize_t ret; + + ret = generic_show(DPFE_CMD_GET_INFO, response, dev, buf); + if (ret) + return ret; + + info = response[MSG_ARG0]; + + return sprintf(buf, "%u.%u.%u.%u\n", + (info >> 24) & 0xff, + (info >> 16) & 0xff, + (info >> 8) & 0xff, + info & 0xff); +} + +static ssize_t show_refresh(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + u32 response[MSG_FIELD_MAX]; + void __iomem *info; + struct private_data *priv; + u8 refresh, sr_abort, ppre, thermal_offs, tuf; + u32 mr4; + ssize_t ret; + + ret = generic_show(DPFE_CMD_GET_REFRESH, response, dev, buf); + if (ret) + return ret; + + priv = dev_get_drvdata(dev); + + info = get_msg_ptr(priv, response[MSG_ARG0], buf, &ret); + if (!info) + return ret; + + mr4 = readl_relaxed(info + DRAM_INFO_MR4) & DRAM_INFO_MR4_MASK; + + refresh = (mr4 >> DRAM_MR4_REFRESH) & DRAM_MR4_REFRESH_MASK; + sr_abort = (mr4 >> DRAM_MR4_SR_ABORT) & DRAM_MR4_SR_ABORT_MASK; + ppre = (mr4 >> DRAM_MR4_PPRE) & DRAM_MR4_PPRE_MASK; + thermal_offs = (mr4 >> DRAM_MR4_TH_OFFS) & DRAM_MR4_TH_OFFS_MASK; + tuf = (mr4 >> DRAM_MR4_TUF) & DRAM_MR4_TUF_MASK; + + return sprintf(buf, "%#x %#x %#x %#x %#x %#x %#x\n", + readl_relaxed(info + DRAM_INFO_INTERVAL), + refresh, sr_abort, ppre, thermal_offs, tuf, + readl_relaxed(info + DRAM_INFO_ERROR)); +} + +static ssize_t store_refresh(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 response[MSG_FIELD_MAX]; + struct private_data *priv; + void __iomem *info; + unsigned long val; + int ret; + + if (kstrtoul(buf, 0, &val) < 0) + return -EINVAL; + + priv = dev_get_drvdata(dev); + + ret = __send_command(priv, DPFE_CMD_GET_REFRESH, response); + if (ret) + return ret; + + info = get_msg_ptr(priv, response[MSG_ARG0], NULL, NULL); + if (!info) + return -EIO; + + writel_relaxed(val, info + DRAM_INFO_INTERVAL); + + return count; +} + +static ssize_t show_vendor(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + u32 response[MSG_FIELD_MAX]; + struct private_data *priv; + void __iomem *info; + ssize_t ret; + + ret = generic_show(DPFE_CMD_GET_VENDOR, response, dev, buf); + if (ret) + return ret; + + priv = dev_get_drvdata(dev); + + info = get_msg_ptr(priv, response[MSG_ARG0], buf, &ret); + if (!info) + return ret; + + return sprintf(buf, "%#x %#x %#x %#x %#x\n", + readl_relaxed(info + DRAM_VENDOR_MR5) & DRAM_VENDOR_MASK, + readl_relaxed(info + DRAM_VENDOR_MR6) & DRAM_VENDOR_MASK, + readl_relaxed(info + DRAM_VENDOR_MR7) & DRAM_VENDOR_MASK, + readl_relaxed(info + DRAM_VENDOR_MR8) & DRAM_VENDOR_MASK, + readl_relaxed(info + DRAM_VENDOR_ERROR) & + DRAM_VENDOR_MASK); +} + +static int brcmstb_dpfe_resume(struct platform_device *pdev) +{ + struct init_data init; + + return brcmstb_dpfe_download_firmware(pdev, &init); +} + +static DEVICE_ATTR(dpfe_info, 0444, show_info, NULL); +static DEVICE_ATTR(dpfe_refresh, 0644, show_refresh, store_refresh); +static DEVICE_ATTR(dpfe_vendor, 0444, show_vendor, NULL); +static struct attribute *dpfe_attrs[] = { + &dev_attr_dpfe_info.attr, + &dev_attr_dpfe_refresh.attr, + &dev_attr_dpfe_vendor.attr, + NULL +}; +ATTRIBUTE_GROUPS(dpfe); + +static int brcmstb_dpfe_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct private_data *priv; + struct init_data init; + struct resource *res; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->lock); + platform_set_drvdata(pdev, priv); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dpfe-cpu"); + priv->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->regs)) { + dev_err(dev, "couldn't map DCPU registers\n"); + return -ENODEV; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dpfe-dmem"); + priv->dmem = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->dmem)) { + dev_err(dev, "Couldn't map DCPU data memory\n"); + return -ENOENT; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dpfe-imem"); + priv->imem = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->imem)) { + dev_err(dev, "Couldn't map DCPU instruction memory\n"); + return -ENOENT; + } + + ret = brcmstb_dpfe_download_firmware(pdev, &init); + if (ret) + return ret; + + ret = sysfs_create_groups(&pdev->dev.kobj, dpfe_groups); + if (!ret) + dev_info(dev, "registered.\n"); + + return ret; +} + +static int brcmstb_dpfe_remove(struct platform_device *pdev) +{ + sysfs_remove_groups(&pdev->dev.kobj, dpfe_groups); + + return 0; +} + +static const struct of_device_id brcmstb_dpfe_of_match[] = { + { .compatible = "brcm,dpfe-cpu", }, + {} +}; +MODULE_DEVICE_TABLE(of, brcmstb_dpfe_of_match); + +static struct platform_driver brcmstb_dpfe_driver = { + .driver = { + .name = DRVNAME, + .of_match_table = brcmstb_dpfe_of_match, + }, + .probe = brcmstb_dpfe_probe, + .remove = brcmstb_dpfe_remove, + .resume = brcmstb_dpfe_resume, +}; + +module_platform_driver(brcmstb_dpfe_driver); + +MODULE_AUTHOR("Markus Mayer <mmayer@broadcom.com>"); +MODULE_DESCRIPTION("BRCMSTB DDR PHY Front End Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/memory/da8xx-ddrctl.c b/drivers/memory/da8xx-ddrctl.c new file mode 100644 index 000000000..030afbe29 --- /dev/null +++ b/drivers/memory/da8xx-ddrctl.c @@ -0,0 +1,173 @@ +/* + * TI da8xx DDR2/mDDR controller driver + * + * Copyright (C) 2016 BayLibre SAS + * + * Author: + * Bartosz Golaszewski <bgolaszewski@baylibre.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +/* + * REVISIT: Linux doesn't have a good framework for the kind of performance + * knobs this driver controls. We can't use device tree properties as it deals + * with hardware configuration rather than description. We also don't want to + * commit to maintaining some random sysfs attributes. + * + * For now we just hardcode the register values for the boards that need + * some changes (as is the case for the LCD controller on da850-lcdk - the + * first board we support here). When linux gets an appropriate framework, + * we'll easily convert the driver to it. + */ + +struct da8xx_ddrctl_config_knob { + const char *name; + u32 reg; + u32 mask; + u32 shift; +}; + +static const struct da8xx_ddrctl_config_knob da8xx_ddrctl_knobs[] = { + { + .name = "da850-pbbpr", + .reg = 0x20, + .mask = 0xffffff00, + .shift = 0, + }, +}; + +struct da8xx_ddrctl_setting { + const char *name; + u32 val; +}; + +struct da8xx_ddrctl_board_settings { + const char *board; + const struct da8xx_ddrctl_setting *settings; +}; + +static const struct da8xx_ddrctl_setting da850_lcdk_ddrctl_settings[] = { + { + .name = "da850-pbbpr", + .val = 0x20, + }, + { } +}; + +static const struct da8xx_ddrctl_board_settings da8xx_ddrctl_board_confs[] = { + { + .board = "ti,da850-lcdk", + .settings = da850_lcdk_ddrctl_settings, + }, +}; + +static const struct da8xx_ddrctl_config_knob * +da8xx_ddrctl_match_knob(const struct da8xx_ddrctl_setting *setting) +{ + const struct da8xx_ddrctl_config_knob *knob; + int i; + + for (i = 0; i < ARRAY_SIZE(da8xx_ddrctl_knobs); i++) { + knob = &da8xx_ddrctl_knobs[i]; + + if (strcmp(knob->name, setting->name) == 0) + return knob; + } + + return NULL; +} + +static const struct da8xx_ddrctl_setting *da8xx_ddrctl_get_board_settings(void) +{ + const struct da8xx_ddrctl_board_settings *board_settings; + int i; + + for (i = 0; i < ARRAY_SIZE(da8xx_ddrctl_board_confs); i++) { + board_settings = &da8xx_ddrctl_board_confs[i]; + + if (of_machine_is_compatible(board_settings->board)) + return board_settings->settings; + } + + return NULL; +} + +static int da8xx_ddrctl_probe(struct platform_device *pdev) +{ + const struct da8xx_ddrctl_config_knob *knob; + const struct da8xx_ddrctl_setting *setting; + struct device_node *node; + struct resource *res; + void __iomem *ddrctl; + struct device *dev; + u32 reg; + + dev = &pdev->dev; + node = dev->of_node; + + setting = da8xx_ddrctl_get_board_settings(); + if (!setting) { + dev_err(dev, "no settings defined for this board\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ddrctl = devm_ioremap_resource(dev, res); + if (IS_ERR(ddrctl)) { + dev_err(dev, "unable to map memory controller registers\n"); + return PTR_ERR(ddrctl); + } + + for (; setting->name; setting++) { + knob = da8xx_ddrctl_match_knob(setting); + if (!knob) { + dev_warn(dev, + "no such config option: %s\n", setting->name); + continue; + } + + if (knob->reg + sizeof(u32) > resource_size(res)) { + dev_warn(dev, + "register offset of '%s' exceeds mapped memory size\n", + knob->name); + continue; + } + + reg = readl(ddrctl + knob->reg); + reg &= knob->mask; + reg |= setting->val << knob->shift; + + dev_dbg(dev, "writing 0x%08x to %s\n", reg, setting->name); + + writel(reg, ddrctl + knob->reg); + } + + return 0; +} + +static const struct of_device_id da8xx_ddrctl_of_match[] = { + { .compatible = "ti,da850-ddr-controller", }, + { }, +}; + +static struct platform_driver da8xx_ddrctl_driver = { + .probe = da8xx_ddrctl_probe, + .driver = { + .name = "da850-ddr-controller", + .of_match_table = da8xx_ddrctl_of_match, + }, +}; +module_platform_driver(da8xx_ddrctl_driver); + +MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>"); +MODULE_DESCRIPTION("TI da8xx DDR2/mDDR controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/memory/emif-asm-offsets.c b/drivers/memory/emif-asm-offsets.c new file mode 100644 index 000000000..db8043019 --- /dev/null +++ b/drivers/memory/emif-asm-offsets.c @@ -0,0 +1,22 @@ +/* + * TI AM33XX EMIF PM Assembly Offsets + * + * Copyright (C) 2016-2017 Texas Instruments Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/ti-emif-sram.h> + +int main(void) +{ + ti_emif_asm_offsets(); + + return 0; +} diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c new file mode 100644 index 000000000..45f78b38b --- /dev/null +++ b/drivers/memory/emif.c @@ -0,0 +1,1919 @@ +/* + * EMIF driver + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Aneesh V <aneesh@ti.com> + * Santosh Shilimkar <santosh.shilimkar@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/reboot.h> +#include <linux/platform_data/emif_plat.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/module.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/pm.h> +#include <memory/jedec_ddr.h> +#include "emif.h" +#include "of_memory.h" + +/** + * struct emif_data - Per device static data for driver's use + * @duplicate: Whether the DDR devices attached to this EMIF + * instance are exactly same as that on EMIF1. In + * this case we can save some memory and processing + * @temperature_level: Maximum temperature of LPDDR2 devices attached + * to this EMIF - read from MR4 register. If there + * are two devices attached to this EMIF, this + * value is the maximum of the two temperature + * levels. + * @node: node in the device list + * @base: base address of memory-mapped IO registers. + * @dev: device pointer. + * @addressing table with addressing information from the spec + * @regs_cache: An array of 'struct emif_regs' that stores + * calculated register values for different + * frequencies, to avoid re-calculating them on + * each DVFS transition. + * @curr_regs: The set of register values used in the last + * frequency change (i.e. corresponding to the + * frequency in effect at the moment) + * @plat_data: Pointer to saved platform data. + * @debugfs_root: dentry to the root folder for EMIF in debugfs + * @np_ddr: Pointer to ddr device tree node + */ +struct emif_data { + u8 duplicate; + u8 temperature_level; + u8 lpmode; + struct list_head node; + unsigned long irq_state; + void __iomem *base; + struct device *dev; + const struct lpddr2_addressing *addressing; + struct emif_regs *regs_cache[EMIF_MAX_NUM_FREQUENCIES]; + struct emif_regs *curr_regs; + struct emif_platform_data *plat_data; + struct dentry *debugfs_root; + struct device_node *np_ddr; +}; + +static struct emif_data *emif1; +static spinlock_t emif_lock; +static unsigned long irq_state; +static u32 t_ck; /* DDR clock period in ps */ +static LIST_HEAD(device_list); + +#ifdef CONFIG_DEBUG_FS +static void do_emif_regdump_show(struct seq_file *s, struct emif_data *emif, + struct emif_regs *regs) +{ + u32 type = emif->plat_data->device_info->type; + u32 ip_rev = emif->plat_data->ip_rev; + + seq_printf(s, "EMIF register cache dump for %dMHz\n", + regs->freq/1000000); + + seq_printf(s, "ref_ctrl_shdw\t: 0x%08x\n", regs->ref_ctrl_shdw); + seq_printf(s, "sdram_tim1_shdw\t: 0x%08x\n", regs->sdram_tim1_shdw); + seq_printf(s, "sdram_tim2_shdw\t: 0x%08x\n", regs->sdram_tim2_shdw); + seq_printf(s, "sdram_tim3_shdw\t: 0x%08x\n", regs->sdram_tim3_shdw); + + if (ip_rev == EMIF_4D) { + seq_printf(s, "read_idle_ctrl_shdw_normal\t: 0x%08x\n", + regs->read_idle_ctrl_shdw_normal); + seq_printf(s, "read_idle_ctrl_shdw_volt_ramp\t: 0x%08x\n", + regs->read_idle_ctrl_shdw_volt_ramp); + } else if (ip_rev == EMIF_4D5) { + seq_printf(s, "dll_calib_ctrl_shdw_normal\t: 0x%08x\n", + regs->dll_calib_ctrl_shdw_normal); + seq_printf(s, "dll_calib_ctrl_shdw_volt_ramp\t: 0x%08x\n", + regs->dll_calib_ctrl_shdw_volt_ramp); + } + + if (type == DDR_TYPE_LPDDR2_S2 || type == DDR_TYPE_LPDDR2_S4) { + seq_printf(s, "ref_ctrl_shdw_derated\t: 0x%08x\n", + regs->ref_ctrl_shdw_derated); + seq_printf(s, "sdram_tim1_shdw_derated\t: 0x%08x\n", + regs->sdram_tim1_shdw_derated); + seq_printf(s, "sdram_tim3_shdw_derated\t: 0x%08x\n", + regs->sdram_tim3_shdw_derated); + } +} + +static int emif_regdump_show(struct seq_file *s, void *unused) +{ + struct emif_data *emif = s->private; + struct emif_regs **regs_cache; + int i; + + if (emif->duplicate) + regs_cache = emif1->regs_cache; + else + regs_cache = emif->regs_cache; + + for (i = 0; i < EMIF_MAX_NUM_FREQUENCIES && regs_cache[i]; i++) { + do_emif_regdump_show(s, emif, regs_cache[i]); + seq_putc(s, '\n'); + } + + return 0; +} + +static int emif_regdump_open(struct inode *inode, struct file *file) +{ + return single_open(file, emif_regdump_show, inode->i_private); +} + +static const struct file_operations emif_regdump_fops = { + .open = emif_regdump_open, + .read = seq_read, + .release = single_release, +}; + +static int emif_mr4_show(struct seq_file *s, void *unused) +{ + struct emif_data *emif = s->private; + + seq_printf(s, "MR4=%d\n", emif->temperature_level); + return 0; +} + +static int emif_mr4_open(struct inode *inode, struct file *file) +{ + return single_open(file, emif_mr4_show, inode->i_private); +} + +static const struct file_operations emif_mr4_fops = { + .open = emif_mr4_open, + .read = seq_read, + .release = single_release, +}; + +static int __init_or_module emif_debugfs_init(struct emif_data *emif) +{ + emif->debugfs_root = debugfs_create_dir(dev_name(emif->dev), NULL); + debugfs_create_file("regcache_dump", S_IRUGO, emif->debugfs_root, emif, + &emif_regdump_fops); + debugfs_create_file("mr4", S_IRUGO, emif->debugfs_root, emif, + &emif_mr4_fops); + return 0; +} + +static void __exit emif_debugfs_exit(struct emif_data *emif) +{ + debugfs_remove_recursive(emif->debugfs_root); + emif->debugfs_root = NULL; +} +#else +static inline int __init_or_module emif_debugfs_init(struct emif_data *emif) +{ + return 0; +} + +static inline void __exit emif_debugfs_exit(struct emif_data *emif) +{ +} +#endif + +/* + * Calculate the period of DDR clock from frequency value + */ +static void set_ddr_clk_period(u32 freq) +{ + /* Divide 10^12 by frequency to get period in ps */ + t_ck = (u32)DIV_ROUND_UP_ULL(1000000000000ull, freq); +} + +/* + * Get bus width used by EMIF. Note that this may be different from the + * bus width of the DDR devices used. For instance two 16-bit DDR devices + * may be connected to a given CS of EMIF. In this case bus width as far + * as EMIF is concerned is 32, where as the DDR bus width is 16 bits. + */ +static u32 get_emif_bus_width(struct emif_data *emif) +{ + u32 width; + void __iomem *base = emif->base; + + width = (readl(base + EMIF_SDRAM_CONFIG) & NARROW_MODE_MASK) + >> NARROW_MODE_SHIFT; + width = width == 0 ? 32 : 16; + + return width; +} + +/* + * Get the CL from SDRAM_CONFIG register + */ +static u32 get_cl(struct emif_data *emif) +{ + u32 cl; + void __iomem *base = emif->base; + + cl = (readl(base + EMIF_SDRAM_CONFIG) & CL_MASK) >> CL_SHIFT; + + return cl; +} + +static void set_lpmode(struct emif_data *emif, u8 lpmode) +{ + u32 temp; + void __iomem *base = emif->base; + + /* + * Workaround for errata i743 - LPDDR2 Power-Down State is Not + * Efficient + * + * i743 DESCRIPTION: + * The EMIF supports power-down state for low power. The EMIF + * automatically puts the SDRAM into power-down after the memory is + * not accessed for a defined number of cycles and the + * EMIF_PWR_MGMT_CTRL[10:8] REG_LP_MODE bit field is set to 0x4. + * As the EMIF supports automatic output impedance calibration, a ZQ + * calibration long command is issued every time it exits active + * power-down and precharge power-down modes. The EMIF waits and + * blocks any other command during this calibration. + * The EMIF does not allow selective disabling of ZQ calibration upon + * exit of power-down mode. Due to very short periods of power-down + * cycles, ZQ calibration overhead creates bandwidth issues and + * increases overall system power consumption. On the other hand, + * issuing ZQ calibration long commands when exiting self-refresh is + * still required. + * + * WORKAROUND + * Because there is no power consumption benefit of the power-down due + * to the calibration and there is a performance risk, the guideline + * is to not allow power-down state and, therefore, to not have set + * the EMIF_PWR_MGMT_CTRL[10:8] REG_LP_MODE bit field to 0x4. + */ + if ((emif->plat_data->ip_rev == EMIF_4D) && + (EMIF_LP_MODE_PWR_DN == lpmode)) { + WARN_ONCE(1, + "REG_LP_MODE = LP_MODE_PWR_DN(4) is prohibited by" + "erratum i743 switch to LP_MODE_SELF_REFRESH(2)\n"); + /* rollback LP_MODE to Self-refresh mode */ + lpmode = EMIF_LP_MODE_SELF_REFRESH; + } + + temp = readl(base + EMIF_POWER_MANAGEMENT_CONTROL); + temp &= ~LP_MODE_MASK; + temp |= (lpmode << LP_MODE_SHIFT); + writel(temp, base + EMIF_POWER_MANAGEMENT_CONTROL); +} + +static void do_freq_update(void) +{ + struct emif_data *emif; + + /* + * Workaround for errata i728: Disable LPMODE during FREQ_UPDATE + * + * i728 DESCRIPTION: + * The EMIF automatically puts the SDRAM into self-refresh mode + * after the EMIF has not performed accesses during + * EMIF_PWR_MGMT_CTRL[7:4] REG_SR_TIM number of DDR clock cycles + * and the EMIF_PWR_MGMT_CTRL[10:8] REG_LP_MODE bit field is set + * to 0x2. If during a small window the following three events + * occur: + * - The SR_TIMING counter expires + * - And frequency change is requested + * - And OCP access is requested + * Then it causes instable clock on the DDR interface. + * + * WORKAROUND + * To avoid the occurrence of the three events, the workaround + * is to disable the self-refresh when requesting a frequency + * change. Before requesting a frequency change the software must + * program EMIF_PWR_MGMT_CTRL[10:8] REG_LP_MODE to 0x0. When the + * frequency change has been done, the software can reprogram + * EMIF_PWR_MGMT_CTRL[10:8] REG_LP_MODE to 0x2 + */ + list_for_each_entry(emif, &device_list, node) { + if (emif->lpmode == EMIF_LP_MODE_SELF_REFRESH) + set_lpmode(emif, EMIF_LP_MODE_DISABLE); + } + + /* + * TODO: Do FREQ_UPDATE here when an API + * is available for this as part of the new + * clock framework + */ + + list_for_each_entry(emif, &device_list, node) { + if (emif->lpmode == EMIF_LP_MODE_SELF_REFRESH) + set_lpmode(emif, EMIF_LP_MODE_SELF_REFRESH); + } +} + +/* Find addressing table entry based on the device's type and density */ +static const struct lpddr2_addressing *get_addressing_table( + const struct ddr_device_info *device_info) +{ + u32 index, type, density; + + type = device_info->type; + density = device_info->density; + + switch (type) { + case DDR_TYPE_LPDDR2_S4: + index = density - 1; + break; + case DDR_TYPE_LPDDR2_S2: + switch (density) { + case DDR_DENSITY_1Gb: + case DDR_DENSITY_2Gb: + index = density + 3; + break; + default: + index = density - 1; + } + break; + default: + return NULL; + } + + return &lpddr2_jedec_addressing_table[index]; +} + +/* + * Find the the right timing table from the array of timing + * tables of the device using DDR clock frequency + */ +static const struct lpddr2_timings *get_timings_table(struct emif_data *emif, + u32 freq) +{ + u32 i, min, max, freq_nearest; + const struct lpddr2_timings *timings = NULL; + const struct lpddr2_timings *timings_arr = emif->plat_data->timings; + struct device *dev = emif->dev; + + /* Start with a very high frequency - 1GHz */ + freq_nearest = 1000000000; + + /* + * Find the timings table such that: + * 1. the frequency range covers the required frequency(safe) AND + * 2. the max_freq is closest to the required frequency(optimal) + */ + for (i = 0; i < emif->plat_data->timings_arr_size; i++) { + max = timings_arr[i].max_freq; + min = timings_arr[i].min_freq; + if ((freq >= min) && (freq <= max) && (max < freq_nearest)) { + freq_nearest = max; + timings = &timings_arr[i]; + } + } + + if (!timings) + dev_err(dev, "%s: couldn't find timings for - %dHz\n", + __func__, freq); + + dev_dbg(dev, "%s: timings table: freq %d, speed bin freq %d\n", + __func__, freq, freq_nearest); + + return timings; +} + +static u32 get_sdram_ref_ctrl_shdw(u32 freq, + const struct lpddr2_addressing *addressing) +{ + u32 ref_ctrl_shdw = 0, val = 0, freq_khz, t_refi; + + /* Scale down frequency and t_refi to avoid overflow */ + freq_khz = freq / 1000; + t_refi = addressing->tREFI_ns / 100; + + /* + * refresh rate to be set is 'tREFI(in us) * freq in MHz + * division by 10000 to account for change in units + */ + val = t_refi * freq_khz / 10000; + ref_ctrl_shdw |= val << REFRESH_RATE_SHIFT; + + return ref_ctrl_shdw; +} + +static u32 get_sdram_tim_1_shdw(const struct lpddr2_timings *timings, + const struct lpddr2_min_tck *min_tck, + const struct lpddr2_addressing *addressing) +{ + u32 tim1 = 0, val = 0; + + val = max(min_tck->tWTR, DIV_ROUND_UP(timings->tWTR, t_ck)) - 1; + tim1 |= val << T_WTR_SHIFT; + + if (addressing->num_banks == B8) + val = DIV_ROUND_UP(timings->tFAW, t_ck*4); + else + val = max(min_tck->tRRD, DIV_ROUND_UP(timings->tRRD, t_ck)); + tim1 |= (val - 1) << T_RRD_SHIFT; + + val = DIV_ROUND_UP(timings->tRAS_min + timings->tRPab, t_ck) - 1; + tim1 |= val << T_RC_SHIFT; + + val = max(min_tck->tRASmin, DIV_ROUND_UP(timings->tRAS_min, t_ck)); + tim1 |= (val - 1) << T_RAS_SHIFT; + + val = max(min_tck->tWR, DIV_ROUND_UP(timings->tWR, t_ck)) - 1; + tim1 |= val << T_WR_SHIFT; + + val = max(min_tck->tRCD, DIV_ROUND_UP(timings->tRCD, t_ck)) - 1; + tim1 |= val << T_RCD_SHIFT; + + val = max(min_tck->tRPab, DIV_ROUND_UP(timings->tRPab, t_ck)) - 1; + tim1 |= val << T_RP_SHIFT; + + return tim1; +} + +static u32 get_sdram_tim_1_shdw_derated(const struct lpddr2_timings *timings, + const struct lpddr2_min_tck *min_tck, + const struct lpddr2_addressing *addressing) +{ + u32 tim1 = 0, val = 0; + + val = max(min_tck->tWTR, DIV_ROUND_UP(timings->tWTR, t_ck)) - 1; + tim1 = val << T_WTR_SHIFT; + + /* + * tFAW is approximately 4 times tRRD. So add 1875*4 = 7500ps + * to tFAW for de-rating + */ + if (addressing->num_banks == B8) { + val = DIV_ROUND_UP(timings->tFAW + 7500, 4 * t_ck) - 1; + } else { + val = DIV_ROUND_UP(timings->tRRD + 1875, t_ck); + val = max(min_tck->tRRD, val) - 1; + } + tim1 |= val << T_RRD_SHIFT; + + val = DIV_ROUND_UP(timings->tRAS_min + timings->tRPab + 1875, t_ck); + tim1 |= (val - 1) << T_RC_SHIFT; + + val = DIV_ROUND_UP(timings->tRAS_min + 1875, t_ck); + val = max(min_tck->tRASmin, val) - 1; + tim1 |= val << T_RAS_SHIFT; + + val = max(min_tck->tWR, DIV_ROUND_UP(timings->tWR, t_ck)) - 1; + tim1 |= val << T_WR_SHIFT; + + val = max(min_tck->tRCD, DIV_ROUND_UP(timings->tRCD + 1875, t_ck)); + tim1 |= (val - 1) << T_RCD_SHIFT; + + val = max(min_tck->tRPab, DIV_ROUND_UP(timings->tRPab + 1875, t_ck)); + tim1 |= (val - 1) << T_RP_SHIFT; + + return tim1; +} + +static u32 get_sdram_tim_2_shdw(const struct lpddr2_timings *timings, + const struct lpddr2_min_tck *min_tck, + const struct lpddr2_addressing *addressing, + u32 type) +{ + u32 tim2 = 0, val = 0; + + val = min_tck->tCKE - 1; + tim2 |= val << T_CKE_SHIFT; + + val = max(min_tck->tRTP, DIV_ROUND_UP(timings->tRTP, t_ck)) - 1; + tim2 |= val << T_RTP_SHIFT; + + /* tXSNR = tRFCab_ps + 10 ns(tRFCab_ps for LPDDR2). */ + val = DIV_ROUND_UP(addressing->tRFCab_ps + 10000, t_ck) - 1; + tim2 |= val << T_XSNR_SHIFT; + + /* XSRD same as XSNR for LPDDR2 */ + tim2 |= val << T_XSRD_SHIFT; + + val = max(min_tck->tXP, DIV_ROUND_UP(timings->tXP, t_ck)) - 1; + tim2 |= val << T_XP_SHIFT; + + return tim2; +} + +static u32 get_sdram_tim_3_shdw(const struct lpddr2_timings *timings, + const struct lpddr2_min_tck *min_tck, + const struct lpddr2_addressing *addressing, + u32 type, u32 ip_rev, u32 derated) +{ + u32 tim3 = 0, val = 0, t_dqsck; + + val = timings->tRAS_max_ns / addressing->tREFI_ns - 1; + val = val > 0xF ? 0xF : val; + tim3 |= val << T_RAS_MAX_SHIFT; + + val = DIV_ROUND_UP(addressing->tRFCab_ps, t_ck) - 1; + tim3 |= val << T_RFC_SHIFT; + + t_dqsck = (derated == EMIF_DERATED_TIMINGS) ? + timings->tDQSCK_max_derated : timings->tDQSCK_max; + if (ip_rev == EMIF_4D5) + val = DIV_ROUND_UP(t_dqsck + 1000, t_ck) - 1; + else + val = DIV_ROUND_UP(t_dqsck, t_ck) - 1; + + tim3 |= val << T_TDQSCKMAX_SHIFT; + + val = DIV_ROUND_UP(timings->tZQCS, t_ck) - 1; + tim3 |= val << ZQ_ZQCS_SHIFT; + + val = DIV_ROUND_UP(timings->tCKESR, t_ck); + val = max(min_tck->tCKESR, val) - 1; + tim3 |= val << T_CKESR_SHIFT; + + if (ip_rev == EMIF_4D5) { + tim3 |= (EMIF_T_CSTA - 1) << T_CSTA_SHIFT; + + val = DIV_ROUND_UP(EMIF_T_PDLL_UL, 128) - 1; + tim3 |= val << T_PDLL_UL_SHIFT; + } + + return tim3; +} + +static u32 get_zq_config_reg(const struct lpddr2_addressing *addressing, + bool cs1_used, bool cal_resistors_per_cs) +{ + u32 zq = 0, val = 0; + + val = EMIF_ZQCS_INTERVAL_US * 1000 / addressing->tREFI_ns; + zq |= val << ZQ_REFINTERVAL_SHIFT; + + val = DIV_ROUND_UP(T_ZQCL_DEFAULT_NS, T_ZQCS_DEFAULT_NS) - 1; + zq |= val << ZQ_ZQCL_MULT_SHIFT; + + val = DIV_ROUND_UP(T_ZQINIT_DEFAULT_NS, T_ZQCL_DEFAULT_NS) - 1; + zq |= val << ZQ_ZQINIT_MULT_SHIFT; + + zq |= ZQ_SFEXITEN_ENABLE << ZQ_SFEXITEN_SHIFT; + + if (cal_resistors_per_cs) + zq |= ZQ_DUALCALEN_ENABLE << ZQ_DUALCALEN_SHIFT; + else + zq |= ZQ_DUALCALEN_DISABLE << ZQ_DUALCALEN_SHIFT; + + zq |= ZQ_CS0EN_MASK; /* CS0 is used for sure */ + + val = cs1_used ? 1 : 0; + zq |= val << ZQ_CS1EN_SHIFT; + + return zq; +} + +static u32 get_temp_alert_config(const struct lpddr2_addressing *addressing, + const struct emif_custom_configs *custom_configs, bool cs1_used, + u32 sdram_io_width, u32 emif_bus_width) +{ + u32 alert = 0, interval, devcnt; + + if (custom_configs && (custom_configs->mask & + EMIF_CUSTOM_CONFIG_TEMP_ALERT_POLL_INTERVAL)) + interval = custom_configs->temp_alert_poll_interval_ms; + else + interval = TEMP_ALERT_POLL_INTERVAL_DEFAULT_MS; + + interval *= 1000000; /* Convert to ns */ + interval /= addressing->tREFI_ns; /* Convert to refresh cycles */ + alert |= (interval << TA_REFINTERVAL_SHIFT); + + /* + * sdram_io_width is in 'log2(x) - 1' form. Convert emif_bus_width + * also to this form and subtract to get TA_DEVCNT, which is + * in log2(x) form. + */ + emif_bus_width = __fls(emif_bus_width) - 1; + devcnt = emif_bus_width - sdram_io_width; + alert |= devcnt << TA_DEVCNT_SHIFT; + + /* DEVWDT is in 'log2(x) - 3' form */ + alert |= (sdram_io_width - 2) << TA_DEVWDT_SHIFT; + + alert |= 1 << TA_SFEXITEN_SHIFT; + alert |= 1 << TA_CS0EN_SHIFT; + alert |= (cs1_used ? 1 : 0) << TA_CS1EN_SHIFT; + + return alert; +} + +static u32 get_read_idle_ctrl_shdw(u8 volt_ramp) +{ + u32 idle = 0, val = 0; + + /* + * Maximum value in normal conditions and increased frequency + * when voltage is ramping + */ + if (volt_ramp) + val = READ_IDLE_INTERVAL_DVFS / t_ck / 64 - 1; + else + val = 0x1FF; + + /* + * READ_IDLE_CTRL register in EMIF4D has same offset and fields + * as DLL_CALIB_CTRL in EMIF4D5, so use the same shifts + */ + idle |= val << DLL_CALIB_INTERVAL_SHIFT; + idle |= EMIF_READ_IDLE_LEN_VAL << ACK_WAIT_SHIFT; + + return idle; +} + +static u32 get_dll_calib_ctrl_shdw(u8 volt_ramp) +{ + u32 calib = 0, val = 0; + + if (volt_ramp == DDR_VOLTAGE_RAMPING) + val = DLL_CALIB_INTERVAL_DVFS / t_ck / 16 - 1; + else + val = 0; /* Disabled when voltage is stable */ + + calib |= val << DLL_CALIB_INTERVAL_SHIFT; + calib |= DLL_CALIB_ACK_WAIT_VAL << ACK_WAIT_SHIFT; + + return calib; +} + +static u32 get_ddr_phy_ctrl_1_attilaphy_4d(const struct lpddr2_timings *timings, + u32 freq, u8 RL) +{ + u32 phy = EMIF_DDR_PHY_CTRL_1_BASE_VAL_ATTILAPHY, val = 0; + + val = RL + DIV_ROUND_UP(timings->tDQSCK_max, t_ck) - 1; + phy |= val << READ_LATENCY_SHIFT_4D; + + if (freq <= 100000000) + val = EMIF_DLL_SLAVE_DLY_CTRL_100_MHZ_AND_LESS_ATTILAPHY; + else if (freq <= 200000000) + val = EMIF_DLL_SLAVE_DLY_CTRL_200_MHZ_ATTILAPHY; + else + val = EMIF_DLL_SLAVE_DLY_CTRL_400_MHZ_ATTILAPHY; + + phy |= val << DLL_SLAVE_DLY_CTRL_SHIFT_4D; + + return phy; +} + +static u32 get_phy_ctrl_1_intelliphy_4d5(u32 freq, u8 cl) +{ + u32 phy = EMIF_DDR_PHY_CTRL_1_BASE_VAL_INTELLIPHY, half_delay; + + /* + * DLL operates at 266 MHz. If DDR frequency is near 266 MHz, + * half-delay is not needed else set half-delay + */ + if (freq >= 265000000 && freq < 267000000) + half_delay = 0; + else + half_delay = 1; + + phy |= half_delay << DLL_HALF_DELAY_SHIFT_4D5; + phy |= ((cl + DIV_ROUND_UP(EMIF_PHY_TOTAL_READ_LATENCY_INTELLIPHY_PS, + t_ck) - 1) << READ_LATENCY_SHIFT_4D5); + + return phy; +} + +static u32 get_ext_phy_ctrl_2_intelliphy_4d5(void) +{ + u32 fifo_we_slave_ratio; + + fifo_we_slave_ratio = DIV_ROUND_CLOSEST( + EMIF_INTELLI_PHY_DQS_GATE_OPENING_DELAY_PS * 256 , t_ck); + + return fifo_we_slave_ratio | fifo_we_slave_ratio << 11 | + fifo_we_slave_ratio << 22; +} + +static u32 get_ext_phy_ctrl_3_intelliphy_4d5(void) +{ + u32 fifo_we_slave_ratio; + + fifo_we_slave_ratio = DIV_ROUND_CLOSEST( + EMIF_INTELLI_PHY_DQS_GATE_OPENING_DELAY_PS * 256 , t_ck); + + return fifo_we_slave_ratio >> 10 | fifo_we_slave_ratio << 1 | + fifo_we_slave_ratio << 12 | fifo_we_slave_ratio << 23; +} + +static u32 get_ext_phy_ctrl_4_intelliphy_4d5(void) +{ + u32 fifo_we_slave_ratio; + + fifo_we_slave_ratio = DIV_ROUND_CLOSEST( + EMIF_INTELLI_PHY_DQS_GATE_OPENING_DELAY_PS * 256 , t_ck); + + return fifo_we_slave_ratio >> 9 | fifo_we_slave_ratio << 2 | + fifo_we_slave_ratio << 13; +} + +static u32 get_pwr_mgmt_ctrl(u32 freq, struct emif_data *emif, u32 ip_rev) +{ + u32 pwr_mgmt_ctrl = 0, timeout; + u32 lpmode = EMIF_LP_MODE_SELF_REFRESH; + u32 timeout_perf = EMIF_LP_MODE_TIMEOUT_PERFORMANCE; + u32 timeout_pwr = EMIF_LP_MODE_TIMEOUT_POWER; + u32 freq_threshold = EMIF_LP_MODE_FREQ_THRESHOLD; + u32 mask; + u8 shift; + + struct emif_custom_configs *cust_cfgs = emif->plat_data->custom_configs; + + if (cust_cfgs && (cust_cfgs->mask & EMIF_CUSTOM_CONFIG_LPMODE)) { + lpmode = cust_cfgs->lpmode; + timeout_perf = cust_cfgs->lpmode_timeout_performance; + timeout_pwr = cust_cfgs->lpmode_timeout_power; + freq_threshold = cust_cfgs->lpmode_freq_threshold; + } + + /* Timeout based on DDR frequency */ + timeout = freq >= freq_threshold ? timeout_perf : timeout_pwr; + + /* + * The value to be set in register is "log2(timeout) - 3" + * if timeout < 16 load 0 in register + * if timeout is not a power of 2, round to next highest power of 2 + */ + if (timeout < 16) { + timeout = 0; + } else { + if (timeout & (timeout - 1)) + timeout <<= 1; + timeout = __fls(timeout) - 3; + } + + switch (lpmode) { + case EMIF_LP_MODE_CLOCK_STOP: + shift = CS_TIM_SHIFT; + mask = CS_TIM_MASK; + break; + case EMIF_LP_MODE_SELF_REFRESH: + /* Workaround for errata i735 */ + if (timeout < 6) + timeout = 6; + + shift = SR_TIM_SHIFT; + mask = SR_TIM_MASK; + break; + case EMIF_LP_MODE_PWR_DN: + shift = PD_TIM_SHIFT; + mask = PD_TIM_MASK; + break; + case EMIF_LP_MODE_DISABLE: + default: + mask = 0; + shift = 0; + break; + } + /* Round to maximum in case of overflow, BUT warn! */ + if (lpmode != EMIF_LP_MODE_DISABLE && timeout > mask >> shift) { + pr_err("TIMEOUT Overflow - lpmode=%d perf=%d pwr=%d freq=%d\n", + lpmode, + timeout_perf, + timeout_pwr, + freq_threshold); + WARN(1, "timeout=0x%02x greater than 0x%02x. Using max\n", + timeout, mask >> shift); + timeout = mask >> shift; + } + + /* Setup required timing */ + pwr_mgmt_ctrl = (timeout << shift) & mask; + /* setup a default mask for rest of the modes */ + pwr_mgmt_ctrl |= (SR_TIM_MASK | CS_TIM_MASK | PD_TIM_MASK) & + ~mask; + + /* No CS_TIM in EMIF_4D5 */ + if (ip_rev == EMIF_4D5) + pwr_mgmt_ctrl &= ~CS_TIM_MASK; + + pwr_mgmt_ctrl |= lpmode << LP_MODE_SHIFT; + + return pwr_mgmt_ctrl; +} + +/* + * Get the temperature level of the EMIF instance: + * Reads the MR4 register of attached SDRAM parts to find out the temperature + * level. If there are two parts attached(one on each CS), then the temperature + * level for the EMIF instance is the higher of the two temperatures. + */ +static void get_temperature_level(struct emif_data *emif) +{ + u32 temp, temperature_level; + void __iomem *base; + + base = emif->base; + + /* Read mode register 4 */ + writel(DDR_MR4, base + EMIF_LPDDR2_MODE_REG_CONFIG); + temperature_level = readl(base + EMIF_LPDDR2_MODE_REG_DATA); + temperature_level = (temperature_level & MR4_SDRAM_REF_RATE_MASK) >> + MR4_SDRAM_REF_RATE_SHIFT; + + if (emif->plat_data->device_info->cs1_used) { + writel(DDR_MR4 | CS_MASK, base + EMIF_LPDDR2_MODE_REG_CONFIG); + temp = readl(base + EMIF_LPDDR2_MODE_REG_DATA); + temp = (temp & MR4_SDRAM_REF_RATE_MASK) + >> MR4_SDRAM_REF_RATE_SHIFT; + temperature_level = max(temp, temperature_level); + } + + /* treat everything less than nominal(3) in MR4 as nominal */ + if (unlikely(temperature_level < SDRAM_TEMP_NOMINAL)) + temperature_level = SDRAM_TEMP_NOMINAL; + + /* if we get reserved value in MR4 persist with the existing value */ + if (likely(temperature_level != SDRAM_TEMP_RESERVED_4)) + emif->temperature_level = temperature_level; +} + +/* + * Program EMIF shadow registers that are not dependent on temperature + * or voltage + */ +static void setup_registers(struct emif_data *emif, struct emif_regs *regs) +{ + void __iomem *base = emif->base; + + writel(regs->sdram_tim2_shdw, base + EMIF_SDRAM_TIMING_2_SHDW); + writel(regs->phy_ctrl_1_shdw, base + EMIF_DDR_PHY_CTRL_1_SHDW); + writel(regs->pwr_mgmt_ctrl_shdw, + base + EMIF_POWER_MANAGEMENT_CTRL_SHDW); + + /* Settings specific for EMIF4D5 */ + if (emif->plat_data->ip_rev != EMIF_4D5) + return; + writel(regs->ext_phy_ctrl_2_shdw, base + EMIF_EXT_PHY_CTRL_2_SHDW); + writel(regs->ext_phy_ctrl_3_shdw, base + EMIF_EXT_PHY_CTRL_3_SHDW); + writel(regs->ext_phy_ctrl_4_shdw, base + EMIF_EXT_PHY_CTRL_4_SHDW); +} + +/* + * When voltage ramps dll calibration and forced read idle should + * happen more often + */ +static void setup_volt_sensitive_regs(struct emif_data *emif, + struct emif_regs *regs, u32 volt_state) +{ + u32 calib_ctrl; + void __iomem *base = emif->base; + + /* + * EMIF_READ_IDLE_CTRL in EMIF4D refers to the same register as + * EMIF_DLL_CALIB_CTRL in EMIF4D5 and dll_calib_ctrl_shadow_* + * is an alias of the respective read_idle_ctrl_shdw_* (members of + * a union). So, the below code takes care of both cases + */ + if (volt_state == DDR_VOLTAGE_RAMPING) + calib_ctrl = regs->dll_calib_ctrl_shdw_volt_ramp; + else + calib_ctrl = regs->dll_calib_ctrl_shdw_normal; + + writel(calib_ctrl, base + EMIF_DLL_CALIB_CTRL_SHDW); +} + +/* + * setup_temperature_sensitive_regs() - set the timings for temperature + * sensitive registers. This happens once at initialisation time based + * on the temperature at boot time and subsequently based on the temperature + * alert interrupt. Temperature alert can happen when the temperature + * increases or drops. So this function can have the effect of either + * derating the timings or going back to nominal values. + */ +static void setup_temperature_sensitive_regs(struct emif_data *emif, + struct emif_regs *regs) +{ + u32 tim1, tim3, ref_ctrl, type; + void __iomem *base = emif->base; + u32 temperature; + + type = emif->plat_data->device_info->type; + + tim1 = regs->sdram_tim1_shdw; + tim3 = regs->sdram_tim3_shdw; + ref_ctrl = regs->ref_ctrl_shdw; + + /* No de-rating for non-lpddr2 devices */ + if (type != DDR_TYPE_LPDDR2_S2 && type != DDR_TYPE_LPDDR2_S4) + goto out; + + temperature = emif->temperature_level; + if (temperature == SDRAM_TEMP_HIGH_DERATE_REFRESH) { + ref_ctrl = regs->ref_ctrl_shdw_derated; + } else if (temperature == SDRAM_TEMP_HIGH_DERATE_REFRESH_AND_TIMINGS) { + tim1 = regs->sdram_tim1_shdw_derated; + tim3 = regs->sdram_tim3_shdw_derated; + ref_ctrl = regs->ref_ctrl_shdw_derated; + } + +out: + writel(tim1, base + EMIF_SDRAM_TIMING_1_SHDW); + writel(tim3, base + EMIF_SDRAM_TIMING_3_SHDW); + writel(ref_ctrl, base + EMIF_SDRAM_REFRESH_CTRL_SHDW); +} + +static irqreturn_t handle_temp_alert(void __iomem *base, struct emif_data *emif) +{ + u32 old_temp_level; + irqreturn_t ret = IRQ_HANDLED; + struct emif_custom_configs *custom_configs; + + spin_lock_irqsave(&emif_lock, irq_state); + old_temp_level = emif->temperature_level; + get_temperature_level(emif); + + if (unlikely(emif->temperature_level == old_temp_level)) { + goto out; + } else if (!emif->curr_regs) { + dev_err(emif->dev, "temperature alert before registers are calculated, not de-rating timings\n"); + goto out; + } + + custom_configs = emif->plat_data->custom_configs; + + /* + * IF we detect higher than "nominal rating" from DDR sensor + * on an unsupported DDR part, shutdown system + */ + if (custom_configs && !(custom_configs->mask & + EMIF_CUSTOM_CONFIG_EXTENDED_TEMP_PART)) { + if (emif->temperature_level >= SDRAM_TEMP_HIGH_DERATE_REFRESH) { + dev_err(emif->dev, + "%s:NOT Extended temperature capable memory." + "Converting MR4=0x%02x as shutdown event\n", + __func__, emif->temperature_level); + /* + * Temperature far too high - do kernel_power_off() + * from thread context + */ + emif->temperature_level = SDRAM_TEMP_VERY_HIGH_SHUTDOWN; + ret = IRQ_WAKE_THREAD; + goto out; + } + } + + if (emif->temperature_level < old_temp_level || + emif->temperature_level == SDRAM_TEMP_VERY_HIGH_SHUTDOWN) { + /* + * Temperature coming down - defer handling to thread OR + * Temperature far too high - do kernel_power_off() from + * thread context + */ + ret = IRQ_WAKE_THREAD; + } else { + /* Temperature is going up - handle immediately */ + setup_temperature_sensitive_regs(emif, emif->curr_regs); + do_freq_update(); + } + +out: + spin_unlock_irqrestore(&emif_lock, irq_state); + return ret; +} + +static irqreturn_t emif_interrupt_handler(int irq, void *dev_id) +{ + u32 interrupts; + struct emif_data *emif = dev_id; + void __iomem *base = emif->base; + struct device *dev = emif->dev; + irqreturn_t ret = IRQ_HANDLED; + + /* Save the status and clear it */ + interrupts = readl(base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS); + writel(interrupts, base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS); + + /* + * Handle temperature alert + * Temperature alert should be same for all ports + * So, it's enough to process it only for one of the ports + */ + if (interrupts & TA_SYS_MASK) + ret = handle_temp_alert(base, emif); + + if (interrupts & ERR_SYS_MASK) + dev_err(dev, "Access error from SYS port - %x\n", interrupts); + + if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE) { + /* Save the status and clear it */ + interrupts = readl(base + EMIF_LL_OCP_INTERRUPT_STATUS); + writel(interrupts, base + EMIF_LL_OCP_INTERRUPT_STATUS); + + if (interrupts & ERR_LL_MASK) + dev_err(dev, "Access error from LL port - %x\n", + interrupts); + } + + return ret; +} + +static irqreturn_t emif_threaded_isr(int irq, void *dev_id) +{ + struct emif_data *emif = dev_id; + + if (emif->temperature_level == SDRAM_TEMP_VERY_HIGH_SHUTDOWN) { + dev_emerg(emif->dev, "SDRAM temperature exceeds operating limit.. Needs shut down!!!\n"); + + /* If we have Power OFF ability, use it, else try restarting */ + if (pm_power_off) { + kernel_power_off(); + } else { + WARN(1, "FIXME: NO pm_power_off!!! trying restart\n"); + kernel_restart("SDRAM Over-temp Emergency restart"); + } + return IRQ_HANDLED; + } + + spin_lock_irqsave(&emif_lock, irq_state); + + if (emif->curr_regs) { + setup_temperature_sensitive_regs(emif, emif->curr_regs); + do_freq_update(); + } else { + dev_err(emif->dev, "temperature alert before registers are calculated, not de-rating timings\n"); + } + + spin_unlock_irqrestore(&emif_lock, irq_state); + + return IRQ_HANDLED; +} + +static void clear_all_interrupts(struct emif_data *emif) +{ + void __iomem *base = emif->base; + + writel(readl(base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS), + base + EMIF_SYSTEM_OCP_INTERRUPT_STATUS); + if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE) + writel(readl(base + EMIF_LL_OCP_INTERRUPT_STATUS), + base + EMIF_LL_OCP_INTERRUPT_STATUS); +} + +static void disable_and_clear_all_interrupts(struct emif_data *emif) +{ + void __iomem *base = emif->base; + + /* Disable all interrupts */ + writel(readl(base + EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_SET), + base + EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_CLEAR); + if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE) + writel(readl(base + EMIF_LL_OCP_INTERRUPT_ENABLE_SET), + base + EMIF_LL_OCP_INTERRUPT_ENABLE_CLEAR); + + /* Clear all interrupts */ + clear_all_interrupts(emif); +} + +static int __init_or_module setup_interrupts(struct emif_data *emif, u32 irq) +{ + u32 interrupts, type; + void __iomem *base = emif->base; + + type = emif->plat_data->device_info->type; + + clear_all_interrupts(emif); + + /* Enable interrupts for SYS interface */ + interrupts = EN_ERR_SYS_MASK; + if (type == DDR_TYPE_LPDDR2_S2 || type == DDR_TYPE_LPDDR2_S4) + interrupts |= EN_TA_SYS_MASK; + writel(interrupts, base + EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_SET); + + /* Enable interrupts for LL interface */ + if (emif->plat_data->hw_caps & EMIF_HW_CAPS_LL_INTERFACE) { + /* TA need not be enabled for LL */ + interrupts = EN_ERR_LL_MASK; + writel(interrupts, base + EMIF_LL_OCP_INTERRUPT_ENABLE_SET); + } + + /* setup IRQ handlers */ + return devm_request_threaded_irq(emif->dev, irq, + emif_interrupt_handler, + emif_threaded_isr, + 0, dev_name(emif->dev), + emif); + +} + +static void __init_or_module emif_onetime_settings(struct emif_data *emif) +{ + u32 pwr_mgmt_ctrl, zq, temp_alert_cfg; + void __iomem *base = emif->base; + const struct lpddr2_addressing *addressing; + const struct ddr_device_info *device_info; + + device_info = emif->plat_data->device_info; + addressing = get_addressing_table(device_info); + + /* + * Init power management settings + * We don't know the frequency yet. Use a high frequency + * value for a conservative timeout setting + */ + pwr_mgmt_ctrl = get_pwr_mgmt_ctrl(1000000000, emif, + emif->plat_data->ip_rev); + emif->lpmode = (pwr_mgmt_ctrl & LP_MODE_MASK) >> LP_MODE_SHIFT; + writel(pwr_mgmt_ctrl, base + EMIF_POWER_MANAGEMENT_CONTROL); + + /* Init ZQ calibration settings */ + zq = get_zq_config_reg(addressing, device_info->cs1_used, + device_info->cal_resistors_per_cs); + writel(zq, base + EMIF_SDRAM_OUTPUT_IMPEDANCE_CALIBRATION_CONFIG); + + /* Check temperature level temperature level*/ + get_temperature_level(emif); + if (emif->temperature_level == SDRAM_TEMP_VERY_HIGH_SHUTDOWN) + dev_emerg(emif->dev, "SDRAM temperature exceeds operating limit.. Needs shut down!!!\n"); + + /* Init temperature polling */ + temp_alert_cfg = get_temp_alert_config(addressing, + emif->plat_data->custom_configs, device_info->cs1_used, + device_info->io_width, get_emif_bus_width(emif)); + writel(temp_alert_cfg, base + EMIF_TEMPERATURE_ALERT_CONFIG); + + /* + * Program external PHY control registers that are not frequency + * dependent + */ + if (emif->plat_data->phy_type != EMIF_PHY_TYPE_INTELLIPHY) + return; + writel(EMIF_EXT_PHY_CTRL_1_VAL, base + EMIF_EXT_PHY_CTRL_1_SHDW); + writel(EMIF_EXT_PHY_CTRL_5_VAL, base + EMIF_EXT_PHY_CTRL_5_SHDW); + writel(EMIF_EXT_PHY_CTRL_6_VAL, base + EMIF_EXT_PHY_CTRL_6_SHDW); + writel(EMIF_EXT_PHY_CTRL_7_VAL, base + EMIF_EXT_PHY_CTRL_7_SHDW); + writel(EMIF_EXT_PHY_CTRL_8_VAL, base + EMIF_EXT_PHY_CTRL_8_SHDW); + writel(EMIF_EXT_PHY_CTRL_9_VAL, base + EMIF_EXT_PHY_CTRL_9_SHDW); + writel(EMIF_EXT_PHY_CTRL_10_VAL, base + EMIF_EXT_PHY_CTRL_10_SHDW); + writel(EMIF_EXT_PHY_CTRL_11_VAL, base + EMIF_EXT_PHY_CTRL_11_SHDW); + writel(EMIF_EXT_PHY_CTRL_12_VAL, base + EMIF_EXT_PHY_CTRL_12_SHDW); + writel(EMIF_EXT_PHY_CTRL_13_VAL, base + EMIF_EXT_PHY_CTRL_13_SHDW); + writel(EMIF_EXT_PHY_CTRL_14_VAL, base + EMIF_EXT_PHY_CTRL_14_SHDW); + writel(EMIF_EXT_PHY_CTRL_15_VAL, base + EMIF_EXT_PHY_CTRL_15_SHDW); + writel(EMIF_EXT_PHY_CTRL_16_VAL, base + EMIF_EXT_PHY_CTRL_16_SHDW); + writel(EMIF_EXT_PHY_CTRL_17_VAL, base + EMIF_EXT_PHY_CTRL_17_SHDW); + writel(EMIF_EXT_PHY_CTRL_18_VAL, base + EMIF_EXT_PHY_CTRL_18_SHDW); + writel(EMIF_EXT_PHY_CTRL_19_VAL, base + EMIF_EXT_PHY_CTRL_19_SHDW); + writel(EMIF_EXT_PHY_CTRL_20_VAL, base + EMIF_EXT_PHY_CTRL_20_SHDW); + writel(EMIF_EXT_PHY_CTRL_21_VAL, base + EMIF_EXT_PHY_CTRL_21_SHDW); + writel(EMIF_EXT_PHY_CTRL_22_VAL, base + EMIF_EXT_PHY_CTRL_22_SHDW); + writel(EMIF_EXT_PHY_CTRL_23_VAL, base + EMIF_EXT_PHY_CTRL_23_SHDW); + writel(EMIF_EXT_PHY_CTRL_24_VAL, base + EMIF_EXT_PHY_CTRL_24_SHDW); +} + +static void get_default_timings(struct emif_data *emif) +{ + struct emif_platform_data *pd = emif->plat_data; + + pd->timings = lpddr2_jedec_timings; + pd->timings_arr_size = ARRAY_SIZE(lpddr2_jedec_timings); + + dev_warn(emif->dev, "%s: using default timings\n", __func__); +} + +static int is_dev_data_valid(u32 type, u32 density, u32 io_width, u32 phy_type, + u32 ip_rev, struct device *dev) +{ + int valid; + + valid = (type == DDR_TYPE_LPDDR2_S4 || + type == DDR_TYPE_LPDDR2_S2) + && (density >= DDR_DENSITY_64Mb + && density <= DDR_DENSITY_8Gb) + && (io_width >= DDR_IO_WIDTH_8 + && io_width <= DDR_IO_WIDTH_32); + + /* Combinations of EMIF and PHY revisions that we support today */ + switch (ip_rev) { + case EMIF_4D: + valid = valid && (phy_type == EMIF_PHY_TYPE_ATTILAPHY); + break; + case EMIF_4D5: + valid = valid && (phy_type == EMIF_PHY_TYPE_INTELLIPHY); + break; + default: + valid = 0; + } + + if (!valid) + dev_err(dev, "%s: invalid DDR details\n", __func__); + return valid; +} + +static int is_custom_config_valid(struct emif_custom_configs *cust_cfgs, + struct device *dev) +{ + int valid = 1; + + if ((cust_cfgs->mask & EMIF_CUSTOM_CONFIG_LPMODE) && + (cust_cfgs->lpmode != EMIF_LP_MODE_DISABLE)) + valid = cust_cfgs->lpmode_freq_threshold && + cust_cfgs->lpmode_timeout_performance && + cust_cfgs->lpmode_timeout_power; + + if (cust_cfgs->mask & EMIF_CUSTOM_CONFIG_TEMP_ALERT_POLL_INTERVAL) + valid = valid && cust_cfgs->temp_alert_poll_interval_ms; + + if (!valid) + dev_warn(dev, "%s: invalid custom configs\n", __func__); + + return valid; +} + +#if defined(CONFIG_OF) +static void __init_or_module of_get_custom_configs(struct device_node *np_emif, + struct emif_data *emif) +{ + struct emif_custom_configs *cust_cfgs = NULL; + int len; + const __be32 *lpmode, *poll_intvl; + + lpmode = of_get_property(np_emif, "low-power-mode", &len); + poll_intvl = of_get_property(np_emif, "temp-alert-poll-interval", &len); + + if (lpmode || poll_intvl) + cust_cfgs = devm_kzalloc(emif->dev, sizeof(*cust_cfgs), + GFP_KERNEL); + + if (!cust_cfgs) + return; + + if (lpmode) { + cust_cfgs->mask |= EMIF_CUSTOM_CONFIG_LPMODE; + cust_cfgs->lpmode = be32_to_cpup(lpmode); + of_property_read_u32(np_emif, + "low-power-mode-timeout-performance", + &cust_cfgs->lpmode_timeout_performance); + of_property_read_u32(np_emif, + "low-power-mode-timeout-power", + &cust_cfgs->lpmode_timeout_power); + of_property_read_u32(np_emif, + "low-power-mode-freq-threshold", + &cust_cfgs->lpmode_freq_threshold); + } + + if (poll_intvl) { + cust_cfgs->mask |= + EMIF_CUSTOM_CONFIG_TEMP_ALERT_POLL_INTERVAL; + cust_cfgs->temp_alert_poll_interval_ms = + be32_to_cpup(poll_intvl); + } + + if (of_find_property(np_emif, "extended-temp-part", &len)) + cust_cfgs->mask |= EMIF_CUSTOM_CONFIG_EXTENDED_TEMP_PART; + + if (!is_custom_config_valid(cust_cfgs, emif->dev)) { + devm_kfree(emif->dev, cust_cfgs); + return; + } + + emif->plat_data->custom_configs = cust_cfgs; +} + +static void __init_or_module of_get_ddr_info(struct device_node *np_emif, + struct device_node *np_ddr, + struct ddr_device_info *dev_info) +{ + u32 density = 0, io_width = 0; + int len; + + if (of_find_property(np_emif, "cs1-used", &len)) + dev_info->cs1_used = true; + + if (of_find_property(np_emif, "cal-resistor-per-cs", &len)) + dev_info->cal_resistors_per_cs = true; + + if (of_device_is_compatible(np_ddr , "jedec,lpddr2-s4")) + dev_info->type = DDR_TYPE_LPDDR2_S4; + else if (of_device_is_compatible(np_ddr , "jedec,lpddr2-s2")) + dev_info->type = DDR_TYPE_LPDDR2_S2; + + of_property_read_u32(np_ddr, "density", &density); + of_property_read_u32(np_ddr, "io-width", &io_width); + + /* Convert from density in Mb to the density encoding in jedc_ddr.h */ + if (density & (density - 1)) + dev_info->density = 0; + else + dev_info->density = __fls(density) - 5; + + /* Convert from io_width in bits to io_width encoding in jedc_ddr.h */ + if (io_width & (io_width - 1)) + dev_info->io_width = 0; + else + dev_info->io_width = __fls(io_width) - 1; +} + +static struct emif_data * __init_or_module of_get_memory_device_details( + struct device_node *np_emif, struct device *dev) +{ + struct emif_data *emif = NULL; + struct ddr_device_info *dev_info = NULL; + struct emif_platform_data *pd = NULL; + struct device_node *np_ddr; + int len; + + np_ddr = of_parse_phandle(np_emif, "device-handle", 0); + if (!np_ddr) + goto error; + emif = devm_kzalloc(dev, sizeof(struct emif_data), GFP_KERNEL); + pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + dev_info = devm_kzalloc(dev, sizeof(*dev_info), GFP_KERNEL); + + if (!emif || !pd || !dev_info) { + dev_err(dev, "%s: Out of memory!!\n", + __func__); + goto error; + } + + emif->plat_data = pd; + pd->device_info = dev_info; + emif->dev = dev; + emif->np_ddr = np_ddr; + emif->temperature_level = SDRAM_TEMP_NOMINAL; + + if (of_device_is_compatible(np_emif, "ti,emif-4d")) + emif->plat_data->ip_rev = EMIF_4D; + else if (of_device_is_compatible(np_emif, "ti,emif-4d5")) + emif->plat_data->ip_rev = EMIF_4D5; + + of_property_read_u32(np_emif, "phy-type", &pd->phy_type); + + if (of_find_property(np_emif, "hw-caps-ll-interface", &len)) + pd->hw_caps |= EMIF_HW_CAPS_LL_INTERFACE; + + of_get_ddr_info(np_emif, np_ddr, dev_info); + if (!is_dev_data_valid(pd->device_info->type, pd->device_info->density, + pd->device_info->io_width, pd->phy_type, pd->ip_rev, + emif->dev)) { + dev_err(dev, "%s: invalid device data!!\n", __func__); + goto error; + } + /* + * For EMIF instances other than EMIF1 see if the devices connected + * are exactly same as on EMIF1(which is typically the case). If so, + * mark it as a duplicate of EMIF1. This will save some memory and + * computation. + */ + if (emif1 && emif1->np_ddr == np_ddr) { + emif->duplicate = true; + goto out; + } else if (emif1) { + dev_warn(emif->dev, "%s: Non-symmetric DDR geometry\n", + __func__); + } + + of_get_custom_configs(np_emif, emif); + emif->plat_data->timings = of_get_ddr_timings(np_ddr, emif->dev, + emif->plat_data->device_info->type, + &emif->plat_data->timings_arr_size); + + emif->plat_data->min_tck = of_get_min_tck(np_ddr, emif->dev); + goto out; + +error: + return NULL; +out: + return emif; +} + +#else + +static struct emif_data * __init_or_module of_get_memory_device_details( + struct device_node *np_emif, struct device *dev) +{ + return NULL; +} +#endif + +static struct emif_data *__init_or_module get_device_details( + struct platform_device *pdev) +{ + u32 size; + struct emif_data *emif = NULL; + struct ddr_device_info *dev_info; + struct emif_custom_configs *cust_cfgs; + struct emif_platform_data *pd; + struct device *dev; + void *temp; + + pd = pdev->dev.platform_data; + dev = &pdev->dev; + + if (!(pd && pd->device_info && is_dev_data_valid(pd->device_info->type, + pd->device_info->density, pd->device_info->io_width, + pd->phy_type, pd->ip_rev, dev))) { + dev_err(dev, "%s: invalid device data\n", __func__); + goto error; + } + + emif = devm_kzalloc(dev, sizeof(*emif), GFP_KERNEL); + temp = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + dev_info = devm_kzalloc(dev, sizeof(*dev_info), GFP_KERNEL); + + if (!emif || !temp || !dev_info) { + dev_err(dev, "%s:%d: allocation error\n", __func__, __LINE__); + goto error; + } + + memcpy(temp, pd, sizeof(*pd)); + pd = temp; + memcpy(dev_info, pd->device_info, sizeof(*dev_info)); + + pd->device_info = dev_info; + emif->plat_data = pd; + emif->dev = dev; + emif->temperature_level = SDRAM_TEMP_NOMINAL; + + /* + * For EMIF instances other than EMIF1 see if the devices connected + * are exactly same as on EMIF1(which is typically the case). If so, + * mark it as a duplicate of EMIF1 and skip copying timings data. + * This will save some memory and some computation later. + */ + emif->duplicate = emif1 && (memcmp(dev_info, + emif1->plat_data->device_info, + sizeof(struct ddr_device_info)) == 0); + + if (emif->duplicate) { + pd->timings = NULL; + pd->min_tck = NULL; + goto out; + } else if (emif1) { + dev_warn(emif->dev, "%s: Non-symmetric DDR geometry\n", + __func__); + } + + /* + * Copy custom configs - ignore allocation error, if any, as + * custom_configs is not very critical + */ + cust_cfgs = pd->custom_configs; + if (cust_cfgs && is_custom_config_valid(cust_cfgs, dev)) { + temp = devm_kzalloc(dev, sizeof(*cust_cfgs), GFP_KERNEL); + if (temp) + memcpy(temp, cust_cfgs, sizeof(*cust_cfgs)); + else + dev_warn(dev, "%s:%d: allocation error\n", __func__, + __LINE__); + pd->custom_configs = temp; + } + + /* + * Copy timings and min-tck values from platform data. If it is not + * available or if memory allocation fails, use JEDEC defaults + */ + size = sizeof(struct lpddr2_timings) * pd->timings_arr_size; + if (pd->timings) { + temp = devm_kzalloc(dev, size, GFP_KERNEL); + if (temp) { + memcpy(temp, pd->timings, size); + pd->timings = temp; + } else { + dev_warn(dev, "%s:%d: allocation error\n", __func__, + __LINE__); + get_default_timings(emif); + } + } else { + get_default_timings(emif); + } + + if (pd->min_tck) { + temp = devm_kzalloc(dev, sizeof(*pd->min_tck), GFP_KERNEL); + if (temp) { + memcpy(temp, pd->min_tck, sizeof(*pd->min_tck)); + pd->min_tck = temp; + } else { + dev_warn(dev, "%s:%d: allocation error\n", __func__, + __LINE__); + pd->min_tck = &lpddr2_jedec_min_tck; + } + } else { + pd->min_tck = &lpddr2_jedec_min_tck; + } + +out: + return emif; + +error: + return NULL; +} + +static int __init_or_module emif_probe(struct platform_device *pdev) +{ + struct emif_data *emif; + struct resource *res; + int irq, ret; + + if (pdev->dev.of_node) + emif = of_get_memory_device_details(pdev->dev.of_node, &pdev->dev); + else + emif = get_device_details(pdev); + + if (!emif) { + pr_err("%s: error getting device data\n", __func__); + goto error; + } + + list_add(&emif->node, &device_list); + emif->addressing = get_addressing_table(emif->plat_data->device_info); + + /* Save pointers to each other in emif and device structures */ + emif->dev = &pdev->dev; + platform_set_drvdata(pdev, emif); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + emif->base = devm_ioremap_resource(emif->dev, res); + if (IS_ERR(emif->base)) + goto error; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(emif->dev, "%s: error getting IRQ resource - %d\n", + __func__, irq); + goto error; + } + + emif_onetime_settings(emif); + emif_debugfs_init(emif); + disable_and_clear_all_interrupts(emif); + ret = setup_interrupts(emif, irq); + if (ret) + goto error; + + /* One-time actions taken on probing the first device */ + if (!emif1) { + emif1 = emif; + spin_lock_init(&emif_lock); + + /* + * TODO: register notifiers for frequency and voltage + * change here once the respective frameworks are + * available + */ + } + + dev_info(&pdev->dev, "%s: device configured with addr = %p and IRQ%d\n", + __func__, emif->base, irq); + + return 0; +error: + return -ENODEV; +} + +static int __exit emif_remove(struct platform_device *pdev) +{ + struct emif_data *emif = platform_get_drvdata(pdev); + + emif_debugfs_exit(emif); + + return 0; +} + +static void emif_shutdown(struct platform_device *pdev) +{ + struct emif_data *emif = platform_get_drvdata(pdev); + + disable_and_clear_all_interrupts(emif); +} + +static int get_emif_reg_values(struct emif_data *emif, u32 freq, + struct emif_regs *regs) +{ + u32 cs1_used, ip_rev, phy_type; + u32 cl, type; + const struct lpddr2_timings *timings; + const struct lpddr2_min_tck *min_tck; + const struct ddr_device_info *device_info; + const struct lpddr2_addressing *addressing; + struct emif_data *emif_for_calc; + struct device *dev; + const struct emif_custom_configs *custom_configs; + + dev = emif->dev; + /* + * If the devices on this EMIF instance is duplicate of EMIF1, + * use EMIF1 details for the calculation + */ + emif_for_calc = emif->duplicate ? emif1 : emif; + timings = get_timings_table(emif_for_calc, freq); + addressing = emif_for_calc->addressing; + if (!timings || !addressing) { + dev_err(dev, "%s: not enough data available for %dHz", + __func__, freq); + return -1; + } + + device_info = emif_for_calc->plat_data->device_info; + type = device_info->type; + cs1_used = device_info->cs1_used; + ip_rev = emif_for_calc->plat_data->ip_rev; + phy_type = emif_for_calc->plat_data->phy_type; + + min_tck = emif_for_calc->plat_data->min_tck; + custom_configs = emif_for_calc->plat_data->custom_configs; + + set_ddr_clk_period(freq); + + regs->ref_ctrl_shdw = get_sdram_ref_ctrl_shdw(freq, addressing); + regs->sdram_tim1_shdw = get_sdram_tim_1_shdw(timings, min_tck, + addressing); + regs->sdram_tim2_shdw = get_sdram_tim_2_shdw(timings, min_tck, + addressing, type); + regs->sdram_tim3_shdw = get_sdram_tim_3_shdw(timings, min_tck, + addressing, type, ip_rev, EMIF_NORMAL_TIMINGS); + + cl = get_cl(emif); + + if (phy_type == EMIF_PHY_TYPE_ATTILAPHY && ip_rev == EMIF_4D) { + regs->phy_ctrl_1_shdw = get_ddr_phy_ctrl_1_attilaphy_4d( + timings, freq, cl); + } else if (phy_type == EMIF_PHY_TYPE_INTELLIPHY && ip_rev == EMIF_4D5) { + regs->phy_ctrl_1_shdw = get_phy_ctrl_1_intelliphy_4d5(freq, cl); + regs->ext_phy_ctrl_2_shdw = get_ext_phy_ctrl_2_intelliphy_4d5(); + regs->ext_phy_ctrl_3_shdw = get_ext_phy_ctrl_3_intelliphy_4d5(); + regs->ext_phy_ctrl_4_shdw = get_ext_phy_ctrl_4_intelliphy_4d5(); + } else { + return -1; + } + + /* Only timeout values in pwr_mgmt_ctrl_shdw register */ + regs->pwr_mgmt_ctrl_shdw = + get_pwr_mgmt_ctrl(freq, emif_for_calc, ip_rev) & + (CS_TIM_MASK | SR_TIM_MASK | PD_TIM_MASK); + + if (ip_rev & EMIF_4D) { + regs->read_idle_ctrl_shdw_normal = + get_read_idle_ctrl_shdw(DDR_VOLTAGE_STABLE); + + regs->read_idle_ctrl_shdw_volt_ramp = + get_read_idle_ctrl_shdw(DDR_VOLTAGE_RAMPING); + } else if (ip_rev & EMIF_4D5) { + regs->dll_calib_ctrl_shdw_normal = + get_dll_calib_ctrl_shdw(DDR_VOLTAGE_STABLE); + + regs->dll_calib_ctrl_shdw_volt_ramp = + get_dll_calib_ctrl_shdw(DDR_VOLTAGE_RAMPING); + } + + if (type == DDR_TYPE_LPDDR2_S2 || type == DDR_TYPE_LPDDR2_S4) { + regs->ref_ctrl_shdw_derated = get_sdram_ref_ctrl_shdw(freq / 4, + addressing); + + regs->sdram_tim1_shdw_derated = + get_sdram_tim_1_shdw_derated(timings, min_tck, + addressing); + + regs->sdram_tim3_shdw_derated = get_sdram_tim_3_shdw(timings, + min_tck, addressing, type, ip_rev, + EMIF_DERATED_TIMINGS); + } + + regs->freq = freq; + + return 0; +} + +/* + * get_regs() - gets the cached emif_regs structure for a given EMIF instance + * given frequency(freq): + * + * As an optimisation, every EMIF instance other than EMIF1 shares the + * register cache with EMIF1 if the devices connected on this instance + * are same as that on EMIF1(indicated by the duplicate flag) + * + * If we do not have an entry corresponding to the frequency given, we + * allocate a new entry and calculate the values + * + * Upon finding the right reg dump, save it in curr_regs. It can be + * directly used for thermal de-rating and voltage ramping changes. + */ +static struct emif_regs *get_regs(struct emif_data *emif, u32 freq) +{ + int i; + struct emif_regs **regs_cache; + struct emif_regs *regs = NULL; + struct device *dev; + + dev = emif->dev; + if (emif->curr_regs && emif->curr_regs->freq == freq) { + dev_dbg(dev, "%s: using curr_regs - %u Hz", __func__, freq); + return emif->curr_regs; + } + + if (emif->duplicate) + regs_cache = emif1->regs_cache; + else + regs_cache = emif->regs_cache; + + for (i = 0; i < EMIF_MAX_NUM_FREQUENCIES && regs_cache[i]; i++) { + if (regs_cache[i]->freq == freq) { + regs = regs_cache[i]; + dev_dbg(dev, + "%s: reg dump found in reg cache for %u Hz\n", + __func__, freq); + break; + } + } + + /* + * If we don't have an entry for this frequency in the cache create one + * and calculate the values + */ + if (!regs) { + regs = devm_kzalloc(emif->dev, sizeof(*regs), GFP_ATOMIC); + if (!regs) + return NULL; + + if (get_emif_reg_values(emif, freq, regs)) { + devm_kfree(emif->dev, regs); + return NULL; + } + + /* + * Now look for an un-used entry in the cache and save the + * newly created struct. If there are no free entries + * over-write the last entry + */ + for (i = 0; i < EMIF_MAX_NUM_FREQUENCIES && regs_cache[i]; i++) + ; + + if (i >= EMIF_MAX_NUM_FREQUENCIES) { + dev_warn(dev, "%s: regs_cache full - reusing a slot!!\n", + __func__); + i = EMIF_MAX_NUM_FREQUENCIES - 1; + devm_kfree(emif->dev, regs_cache[i]); + } + regs_cache[i] = regs; + } + + return regs; +} + +static void do_volt_notify_handling(struct emif_data *emif, u32 volt_state) +{ + dev_dbg(emif->dev, "%s: voltage notification : %d", __func__, + volt_state); + + if (!emif->curr_regs) { + dev_err(emif->dev, + "%s: volt-notify before registers are ready: %d\n", + __func__, volt_state); + return; + } + + setup_volt_sensitive_regs(emif, emif->curr_regs, volt_state); +} + +/* + * TODO: voltage notify handling should be hooked up to + * regulator framework as soon as the necessary support + * is available in mainline kernel. This function is un-used + * right now. + */ +static void __attribute__((unused)) volt_notify_handling(u32 volt_state) +{ + struct emif_data *emif; + + spin_lock_irqsave(&emif_lock, irq_state); + + list_for_each_entry(emif, &device_list, node) + do_volt_notify_handling(emif, volt_state); + do_freq_update(); + + spin_unlock_irqrestore(&emif_lock, irq_state); +} + +static void do_freq_pre_notify_handling(struct emif_data *emif, u32 new_freq) +{ + struct emif_regs *regs; + + regs = get_regs(emif, new_freq); + if (!regs) + return; + + emif->curr_regs = regs; + + /* + * Update the shadow registers: + * Temperature and voltage-ramp sensitive settings are also configured + * in terms of DDR cycles. So, we need to update them too when there + * is a freq change + */ + dev_dbg(emif->dev, "%s: setting up shadow registers for %uHz", + __func__, new_freq); + setup_registers(emif, regs); + setup_temperature_sensitive_regs(emif, regs); + setup_volt_sensitive_regs(emif, regs, DDR_VOLTAGE_STABLE); + + /* + * Part of workaround for errata i728. See do_freq_update() + * for more details + */ + if (emif->lpmode == EMIF_LP_MODE_SELF_REFRESH) + set_lpmode(emif, EMIF_LP_MODE_DISABLE); +} + +/* + * TODO: frequency notify handling should be hooked up to + * clock framework as soon as the necessary support is + * available in mainline kernel. This function is un-used + * right now. + */ +static void __attribute__((unused)) freq_pre_notify_handling(u32 new_freq) +{ + struct emif_data *emif; + + /* + * NOTE: we are taking the spin-lock here and releases it + * only in post-notifier. This doesn't look good and + * Sparse complains about it, but this seems to be + * un-avoidable. We need to lock a sequence of events + * that is split between EMIF and clock framework. + * + * 1. EMIF driver updates EMIF timings in shadow registers in the + * frequency pre-notify callback from clock framework + * 2. clock framework sets up the registers for the new frequency + * 3. clock framework initiates a hw-sequence that updates + * the frequency EMIF timings synchronously. + * + * All these 3 steps should be performed as an atomic operation + * vis-a-vis similar sequence in the EMIF interrupt handler + * for temperature events. Otherwise, there could be race + * conditions that could result in incorrect EMIF timings for + * a given frequency + */ + spin_lock_irqsave(&emif_lock, irq_state); + + list_for_each_entry(emif, &device_list, node) + do_freq_pre_notify_handling(emif, new_freq); +} + +static void do_freq_post_notify_handling(struct emif_data *emif) +{ + /* + * Part of workaround for errata i728. See do_freq_update() + * for more details + */ + if (emif->lpmode == EMIF_LP_MODE_SELF_REFRESH) + set_lpmode(emif, EMIF_LP_MODE_SELF_REFRESH); +} + +/* + * TODO: frequency notify handling should be hooked up to + * clock framework as soon as the necessary support is + * available in mainline kernel. This function is un-used + * right now. + */ +static void __attribute__((unused)) freq_post_notify_handling(void) +{ + struct emif_data *emif; + + list_for_each_entry(emif, &device_list, node) + do_freq_post_notify_handling(emif); + + /* + * Lock is done in pre-notify handler. See freq_pre_notify_handling() + * for more details + */ + spin_unlock_irqrestore(&emif_lock, irq_state); +} + +#if defined(CONFIG_OF) +static const struct of_device_id emif_of_match[] = { + { .compatible = "ti,emif-4d" }, + { .compatible = "ti,emif-4d5" }, + {}, +}; +MODULE_DEVICE_TABLE(of, emif_of_match); +#endif + +static struct platform_driver emif_driver = { + .remove = __exit_p(emif_remove), + .shutdown = emif_shutdown, + .driver = { + .name = "emif", + .of_match_table = of_match_ptr(emif_of_match), + }, +}; + +module_platform_driver_probe(emif_driver, emif_probe); + +MODULE_DESCRIPTION("TI EMIF SDRAM Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:emif"); +MODULE_AUTHOR("Texas Instruments Inc"); diff --git a/drivers/memory/emif.h b/drivers/memory/emif.h new file mode 100644 index 000000000..9e9f80379 --- /dev/null +++ b/drivers/memory/emif.h @@ -0,0 +1,606 @@ +/* + * Defines for the EMIF driver + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * Benoit Cousson (b-cousson@ti.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __EMIF_H +#define __EMIF_H + +/* + * Maximum number of different frequencies supported by EMIF driver + * Determines the number of entries in the pointer array for register + * cache + */ +#define EMIF_MAX_NUM_FREQUENCIES 6 + +/* State of the core voltage */ +#define DDR_VOLTAGE_STABLE 0 +#define DDR_VOLTAGE_RAMPING 1 + +/* Defines for timing De-rating */ +#define EMIF_NORMAL_TIMINGS 0 +#define EMIF_DERATED_TIMINGS 1 + +/* Length of the forced read idle period in terms of cycles */ +#define EMIF_READ_IDLE_LEN_VAL 5 + +/* + * forced read idle interval to be used when voltage + * is changed as part of DVFS/DPS - 1ms + */ +#define READ_IDLE_INTERVAL_DVFS (1*1000000) + +/* + * Forced read idle interval to be used when voltage is stable + * 50us - or maximum value will do + */ +#define READ_IDLE_INTERVAL_NORMAL (50*1000000) + +/* DLL calibration interval when voltage is NOT stable - 1us */ +#define DLL_CALIB_INTERVAL_DVFS (1*1000000) + +#define DLL_CALIB_ACK_WAIT_VAL 5 + +/* Interval between ZQCS commands - hw team recommended value */ +#define EMIF_ZQCS_INTERVAL_US (50*1000) +/* Enable ZQ Calibration on exiting Self-refresh */ +#define ZQ_SFEXITEN_ENABLE 1 +/* + * ZQ Calibration simultaneously on both chip-selects: + * Needs one calibration resistor per CS + */ +#define ZQ_DUALCALEN_DISABLE 0 +#define ZQ_DUALCALEN_ENABLE 1 + +#define T_ZQCS_DEFAULT_NS 90 +#define T_ZQCL_DEFAULT_NS 360 +#define T_ZQINIT_DEFAULT_NS 1000 + +/* DPD_EN */ +#define DPD_DISABLE 0 +#define DPD_ENABLE 1 + +/* + * Default values for the low-power entry to be used if not provided by user. + * OMAP4/5 has a hw bug(i735) due to which this value can not be less than 512 + * Timeout values are in DDR clock 'cycles' and frequency threshold in Hz + */ +#define EMIF_LP_MODE_TIMEOUT_PERFORMANCE 2048 +#define EMIF_LP_MODE_TIMEOUT_POWER 512 +#define EMIF_LP_MODE_FREQ_THRESHOLD 400000000 + +/* DDR_PHY_CTRL_1 values for EMIF4D - ATTILA PHY combination */ +#define EMIF_DDR_PHY_CTRL_1_BASE_VAL_ATTILAPHY 0x049FF000 +#define EMIF_DLL_SLAVE_DLY_CTRL_400_MHZ_ATTILAPHY 0x41 +#define EMIF_DLL_SLAVE_DLY_CTRL_200_MHZ_ATTILAPHY 0x80 +#define EMIF_DLL_SLAVE_DLY_CTRL_100_MHZ_AND_LESS_ATTILAPHY 0xFF + +/* DDR_PHY_CTRL_1 values for EMIF4D5 INTELLIPHY combination */ +#define EMIF_DDR_PHY_CTRL_1_BASE_VAL_INTELLIPHY 0x0E084200 +#define EMIF_PHY_TOTAL_READ_LATENCY_INTELLIPHY_PS 10000 + +/* TEMP_ALERT_CONFIG - corresponding to temp gradient 5 C/s */ +#define TEMP_ALERT_POLL_INTERVAL_DEFAULT_MS 360 + +#define EMIF_T_CSTA 3 +#define EMIF_T_PDLL_UL 128 + +/* External PHY control registers magic values */ +#define EMIF_EXT_PHY_CTRL_1_VAL 0x04020080 +#define EMIF_EXT_PHY_CTRL_5_VAL 0x04010040 +#define EMIF_EXT_PHY_CTRL_6_VAL 0x01004010 +#define EMIF_EXT_PHY_CTRL_7_VAL 0x00001004 +#define EMIF_EXT_PHY_CTRL_8_VAL 0x04010040 +#define EMIF_EXT_PHY_CTRL_9_VAL 0x01004010 +#define EMIF_EXT_PHY_CTRL_10_VAL 0x00001004 +#define EMIF_EXT_PHY_CTRL_11_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_12_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_13_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_14_VAL 0x80080080 +#define EMIF_EXT_PHY_CTRL_15_VAL 0x00800800 +#define EMIF_EXT_PHY_CTRL_16_VAL 0x08102040 +#define EMIF_EXT_PHY_CTRL_17_VAL 0x00000001 +#define EMIF_EXT_PHY_CTRL_18_VAL 0x540A8150 +#define EMIF_EXT_PHY_CTRL_19_VAL 0xA81502A0 +#define EMIF_EXT_PHY_CTRL_20_VAL 0x002A0540 +#define EMIF_EXT_PHY_CTRL_21_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_22_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_23_VAL 0x00000000 +#define EMIF_EXT_PHY_CTRL_24_VAL 0x00000077 + +#define EMIF_INTELLI_PHY_DQS_GATE_OPENING_DELAY_PS 1200 + +/* Registers offset */ +#define EMIF_MODULE_ID_AND_REVISION 0x0000 +#define EMIF_STATUS 0x0004 +#define EMIF_SDRAM_CONFIG 0x0008 +#define EMIF_SDRAM_CONFIG_2 0x000c +#define EMIF_SDRAM_REFRESH_CONTROL 0x0010 +#define EMIF_SDRAM_REFRESH_CTRL_SHDW 0x0014 +#define EMIF_SDRAM_TIMING_1 0x0018 +#define EMIF_SDRAM_TIMING_1_SHDW 0x001c +#define EMIF_SDRAM_TIMING_2 0x0020 +#define EMIF_SDRAM_TIMING_2_SHDW 0x0024 +#define EMIF_SDRAM_TIMING_3 0x0028 +#define EMIF_SDRAM_TIMING_3_SHDW 0x002c +#define EMIF_LPDDR2_NVM_TIMING 0x0030 +#define EMIF_LPDDR2_NVM_TIMING_SHDW 0x0034 +#define EMIF_POWER_MANAGEMENT_CONTROL 0x0038 +#define EMIF_POWER_MANAGEMENT_CTRL_SHDW 0x003c +#define EMIF_LPDDR2_MODE_REG_DATA 0x0040 +#define EMIF_LPDDR2_MODE_REG_CONFIG 0x0050 +#define EMIF_OCP_CONFIG 0x0054 +#define EMIF_OCP_CONFIG_VALUE_1 0x0058 +#define EMIF_OCP_CONFIG_VALUE_2 0x005c +#define EMIF_IODFT_TEST_LOGIC_GLOBAL_CONTROL 0x0060 +#define EMIF_IODFT_TEST_LOGIC_CTRL_MISR_RESULT 0x0064 +#define EMIF_IODFT_TEST_LOGIC_ADDRESS_MISR_RESULT 0x0068 +#define EMIF_IODFT_TEST_LOGIC_DATA_MISR_RESULT_1 0x006c +#define EMIF_IODFT_TEST_LOGIC_DATA_MISR_RESULT_2 0x0070 +#define EMIF_IODFT_TEST_LOGIC_DATA_MISR_RESULT_3 0x0074 +#define EMIF_PERFORMANCE_COUNTER_1 0x0080 +#define EMIF_PERFORMANCE_COUNTER_2 0x0084 +#define EMIF_PERFORMANCE_COUNTER_CONFIG 0x0088 +#define EMIF_PERFORMANCE_COUNTER_MASTER_REGION_SELECT 0x008c +#define EMIF_PERFORMANCE_COUNTER_TIME 0x0090 +#define EMIF_MISC_REG 0x0094 +#define EMIF_DLL_CALIB_CTRL 0x0098 +#define EMIF_DLL_CALIB_CTRL_SHDW 0x009c +#define EMIF_END_OF_INTERRUPT 0x00a0 +#define EMIF_SYSTEM_OCP_INTERRUPT_RAW_STATUS 0x00a4 +#define EMIF_LL_OCP_INTERRUPT_RAW_STATUS 0x00a8 +#define EMIF_SYSTEM_OCP_INTERRUPT_STATUS 0x00ac +#define EMIF_LL_OCP_INTERRUPT_STATUS 0x00b0 +#define EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_SET 0x00b4 +#define EMIF_LL_OCP_INTERRUPT_ENABLE_SET 0x00b8 +#define EMIF_SYSTEM_OCP_INTERRUPT_ENABLE_CLEAR 0x00bc +#define EMIF_LL_OCP_INTERRUPT_ENABLE_CLEAR 0x00c0 +#define EMIF_SDRAM_OUTPUT_IMPEDANCE_CALIBRATION_CONFIG 0x00c8 +#define EMIF_TEMPERATURE_ALERT_CONFIG 0x00cc +#define EMIF_OCP_ERROR_LOG 0x00d0 +#define EMIF_READ_WRITE_LEVELING_RAMP_WINDOW 0x00d4 +#define EMIF_READ_WRITE_LEVELING_RAMP_CONTROL 0x00d8 +#define EMIF_READ_WRITE_LEVELING_CONTROL 0x00dc +#define EMIF_DDR_PHY_CTRL_1 0x00e4 +#define EMIF_DDR_PHY_CTRL_1_SHDW 0x00e8 +#define EMIF_DDR_PHY_CTRL_2 0x00ec +#define EMIF_PRIORITY_TO_CLASS_OF_SERVICE_MAPPING 0x0100 +#define EMIF_CONNECTION_ID_TO_CLASS_OF_SERVICE_1_MAPPING 0x0104 +#define EMIF_CONNECTION_ID_TO_CLASS_OF_SERVICE_2_MAPPING 0x0108 +#define EMIF_READ_WRITE_EXECUTION_THRESHOLD 0x0120 +#define EMIF_COS_CONFIG 0x0124 +#define EMIF_PHY_STATUS_1 0x0140 +#define EMIF_PHY_STATUS_2 0x0144 +#define EMIF_PHY_STATUS_3 0x0148 +#define EMIF_PHY_STATUS_4 0x014c +#define EMIF_PHY_STATUS_5 0x0150 +#define EMIF_PHY_STATUS_6 0x0154 +#define EMIF_PHY_STATUS_7 0x0158 +#define EMIF_PHY_STATUS_8 0x015c +#define EMIF_PHY_STATUS_9 0x0160 +#define EMIF_PHY_STATUS_10 0x0164 +#define EMIF_PHY_STATUS_11 0x0168 +#define EMIF_PHY_STATUS_12 0x016c +#define EMIF_PHY_STATUS_13 0x0170 +#define EMIF_PHY_STATUS_14 0x0174 +#define EMIF_PHY_STATUS_15 0x0178 +#define EMIF_PHY_STATUS_16 0x017c +#define EMIF_PHY_STATUS_17 0x0180 +#define EMIF_PHY_STATUS_18 0x0184 +#define EMIF_PHY_STATUS_19 0x0188 +#define EMIF_PHY_STATUS_20 0x018c +#define EMIF_PHY_STATUS_21 0x0190 +#define EMIF_EXT_PHY_CTRL_1 0x0200 +#define EMIF_EXT_PHY_CTRL_1_SHDW 0x0204 +#define EMIF_EXT_PHY_CTRL_2 0x0208 +#define EMIF_EXT_PHY_CTRL_2_SHDW 0x020c +#define EMIF_EXT_PHY_CTRL_3 0x0210 +#define EMIF_EXT_PHY_CTRL_3_SHDW 0x0214 +#define EMIF_EXT_PHY_CTRL_4 0x0218 +#define EMIF_EXT_PHY_CTRL_4_SHDW 0x021c +#define EMIF_EXT_PHY_CTRL_5 0x0220 +#define EMIF_EXT_PHY_CTRL_5_SHDW 0x0224 +#define EMIF_EXT_PHY_CTRL_6 0x0228 +#define EMIF_EXT_PHY_CTRL_6_SHDW 0x022c +#define EMIF_EXT_PHY_CTRL_7 0x0230 +#define EMIF_EXT_PHY_CTRL_7_SHDW 0x0234 +#define EMIF_EXT_PHY_CTRL_8 0x0238 +#define EMIF_EXT_PHY_CTRL_8_SHDW 0x023c +#define EMIF_EXT_PHY_CTRL_9 0x0240 +#define EMIF_EXT_PHY_CTRL_9_SHDW 0x0244 +#define EMIF_EXT_PHY_CTRL_10 0x0248 +#define EMIF_EXT_PHY_CTRL_10_SHDW 0x024c +#define EMIF_EXT_PHY_CTRL_11 0x0250 +#define EMIF_EXT_PHY_CTRL_11_SHDW 0x0254 +#define EMIF_EXT_PHY_CTRL_12 0x0258 +#define EMIF_EXT_PHY_CTRL_12_SHDW 0x025c +#define EMIF_EXT_PHY_CTRL_13 0x0260 +#define EMIF_EXT_PHY_CTRL_13_SHDW 0x0264 +#define EMIF_EXT_PHY_CTRL_14 0x0268 +#define EMIF_EXT_PHY_CTRL_14_SHDW 0x026c +#define EMIF_EXT_PHY_CTRL_15 0x0270 +#define EMIF_EXT_PHY_CTRL_15_SHDW 0x0274 +#define EMIF_EXT_PHY_CTRL_16 0x0278 +#define EMIF_EXT_PHY_CTRL_16_SHDW 0x027c +#define EMIF_EXT_PHY_CTRL_17 0x0280 +#define EMIF_EXT_PHY_CTRL_17_SHDW 0x0284 +#define EMIF_EXT_PHY_CTRL_18 0x0288 +#define EMIF_EXT_PHY_CTRL_18_SHDW 0x028c +#define EMIF_EXT_PHY_CTRL_19 0x0290 +#define EMIF_EXT_PHY_CTRL_19_SHDW 0x0294 +#define EMIF_EXT_PHY_CTRL_20 0x0298 +#define EMIF_EXT_PHY_CTRL_20_SHDW 0x029c +#define EMIF_EXT_PHY_CTRL_21 0x02a0 +#define EMIF_EXT_PHY_CTRL_21_SHDW 0x02a4 +#define EMIF_EXT_PHY_CTRL_22 0x02a8 +#define EMIF_EXT_PHY_CTRL_22_SHDW 0x02ac +#define EMIF_EXT_PHY_CTRL_23 0x02b0 +#define EMIF_EXT_PHY_CTRL_23_SHDW 0x02b4 +#define EMIF_EXT_PHY_CTRL_24 0x02b8 +#define EMIF_EXT_PHY_CTRL_24_SHDW 0x02bc +#define EMIF_EXT_PHY_CTRL_25 0x02c0 +#define EMIF_EXT_PHY_CTRL_25_SHDW 0x02c4 +#define EMIF_EXT_PHY_CTRL_26 0x02c8 +#define EMIF_EXT_PHY_CTRL_26_SHDW 0x02cc +#define EMIF_EXT_PHY_CTRL_27 0x02d0 +#define EMIF_EXT_PHY_CTRL_27_SHDW 0x02d4 +#define EMIF_EXT_PHY_CTRL_28 0x02d8 +#define EMIF_EXT_PHY_CTRL_28_SHDW 0x02dc +#define EMIF_EXT_PHY_CTRL_29 0x02e0 +#define EMIF_EXT_PHY_CTRL_29_SHDW 0x02e4 +#define EMIF_EXT_PHY_CTRL_30 0x02e8 +#define EMIF_EXT_PHY_CTRL_30_SHDW 0x02ec + +/* Registers shifts and masks */ + +/* EMIF_MODULE_ID_AND_REVISION */ +#define SCHEME_SHIFT 30 +#define SCHEME_MASK (0x3 << 30) +#define MODULE_ID_SHIFT 16 +#define MODULE_ID_MASK (0xfff << 16) +#define RTL_VERSION_SHIFT 11 +#define RTL_VERSION_MASK (0x1f << 11) +#define MAJOR_REVISION_SHIFT 8 +#define MAJOR_REVISION_MASK (0x7 << 8) +#define MINOR_REVISION_SHIFT 0 +#define MINOR_REVISION_MASK (0x3f << 0) + +/* STATUS */ +#define BE_SHIFT 31 +#define BE_MASK (1 << 31) +#define DUAL_CLK_MODE_SHIFT 30 +#define DUAL_CLK_MODE_MASK (1 << 30) +#define FAST_INIT_SHIFT 29 +#define FAST_INIT_MASK (1 << 29) +#define RDLVLGATETO_SHIFT 6 +#define RDLVLGATETO_MASK (1 << 6) +#define RDLVLTO_SHIFT 5 +#define RDLVLTO_MASK (1 << 5) +#define WRLVLTO_SHIFT 4 +#define WRLVLTO_MASK (1 << 4) +#define PHY_DLL_READY_SHIFT 2 +#define PHY_DLL_READY_MASK (1 << 2) + +/* SDRAM_CONFIG */ +#define SDRAM_TYPE_SHIFT 29 +#define SDRAM_TYPE_MASK (0x7 << 29) +#define IBANK_POS_SHIFT 27 +#define IBANK_POS_MASK (0x3 << 27) +#define DDR_TERM_SHIFT 24 +#define DDR_TERM_MASK (0x7 << 24) +#define DDR2_DDQS_SHIFT 23 +#define DDR2_DDQS_MASK (1 << 23) +#define DYN_ODT_SHIFT 21 +#define DYN_ODT_MASK (0x3 << 21) +#define DDR_DISABLE_DLL_SHIFT 20 +#define DDR_DISABLE_DLL_MASK (1 << 20) +#define SDRAM_DRIVE_SHIFT 18 +#define SDRAM_DRIVE_MASK (0x3 << 18) +#define CWL_SHIFT 16 +#define CWL_MASK (0x3 << 16) +#define NARROW_MODE_SHIFT 14 +#define NARROW_MODE_MASK (0x3 << 14) +#define CL_SHIFT 10 +#define CL_MASK (0xf << 10) +#define ROWSIZE_SHIFT 7 +#define ROWSIZE_MASK (0x7 << 7) +#define IBANK_SHIFT 4 +#define IBANK_MASK (0x7 << 4) +#define EBANK_SHIFT 3 +#define EBANK_MASK (1 << 3) +#define PAGESIZE_SHIFT 0 +#define PAGESIZE_MASK (0x7 << 0) + +/* SDRAM_CONFIG_2 */ +#define CS1NVMEN_SHIFT 30 +#define CS1NVMEN_MASK (1 << 30) +#define EBANK_POS_SHIFT 27 +#define EBANK_POS_MASK (1 << 27) +#define RDBNUM_SHIFT 4 +#define RDBNUM_MASK (0x3 << 4) +#define RDBSIZE_SHIFT 0 +#define RDBSIZE_MASK (0x7 << 0) + +/* SDRAM_REFRESH_CONTROL */ +#define INITREF_DIS_SHIFT 31 +#define INITREF_DIS_MASK (1 << 31) +#define SRT_SHIFT 29 +#define SRT_MASK (1 << 29) +#define ASR_SHIFT 28 +#define ASR_MASK (1 << 28) +#define PASR_SHIFT 24 +#define PASR_MASK (0x7 << 24) +#define REFRESH_RATE_SHIFT 0 +#define REFRESH_RATE_MASK (0xffff << 0) + +/* SDRAM_TIMING_1 */ +#define T_RTW_SHIFT 29 +#define T_RTW_MASK (0x7 << 29) +#define T_RP_SHIFT 25 +#define T_RP_MASK (0xf << 25) +#define T_RCD_SHIFT 21 +#define T_RCD_MASK (0xf << 21) +#define T_WR_SHIFT 17 +#define T_WR_MASK (0xf << 17) +#define T_RAS_SHIFT 12 +#define T_RAS_MASK (0x1f << 12) +#define T_RC_SHIFT 6 +#define T_RC_MASK (0x3f << 6) +#define T_RRD_SHIFT 3 +#define T_RRD_MASK (0x7 << 3) +#define T_WTR_SHIFT 0 +#define T_WTR_MASK (0x7 << 0) + +/* SDRAM_TIMING_2 */ +#define T_XP_SHIFT 28 +#define T_XP_MASK (0x7 << 28) +#define T_ODT_SHIFT 25 +#define T_ODT_MASK (0x7 << 25) +#define T_XSNR_SHIFT 16 +#define T_XSNR_MASK (0x1ff << 16) +#define T_XSRD_SHIFT 6 +#define T_XSRD_MASK (0x3ff << 6) +#define T_RTP_SHIFT 3 +#define T_RTP_MASK (0x7 << 3) +#define T_CKE_SHIFT 0 +#define T_CKE_MASK (0x7 << 0) + +/* SDRAM_TIMING_3 */ +#define T_PDLL_UL_SHIFT 28 +#define T_PDLL_UL_MASK (0xf << 28) +#define T_CSTA_SHIFT 24 +#define T_CSTA_MASK (0xf << 24) +#define T_CKESR_SHIFT 21 +#define T_CKESR_MASK (0x7 << 21) +#define ZQ_ZQCS_SHIFT 15 +#define ZQ_ZQCS_MASK (0x3f << 15) +#define T_TDQSCKMAX_SHIFT 13 +#define T_TDQSCKMAX_MASK (0x3 << 13) +#define T_RFC_SHIFT 4 +#define T_RFC_MASK (0x1ff << 4) +#define T_RAS_MAX_SHIFT 0 +#define T_RAS_MAX_MASK (0xf << 0) + +/* POWER_MANAGEMENT_CONTROL */ +#define PD_TIM_SHIFT 12 +#define PD_TIM_MASK (0xf << 12) +#define DPD_EN_SHIFT 11 +#define DPD_EN_MASK (1 << 11) +#define LP_MODE_SHIFT 8 +#define LP_MODE_MASK (0x7 << 8) +#define SR_TIM_SHIFT 4 +#define SR_TIM_MASK (0xf << 4) +#define CS_TIM_SHIFT 0 +#define CS_TIM_MASK (0xf << 0) + +/* LPDDR2_MODE_REG_DATA */ +#define VALUE_0_SHIFT 0 +#define VALUE_0_MASK (0x7f << 0) + +/* LPDDR2_MODE_REG_CONFIG */ +#define CS_SHIFT 31 +#define CS_MASK (1 << 31) +#define REFRESH_EN_SHIFT 30 +#define REFRESH_EN_MASK (1 << 30) +#define ADDRESS_SHIFT 0 +#define ADDRESS_MASK (0xff << 0) + +/* OCP_CONFIG */ +#define SYS_THRESH_MAX_SHIFT 24 +#define SYS_THRESH_MAX_MASK (0xf << 24) +#define MPU_THRESH_MAX_SHIFT 20 +#define MPU_THRESH_MAX_MASK (0xf << 20) +#define LL_THRESH_MAX_SHIFT 16 +#define LL_THRESH_MAX_MASK (0xf << 16) + +/* PERFORMANCE_COUNTER_1 */ +#define COUNTER1_SHIFT 0 +#define COUNTER1_MASK (0xffffffff << 0) + +/* PERFORMANCE_COUNTER_2 */ +#define COUNTER2_SHIFT 0 +#define COUNTER2_MASK (0xffffffff << 0) + +/* PERFORMANCE_COUNTER_CONFIG */ +#define CNTR2_MCONNID_EN_SHIFT 31 +#define CNTR2_MCONNID_EN_MASK (1 << 31) +#define CNTR2_REGION_EN_SHIFT 30 +#define CNTR2_REGION_EN_MASK (1 << 30) +#define CNTR2_CFG_SHIFT 16 +#define CNTR2_CFG_MASK (0xf << 16) +#define CNTR1_MCONNID_EN_SHIFT 15 +#define CNTR1_MCONNID_EN_MASK (1 << 15) +#define CNTR1_REGION_EN_SHIFT 14 +#define CNTR1_REGION_EN_MASK (1 << 14) +#define CNTR1_CFG_SHIFT 0 +#define CNTR1_CFG_MASK (0xf << 0) + +/* PERFORMANCE_COUNTER_MASTER_REGION_SELECT */ +#define MCONNID2_SHIFT 24 +#define MCONNID2_MASK (0xff << 24) +#define REGION_SEL2_SHIFT 16 +#define REGION_SEL2_MASK (0x3 << 16) +#define MCONNID1_SHIFT 8 +#define MCONNID1_MASK (0xff << 8) +#define REGION_SEL1_SHIFT 0 +#define REGION_SEL1_MASK (0x3 << 0) + +/* PERFORMANCE_COUNTER_TIME */ +#define TOTAL_TIME_SHIFT 0 +#define TOTAL_TIME_MASK (0xffffffff << 0) + +/* DLL_CALIB_CTRL */ +#define ACK_WAIT_SHIFT 16 +#define ACK_WAIT_MASK (0xf << 16) +#define DLL_CALIB_INTERVAL_SHIFT 0 +#define DLL_CALIB_INTERVAL_MASK (0x1ff << 0) + +/* END_OF_INTERRUPT */ +#define EOI_SHIFT 0 +#define EOI_MASK (1 << 0) + +/* SYSTEM_OCP_INTERRUPT_RAW_STATUS */ +#define DNV_SYS_SHIFT 2 +#define DNV_SYS_MASK (1 << 2) +#define TA_SYS_SHIFT 1 +#define TA_SYS_MASK (1 << 1) +#define ERR_SYS_SHIFT 0 +#define ERR_SYS_MASK (1 << 0) + +/* LOW_LATENCY_OCP_INTERRUPT_RAW_STATUS */ +#define DNV_LL_SHIFT 2 +#define DNV_LL_MASK (1 << 2) +#define TA_LL_SHIFT 1 +#define TA_LL_MASK (1 << 1) +#define ERR_LL_SHIFT 0 +#define ERR_LL_MASK (1 << 0) + +/* SYSTEM_OCP_INTERRUPT_ENABLE_SET */ +#define EN_DNV_SYS_SHIFT 2 +#define EN_DNV_SYS_MASK (1 << 2) +#define EN_TA_SYS_SHIFT 1 +#define EN_TA_SYS_MASK (1 << 1) +#define EN_ERR_SYS_SHIFT 0 +#define EN_ERR_SYS_MASK (1 << 0) + +/* LOW_LATENCY_OCP_INTERRUPT_ENABLE_SET */ +#define EN_DNV_LL_SHIFT 2 +#define EN_DNV_LL_MASK (1 << 2) +#define EN_TA_LL_SHIFT 1 +#define EN_TA_LL_MASK (1 << 1) +#define EN_ERR_LL_SHIFT 0 +#define EN_ERR_LL_MASK (1 << 0) + +/* SDRAM_OUTPUT_IMPEDANCE_CALIBRATION_CONFIG */ +#define ZQ_CS1EN_SHIFT 31 +#define ZQ_CS1EN_MASK (1 << 31) +#define ZQ_CS0EN_SHIFT 30 +#define ZQ_CS0EN_MASK (1 << 30) +#define ZQ_DUALCALEN_SHIFT 29 +#define ZQ_DUALCALEN_MASK (1 << 29) +#define ZQ_SFEXITEN_SHIFT 28 +#define ZQ_SFEXITEN_MASK (1 << 28) +#define ZQ_ZQINIT_MULT_SHIFT 18 +#define ZQ_ZQINIT_MULT_MASK (0x3 << 18) +#define ZQ_ZQCL_MULT_SHIFT 16 +#define ZQ_ZQCL_MULT_MASK (0x3 << 16) +#define ZQ_REFINTERVAL_SHIFT 0 +#define ZQ_REFINTERVAL_MASK (0xffff << 0) + +/* TEMPERATURE_ALERT_CONFIG */ +#define TA_CS1EN_SHIFT 31 +#define TA_CS1EN_MASK (1 << 31) +#define TA_CS0EN_SHIFT 30 +#define TA_CS0EN_MASK (1 << 30) +#define TA_SFEXITEN_SHIFT 28 +#define TA_SFEXITEN_MASK (1 << 28) +#define TA_DEVWDT_SHIFT 26 +#define TA_DEVWDT_MASK (0x3 << 26) +#define TA_DEVCNT_SHIFT 24 +#define TA_DEVCNT_MASK (0x3 << 24) +#define TA_REFINTERVAL_SHIFT 0 +#define TA_REFINTERVAL_MASK (0x3fffff << 0) + +/* OCP_ERROR_LOG */ +#define MADDRSPACE_SHIFT 14 +#define MADDRSPACE_MASK (0x3 << 14) +#define MBURSTSEQ_SHIFT 11 +#define MBURSTSEQ_MASK (0x7 << 11) +#define MCMD_SHIFT 8 +#define MCMD_MASK (0x7 << 8) +#define MCONNID_SHIFT 0 +#define MCONNID_MASK (0xff << 0) + +/* DDR_PHY_CTRL_1 - EMIF4D */ +#define DLL_SLAVE_DLY_CTRL_SHIFT_4D 4 +#define DLL_SLAVE_DLY_CTRL_MASK_4D (0xFF << 4) +#define READ_LATENCY_SHIFT_4D 0 +#define READ_LATENCY_MASK_4D (0xf << 0) + +/* DDR_PHY_CTRL_1 - EMIF4D5 */ +#define DLL_HALF_DELAY_SHIFT_4D5 21 +#define DLL_HALF_DELAY_MASK_4D5 (1 << 21) +#define READ_LATENCY_SHIFT_4D5 0 +#define READ_LATENCY_MASK_4D5 (0x1f << 0) + +/* DDR_PHY_CTRL_1_SHDW */ +#define DDR_PHY_CTRL_1_SHDW_SHIFT 5 +#define DDR_PHY_CTRL_1_SHDW_MASK (0x7ffffff << 5) +#define READ_LATENCY_SHDW_SHIFT 0 +#define READ_LATENCY_SHDW_MASK (0x1f << 0) + +#define EMIF_SRAM_AM33_REG_LAYOUT 0x00000000 +#define EMIF_SRAM_AM43_REG_LAYOUT 0x00000001 + +#ifndef __ASSEMBLY__ +/* + * Structure containing shadow of important registers in EMIF + * The calculation function fills in this structure to be later used for + * initialisation and DVFS + */ +struct emif_regs { + u32 freq; + u32 ref_ctrl_shdw; + u32 ref_ctrl_shdw_derated; + u32 sdram_tim1_shdw; + u32 sdram_tim1_shdw_derated; + u32 sdram_tim2_shdw; + u32 sdram_tim3_shdw; + u32 sdram_tim3_shdw_derated; + u32 pwr_mgmt_ctrl_shdw; + union { + u32 read_idle_ctrl_shdw_normal; + u32 dll_calib_ctrl_shdw_normal; + }; + union { + u32 read_idle_ctrl_shdw_volt_ramp; + u32 dll_calib_ctrl_shdw_volt_ramp; + }; + + u32 phy_ctrl_1_shdw; + u32 ext_phy_ctrl_2_shdw; + u32 ext_phy_ctrl_3_shdw; + u32 ext_phy_ctrl_4_shdw; +}; + +struct ti_emif_pm_functions; + +extern unsigned int ti_emif_sram; +extern unsigned int ti_emif_sram_sz; +extern struct ti_emif_pm_data ti_emif_pm_sram_data; +extern struct emif_regs_amx3 ti_emif_regs_amx3; + +void ti_emif_save_context(void); +void ti_emif_restore_context(void); +void ti_emif_enter_sr(void); +void ti_emif_exit_sr(void); +void ti_emif_abort_sr(void); + +#endif /* __ASSEMBLY__ */ +#endif /* __EMIF_H */ diff --git a/drivers/memory/fsl-corenet-cf.c b/drivers/memory/fsl-corenet-cf.c new file mode 100644 index 000000000..2fbf8d09a --- /dev/null +++ b/drivers/memory/fsl-corenet-cf.c @@ -0,0 +1,281 @@ +/* + * CoreNet Coherency Fabric error reporting + * + * Copyright 2014 Freescale Semiconductor Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> + +enum ccf_version { + CCF1, + CCF2, +}; + +struct ccf_info { + enum ccf_version version; + int err_reg_offs; + bool has_brr; +}; + +static const struct ccf_info ccf1_info = { + .version = CCF1, + .err_reg_offs = 0xa00, + .has_brr = false, +}; + +static const struct ccf_info ccf2_info = { + .version = CCF2, + .err_reg_offs = 0xe40, + .has_brr = true, +}; + +/* + * This register is present but not documented, with different values for + * IP_ID, on other chips with fsl,corenet2-cf such as t4240 and b4860. + */ +#define CCF_BRR 0xbf8 +#define CCF_BRR_IPID 0xffff0000 +#define CCF_BRR_IPID_T1040 0x09310000 + +static const struct of_device_id ccf_matches[] = { + { + .compatible = "fsl,corenet1-cf", + .data = &ccf1_info, + }, + { + .compatible = "fsl,corenet2-cf", + .data = &ccf2_info, + }, + {} +}; +MODULE_DEVICE_TABLE(of, ccf_matches); + +struct ccf_err_regs { + u32 errdet; /* 0x00 Error Detect Register */ + /* 0x04 Error Enable (ccf1)/Disable (ccf2) Register */ + u32 errdis; + /* 0x08 Error Interrupt Enable Register (ccf2 only) */ + u32 errinten; + u32 cecar; /* 0x0c Error Capture Attribute Register */ + u32 cecaddrh; /* 0x10 Error Capture Address High */ + u32 cecaddrl; /* 0x14 Error Capture Address Low */ + u32 cecar2; /* 0x18 Error Capture Attribute Register 2 */ +}; + +/* LAE/CV also valid for errdis and errinten */ +#define ERRDET_LAE (1 << 0) /* Local Access Error */ +#define ERRDET_CV (1 << 1) /* Coherency Violation */ +#define ERRDET_UTID (1 << 2) /* Unavailable Target ID (t1040) */ +#define ERRDET_MCST (1 << 3) /* Multicast Stash (t1040) */ +#define ERRDET_CTYPE_SHIFT 26 /* Capture Type (ccf2 only) */ +#define ERRDET_CTYPE_MASK (0x1f << ERRDET_CTYPE_SHIFT) +#define ERRDET_CAP (1 << 31) /* Capture Valid (ccf2 only) */ + +#define CECAR_VAL (1 << 0) /* Valid (ccf1 only) */ +#define CECAR_UVT (1 << 15) /* Unavailable target ID (ccf1) */ +#define CECAR_SRCID_SHIFT_CCF1 24 +#define CECAR_SRCID_MASK_CCF1 (0xff << CECAR_SRCID_SHIFT_CCF1) +#define CECAR_SRCID_SHIFT_CCF2 18 +#define CECAR_SRCID_MASK_CCF2 (0xff << CECAR_SRCID_SHIFT_CCF2) + +#define CECADDRH_ADDRH 0xff + +struct ccf_private { + const struct ccf_info *info; + struct device *dev; + void __iomem *regs; + struct ccf_err_regs __iomem *err_regs; + bool t1040; +}; + +static irqreturn_t ccf_irq(int irq, void *dev_id) +{ + struct ccf_private *ccf = dev_id; + static DEFINE_RATELIMIT_STATE(ratelimit, DEFAULT_RATELIMIT_INTERVAL, + DEFAULT_RATELIMIT_BURST); + u32 errdet, cecar, cecar2; + u64 addr; + u32 src_id; + bool uvt = false; + bool cap_valid = false; + + errdet = ioread32be(&ccf->err_regs->errdet); + cecar = ioread32be(&ccf->err_regs->cecar); + cecar2 = ioread32be(&ccf->err_regs->cecar2); + addr = ioread32be(&ccf->err_regs->cecaddrl); + addr |= ((u64)(ioread32be(&ccf->err_regs->cecaddrh) & + CECADDRH_ADDRH)) << 32; + + if (!__ratelimit(&ratelimit)) + goto out; + + switch (ccf->info->version) { + case CCF1: + if (cecar & CECAR_VAL) { + if (cecar & CECAR_UVT) + uvt = true; + + src_id = (cecar & CECAR_SRCID_MASK_CCF1) >> + CECAR_SRCID_SHIFT_CCF1; + cap_valid = true; + } + + break; + case CCF2: + if (errdet & ERRDET_CAP) { + src_id = (cecar & CECAR_SRCID_MASK_CCF2) >> + CECAR_SRCID_SHIFT_CCF2; + cap_valid = true; + } + + break; + } + + dev_crit(ccf->dev, "errdet 0x%08x cecar 0x%08x cecar2 0x%08x\n", + errdet, cecar, cecar2); + + if (errdet & ERRDET_LAE) { + if (uvt) + dev_crit(ccf->dev, "LAW Unavailable Target ID\n"); + else + dev_crit(ccf->dev, "Local Access Window Error\n"); + } + + if (errdet & ERRDET_CV) + dev_crit(ccf->dev, "Coherency Violation\n"); + + if (errdet & ERRDET_UTID) + dev_crit(ccf->dev, "Unavailable Target ID\n"); + + if (errdet & ERRDET_MCST) + dev_crit(ccf->dev, "Multicast Stash\n"); + + if (cap_valid) { + dev_crit(ccf->dev, "address 0x%09llx, src id 0x%x\n", + addr, src_id); + } + +out: + iowrite32be(errdet, &ccf->err_regs->errdet); + return errdet ? IRQ_HANDLED : IRQ_NONE; +} + +static int ccf_probe(struct platform_device *pdev) +{ + struct ccf_private *ccf; + struct resource *r; + const struct of_device_id *match; + u32 errinten; + int ret, irq; + + match = of_match_device(ccf_matches, &pdev->dev); + if (WARN_ON(!match)) + return -ENODEV; + + ccf = devm_kzalloc(&pdev->dev, sizeof(*ccf), GFP_KERNEL); + if (!ccf) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "%s: no mem resource\n", __func__); + return -ENXIO; + } + + ccf->regs = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(ccf->regs)) { + dev_err(&pdev->dev, "%s: can't map mem resource\n", __func__); + return PTR_ERR(ccf->regs); + } + + ccf->dev = &pdev->dev; + ccf->info = match->data; + ccf->err_regs = ccf->regs + ccf->info->err_reg_offs; + + if (ccf->info->has_brr) { + u32 brr = ioread32be(ccf->regs + CCF_BRR); + + if ((brr & CCF_BRR_IPID) == CCF_BRR_IPID_T1040) + ccf->t1040 = true; + } + + dev_set_drvdata(&pdev->dev, ccf); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, ccf_irq, 0, pdev->name, ccf); + if (ret) { + dev_err(&pdev->dev, "%s: can't request irq\n", __func__); + return ret; + } + + errinten = ERRDET_LAE | ERRDET_CV; + if (ccf->t1040) + errinten |= ERRDET_UTID | ERRDET_MCST; + + switch (ccf->info->version) { + case CCF1: + /* On CCF1 this register enables rather than disables. */ + iowrite32be(errinten, &ccf->err_regs->errdis); + break; + + case CCF2: + iowrite32be(0, &ccf->err_regs->errdis); + iowrite32be(errinten, &ccf->err_regs->errinten); + break; + } + + return 0; +} + +static int ccf_remove(struct platform_device *pdev) +{ + struct ccf_private *ccf = dev_get_drvdata(&pdev->dev); + + switch (ccf->info->version) { + case CCF1: + iowrite32be(0, &ccf->err_regs->errdis); + break; + + case CCF2: + /* + * We clear errdis on ccf1 because that's the only way to + * disable interrupts, but on ccf2 there's no need to disable + * detection. + */ + iowrite32be(0, &ccf->err_regs->errinten); + break; + } + + return 0; +} + +static struct platform_driver ccf_driver = { + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ccf_matches, + }, + .probe = ccf_probe, + .remove = ccf_remove, +}; + +module_platform_driver(ccf_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Freescale CoreNet Coherency Fabric error reporting"); diff --git a/drivers/memory/fsl_ifc.c b/drivers/memory/fsl_ifc.c new file mode 100644 index 000000000..9c0e70b04 --- /dev/null +++ b/drivers/memory/fsl_ifc.c @@ -0,0 +1,337 @@ +/* + * Copyright 2011 Freescale Semiconductor, Inc + * + * Freescale Integrated Flash Controller + * + * Author: Dipen Dudhat <Dipen.Dudhat@freescale.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/compiler.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/fsl_ifc.h> +#include <linux/irqdomain.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +struct fsl_ifc_ctrl *fsl_ifc_ctrl_dev; +EXPORT_SYMBOL(fsl_ifc_ctrl_dev); + +/* + * convert_ifc_address - convert the base address + * @addr_base: base address of the memory bank + */ +unsigned int convert_ifc_address(phys_addr_t addr_base) +{ + return addr_base & CSPR_BA; +} +EXPORT_SYMBOL(convert_ifc_address); + +/* + * fsl_ifc_find - find IFC bank + * @addr_base: base address of the memory bank + * + * This function walks IFC banks comparing "Base address" field of the CSPR + * registers with the supplied addr_base argument. When bases match this + * function returns bank number (starting with 0), otherwise it returns + * appropriate errno value. + */ +int fsl_ifc_find(phys_addr_t addr_base) +{ + int i = 0; + + if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->gregs) + return -ENODEV; + + for (i = 0; i < fsl_ifc_ctrl_dev->banks; i++) { + u32 cspr = ifc_in32(&fsl_ifc_ctrl_dev->gregs->cspr_cs[i].cspr); + if (cspr & CSPR_V && (cspr & CSPR_BA) == + convert_ifc_address(addr_base)) + return i; + } + + return -ENOENT; +} +EXPORT_SYMBOL(fsl_ifc_find); + +static int fsl_ifc_ctrl_init(struct fsl_ifc_ctrl *ctrl) +{ + struct fsl_ifc_global __iomem *ifc = ctrl->gregs; + + /* + * Clear all the common status and event registers + */ + if (ifc_in32(&ifc->cm_evter_stat) & IFC_CM_EVTER_STAT_CSER) + ifc_out32(IFC_CM_EVTER_STAT_CSER, &ifc->cm_evter_stat); + + /* enable all error and events */ + ifc_out32(IFC_CM_EVTER_EN_CSEREN, &ifc->cm_evter_en); + + /* enable all error and event interrupts */ + ifc_out32(IFC_CM_EVTER_INTR_EN_CSERIREN, &ifc->cm_evter_intr_en); + ifc_out32(0x0, &ifc->cm_erattr0); + ifc_out32(0x0, &ifc->cm_erattr1); + + return 0; +} + +static int fsl_ifc_ctrl_remove(struct platform_device *dev) +{ + struct fsl_ifc_ctrl *ctrl = dev_get_drvdata(&dev->dev); + + free_irq(ctrl->nand_irq, ctrl); + free_irq(ctrl->irq, ctrl); + + irq_dispose_mapping(ctrl->nand_irq); + irq_dispose_mapping(ctrl->irq); + + iounmap(ctrl->gregs); + + dev_set_drvdata(&dev->dev, NULL); + + return 0; +} + +/* + * NAND events are split between an operational interrupt which only + * receives OPC, and an error interrupt that receives everything else, + * including non-NAND errors. Whichever interrupt gets to it first + * records the status and wakes the wait queue. + */ +static DEFINE_SPINLOCK(nand_irq_lock); + +static u32 check_nand_stat(struct fsl_ifc_ctrl *ctrl) +{ + struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + unsigned long flags; + u32 stat; + + spin_lock_irqsave(&nand_irq_lock, flags); + + stat = ifc_in32(&ifc->ifc_nand.nand_evter_stat); + if (stat) { + ifc_out32(stat, &ifc->ifc_nand.nand_evter_stat); + ctrl->nand_stat = stat; + wake_up(&ctrl->nand_wait); + } + + spin_unlock_irqrestore(&nand_irq_lock, flags); + + return stat; +} + +static irqreturn_t fsl_ifc_nand_irq(int irqno, void *data) +{ + struct fsl_ifc_ctrl *ctrl = data; + + if (check_nand_stat(ctrl)) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +/* + * NOTE: This interrupt is used to report ifc events of various kinds, + * such as transaction errors on the chipselects. + */ +static irqreturn_t fsl_ifc_ctrl_irq(int irqno, void *data) +{ + struct fsl_ifc_ctrl *ctrl = data; + struct fsl_ifc_global __iomem *ifc = ctrl->gregs; + u32 err_axiid, err_srcid, status, cs_err, err_addr; + irqreturn_t ret = IRQ_NONE; + + /* read for chip select error */ + cs_err = ifc_in32(&ifc->cm_evter_stat); + if (cs_err) { + dev_err(ctrl->dev, "transaction sent to IFC is not mapped to" + "any memory bank 0x%08X\n", cs_err); + /* clear the chip select error */ + ifc_out32(IFC_CM_EVTER_STAT_CSER, &ifc->cm_evter_stat); + + /* read error attribute registers print the error information */ + status = ifc_in32(&ifc->cm_erattr0); + err_addr = ifc_in32(&ifc->cm_erattr1); + + if (status & IFC_CM_ERATTR0_ERTYP_READ) + dev_err(ctrl->dev, "Read transaction error" + "CM_ERATTR0 0x%08X\n", status); + else + dev_err(ctrl->dev, "Write transaction error" + "CM_ERATTR0 0x%08X\n", status); + + err_axiid = (status & IFC_CM_ERATTR0_ERAID) >> + IFC_CM_ERATTR0_ERAID_SHIFT; + dev_err(ctrl->dev, "AXI ID of the error" + "transaction 0x%08X\n", err_axiid); + + err_srcid = (status & IFC_CM_ERATTR0_ESRCID) >> + IFC_CM_ERATTR0_ESRCID_SHIFT; + dev_err(ctrl->dev, "SRC ID of the error" + "transaction 0x%08X\n", err_srcid); + + dev_err(ctrl->dev, "Transaction Address corresponding to error" + "ERADDR 0x%08X\n", err_addr); + + ret = IRQ_HANDLED; + } + + if (check_nand_stat(ctrl)) + ret = IRQ_HANDLED; + + return ret; +} + +/* + * fsl_ifc_ctrl_probe + * + * called by device layer when it finds a device matching + * one our driver can handled. This code allocates all of + * the resources needed for the controller only. The + * resources for the NAND banks themselves are allocated + * in the chip probe function. +*/ +static int fsl_ifc_ctrl_probe(struct platform_device *dev) +{ + int ret = 0; + int version, banks; + void __iomem *addr; + + dev_info(&dev->dev, "Freescale Integrated Flash Controller\n"); + + fsl_ifc_ctrl_dev = devm_kzalloc(&dev->dev, sizeof(*fsl_ifc_ctrl_dev), + GFP_KERNEL); + if (!fsl_ifc_ctrl_dev) + return -ENOMEM; + + dev_set_drvdata(&dev->dev, fsl_ifc_ctrl_dev); + + /* IOMAP the entire IFC region */ + fsl_ifc_ctrl_dev->gregs = of_iomap(dev->dev.of_node, 0); + if (!fsl_ifc_ctrl_dev->gregs) { + dev_err(&dev->dev, "failed to get memory region\n"); + return -ENODEV; + } + + if (of_property_read_bool(dev->dev.of_node, "little-endian")) { + fsl_ifc_ctrl_dev->little_endian = true; + dev_dbg(&dev->dev, "IFC REGISTERS are LITTLE endian\n"); + } else { + fsl_ifc_ctrl_dev->little_endian = false; + dev_dbg(&dev->dev, "IFC REGISTERS are BIG endian\n"); + } + + version = ifc_in32(&fsl_ifc_ctrl_dev->gregs->ifc_rev) & + FSL_IFC_VERSION_MASK; + + banks = (version == FSL_IFC_VERSION_1_0_0) ? 4 : 8; + dev_info(&dev->dev, "IFC version %d.%d, %d banks\n", + version >> 24, (version >> 16) & 0xf, banks); + + fsl_ifc_ctrl_dev->version = version; + fsl_ifc_ctrl_dev->banks = banks; + + addr = fsl_ifc_ctrl_dev->gregs; + if (version >= FSL_IFC_VERSION_2_0_0) + addr += PGOFFSET_64K; + else + addr += PGOFFSET_4K; + fsl_ifc_ctrl_dev->rregs = addr; + + /* get the Controller level irq */ + fsl_ifc_ctrl_dev->irq = irq_of_parse_and_map(dev->dev.of_node, 0); + if (fsl_ifc_ctrl_dev->irq == 0) { + dev_err(&dev->dev, "failed to get irq resource " + "for IFC\n"); + ret = -ENODEV; + goto err; + } + + /* get the nand machine irq */ + fsl_ifc_ctrl_dev->nand_irq = + irq_of_parse_and_map(dev->dev.of_node, 1); + + fsl_ifc_ctrl_dev->dev = &dev->dev; + + ret = fsl_ifc_ctrl_init(fsl_ifc_ctrl_dev); + if (ret < 0) + goto err_unmap_nandirq; + + init_waitqueue_head(&fsl_ifc_ctrl_dev->nand_wait); + + ret = request_irq(fsl_ifc_ctrl_dev->irq, fsl_ifc_ctrl_irq, IRQF_SHARED, + "fsl-ifc", fsl_ifc_ctrl_dev); + if (ret != 0) { + dev_err(&dev->dev, "failed to install irq (%d)\n", + fsl_ifc_ctrl_dev->irq); + goto err_unmap_nandirq; + } + + if (fsl_ifc_ctrl_dev->nand_irq) { + ret = request_irq(fsl_ifc_ctrl_dev->nand_irq, fsl_ifc_nand_irq, + 0, "fsl-ifc-nand", fsl_ifc_ctrl_dev); + if (ret != 0) { + dev_err(&dev->dev, "failed to install irq (%d)\n", + fsl_ifc_ctrl_dev->nand_irq); + goto err_free_irq; + } + } + + return 0; + +err_free_irq: + free_irq(fsl_ifc_ctrl_dev->irq, fsl_ifc_ctrl_dev); +err_unmap_nandirq: + irq_dispose_mapping(fsl_ifc_ctrl_dev->nand_irq); + irq_dispose_mapping(fsl_ifc_ctrl_dev->irq); +err: + iounmap(fsl_ifc_ctrl_dev->gregs); + return ret; +} + +static const struct of_device_id fsl_ifc_match[] = { + { + .compatible = "fsl,ifc", + }, + {}, +}; + +static struct platform_driver fsl_ifc_ctrl_driver = { + .driver = { + .name = "fsl-ifc", + .of_match_table = fsl_ifc_match, + }, + .probe = fsl_ifc_ctrl_probe, + .remove = fsl_ifc_ctrl_remove, +}; + +static int __init fsl_ifc_init(void) +{ + return platform_driver_register(&fsl_ifc_ctrl_driver); +} +subsys_initcall(fsl_ifc_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor"); +MODULE_DESCRIPTION("Freescale Integrated Flash Controller driver"); diff --git a/drivers/memory/jz4780-nemc.c b/drivers/memory/jz4780-nemc.c new file mode 100644 index 000000000..bcf06adef --- /dev/null +++ b/drivers/memory/jz4780-nemc.c @@ -0,0 +1,391 @@ +/* + * JZ4780 NAND/external memory controller (NEMC) + * + * Copyright (c) 2015 Imagination Technologies + * Author: Alex Smith <alex@alex-smith.me.uk> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/init.h> +#include <linux/math64.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include <linux/jz4780-nemc.h> + +#define NEMC_SMCRn(n) (0x14 + (((n) - 1) * 4)) +#define NEMC_NFCSR 0x50 + +#define NEMC_SMCR_SMT BIT(0) +#define NEMC_SMCR_BW_SHIFT 6 +#define NEMC_SMCR_BW_MASK (0x3 << NEMC_SMCR_BW_SHIFT) +#define NEMC_SMCR_BW_8 (0 << 6) +#define NEMC_SMCR_TAS_SHIFT 8 +#define NEMC_SMCR_TAS_MASK (0xf << NEMC_SMCR_TAS_SHIFT) +#define NEMC_SMCR_TAH_SHIFT 12 +#define NEMC_SMCR_TAH_MASK (0xf << NEMC_SMCR_TAH_SHIFT) +#define NEMC_SMCR_TBP_SHIFT 16 +#define NEMC_SMCR_TBP_MASK (0xf << NEMC_SMCR_TBP_SHIFT) +#define NEMC_SMCR_TAW_SHIFT 20 +#define NEMC_SMCR_TAW_MASK (0xf << NEMC_SMCR_TAW_SHIFT) +#define NEMC_SMCR_TSTRV_SHIFT 24 +#define NEMC_SMCR_TSTRV_MASK (0x3f << NEMC_SMCR_TSTRV_SHIFT) + +#define NEMC_NFCSR_NFEn(n) BIT(((n) - 1) << 1) +#define NEMC_NFCSR_NFCEn(n) BIT((((n) - 1) << 1) + 1) +#define NEMC_NFCSR_TNFEn(n) BIT(16 + (n) - 1) + +struct jz4780_nemc { + spinlock_t lock; + struct device *dev; + void __iomem *base; + struct clk *clk; + uint32_t clk_period; + unsigned long banks_present; +}; + +/** + * jz4780_nemc_num_banks() - count the number of banks referenced by a device + * @dev: device to count banks for, must be a child of the NEMC. + * + * Return: The number of unique NEMC banks referred to by the specified NEMC + * child device. Unique here means that a device that references the same bank + * multiple times in the its "reg" property will only count once. + */ +unsigned int jz4780_nemc_num_banks(struct device *dev) +{ + const __be32 *prop; + unsigned int bank, count = 0; + unsigned long referenced = 0; + int i = 0; + + while ((prop = of_get_address(dev->of_node, i++, NULL, NULL))) { + bank = of_read_number(prop, 1); + if (!(referenced & BIT(bank))) { + referenced |= BIT(bank); + count++; + } + } + + return count; +} +EXPORT_SYMBOL(jz4780_nemc_num_banks); + +/** + * jz4780_nemc_set_type() - set the type of device connected to a bank + * @dev: child device of the NEMC. + * @bank: bank number to configure. + * @type: type of device connected to the bank. + */ +void jz4780_nemc_set_type(struct device *dev, unsigned int bank, + enum jz4780_nemc_bank_type type) +{ + struct jz4780_nemc *nemc = dev_get_drvdata(dev->parent); + uint32_t nfcsr; + + nfcsr = readl(nemc->base + NEMC_NFCSR); + + /* TODO: Support toggle NAND devices. */ + switch (type) { + case JZ4780_NEMC_BANK_SRAM: + nfcsr &= ~(NEMC_NFCSR_TNFEn(bank) | NEMC_NFCSR_NFEn(bank)); + break; + case JZ4780_NEMC_BANK_NAND: + nfcsr &= ~NEMC_NFCSR_TNFEn(bank); + nfcsr |= NEMC_NFCSR_NFEn(bank); + break; + } + + writel(nfcsr, nemc->base + NEMC_NFCSR); +} +EXPORT_SYMBOL(jz4780_nemc_set_type); + +/** + * jz4780_nemc_assert() - (de-)assert a NAND device's chip enable pin + * @dev: child device of the NEMC. + * @bank: bank number of device. + * @assert: whether the chip enable pin should be asserted. + * + * (De-)asserts the chip enable pin for the NAND device connected to the + * specified bank. + */ +void jz4780_nemc_assert(struct device *dev, unsigned int bank, bool assert) +{ + struct jz4780_nemc *nemc = dev_get_drvdata(dev->parent); + uint32_t nfcsr; + + nfcsr = readl(nemc->base + NEMC_NFCSR); + + if (assert) + nfcsr |= NEMC_NFCSR_NFCEn(bank); + else + nfcsr &= ~NEMC_NFCSR_NFCEn(bank); + + writel(nfcsr, nemc->base + NEMC_NFCSR); +} +EXPORT_SYMBOL(jz4780_nemc_assert); + +static uint32_t jz4780_nemc_clk_period(struct jz4780_nemc *nemc) +{ + unsigned long rate; + + rate = clk_get_rate(nemc->clk); + if (!rate) + return 0; + + /* Return in picoseconds. */ + return div64_ul(1000000000000ull, rate); +} + +static uint32_t jz4780_nemc_ns_to_cycles(struct jz4780_nemc *nemc, uint32_t ns) +{ + return ((ns * 1000) + nemc->clk_period - 1) / nemc->clk_period; +} + +static bool jz4780_nemc_configure_bank(struct jz4780_nemc *nemc, + unsigned int bank, + struct device_node *node) +{ + uint32_t smcr, val, cycles; + + /* + * Conversion of tBP and tAW cycle counts to values supported by the + * hardware (round up to the next supported value). + */ + static const uint32_t convert_tBP_tAW[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + + /* 11 - 12 -> 12 cycles */ + 11, 11, + + /* 13 - 15 -> 15 cycles */ + 12, 12, 12, + + /* 16 - 20 -> 20 cycles */ + 13, 13, 13, 13, 13, + + /* 21 - 25 -> 25 cycles */ + 14, 14, 14, 14, 14, + + /* 26 - 31 -> 31 cycles */ + 15, 15, 15, 15, 15, 15 + }; + + smcr = readl(nemc->base + NEMC_SMCRn(bank)); + smcr &= ~NEMC_SMCR_SMT; + + if (!of_property_read_u32(node, "ingenic,nemc-bus-width", &val)) { + smcr &= ~NEMC_SMCR_BW_MASK; + switch (val) { + case 8: + smcr |= NEMC_SMCR_BW_8; + break; + default: + /* + * Earlier SoCs support a 16 bit bus width (the 4780 + * does not), until those are properly supported, error. + */ + dev_err(nemc->dev, "unsupported bus width: %u\n", val); + return false; + } + } + + if (of_property_read_u32(node, "ingenic,nemc-tAS", &val) == 0) { + smcr &= ~NEMC_SMCR_TAS_MASK; + cycles = jz4780_nemc_ns_to_cycles(nemc, val); + if (cycles > 15) { + dev_err(nemc->dev, "tAS %u is too high (%u cycles)\n", + val, cycles); + return false; + } + + smcr |= cycles << NEMC_SMCR_TAS_SHIFT; + } + + if (of_property_read_u32(node, "ingenic,nemc-tAH", &val) == 0) { + smcr &= ~NEMC_SMCR_TAH_MASK; + cycles = jz4780_nemc_ns_to_cycles(nemc, val); + if (cycles > 15) { + dev_err(nemc->dev, "tAH %u is too high (%u cycles)\n", + val, cycles); + return false; + } + + smcr |= cycles << NEMC_SMCR_TAH_SHIFT; + } + + if (of_property_read_u32(node, "ingenic,nemc-tBP", &val) == 0) { + smcr &= ~NEMC_SMCR_TBP_MASK; + cycles = jz4780_nemc_ns_to_cycles(nemc, val); + if (cycles > 31) { + dev_err(nemc->dev, "tBP %u is too high (%u cycles)\n", + val, cycles); + return false; + } + + smcr |= convert_tBP_tAW[cycles] << NEMC_SMCR_TBP_SHIFT; + } + + if (of_property_read_u32(node, "ingenic,nemc-tAW", &val) == 0) { + smcr &= ~NEMC_SMCR_TAW_MASK; + cycles = jz4780_nemc_ns_to_cycles(nemc, val); + if (cycles > 31) { + dev_err(nemc->dev, "tAW %u is too high (%u cycles)\n", + val, cycles); + return false; + } + + smcr |= convert_tBP_tAW[cycles] << NEMC_SMCR_TAW_SHIFT; + } + + if (of_property_read_u32(node, "ingenic,nemc-tSTRV", &val) == 0) { + smcr &= ~NEMC_SMCR_TSTRV_MASK; + cycles = jz4780_nemc_ns_to_cycles(nemc, val); + if (cycles > 63) { + dev_err(nemc->dev, "tSTRV %u is too high (%u cycles)\n", + val, cycles); + return false; + } + + smcr |= cycles << NEMC_SMCR_TSTRV_SHIFT; + } + + writel(smcr, nemc->base + NEMC_SMCRn(bank)); + return true; +} + +static int jz4780_nemc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct jz4780_nemc *nemc; + struct resource *res; + struct device_node *child; + const __be32 *prop; + unsigned int bank; + unsigned long referenced; + int i, ret; + + nemc = devm_kzalloc(dev, sizeof(*nemc), GFP_KERNEL); + if (!nemc) + return -ENOMEM; + + spin_lock_init(&nemc->lock); + nemc->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + nemc->base = devm_ioremap_resource(dev, res); + if (IS_ERR(nemc->base)) { + dev_err(dev, "failed to get I/O memory\n"); + return PTR_ERR(nemc->base); + } + + writel(0, nemc->base + NEMC_NFCSR); + + nemc->clk = devm_clk_get(dev, NULL); + if (IS_ERR(nemc->clk)) { + dev_err(dev, "failed to get clock\n"); + return PTR_ERR(nemc->clk); + } + + ret = clk_prepare_enable(nemc->clk); + if (ret) { + dev_err(dev, "failed to enable clock: %d\n", ret); + return ret; + } + + nemc->clk_period = jz4780_nemc_clk_period(nemc); + if (!nemc->clk_period) { + dev_err(dev, "failed to calculate clock period\n"); + clk_disable_unprepare(nemc->clk); + return -EINVAL; + } + + /* + * Iterate over child devices, check that they do not conflict with + * each other, and register child devices for them. If a child device + * has invalid properties, it is ignored and no platform device is + * registered for it. + */ + for_each_child_of_node(nemc->dev->of_node, child) { + referenced = 0; + i = 0; + while ((prop = of_get_address(child, i++, NULL, NULL))) { + bank = of_read_number(prop, 1); + if (bank < 1 || bank >= JZ4780_NEMC_NUM_BANKS) { + dev_err(nemc->dev, + "%pOF requests invalid bank %u\n", + child, bank); + + /* Will continue the outer loop below. */ + referenced = 0; + break; + } + + referenced |= BIT(bank); + } + + if (!referenced) { + dev_err(nemc->dev, "%pOF has no addresses\n", + child); + continue; + } else if (nemc->banks_present & referenced) { + dev_err(nemc->dev, "%pOF conflicts with another node\n", + child); + continue; + } + + /* Configure bank parameters. */ + for_each_set_bit(bank, &referenced, JZ4780_NEMC_NUM_BANKS) { + if (!jz4780_nemc_configure_bank(nemc, bank, child)) { + referenced = 0; + break; + } + } + + if (referenced) { + if (of_platform_device_create(child, NULL, nemc->dev)) + nemc->banks_present |= referenced; + } + } + + platform_set_drvdata(pdev, nemc); + dev_info(dev, "JZ4780 NEMC initialised\n"); + return 0; +} + +static int jz4780_nemc_remove(struct platform_device *pdev) +{ + struct jz4780_nemc *nemc = platform_get_drvdata(pdev); + + clk_disable_unprepare(nemc->clk); + return 0; +} + +static const struct of_device_id jz4780_nemc_dt_match[] = { + { .compatible = "ingenic,jz4780-nemc" }, + {}, +}; + +static struct platform_driver jz4780_nemc_driver = { + .probe = jz4780_nemc_probe, + .remove = jz4780_nemc_remove, + .driver = { + .name = "jz4780-nemc", + .of_match_table = of_match_ptr(jz4780_nemc_dt_match), + }, +}; + +static int __init jz4780_nemc_init(void) +{ + return platform_driver_register(&jz4780_nemc_driver); +} +subsys_initcall(jz4780_nemc_init); diff --git a/drivers/memory/mtk-smi.c b/drivers/memory/mtk-smi.c new file mode 100644 index 000000000..8f2d152a7 --- /dev/null +++ b/drivers/memory/mtk-smi.c @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2015-2016 MediaTek Inc. + * Author: Yong Wu <yong.wu@mediatek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <soc/mediatek/smi.h> +#include <dt-bindings/memory/mt2701-larb-port.h> + +/* mt8173 */ +#define SMI_LARB_MMU_EN 0xf00 + +/* mt2701 */ +#define REG_SMI_SECUR_CON_BASE 0x5c0 + +/* every register control 8 port, register offset 0x4 */ +#define REG_SMI_SECUR_CON_OFFSET(id) (((id) >> 3) << 2) +#define REG_SMI_SECUR_CON_ADDR(id) \ + (REG_SMI_SECUR_CON_BASE + REG_SMI_SECUR_CON_OFFSET(id)) + +/* + * every port have 4 bit to control, bit[port + 3] control virtual or physical, + * bit[port + 2 : port + 1] control the domain, bit[port] control the security + * or non-security. + */ +#define SMI_SECUR_CON_VAL_MSK(id) (~(0xf << (((id) & 0x7) << 2))) +#define SMI_SECUR_CON_VAL_VIRT(id) BIT((((id) & 0x7) << 2) + 3) +/* mt2701 domain should be set to 3 */ +#define SMI_SECUR_CON_VAL_DOMAIN(id) (0x3 << ((((id) & 0x7) << 2) + 1)) + +/* mt2712 */ +#define SMI_LARB_NONSEC_CON(id) (0x380 + ((id) * 4)) +#define F_MMU_EN BIT(0) + +struct mtk_smi_larb_gen { + bool need_larbid; + int port_in_larb[MTK_LARB_NR_MAX + 1]; + void (*config_port)(struct device *); +}; + +struct mtk_smi { + struct device *dev; + struct clk *clk_apb, *clk_smi; + struct clk *clk_async; /*only needed by mt2701*/ + void __iomem *smi_ao_base; +}; + +struct mtk_smi_larb { /* larb: local arbiter */ + struct mtk_smi smi; + void __iomem *base; + struct device *smi_common_dev; + const struct mtk_smi_larb_gen *larb_gen; + int larbid; + u32 *mmu; +}; + +enum mtk_smi_gen { + MTK_SMI_GEN1, + MTK_SMI_GEN2 +}; + +static int mtk_smi_enable(const struct mtk_smi *smi) +{ + int ret; + + ret = pm_runtime_get_sync(smi->dev); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(smi->clk_apb); + if (ret) + goto err_put_pm; + + ret = clk_prepare_enable(smi->clk_smi); + if (ret) + goto err_disable_apb; + + return 0; + +err_disable_apb: + clk_disable_unprepare(smi->clk_apb); +err_put_pm: + pm_runtime_put_sync(smi->dev); + return ret; +} + +static void mtk_smi_disable(const struct mtk_smi *smi) +{ + clk_disable_unprepare(smi->clk_smi); + clk_disable_unprepare(smi->clk_apb); + pm_runtime_put_sync(smi->dev); +} + +int mtk_smi_larb_get(struct device *larbdev) +{ + struct mtk_smi_larb *larb = dev_get_drvdata(larbdev); + const struct mtk_smi_larb_gen *larb_gen = larb->larb_gen; + struct mtk_smi *common = dev_get_drvdata(larb->smi_common_dev); + int ret; + + /* Enable the smi-common's power and clocks */ + ret = mtk_smi_enable(common); + if (ret) + return ret; + + /* Enable the larb's power and clocks */ + ret = mtk_smi_enable(&larb->smi); + if (ret) { + mtk_smi_disable(common); + return ret; + } + + /* Configure the iommu info for this larb */ + larb_gen->config_port(larbdev); + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_smi_larb_get); + +void mtk_smi_larb_put(struct device *larbdev) +{ + struct mtk_smi_larb *larb = dev_get_drvdata(larbdev); + struct mtk_smi *common = dev_get_drvdata(larb->smi_common_dev); + + /* + * Don't de-configure the iommu info for this larb since there may be + * several modules in this larb. + * The iommu info will be reset after power off. + */ + + mtk_smi_disable(&larb->smi); + mtk_smi_disable(common); +} +EXPORT_SYMBOL_GPL(mtk_smi_larb_put); + +static int +mtk_smi_larb_bind(struct device *dev, struct device *master, void *data) +{ + struct mtk_smi_larb *larb = dev_get_drvdata(dev); + struct mtk_smi_iommu *smi_iommu = data; + unsigned int i; + + if (larb->larb_gen->need_larbid) { + larb->mmu = &smi_iommu->larb_imu[larb->larbid].mmu; + return 0; + } + + /* + * If there is no larbid property, Loop to find the corresponding + * iommu information. + */ + for (i = 0; i < smi_iommu->larb_nr; i++) { + if (dev == smi_iommu->larb_imu[i].dev) { + /* The 'mmu' may be updated in iommu-attach/detach. */ + larb->mmu = &smi_iommu->larb_imu[i].mmu; + return 0; + } + } + return -ENODEV; +} + +static void mtk_smi_larb_config_port_mt2712(struct device *dev) +{ + struct mtk_smi_larb *larb = dev_get_drvdata(dev); + u32 reg; + int i; + + /* + * larb 8/9 is the bdpsys larb, the iommu_en is enabled defaultly. + * Don't need to set it again. + */ + if (larb->larbid == 8 || larb->larbid == 9) + return; + + for_each_set_bit(i, (unsigned long *)larb->mmu, 32) { + reg = readl_relaxed(larb->base + SMI_LARB_NONSEC_CON(i)); + reg |= F_MMU_EN; + writel(reg, larb->base + SMI_LARB_NONSEC_CON(i)); + } +} + +static void mtk_smi_larb_config_port_mt8173(struct device *dev) +{ + struct mtk_smi_larb *larb = dev_get_drvdata(dev); + + writel(*larb->mmu, larb->base + SMI_LARB_MMU_EN); +} + +static void mtk_smi_larb_config_port_gen1(struct device *dev) +{ + struct mtk_smi_larb *larb = dev_get_drvdata(dev); + const struct mtk_smi_larb_gen *larb_gen = larb->larb_gen; + struct mtk_smi *common = dev_get_drvdata(larb->smi_common_dev); + int i, m4u_port_id, larb_port_num; + u32 sec_con_val, reg_val; + + m4u_port_id = larb_gen->port_in_larb[larb->larbid]; + larb_port_num = larb_gen->port_in_larb[larb->larbid + 1] + - larb_gen->port_in_larb[larb->larbid]; + + for (i = 0; i < larb_port_num; i++, m4u_port_id++) { + if (*larb->mmu & BIT(i)) { + /* bit[port + 3] controls the virtual or physical */ + sec_con_val = SMI_SECUR_CON_VAL_VIRT(m4u_port_id); + } else { + /* do not need to enable m4u for this port */ + continue; + } + reg_val = readl(common->smi_ao_base + + REG_SMI_SECUR_CON_ADDR(m4u_port_id)); + reg_val &= SMI_SECUR_CON_VAL_MSK(m4u_port_id); + reg_val |= sec_con_val; + reg_val |= SMI_SECUR_CON_VAL_DOMAIN(m4u_port_id); + writel(reg_val, + common->smi_ao_base + + REG_SMI_SECUR_CON_ADDR(m4u_port_id)); + } +} + +static void +mtk_smi_larb_unbind(struct device *dev, struct device *master, void *data) +{ + /* Do nothing as the iommu is always enabled. */ +} + +static const struct component_ops mtk_smi_larb_component_ops = { + .bind = mtk_smi_larb_bind, + .unbind = mtk_smi_larb_unbind, +}; + +static const struct mtk_smi_larb_gen mtk_smi_larb_mt8173 = { + /* mt8173 do not need the port in larb */ + .config_port = mtk_smi_larb_config_port_mt8173, +}; + +static const struct mtk_smi_larb_gen mtk_smi_larb_mt2701 = { + .need_larbid = true, + .port_in_larb = { + LARB0_PORT_OFFSET, LARB1_PORT_OFFSET, + LARB2_PORT_OFFSET, LARB3_PORT_OFFSET + }, + .config_port = mtk_smi_larb_config_port_gen1, +}; + +static const struct mtk_smi_larb_gen mtk_smi_larb_mt2712 = { + .need_larbid = true, + .config_port = mtk_smi_larb_config_port_mt2712, +}; + +static const struct of_device_id mtk_smi_larb_of_ids[] = { + { + .compatible = "mediatek,mt8173-smi-larb", + .data = &mtk_smi_larb_mt8173 + }, + { + .compatible = "mediatek,mt2701-smi-larb", + .data = &mtk_smi_larb_mt2701 + }, + { + .compatible = "mediatek,mt2712-smi-larb", + .data = &mtk_smi_larb_mt2712 + }, + {} +}; + +static int mtk_smi_larb_probe(struct platform_device *pdev) +{ + struct mtk_smi_larb *larb; + struct resource *res; + struct device *dev = &pdev->dev; + struct device_node *smi_node; + struct platform_device *smi_pdev; + int err; + + larb = devm_kzalloc(dev, sizeof(*larb), GFP_KERNEL); + if (!larb) + return -ENOMEM; + + larb->larb_gen = of_device_get_match_data(dev); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + larb->base = devm_ioremap_resource(dev, res); + if (IS_ERR(larb->base)) + return PTR_ERR(larb->base); + + larb->smi.clk_apb = devm_clk_get(dev, "apb"); + if (IS_ERR(larb->smi.clk_apb)) + return PTR_ERR(larb->smi.clk_apb); + + larb->smi.clk_smi = devm_clk_get(dev, "smi"); + if (IS_ERR(larb->smi.clk_smi)) + return PTR_ERR(larb->smi.clk_smi); + larb->smi.dev = dev; + + if (larb->larb_gen->need_larbid) { + err = of_property_read_u32(dev->of_node, "mediatek,larb-id", + &larb->larbid); + if (err) { + dev_err(dev, "missing larbid property\n"); + return err; + } + } + + smi_node = of_parse_phandle(dev->of_node, "mediatek,smi", 0); + if (!smi_node) + return -EINVAL; + + smi_pdev = of_find_device_by_node(smi_node); + of_node_put(smi_node); + if (smi_pdev) { + if (!platform_get_drvdata(smi_pdev)) + return -EPROBE_DEFER; + larb->smi_common_dev = &smi_pdev->dev; + } else { + dev_err(dev, "Failed to get the smi_common device\n"); + return -EINVAL; + } + + pm_runtime_enable(dev); + platform_set_drvdata(pdev, larb); + return component_add(dev, &mtk_smi_larb_component_ops); +} + +static int mtk_smi_larb_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + component_del(&pdev->dev, &mtk_smi_larb_component_ops); + return 0; +} + +static struct platform_driver mtk_smi_larb_driver = { + .probe = mtk_smi_larb_probe, + .remove = mtk_smi_larb_remove, + .driver = { + .name = "mtk-smi-larb", + .of_match_table = mtk_smi_larb_of_ids, + } +}; + +static const struct of_device_id mtk_smi_common_of_ids[] = { + { + .compatible = "mediatek,mt8173-smi-common", + .data = (void *)MTK_SMI_GEN2 + }, + { + .compatible = "mediatek,mt2701-smi-common", + .data = (void *)MTK_SMI_GEN1 + }, + { + .compatible = "mediatek,mt2712-smi-common", + .data = (void *)MTK_SMI_GEN2 + }, + {} +}; + +static int mtk_smi_common_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_smi *common; + struct resource *res; + enum mtk_smi_gen smi_gen; + int ret; + + common = devm_kzalloc(dev, sizeof(*common), GFP_KERNEL); + if (!common) + return -ENOMEM; + common->dev = dev; + + common->clk_apb = devm_clk_get(dev, "apb"); + if (IS_ERR(common->clk_apb)) + return PTR_ERR(common->clk_apb); + + common->clk_smi = devm_clk_get(dev, "smi"); + if (IS_ERR(common->clk_smi)) + return PTR_ERR(common->clk_smi); + + /* + * for mtk smi gen 1, we need to get the ao(always on) base to config + * m4u port, and we need to enable the aync clock for transform the smi + * clock into emi clock domain, but for mtk smi gen2, there's no smi ao + * base. + */ + smi_gen = (enum mtk_smi_gen)of_device_get_match_data(dev); + if (smi_gen == MTK_SMI_GEN1) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + common->smi_ao_base = devm_ioremap_resource(dev, res); + if (IS_ERR(common->smi_ao_base)) + return PTR_ERR(common->smi_ao_base); + + common->clk_async = devm_clk_get(dev, "async"); + if (IS_ERR(common->clk_async)) + return PTR_ERR(common->clk_async); + + ret = clk_prepare_enable(common->clk_async); + if (ret) + return ret; + } + pm_runtime_enable(dev); + platform_set_drvdata(pdev, common); + return 0; +} + +static int mtk_smi_common_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; +} + +static struct platform_driver mtk_smi_common_driver = { + .probe = mtk_smi_common_probe, + .remove = mtk_smi_common_remove, + .driver = { + .name = "mtk-smi-common", + .of_match_table = mtk_smi_common_of_ids, + } +}; + +static int __init mtk_smi_init(void) +{ + int ret; + + ret = platform_driver_register(&mtk_smi_common_driver); + if (ret != 0) { + pr_err("Failed to register SMI driver\n"); + return ret; + } + + ret = platform_driver_register(&mtk_smi_larb_driver); + if (ret != 0) { + pr_err("Failed to register SMI-LARB driver\n"); + goto err_unreg_smi; + } + return ret; + +err_unreg_smi: + platform_driver_unregister(&mtk_smi_common_driver); + return ret; +} + +module_init(mtk_smi_init); diff --git a/drivers/memory/mvebu-devbus.c b/drivers/memory/mvebu-devbus.c new file mode 100644 index 000000000..981860879 --- /dev/null +++ b/drivers/memory/mvebu-devbus.c @@ -0,0 +1,361 @@ +/* + * Marvell EBU SoC Device Bus Controller + * (memory controller for NOR/NAND/SRAM/FPGA devices) + * + * Copyright (C) 2013-2014 Marvell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/mbus.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> + +/* Register definitions */ +#define ARMADA_DEV_WIDTH_SHIFT 30 +#define ARMADA_BADR_SKEW_SHIFT 28 +#define ARMADA_RD_HOLD_SHIFT 23 +#define ARMADA_ACC_NEXT_SHIFT 17 +#define ARMADA_RD_SETUP_SHIFT 12 +#define ARMADA_ACC_FIRST_SHIFT 6 + +#define ARMADA_SYNC_ENABLE_SHIFT 24 +#define ARMADA_WR_HIGH_SHIFT 16 +#define ARMADA_WR_LOW_SHIFT 8 + +#define ARMADA_READ_PARAM_OFFSET 0x0 +#define ARMADA_WRITE_PARAM_OFFSET 0x4 + +#define ORION_RESERVED (0x2 << 30) +#define ORION_BADR_SKEW_SHIFT 28 +#define ORION_WR_HIGH_EXT_BIT BIT(27) +#define ORION_WR_HIGH_EXT_MASK 0x8 +#define ORION_WR_LOW_EXT_BIT BIT(26) +#define ORION_WR_LOW_EXT_MASK 0x8 +#define ORION_ALE_WR_EXT_BIT BIT(25) +#define ORION_ALE_WR_EXT_MASK 0x8 +#define ORION_ACC_NEXT_EXT_BIT BIT(24) +#define ORION_ACC_NEXT_EXT_MASK 0x10 +#define ORION_ACC_FIRST_EXT_BIT BIT(23) +#define ORION_ACC_FIRST_EXT_MASK 0x10 +#define ORION_TURN_OFF_EXT_BIT BIT(22) +#define ORION_TURN_OFF_EXT_MASK 0x8 +#define ORION_DEV_WIDTH_SHIFT 20 +#define ORION_WR_HIGH_SHIFT 17 +#define ORION_WR_HIGH_MASK 0x7 +#define ORION_WR_LOW_SHIFT 14 +#define ORION_WR_LOW_MASK 0x7 +#define ORION_ALE_WR_SHIFT 11 +#define ORION_ALE_WR_MASK 0x7 +#define ORION_ACC_NEXT_SHIFT 7 +#define ORION_ACC_NEXT_MASK 0xF +#define ORION_ACC_FIRST_SHIFT 3 +#define ORION_ACC_FIRST_MASK 0xF +#define ORION_TURN_OFF_SHIFT 0 +#define ORION_TURN_OFF_MASK 0x7 + +struct devbus_read_params { + u32 bus_width; + u32 badr_skew; + u32 turn_off; + u32 acc_first; + u32 acc_next; + u32 rd_setup; + u32 rd_hold; +}; + +struct devbus_write_params { + u32 sync_enable; + u32 wr_high; + u32 wr_low; + u32 ale_wr; +}; + +struct devbus { + struct device *dev; + void __iomem *base; + unsigned long tick_ps; +}; + +static int get_timing_param_ps(struct devbus *devbus, + struct device_node *node, + const char *name, + u32 *ticks) +{ + u32 time_ps; + int err; + + err = of_property_read_u32(node, name, &time_ps); + if (err < 0) { + dev_err(devbus->dev, "%pOF has no '%s' property\n", + node, name); + return err; + } + + *ticks = (time_ps + devbus->tick_ps - 1) / devbus->tick_ps; + + dev_dbg(devbus->dev, "%s: %u ps -> 0x%x\n", + name, time_ps, *ticks); + return 0; +} + +static int devbus_get_timing_params(struct devbus *devbus, + struct device_node *node, + struct devbus_read_params *r, + struct devbus_write_params *w) +{ + int err; + + err = of_property_read_u32(node, "devbus,bus-width", &r->bus_width); + if (err < 0) { + dev_err(devbus->dev, + "%pOF has no 'devbus,bus-width' property\n", + node); + return err; + } + + /* + * The bus width is encoded into the register as 0 for 8 bits, + * and 1 for 16 bits, so we do the necessary conversion here. + */ + if (r->bus_width == 8) + r->bus_width = 0; + else if (r->bus_width == 16) + r->bus_width = 1; + else { + dev_err(devbus->dev, "invalid bus width %d\n", r->bus_width); + return -EINVAL; + } + + err = get_timing_param_ps(devbus, node, "devbus,badr-skew-ps", + &r->badr_skew); + if (err < 0) + return err; + + err = get_timing_param_ps(devbus, node, "devbus,turn-off-ps", + &r->turn_off); + if (err < 0) + return err; + + err = get_timing_param_ps(devbus, node, "devbus,acc-first-ps", + &r->acc_first); + if (err < 0) + return err; + + err = get_timing_param_ps(devbus, node, "devbus,acc-next-ps", + &r->acc_next); + if (err < 0) + return err; + + if (of_device_is_compatible(devbus->dev->of_node, "marvell,mvebu-devbus")) { + err = get_timing_param_ps(devbus, node, "devbus,rd-setup-ps", + &r->rd_setup); + if (err < 0) + return err; + + err = get_timing_param_ps(devbus, node, "devbus,rd-hold-ps", + &r->rd_hold); + if (err < 0) + return err; + + err = of_property_read_u32(node, "devbus,sync-enable", + &w->sync_enable); + if (err < 0) { + dev_err(devbus->dev, + "%pOF has no 'devbus,sync-enable' property\n", + node); + return err; + } + } + + err = get_timing_param_ps(devbus, node, "devbus,ale-wr-ps", + &w->ale_wr); + if (err < 0) + return err; + + err = get_timing_param_ps(devbus, node, "devbus,wr-low-ps", + &w->wr_low); + if (err < 0) + return err; + + err = get_timing_param_ps(devbus, node, "devbus,wr-high-ps", + &w->wr_high); + if (err < 0) + return err; + + return 0; +} + +static void devbus_orion_set_timing_params(struct devbus *devbus, + struct device_node *node, + struct devbus_read_params *r, + struct devbus_write_params *w) +{ + u32 value; + + /* + * The hardware designers found it would be a good idea to + * split most of the values in the register into two fields: + * one containing all the low-order bits, and another one + * containing just the high-order bit. For all of those + * fields, we have to split the value into these two parts. + */ + value = (r->turn_off & ORION_TURN_OFF_MASK) << ORION_TURN_OFF_SHIFT | + (r->acc_first & ORION_ACC_FIRST_MASK) << ORION_ACC_FIRST_SHIFT | + (r->acc_next & ORION_ACC_NEXT_MASK) << ORION_ACC_NEXT_SHIFT | + (w->ale_wr & ORION_ALE_WR_MASK) << ORION_ALE_WR_SHIFT | + (w->wr_low & ORION_WR_LOW_MASK) << ORION_WR_LOW_SHIFT | + (w->wr_high & ORION_WR_HIGH_MASK) << ORION_WR_HIGH_SHIFT | + r->bus_width << ORION_DEV_WIDTH_SHIFT | + ((r->turn_off & ORION_TURN_OFF_EXT_MASK) ? ORION_TURN_OFF_EXT_BIT : 0) | + ((r->acc_first & ORION_ACC_FIRST_EXT_MASK) ? ORION_ACC_FIRST_EXT_BIT : 0) | + ((r->acc_next & ORION_ACC_NEXT_EXT_MASK) ? ORION_ACC_NEXT_EXT_BIT : 0) | + ((w->ale_wr & ORION_ALE_WR_EXT_MASK) ? ORION_ALE_WR_EXT_BIT : 0) | + ((w->wr_low & ORION_WR_LOW_EXT_MASK) ? ORION_WR_LOW_EXT_BIT : 0) | + ((w->wr_high & ORION_WR_HIGH_EXT_MASK) ? ORION_WR_HIGH_EXT_BIT : 0) | + (r->badr_skew << ORION_BADR_SKEW_SHIFT) | + ORION_RESERVED; + + writel(value, devbus->base); +} + +static void devbus_armada_set_timing_params(struct devbus *devbus, + struct device_node *node, + struct devbus_read_params *r, + struct devbus_write_params *w) +{ + u32 value; + + /* Set read timings */ + value = r->bus_width << ARMADA_DEV_WIDTH_SHIFT | + r->badr_skew << ARMADA_BADR_SKEW_SHIFT | + r->rd_hold << ARMADA_RD_HOLD_SHIFT | + r->acc_next << ARMADA_ACC_NEXT_SHIFT | + r->rd_setup << ARMADA_RD_SETUP_SHIFT | + r->acc_first << ARMADA_ACC_FIRST_SHIFT | + r->turn_off; + + dev_dbg(devbus->dev, "read parameters register 0x%p = 0x%x\n", + devbus->base + ARMADA_READ_PARAM_OFFSET, + value); + + writel(value, devbus->base + ARMADA_READ_PARAM_OFFSET); + + /* Set write timings */ + value = w->sync_enable << ARMADA_SYNC_ENABLE_SHIFT | + w->wr_low << ARMADA_WR_LOW_SHIFT | + w->wr_high << ARMADA_WR_HIGH_SHIFT | + w->ale_wr; + + dev_dbg(devbus->dev, "write parameters register: 0x%p = 0x%x\n", + devbus->base + ARMADA_WRITE_PARAM_OFFSET, + value); + + writel(value, devbus->base + ARMADA_WRITE_PARAM_OFFSET); +} + +static int mvebu_devbus_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct devbus_read_params r; + struct devbus_write_params w; + struct devbus *devbus; + struct resource *res; + struct clk *clk; + unsigned long rate; + int err; + + devbus = devm_kzalloc(&pdev->dev, sizeof(struct devbus), GFP_KERNEL); + if (!devbus) + return -ENOMEM; + + devbus->dev = dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + devbus->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(devbus->base)) + return PTR_ERR(devbus->base); + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + clk_prepare_enable(clk); + + /* + * Obtain clock period in picoseconds, + * we need this in order to convert timing + * parameters from cycles to picoseconds. + */ + rate = clk_get_rate(clk) / 1000; + devbus->tick_ps = 1000000000 / rate; + + dev_dbg(devbus->dev, "Setting timing parameter, tick is %lu ps\n", + devbus->tick_ps); + + if (!of_property_read_bool(node, "devbus,keep-config")) { + /* Read the Device Tree node */ + err = devbus_get_timing_params(devbus, node, &r, &w); + if (err < 0) + return err; + + /* Set the new timing parameters */ + if (of_device_is_compatible(node, "marvell,orion-devbus")) + devbus_orion_set_timing_params(devbus, node, &r, &w); + else + devbus_armada_set_timing_params(devbus, node, &r, &w); + } + + /* + * We need to create a child device explicitly from here to + * guarantee that the child will be probed after the timing + * parameters for the bus are written. + */ + err = of_platform_populate(node, NULL, NULL, dev); + if (err < 0) + return err; + + return 0; +} + +static const struct of_device_id mvebu_devbus_of_match[] = { + { .compatible = "marvell,mvebu-devbus" }, + { .compatible = "marvell,orion-devbus" }, + {}, +}; +MODULE_DEVICE_TABLE(of, mvebu_devbus_of_match); + +static struct platform_driver mvebu_devbus_driver = { + .probe = mvebu_devbus_probe, + .driver = { + .name = "mvebu-devbus", + .of_match_table = mvebu_devbus_of_match, + }, +}; + +static int __init mvebu_devbus_init(void) +{ + return platform_driver_register(&mvebu_devbus_driver); +} +module_init(mvebu_devbus_init); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>"); +MODULE_DESCRIPTION("Marvell EBU SoC Device Bus controller"); diff --git a/drivers/memory/of_memory.c b/drivers/memory/of_memory.c new file mode 100644 index 000000000..2f5ed7366 --- /dev/null +++ b/drivers/memory/of_memory.c @@ -0,0 +1,154 @@ +/* + * OpenFirmware helpers for memory drivers + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/gfp.h> +#include <memory/jedec_ddr.h> +#include <linux/export.h> +#include "of_memory.h" + +/** + * of_get_min_tck() - extract min timing values for ddr + * @np: pointer to ddr device tree node + * @device: device requesting for min timing values + * + * Populates the lpddr2_min_tck structure by extracting data + * from device tree node. Returns a pointer to the populated + * structure. If any error in populating the structure, returns + * default min timings provided by JEDEC. + */ +const struct lpddr2_min_tck *of_get_min_tck(struct device_node *np, + struct device *dev) +{ + int ret = 0; + struct lpddr2_min_tck *min; + + min = devm_kzalloc(dev, sizeof(*min), GFP_KERNEL); + if (!min) + goto default_min_tck; + + ret |= of_property_read_u32(np, "tRPab-min-tck", &min->tRPab); + ret |= of_property_read_u32(np, "tRCD-min-tck", &min->tRCD); + ret |= of_property_read_u32(np, "tWR-min-tck", &min->tWR); + ret |= of_property_read_u32(np, "tRASmin-min-tck", &min->tRASmin); + ret |= of_property_read_u32(np, "tRRD-min-tck", &min->tRRD); + ret |= of_property_read_u32(np, "tWTR-min-tck", &min->tWTR); + ret |= of_property_read_u32(np, "tXP-min-tck", &min->tXP); + ret |= of_property_read_u32(np, "tRTP-min-tck", &min->tRTP); + ret |= of_property_read_u32(np, "tCKE-min-tck", &min->tCKE); + ret |= of_property_read_u32(np, "tCKESR-min-tck", &min->tCKESR); + ret |= of_property_read_u32(np, "tFAW-min-tck", &min->tFAW); + + if (ret) { + devm_kfree(dev, min); + goto default_min_tck; + } + + return min; + +default_min_tck: + dev_warn(dev, "%s: using default min-tck values\n", __func__); + return &lpddr2_jedec_min_tck; +} +EXPORT_SYMBOL(of_get_min_tck); + +static int of_do_get_timings(struct device_node *np, + struct lpddr2_timings *tim) +{ + int ret; + + ret = of_property_read_u32(np, "max-freq", &tim->max_freq); + ret |= of_property_read_u32(np, "min-freq", &tim->min_freq); + ret |= of_property_read_u32(np, "tRPab", &tim->tRPab); + ret |= of_property_read_u32(np, "tRCD", &tim->tRCD); + ret |= of_property_read_u32(np, "tWR", &tim->tWR); + ret |= of_property_read_u32(np, "tRAS-min", &tim->tRAS_min); + ret |= of_property_read_u32(np, "tRRD", &tim->tRRD); + ret |= of_property_read_u32(np, "tWTR", &tim->tWTR); + ret |= of_property_read_u32(np, "tXP", &tim->tXP); + ret |= of_property_read_u32(np, "tRTP", &tim->tRTP); + ret |= of_property_read_u32(np, "tCKESR", &tim->tCKESR); + ret |= of_property_read_u32(np, "tDQSCK-max", &tim->tDQSCK_max); + ret |= of_property_read_u32(np, "tFAW", &tim->tFAW); + ret |= of_property_read_u32(np, "tZQCS", &tim->tZQCS); + ret |= of_property_read_u32(np, "tZQCL", &tim->tZQCL); + ret |= of_property_read_u32(np, "tZQinit", &tim->tZQinit); + ret |= of_property_read_u32(np, "tRAS-max-ns", &tim->tRAS_max_ns); + ret |= of_property_read_u32(np, "tDQSCK-max-derated", + &tim->tDQSCK_max_derated); + + return ret; +} + +/** + * of_get_ddr_timings() - extracts the ddr timings and updates no of + * frequencies available. + * @np_ddr: Pointer to ddr device tree node + * @dev: Device requesting for ddr timings + * @device_type: Type of ddr(LPDDR2 S2/S4) + * @nr_frequencies: No of frequencies available for ddr + * (updated by this function) + * + * Populates lpddr2_timings structure by extracting data from device + * tree node. Returns pointer to populated structure. If any error + * while populating, returns default timings provided by JEDEC. + */ +const struct lpddr2_timings *of_get_ddr_timings(struct device_node *np_ddr, + struct device *dev, u32 device_type, u32 *nr_frequencies) +{ + struct lpddr2_timings *timings = NULL; + u32 arr_sz = 0, i = 0; + struct device_node *np_tim; + char *tim_compat = NULL; + + switch (device_type) { + case DDR_TYPE_LPDDR2_S2: + case DDR_TYPE_LPDDR2_S4: + tim_compat = "jedec,lpddr2-timings"; + break; + default: + dev_warn(dev, "%s: un-supported memory type\n", __func__); + } + + for_each_child_of_node(np_ddr, np_tim) + if (of_device_is_compatible(np_tim, tim_compat)) + arr_sz++; + + if (arr_sz) + timings = devm_kcalloc(dev, arr_sz, sizeof(*timings), + GFP_KERNEL); + + if (!timings) + goto default_timings; + + for_each_child_of_node(np_ddr, np_tim) { + if (of_device_is_compatible(np_tim, tim_compat)) { + if (of_do_get_timings(np_tim, &timings[i])) { + devm_kfree(dev, timings); + goto default_timings; + } + i++; + } + } + + *nr_frequencies = arr_sz; + + return timings; + +default_timings: + dev_warn(dev, "%s: using default timings\n", __func__); + *nr_frequencies = ARRAY_SIZE(lpddr2_jedec_timings); + return lpddr2_jedec_timings; +} +EXPORT_SYMBOL(of_get_ddr_timings); diff --git a/drivers/memory/of_memory.h b/drivers/memory/of_memory.h new file mode 100644 index 000000000..ef2514f55 --- /dev/null +++ b/drivers/memory/of_memory.h @@ -0,0 +1,36 @@ +/* + * OpenFirmware helpers for memory drivers + * + * Copyright (C) 2012 Texas Instruments, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __LINUX_MEMORY_OF_REG_H +#define __LINUX_MEMORY_OF_REG_H + +#if defined(CONFIG_OF) && defined(CONFIG_DDR) +extern const struct lpddr2_min_tck *of_get_min_tck(struct device_node *np, + struct device *dev); +extern const struct lpddr2_timings + *of_get_ddr_timings(struct device_node *np_ddr, struct device *dev, + u32 device_type, u32 *nr_frequencies); +#else +static inline const struct lpddr2_min_tck + *of_get_min_tck(struct device_node *np, struct device *dev) +{ + return NULL; +} + +static inline const struct lpddr2_timings + *of_get_ddr_timings(struct device_node *np_ddr, struct device *dev, + u32 device_type, u32 *nr_frequencies) +{ + return NULL; +} +#endif /* CONFIG_OF && CONFIG_DDR */ + +#endif /* __LINUX_MEMORY_OF_REG_ */ diff --git a/drivers/memory/omap-gpmc.c b/drivers/memory/omap-gpmc.c new file mode 100644 index 000000000..d8f2cacea --- /dev/null +++ b/drivers/memory/omap-gpmc.c @@ -0,0 +1,2571 @@ +/* + * GPMC support functions + * + * Copyright (C) 2005-2006 Nokia Corporation + * + * Author: Juha Yrjola + * + * Copyright (C) 2009 Texas Instruments + * Added OMAP4 support - Santosh Shilimkar <santosh.shilimkar@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/gpio/driver.h> +#include <linux/gpio/consumer.h> /* GPIO descriptor enum */ +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/omap-gpmc.h> +#include <linux/pm_runtime.h> + +#include <linux/platform_data/mtd-nand-omap2.h> + +#include <asm/mach-types.h> + +#define DEVICE_NAME "omap-gpmc" + +/* GPMC register offsets */ +#define GPMC_REVISION 0x00 +#define GPMC_SYSCONFIG 0x10 +#define GPMC_SYSSTATUS 0x14 +#define GPMC_IRQSTATUS 0x18 +#define GPMC_IRQENABLE 0x1c +#define GPMC_TIMEOUT_CONTROL 0x40 +#define GPMC_ERR_ADDRESS 0x44 +#define GPMC_ERR_TYPE 0x48 +#define GPMC_CONFIG 0x50 +#define GPMC_STATUS 0x54 +#define GPMC_PREFETCH_CONFIG1 0x1e0 +#define GPMC_PREFETCH_CONFIG2 0x1e4 +#define GPMC_PREFETCH_CONTROL 0x1ec +#define GPMC_PREFETCH_STATUS 0x1f0 +#define GPMC_ECC_CONFIG 0x1f4 +#define GPMC_ECC_CONTROL 0x1f8 +#define GPMC_ECC_SIZE_CONFIG 0x1fc +#define GPMC_ECC1_RESULT 0x200 +#define GPMC_ECC_BCH_RESULT_0 0x240 /* not available on OMAP2 */ +#define GPMC_ECC_BCH_RESULT_1 0x244 /* not available on OMAP2 */ +#define GPMC_ECC_BCH_RESULT_2 0x248 /* not available on OMAP2 */ +#define GPMC_ECC_BCH_RESULT_3 0x24c /* not available on OMAP2 */ +#define GPMC_ECC_BCH_RESULT_4 0x300 /* not available on OMAP2 */ +#define GPMC_ECC_BCH_RESULT_5 0x304 /* not available on OMAP2 */ +#define GPMC_ECC_BCH_RESULT_6 0x308 /* not available on OMAP2 */ + +/* GPMC ECC control settings */ +#define GPMC_ECC_CTRL_ECCCLEAR 0x100 +#define GPMC_ECC_CTRL_ECCDISABLE 0x000 +#define GPMC_ECC_CTRL_ECCREG1 0x001 +#define GPMC_ECC_CTRL_ECCREG2 0x002 +#define GPMC_ECC_CTRL_ECCREG3 0x003 +#define GPMC_ECC_CTRL_ECCREG4 0x004 +#define GPMC_ECC_CTRL_ECCREG5 0x005 +#define GPMC_ECC_CTRL_ECCREG6 0x006 +#define GPMC_ECC_CTRL_ECCREG7 0x007 +#define GPMC_ECC_CTRL_ECCREG8 0x008 +#define GPMC_ECC_CTRL_ECCREG9 0x009 + +#define GPMC_CONFIG_LIMITEDADDRESS BIT(1) + +#define GPMC_STATUS_EMPTYWRITEBUFFERSTATUS BIT(0) + +#define GPMC_CONFIG2_CSEXTRADELAY BIT(7) +#define GPMC_CONFIG3_ADVEXTRADELAY BIT(7) +#define GPMC_CONFIG4_OEEXTRADELAY BIT(7) +#define GPMC_CONFIG4_WEEXTRADELAY BIT(23) +#define GPMC_CONFIG6_CYCLE2CYCLEDIFFCSEN BIT(6) +#define GPMC_CONFIG6_CYCLE2CYCLESAMECSEN BIT(7) + +#define GPMC_CS0_OFFSET 0x60 +#define GPMC_CS_SIZE 0x30 +#define GPMC_BCH_SIZE 0x10 + +/* + * The first 1MB of GPMC address space is typically mapped to + * the internal ROM. Never allocate the first page, to + * facilitate bug detection; even if we didn't boot from ROM. + * As GPMC minimum partition size is 16MB we can only start from + * there. + */ +#define GPMC_MEM_START 0x1000000 +#define GPMC_MEM_END 0x3FFFFFFF + +#define GPMC_CHUNK_SHIFT 24 /* 16 MB */ +#define GPMC_SECTION_SHIFT 28 /* 128 MB */ + +#define CS_NUM_SHIFT 24 +#define ENABLE_PREFETCH (0x1 << 7) +#define DMA_MPU_MODE 2 + +#define GPMC_REVISION_MAJOR(l) ((l >> 4) & 0xf) +#define GPMC_REVISION_MINOR(l) (l & 0xf) + +#define GPMC_HAS_WR_ACCESS 0x1 +#define GPMC_HAS_WR_DATA_MUX_BUS 0x2 +#define GPMC_HAS_MUX_AAD 0x4 + +#define GPMC_NR_WAITPINS 4 + +#define GPMC_CS_CONFIG1 0x00 +#define GPMC_CS_CONFIG2 0x04 +#define GPMC_CS_CONFIG3 0x08 +#define GPMC_CS_CONFIG4 0x0c +#define GPMC_CS_CONFIG5 0x10 +#define GPMC_CS_CONFIG6 0x14 +#define GPMC_CS_CONFIG7 0x18 +#define GPMC_CS_NAND_COMMAND 0x1c +#define GPMC_CS_NAND_ADDRESS 0x20 +#define GPMC_CS_NAND_DATA 0x24 + +/* Control Commands */ +#define GPMC_CONFIG_RDY_BSY 0x00000001 +#define GPMC_CONFIG_DEV_SIZE 0x00000002 +#define GPMC_CONFIG_DEV_TYPE 0x00000003 + +#define GPMC_CONFIG1_WRAPBURST_SUPP (1 << 31) +#define GPMC_CONFIG1_READMULTIPLE_SUPP (1 << 30) +#define GPMC_CONFIG1_READTYPE_ASYNC (0 << 29) +#define GPMC_CONFIG1_READTYPE_SYNC (1 << 29) +#define GPMC_CONFIG1_WRITEMULTIPLE_SUPP (1 << 28) +#define GPMC_CONFIG1_WRITETYPE_ASYNC (0 << 27) +#define GPMC_CONFIG1_WRITETYPE_SYNC (1 << 27) +#define GPMC_CONFIG1_CLKACTIVATIONTIME(val) ((val & 3) << 25) +/** CLKACTIVATIONTIME Max Ticks */ +#define GPMC_CONFIG1_CLKACTIVATIONTIME_MAX 2 +#define GPMC_CONFIG1_PAGE_LEN(val) ((val & 3) << 23) +/** ATTACHEDDEVICEPAGELENGTH Max Value */ +#define GPMC_CONFIG1_ATTACHEDDEVICEPAGELENGTH_MAX 2 +#define GPMC_CONFIG1_WAIT_READ_MON (1 << 22) +#define GPMC_CONFIG1_WAIT_WRITE_MON (1 << 21) +#define GPMC_CONFIG1_WAIT_MON_TIME(val) ((val & 3) << 18) +/** WAITMONITORINGTIME Max Ticks */ +#define GPMC_CONFIG1_WAITMONITORINGTIME_MAX 2 +#define GPMC_CONFIG1_WAIT_PIN_SEL(val) ((val & 3) << 16) +#define GPMC_CONFIG1_DEVICESIZE(val) ((val & 3) << 12) +#define GPMC_CONFIG1_DEVICESIZE_16 GPMC_CONFIG1_DEVICESIZE(1) +/** DEVICESIZE Max Value */ +#define GPMC_CONFIG1_DEVICESIZE_MAX 1 +#define GPMC_CONFIG1_DEVICETYPE(val) ((val & 3) << 10) +#define GPMC_CONFIG1_DEVICETYPE_NOR GPMC_CONFIG1_DEVICETYPE(0) +#define GPMC_CONFIG1_MUXTYPE(val) ((val & 3) << 8) +#define GPMC_CONFIG1_TIME_PARA_GRAN (1 << 4) +#define GPMC_CONFIG1_FCLK_DIV(val) (val & 3) +#define GPMC_CONFIG1_FCLK_DIV2 (GPMC_CONFIG1_FCLK_DIV(1)) +#define GPMC_CONFIG1_FCLK_DIV3 (GPMC_CONFIG1_FCLK_DIV(2)) +#define GPMC_CONFIG1_FCLK_DIV4 (GPMC_CONFIG1_FCLK_DIV(3)) +#define GPMC_CONFIG7_CSVALID (1 << 6) + +#define GPMC_CONFIG7_BASEADDRESS_MASK 0x3f +#define GPMC_CONFIG7_CSVALID_MASK BIT(6) +#define GPMC_CONFIG7_MASKADDRESS_OFFSET 8 +#define GPMC_CONFIG7_MASKADDRESS_MASK (0xf << GPMC_CONFIG7_MASKADDRESS_OFFSET) +/* All CONFIG7 bits except reserved bits */ +#define GPMC_CONFIG7_MASK (GPMC_CONFIG7_BASEADDRESS_MASK | \ + GPMC_CONFIG7_CSVALID_MASK | \ + GPMC_CONFIG7_MASKADDRESS_MASK) + +#define GPMC_DEVICETYPE_NOR 0 +#define GPMC_DEVICETYPE_NAND 2 +#define GPMC_CONFIG_WRITEPROTECT 0x00000010 +#define WR_RD_PIN_MONITORING 0x00600000 + +/* ECC commands */ +#define GPMC_ECC_READ 0 /* Reset Hardware ECC for read */ +#define GPMC_ECC_WRITE 1 /* Reset Hardware ECC for write */ +#define GPMC_ECC_READSYN 2 /* Reset before syndrom is read back */ + +#define GPMC_NR_NAND_IRQS 2 /* number of NAND specific IRQs */ + +enum gpmc_clk_domain { + GPMC_CD_FCLK, + GPMC_CD_CLK +}; + +struct gpmc_cs_data { + const char *name; + +#define GPMC_CS_RESERVED (1 << 0) + u32 flags; + + struct resource mem; +}; + +/* Structure to save gpmc cs context */ +struct gpmc_cs_config { + u32 config1; + u32 config2; + u32 config3; + u32 config4; + u32 config5; + u32 config6; + u32 config7; + int is_valid; +}; + +/* + * Structure to save/restore gpmc context + * to support core off on OMAP3 + */ +struct omap3_gpmc_regs { + u32 sysconfig; + u32 irqenable; + u32 timeout_ctrl; + u32 config; + u32 prefetch_config1; + u32 prefetch_config2; + u32 prefetch_control; + struct gpmc_cs_config cs_context[GPMC_CS_NUM]; +}; + +struct gpmc_device { + struct device *dev; + int irq; + struct irq_chip irq_chip; + struct gpio_chip gpio_chip; + int nirqs; +}; + +static struct irq_domain *gpmc_irq_domain; + +static struct resource gpmc_mem_root; +static struct gpmc_cs_data gpmc_cs[GPMC_CS_NUM]; +static DEFINE_SPINLOCK(gpmc_mem_lock); +/* Define chip-selects as reserved by default until probe completes */ +static unsigned int gpmc_cs_num = GPMC_CS_NUM; +static unsigned int gpmc_nr_waitpins; +static resource_size_t phys_base, mem_size; +static unsigned gpmc_capability; +static void __iomem *gpmc_base; + +static struct clk *gpmc_l3_clk; + +static irqreturn_t gpmc_handle_irq(int irq, void *dev); + +static void gpmc_write_reg(int idx, u32 val) +{ + writel_relaxed(val, gpmc_base + idx); +} + +static u32 gpmc_read_reg(int idx) +{ + return readl_relaxed(gpmc_base + idx); +} + +void gpmc_cs_write_reg(int cs, int idx, u32 val) +{ + void __iomem *reg_addr; + + reg_addr = gpmc_base + GPMC_CS0_OFFSET + (cs * GPMC_CS_SIZE) + idx; + writel_relaxed(val, reg_addr); +} + +static u32 gpmc_cs_read_reg(int cs, int idx) +{ + void __iomem *reg_addr; + + reg_addr = gpmc_base + GPMC_CS0_OFFSET + (cs * GPMC_CS_SIZE) + idx; + return readl_relaxed(reg_addr); +} + +/* TODO: Add support for gpmc_fck to clock framework and use it */ +static unsigned long gpmc_get_fclk_period(void) +{ + unsigned long rate = clk_get_rate(gpmc_l3_clk); + + rate /= 1000; + rate = 1000000000 / rate; /* In picoseconds */ + + return rate; +} + +/** + * gpmc_get_clk_period - get period of selected clock domain in ps + * @cs Chip Select Region. + * @cd Clock Domain. + * + * GPMC_CS_CONFIG1 GPMCFCLKDIVIDER for cs has to be setup + * prior to calling this function with GPMC_CD_CLK. + */ +static unsigned long gpmc_get_clk_period(int cs, enum gpmc_clk_domain cd) +{ + + unsigned long tick_ps = gpmc_get_fclk_period(); + u32 l; + int div; + + switch (cd) { + case GPMC_CD_CLK: + /* get current clk divider */ + l = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG1); + div = (l & 0x03) + 1; + /* get GPMC_CLK period */ + tick_ps *= div; + break; + case GPMC_CD_FCLK: + /* FALL-THROUGH */ + default: + break; + } + + return tick_ps; + +} + +static unsigned int gpmc_ns_to_clk_ticks(unsigned int time_ns, int cs, + enum gpmc_clk_domain cd) +{ + unsigned long tick_ps; + + /* Calculate in picosecs to yield more exact results */ + tick_ps = gpmc_get_clk_period(cs, cd); + + return (time_ns * 1000 + tick_ps - 1) / tick_ps; +} + +static unsigned int gpmc_ns_to_ticks(unsigned int time_ns) +{ + return gpmc_ns_to_clk_ticks(time_ns, /* any CS */ 0, GPMC_CD_FCLK); +} + +static unsigned int gpmc_ps_to_ticks(unsigned int time_ps) +{ + unsigned long tick_ps; + + /* Calculate in picosecs to yield more exact results */ + tick_ps = gpmc_get_fclk_period(); + + return (time_ps + tick_ps - 1) / tick_ps; +} + +static unsigned int gpmc_clk_ticks_to_ns(unsigned int ticks, int cs, + enum gpmc_clk_domain cd) +{ + return ticks * gpmc_get_clk_period(cs, cd) / 1000; +} + +unsigned int gpmc_ticks_to_ns(unsigned int ticks) +{ + return gpmc_clk_ticks_to_ns(ticks, /* any CS */ 0, GPMC_CD_FCLK); +} + +static unsigned int gpmc_ticks_to_ps(unsigned int ticks) +{ + return ticks * gpmc_get_fclk_period(); +} + +static unsigned int gpmc_round_ps_to_ticks(unsigned int time_ps) +{ + unsigned long ticks = gpmc_ps_to_ticks(time_ps); + + return ticks * gpmc_get_fclk_period(); +} + +static inline void gpmc_cs_modify_reg(int cs, int reg, u32 mask, bool value) +{ + u32 l; + + l = gpmc_cs_read_reg(cs, reg); + if (value) + l |= mask; + else + l &= ~mask; + gpmc_cs_write_reg(cs, reg, l); +} + +static void gpmc_cs_bool_timings(int cs, const struct gpmc_bool_timings *p) +{ + gpmc_cs_modify_reg(cs, GPMC_CS_CONFIG1, + GPMC_CONFIG1_TIME_PARA_GRAN, + p->time_para_granularity); + gpmc_cs_modify_reg(cs, GPMC_CS_CONFIG2, + GPMC_CONFIG2_CSEXTRADELAY, p->cs_extra_delay); + gpmc_cs_modify_reg(cs, GPMC_CS_CONFIG3, + GPMC_CONFIG3_ADVEXTRADELAY, p->adv_extra_delay); + gpmc_cs_modify_reg(cs, GPMC_CS_CONFIG4, + GPMC_CONFIG4_OEEXTRADELAY, p->oe_extra_delay); + gpmc_cs_modify_reg(cs, GPMC_CS_CONFIG4, + GPMC_CONFIG4_WEEXTRADELAY, p->we_extra_delay); + gpmc_cs_modify_reg(cs, GPMC_CS_CONFIG6, + GPMC_CONFIG6_CYCLE2CYCLESAMECSEN, + p->cycle2cyclesamecsen); + gpmc_cs_modify_reg(cs, GPMC_CS_CONFIG6, + GPMC_CONFIG6_CYCLE2CYCLEDIFFCSEN, + p->cycle2cyclediffcsen); +} + +#ifdef CONFIG_OMAP_GPMC_DEBUG +/** + * get_gpmc_timing_reg - read a timing parameter and print DTS settings for it. + * @cs: Chip Select Region + * @reg: GPMC_CS_CONFIGn register offset. + * @st_bit: Start Bit + * @end_bit: End Bit. Must be >= @st_bit. + * @ma:x Maximum parameter value (before optional @shift). + * If 0, maximum is as high as @st_bit and @end_bit allow. + * @name: DTS node name, w/o "gpmc," + * @cd: Clock Domain of timing parameter. + * @shift: Parameter value left shifts @shift, which is then printed instead of value. + * @raw: Raw Format Option. + * raw format: gpmc,name = <value> + * tick format: gpmc,name = <value> /‍* x ns -- y ns; x ticks *‍/ + * Where x ns -- y ns result in the same tick value. + * When @max is exceeded, "invalid" is printed inside comment. + * @noval: Parameter values equal to 0 are not printed. + * @return: Specified timing parameter (after optional @shift). + * + */ +static int get_gpmc_timing_reg( + /* timing specifiers */ + int cs, int reg, int st_bit, int end_bit, int max, + const char *name, const enum gpmc_clk_domain cd, + /* value transform */ + int shift, + /* format specifiers */ + bool raw, bool noval) +{ + u32 l; + int nr_bits; + int mask; + bool invalid; + + l = gpmc_cs_read_reg(cs, reg); + nr_bits = end_bit - st_bit + 1; + mask = (1 << nr_bits) - 1; + l = (l >> st_bit) & mask; + if (!max) + max = mask; + invalid = l > max; + if (shift) + l = (shift << l); + if (noval && (l == 0)) + return 0; + if (!raw) { + /* DTS tick format for timings in ns */ + unsigned int time_ns; + unsigned int time_ns_min = 0; + + if (l) + time_ns_min = gpmc_clk_ticks_to_ns(l - 1, cs, cd) + 1; + time_ns = gpmc_clk_ticks_to_ns(l, cs, cd); + pr_info("gpmc,%s = <%u>; /* %u ns - %u ns; %i ticks%s*/\n", + name, time_ns, time_ns_min, time_ns, l, + invalid ? "; invalid " : " "); + } else { + /* raw format */ + pr_info("gpmc,%s = <%u>;%s\n", name, l, + invalid ? " /* invalid */" : ""); + } + + return l; +} + +#define GPMC_PRINT_CONFIG(cs, config) \ + pr_info("cs%i %s: 0x%08x\n", cs, #config, \ + gpmc_cs_read_reg(cs, config)) +#define GPMC_GET_RAW(reg, st, end, field) \ + get_gpmc_timing_reg(cs, (reg), (st), (end), 0, field, GPMC_CD_FCLK, 0, 1, 0) +#define GPMC_GET_RAW_MAX(reg, st, end, max, field) \ + get_gpmc_timing_reg(cs, (reg), (st), (end), (max), field, GPMC_CD_FCLK, 0, 1, 0) +#define GPMC_GET_RAW_BOOL(reg, st, end, field) \ + get_gpmc_timing_reg(cs, (reg), (st), (end), 0, field, GPMC_CD_FCLK, 0, 1, 1) +#define GPMC_GET_RAW_SHIFT_MAX(reg, st, end, shift, max, field) \ + get_gpmc_timing_reg(cs, (reg), (st), (end), (max), field, GPMC_CD_FCLK, (shift), 1, 1) +#define GPMC_GET_TICKS(reg, st, end, field) \ + get_gpmc_timing_reg(cs, (reg), (st), (end), 0, field, GPMC_CD_FCLK, 0, 0, 0) +#define GPMC_GET_TICKS_CD(reg, st, end, field, cd) \ + get_gpmc_timing_reg(cs, (reg), (st), (end), 0, field, (cd), 0, 0, 0) +#define GPMC_GET_TICKS_CD_MAX(reg, st, end, max, field, cd) \ + get_gpmc_timing_reg(cs, (reg), (st), (end), (max), field, (cd), 0, 0, 0) + +static void gpmc_show_regs(int cs, const char *desc) +{ + pr_info("gpmc cs%i %s:\n", cs, desc); + GPMC_PRINT_CONFIG(cs, GPMC_CS_CONFIG1); + GPMC_PRINT_CONFIG(cs, GPMC_CS_CONFIG2); + GPMC_PRINT_CONFIG(cs, GPMC_CS_CONFIG3); + GPMC_PRINT_CONFIG(cs, GPMC_CS_CONFIG4); + GPMC_PRINT_CONFIG(cs, GPMC_CS_CONFIG5); + GPMC_PRINT_CONFIG(cs, GPMC_CS_CONFIG6); +} + +/* + * Note that gpmc,wait-pin handing wrongly assumes bit 8 is available, + * see commit c9fb809. + */ +static void gpmc_cs_show_timings(int cs, const char *desc) +{ + gpmc_show_regs(cs, desc); + + pr_info("gpmc cs%i access configuration:\n", cs); + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG1, 4, 4, "time-para-granularity"); + GPMC_GET_RAW(GPMC_CS_CONFIG1, 8, 9, "mux-add-data"); + GPMC_GET_RAW_SHIFT_MAX(GPMC_CS_CONFIG1, 12, 13, 1, + GPMC_CONFIG1_DEVICESIZE_MAX, "device-width"); + GPMC_GET_RAW(GPMC_CS_CONFIG1, 16, 17, "wait-pin"); + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG1, 21, 21, "wait-on-write"); + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG1, 22, 22, "wait-on-read"); + GPMC_GET_RAW_SHIFT_MAX(GPMC_CS_CONFIG1, 23, 24, 4, + GPMC_CONFIG1_ATTACHEDDEVICEPAGELENGTH_MAX, + "burst-length"); + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG1, 27, 27, "sync-write"); + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG1, 28, 28, "burst-write"); + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG1, 29, 29, "gpmc,sync-read"); + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG1, 30, 30, "burst-read"); + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG1, 31, 31, "burst-wrap"); + + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG2, 7, 7, "cs-extra-delay"); + + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG3, 7, 7, "adv-extra-delay"); + + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG4, 23, 23, "we-extra-delay"); + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG4, 7, 7, "oe-extra-delay"); + + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG6, 7, 7, "cycle2cycle-samecsen"); + GPMC_GET_RAW_BOOL(GPMC_CS_CONFIG6, 6, 6, "cycle2cycle-diffcsen"); + + pr_info("gpmc cs%i timings configuration:\n", cs); + GPMC_GET_TICKS(GPMC_CS_CONFIG2, 0, 3, "cs-on-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG2, 8, 12, "cs-rd-off-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG2, 16, 20, "cs-wr-off-ns"); + + GPMC_GET_TICKS(GPMC_CS_CONFIG3, 0, 3, "adv-on-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG3, 8, 12, "adv-rd-off-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG3, 16, 20, "adv-wr-off-ns"); + if (gpmc_capability & GPMC_HAS_MUX_AAD) { + GPMC_GET_TICKS(GPMC_CS_CONFIG3, 4, 6, "adv-aad-mux-on-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG3, 24, 26, + "adv-aad-mux-rd-off-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG3, 28, 30, + "adv-aad-mux-wr-off-ns"); + } + + GPMC_GET_TICKS(GPMC_CS_CONFIG4, 0, 3, "oe-on-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG4, 8, 12, "oe-off-ns"); + if (gpmc_capability & GPMC_HAS_MUX_AAD) { + GPMC_GET_TICKS(GPMC_CS_CONFIG4, 4, 6, "oe-aad-mux-on-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG4, 13, 15, "oe-aad-mux-off-ns"); + } + GPMC_GET_TICKS(GPMC_CS_CONFIG4, 16, 19, "we-on-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG4, 24, 28, "we-off-ns"); + + GPMC_GET_TICKS(GPMC_CS_CONFIG5, 0, 4, "rd-cycle-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG5, 8, 12, "wr-cycle-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG5, 16, 20, "access-ns"); + + GPMC_GET_TICKS(GPMC_CS_CONFIG5, 24, 27, "page-burst-access-ns"); + + GPMC_GET_TICKS(GPMC_CS_CONFIG6, 0, 3, "bus-turnaround-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG6, 8, 11, "cycle2cycle-delay-ns"); + + GPMC_GET_TICKS_CD_MAX(GPMC_CS_CONFIG1, 18, 19, + GPMC_CONFIG1_WAITMONITORINGTIME_MAX, + "wait-monitoring-ns", GPMC_CD_CLK); + GPMC_GET_TICKS_CD_MAX(GPMC_CS_CONFIG1, 25, 26, + GPMC_CONFIG1_CLKACTIVATIONTIME_MAX, + "clk-activation-ns", GPMC_CD_FCLK); + + GPMC_GET_TICKS(GPMC_CS_CONFIG6, 16, 19, "wr-data-mux-bus-ns"); + GPMC_GET_TICKS(GPMC_CS_CONFIG6, 24, 28, "wr-access-ns"); +} +#else +static inline void gpmc_cs_show_timings(int cs, const char *desc) +{ +} +#endif + +/** + * set_gpmc_timing_reg - set a single timing parameter for Chip Select Region. + * Caller is expected to have initialized CONFIG1 GPMCFCLKDIVIDER + * prior to calling this function with @cd equal to GPMC_CD_CLK. + * + * @cs: Chip Select Region. + * @reg: GPMC_CS_CONFIGn register offset. + * @st_bit: Start Bit + * @end_bit: End Bit. Must be >= @st_bit. + * @max: Maximum parameter value. + * If 0, maximum is as high as @st_bit and @end_bit allow. + * @time: Timing parameter in ns. + * @cd: Timing parameter clock domain. + * @name: Timing parameter name. + * @return: 0 on success, -1 on error. + */ +static int set_gpmc_timing_reg(int cs, int reg, int st_bit, int end_bit, int max, + int time, enum gpmc_clk_domain cd, const char *name) +{ + u32 l; + int ticks, mask, nr_bits; + + if (time == 0) + ticks = 0; + else + ticks = gpmc_ns_to_clk_ticks(time, cs, cd); + nr_bits = end_bit - st_bit + 1; + mask = (1 << nr_bits) - 1; + + if (!max) + max = mask; + + if (ticks > max) { + pr_err("%s: GPMC CS%d: %s %d ns, %d ticks > %d ticks\n", + __func__, cs, name, time, ticks, max); + + return -1; + } + + l = gpmc_cs_read_reg(cs, reg); +#ifdef CONFIG_OMAP_GPMC_DEBUG + pr_info( + "GPMC CS%d: %-17s: %3d ticks, %3lu ns (was %3i ticks) %3d ns\n", + cs, name, ticks, gpmc_get_clk_period(cs, cd) * ticks / 1000, + (l >> st_bit) & mask, time); +#endif + l &= ~(mask << st_bit); + l |= ticks << st_bit; + gpmc_cs_write_reg(cs, reg, l); + + return 0; +} + +#define GPMC_SET_ONE_CD_MAX(reg, st, end, max, field, cd) \ + if (set_gpmc_timing_reg(cs, (reg), (st), (end), (max), \ + t->field, (cd), #field) < 0) \ + return -1 + +#define GPMC_SET_ONE(reg, st, end, field) \ + GPMC_SET_ONE_CD_MAX(reg, st, end, 0, field, GPMC_CD_FCLK) + +/** + * gpmc_calc_waitmonitoring_divider - calculate proper GPMCFCLKDIVIDER based on WAITMONITORINGTIME + * WAITMONITORINGTIME will be _at least_ as long as desired, i.e. + * read --> don't sample bus too early + * write --> data is longer on bus + * + * Formula: + * gpmc_clk_div + 1 = ceil(ceil(waitmonitoringtime_ns / gpmc_fclk_ns) + * / waitmonitoring_ticks) + * WAITMONITORINGTIME resulting in 0 or 1 tick with div = 1 are caught by + * div <= 0 check. + * + * @wait_monitoring: WAITMONITORINGTIME in ns. + * @return: -1 on failure to scale, else proper divider > 0. + */ +static int gpmc_calc_waitmonitoring_divider(unsigned int wait_monitoring) +{ + + int div = gpmc_ns_to_ticks(wait_monitoring); + + div += GPMC_CONFIG1_WAITMONITORINGTIME_MAX - 1; + div /= GPMC_CONFIG1_WAITMONITORINGTIME_MAX; + + if (div > 4) + return -1; + if (div <= 0) + div = 1; + + return div; + +} + +/** + * gpmc_calc_divider - calculate GPMC_FCLK divider for sync_clk GPMC_CLK period. + * @sync_clk: GPMC_CLK period in ps. + * @return: Returns at least 1 if GPMC_FCLK can be divided to GPMC_CLK. + * Else, returns -1. + */ +int gpmc_calc_divider(unsigned int sync_clk) +{ + int div = gpmc_ps_to_ticks(sync_clk); + + if (div > 4) + return -1; + if (div <= 0) + div = 1; + + return div; +} + +/** + * gpmc_cs_set_timings - program timing parameters for Chip Select Region. + * @cs: Chip Select Region. + * @t: GPMC timing parameters. + * @s: GPMC timing settings. + * @return: 0 on success, -1 on error. + */ +int gpmc_cs_set_timings(int cs, const struct gpmc_timings *t, + const struct gpmc_settings *s) +{ + int div; + u32 l; + + div = gpmc_calc_divider(t->sync_clk); + if (div < 0) + return div; + + /* + * See if we need to change the divider for waitmonitoringtime. + * + * Calculate GPMCFCLKDIVIDER independent of gpmc,sync-clk-ps in DT for + * pure asynchronous accesses, i.e. both read and write asynchronous. + * However, only do so if WAITMONITORINGTIME is actually used, i.e. + * either WAITREADMONITORING or WAITWRITEMONITORING is set. + * + * This statement must not change div to scale async WAITMONITORINGTIME + * to protect mixed synchronous and asynchronous accesses. + * + * We raise an error later if WAITMONITORINGTIME does not fit. + */ + if (!s->sync_read && !s->sync_write && + (s->wait_on_read || s->wait_on_write) + ) { + + div = gpmc_calc_waitmonitoring_divider(t->wait_monitoring); + if (div < 0) { + pr_err("%s: waitmonitoringtime %3d ns too large for greatest gpmcfclkdivider.\n", + __func__, + t->wait_monitoring + ); + return -1; + } + } + + GPMC_SET_ONE(GPMC_CS_CONFIG2, 0, 3, cs_on); + GPMC_SET_ONE(GPMC_CS_CONFIG2, 8, 12, cs_rd_off); + GPMC_SET_ONE(GPMC_CS_CONFIG2, 16, 20, cs_wr_off); + + GPMC_SET_ONE(GPMC_CS_CONFIG3, 0, 3, adv_on); + GPMC_SET_ONE(GPMC_CS_CONFIG3, 8, 12, adv_rd_off); + GPMC_SET_ONE(GPMC_CS_CONFIG3, 16, 20, adv_wr_off); + if (gpmc_capability & GPMC_HAS_MUX_AAD) { + GPMC_SET_ONE(GPMC_CS_CONFIG3, 4, 6, adv_aad_mux_on); + GPMC_SET_ONE(GPMC_CS_CONFIG3, 24, 26, adv_aad_mux_rd_off); + GPMC_SET_ONE(GPMC_CS_CONFIG3, 28, 30, adv_aad_mux_wr_off); + } + + GPMC_SET_ONE(GPMC_CS_CONFIG4, 0, 3, oe_on); + GPMC_SET_ONE(GPMC_CS_CONFIG4, 8, 12, oe_off); + if (gpmc_capability & GPMC_HAS_MUX_AAD) { + GPMC_SET_ONE(GPMC_CS_CONFIG4, 4, 6, oe_aad_mux_on); + GPMC_SET_ONE(GPMC_CS_CONFIG4, 13, 15, oe_aad_mux_off); + } + GPMC_SET_ONE(GPMC_CS_CONFIG4, 16, 19, we_on); + GPMC_SET_ONE(GPMC_CS_CONFIG4, 24, 28, we_off); + + GPMC_SET_ONE(GPMC_CS_CONFIG5, 0, 4, rd_cycle); + GPMC_SET_ONE(GPMC_CS_CONFIG5, 8, 12, wr_cycle); + GPMC_SET_ONE(GPMC_CS_CONFIG5, 16, 20, access); + + GPMC_SET_ONE(GPMC_CS_CONFIG5, 24, 27, page_burst_access); + + GPMC_SET_ONE(GPMC_CS_CONFIG6, 0, 3, bus_turnaround); + GPMC_SET_ONE(GPMC_CS_CONFIG6, 8, 11, cycle2cycle_delay); + + if (gpmc_capability & GPMC_HAS_WR_DATA_MUX_BUS) + GPMC_SET_ONE(GPMC_CS_CONFIG6, 16, 19, wr_data_mux_bus); + if (gpmc_capability & GPMC_HAS_WR_ACCESS) + GPMC_SET_ONE(GPMC_CS_CONFIG6, 24, 28, wr_access); + + l = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG1); + l &= ~0x03; + l |= (div - 1); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, l); + + GPMC_SET_ONE_CD_MAX(GPMC_CS_CONFIG1, 18, 19, + GPMC_CONFIG1_WAITMONITORINGTIME_MAX, + wait_monitoring, GPMC_CD_CLK); + GPMC_SET_ONE_CD_MAX(GPMC_CS_CONFIG1, 25, 26, + GPMC_CONFIG1_CLKACTIVATIONTIME_MAX, + clk_activation, GPMC_CD_FCLK); + +#ifdef CONFIG_OMAP_GPMC_DEBUG + pr_info("GPMC CS%d CLK period is %lu ns (div %d)\n", + cs, (div * gpmc_get_fclk_period()) / 1000, div); +#endif + + gpmc_cs_bool_timings(cs, &t->bool_timings); + gpmc_cs_show_timings(cs, "after gpmc_cs_set_timings"); + + return 0; +} + +static int gpmc_cs_set_memconf(int cs, u32 base, u32 size) +{ + u32 l; + u32 mask; + + /* + * Ensure that base address is aligned on a + * boundary equal to or greater than size. + */ + if (base & (size - 1)) + return -EINVAL; + + base >>= GPMC_CHUNK_SHIFT; + mask = (1 << GPMC_SECTION_SHIFT) - size; + mask >>= GPMC_CHUNK_SHIFT; + mask <<= GPMC_CONFIG7_MASKADDRESS_OFFSET; + + l = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG7); + l &= ~GPMC_CONFIG7_MASK; + l |= base & GPMC_CONFIG7_BASEADDRESS_MASK; + l |= mask & GPMC_CONFIG7_MASKADDRESS_MASK; + l |= GPMC_CONFIG7_CSVALID; + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG7, l); + + return 0; +} + +static void gpmc_cs_enable_mem(int cs) +{ + u32 l; + + l = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG7); + l |= GPMC_CONFIG7_CSVALID; + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG7, l); +} + +static void gpmc_cs_disable_mem(int cs) +{ + u32 l; + + l = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG7); + l &= ~GPMC_CONFIG7_CSVALID; + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG7, l); +} + +static void gpmc_cs_get_memconf(int cs, u32 *base, u32 *size) +{ + u32 l; + u32 mask; + + l = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG7); + *base = (l & 0x3f) << GPMC_CHUNK_SHIFT; + mask = (l >> 8) & 0x0f; + *size = (1 << GPMC_SECTION_SHIFT) - (mask << GPMC_CHUNK_SHIFT); +} + +static int gpmc_cs_mem_enabled(int cs) +{ + u32 l; + + l = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG7); + return l & GPMC_CONFIG7_CSVALID; +} + +static void gpmc_cs_set_reserved(int cs, int reserved) +{ + struct gpmc_cs_data *gpmc = &gpmc_cs[cs]; + + gpmc->flags |= GPMC_CS_RESERVED; +} + +static bool gpmc_cs_reserved(int cs) +{ + struct gpmc_cs_data *gpmc = &gpmc_cs[cs]; + + return gpmc->flags & GPMC_CS_RESERVED; +} + +static void gpmc_cs_set_name(int cs, const char *name) +{ + struct gpmc_cs_data *gpmc = &gpmc_cs[cs]; + + gpmc->name = name; +} + +static const char *gpmc_cs_get_name(int cs) +{ + struct gpmc_cs_data *gpmc = &gpmc_cs[cs]; + + return gpmc->name; +} + +static unsigned long gpmc_mem_align(unsigned long size) +{ + int order; + + size = (size - 1) >> (GPMC_CHUNK_SHIFT - 1); + order = GPMC_CHUNK_SHIFT - 1; + do { + size >>= 1; + order++; + } while (size); + size = 1 << order; + return size; +} + +static int gpmc_cs_insert_mem(int cs, unsigned long base, unsigned long size) +{ + struct gpmc_cs_data *gpmc = &gpmc_cs[cs]; + struct resource *res = &gpmc->mem; + int r; + + size = gpmc_mem_align(size); + spin_lock(&gpmc_mem_lock); + res->start = base; + res->end = base + size - 1; + r = request_resource(&gpmc_mem_root, res); + spin_unlock(&gpmc_mem_lock); + + return r; +} + +static int gpmc_cs_delete_mem(int cs) +{ + struct gpmc_cs_data *gpmc = &gpmc_cs[cs]; + struct resource *res = &gpmc->mem; + int r; + + spin_lock(&gpmc_mem_lock); + r = release_resource(res); + res->start = 0; + res->end = 0; + spin_unlock(&gpmc_mem_lock); + + return r; +} + +/** + * gpmc_cs_remap - remaps a chip-select physical base address + * @cs: chip-select to remap + * @base: physical base address to re-map chip-select to + * + * Re-maps a chip-select to a new physical base address specified by + * "base". Returns 0 on success and appropriate negative error code + * on failure. + */ +static int gpmc_cs_remap(int cs, u32 base) +{ + int ret; + u32 old_base, size; + + if (cs >= gpmc_cs_num) { + pr_err("%s: requested chip-select is disabled\n", __func__); + return -ENODEV; + } + + /* + * Make sure we ignore any device offsets from the GPMC partition + * allocated for the chip select and that the new base confirms + * to the GPMC 16MB minimum granularity. + */ + base &= ~(SZ_16M - 1); + + gpmc_cs_get_memconf(cs, &old_base, &size); + if (base == old_base) + return 0; + + ret = gpmc_cs_delete_mem(cs); + if (ret < 0) + return ret; + + ret = gpmc_cs_insert_mem(cs, base, size); + if (ret < 0) + return ret; + + ret = gpmc_cs_set_memconf(cs, base, size); + + return ret; +} + +int gpmc_cs_request(int cs, unsigned long size, unsigned long *base) +{ + struct gpmc_cs_data *gpmc = &gpmc_cs[cs]; + struct resource *res = &gpmc->mem; + int r = -1; + + if (cs >= gpmc_cs_num) { + pr_err("%s: requested chip-select is disabled\n", __func__); + return -ENODEV; + } + size = gpmc_mem_align(size); + if (size > (1 << GPMC_SECTION_SHIFT)) + return -ENOMEM; + + spin_lock(&gpmc_mem_lock); + if (gpmc_cs_reserved(cs)) { + r = -EBUSY; + goto out; + } + if (gpmc_cs_mem_enabled(cs)) + r = adjust_resource(res, res->start & ~(size - 1), size); + if (r < 0) + r = allocate_resource(&gpmc_mem_root, res, size, 0, ~0, + size, NULL, NULL); + if (r < 0) + goto out; + + /* Disable CS while changing base address and size mask */ + gpmc_cs_disable_mem(cs); + + r = gpmc_cs_set_memconf(cs, res->start, resource_size(res)); + if (r < 0) { + release_resource(res); + goto out; + } + + /* Enable CS */ + gpmc_cs_enable_mem(cs); + *base = res->start; + gpmc_cs_set_reserved(cs, 1); +out: + spin_unlock(&gpmc_mem_lock); + return r; +} +EXPORT_SYMBOL(gpmc_cs_request); + +void gpmc_cs_free(int cs) +{ + struct gpmc_cs_data *gpmc; + struct resource *res; + + spin_lock(&gpmc_mem_lock); + if (cs >= gpmc_cs_num || cs < 0 || !gpmc_cs_reserved(cs)) { + printk(KERN_ERR "Trying to free non-reserved GPMC CS%d\n", cs); + BUG(); + spin_unlock(&gpmc_mem_lock); + return; + } + gpmc = &gpmc_cs[cs]; + res = &gpmc->mem; + + gpmc_cs_disable_mem(cs); + if (res->flags) + release_resource(res); + gpmc_cs_set_reserved(cs, 0); + spin_unlock(&gpmc_mem_lock); +} +EXPORT_SYMBOL(gpmc_cs_free); + +/** + * gpmc_configure - write request to configure gpmc + * @cmd: command type + * @wval: value to write + * @return status of the operation + */ +int gpmc_configure(int cmd, int wval) +{ + u32 regval; + + switch (cmd) { + case GPMC_CONFIG_WP: + regval = gpmc_read_reg(GPMC_CONFIG); + if (wval) + regval &= ~GPMC_CONFIG_WRITEPROTECT; /* WP is ON */ + else + regval |= GPMC_CONFIG_WRITEPROTECT; /* WP is OFF */ + gpmc_write_reg(GPMC_CONFIG, regval); + break; + + default: + pr_err("%s: command not supported\n", __func__); + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL(gpmc_configure); + +static bool gpmc_nand_writebuffer_empty(void) +{ + if (gpmc_read_reg(GPMC_STATUS) & GPMC_STATUS_EMPTYWRITEBUFFERSTATUS) + return true; + + return false; +} + +static struct gpmc_nand_ops nand_ops = { + .nand_writebuffer_empty = gpmc_nand_writebuffer_empty, +}; + +/** + * gpmc_omap_get_nand_ops - Get the GPMC NAND interface + * @regs: the GPMC NAND register map exclusive for NAND use. + * @cs: GPMC chip select number on which the NAND sits. The + * register map returned will be specific to this chip select. + * + * Returns NULL on error e.g. invalid cs. + */ +struct gpmc_nand_ops *gpmc_omap_get_nand_ops(struct gpmc_nand_regs *reg, int cs) +{ + int i; + + if (cs >= gpmc_cs_num) + return NULL; + + reg->gpmc_nand_command = gpmc_base + GPMC_CS0_OFFSET + + GPMC_CS_NAND_COMMAND + GPMC_CS_SIZE * cs; + reg->gpmc_nand_address = gpmc_base + GPMC_CS0_OFFSET + + GPMC_CS_NAND_ADDRESS + GPMC_CS_SIZE * cs; + reg->gpmc_nand_data = gpmc_base + GPMC_CS0_OFFSET + + GPMC_CS_NAND_DATA + GPMC_CS_SIZE * cs; + reg->gpmc_prefetch_config1 = gpmc_base + GPMC_PREFETCH_CONFIG1; + reg->gpmc_prefetch_config2 = gpmc_base + GPMC_PREFETCH_CONFIG2; + reg->gpmc_prefetch_control = gpmc_base + GPMC_PREFETCH_CONTROL; + reg->gpmc_prefetch_status = gpmc_base + GPMC_PREFETCH_STATUS; + reg->gpmc_ecc_config = gpmc_base + GPMC_ECC_CONFIG; + reg->gpmc_ecc_control = gpmc_base + GPMC_ECC_CONTROL; + reg->gpmc_ecc_size_config = gpmc_base + GPMC_ECC_SIZE_CONFIG; + reg->gpmc_ecc1_result = gpmc_base + GPMC_ECC1_RESULT; + + for (i = 0; i < GPMC_BCH_NUM_REMAINDER; i++) { + reg->gpmc_bch_result0[i] = gpmc_base + GPMC_ECC_BCH_RESULT_0 + + GPMC_BCH_SIZE * i; + reg->gpmc_bch_result1[i] = gpmc_base + GPMC_ECC_BCH_RESULT_1 + + GPMC_BCH_SIZE * i; + reg->gpmc_bch_result2[i] = gpmc_base + GPMC_ECC_BCH_RESULT_2 + + GPMC_BCH_SIZE * i; + reg->gpmc_bch_result3[i] = gpmc_base + GPMC_ECC_BCH_RESULT_3 + + GPMC_BCH_SIZE * i; + reg->gpmc_bch_result4[i] = gpmc_base + GPMC_ECC_BCH_RESULT_4 + + i * GPMC_BCH_SIZE; + reg->gpmc_bch_result5[i] = gpmc_base + GPMC_ECC_BCH_RESULT_5 + + i * GPMC_BCH_SIZE; + reg->gpmc_bch_result6[i] = gpmc_base + GPMC_ECC_BCH_RESULT_6 + + i * GPMC_BCH_SIZE; + } + + return &nand_ops; +} +EXPORT_SYMBOL_GPL(gpmc_omap_get_nand_ops); + +static void gpmc_omap_onenand_calc_sync_timings(struct gpmc_timings *t, + struct gpmc_settings *s, + int freq, int latency) +{ + struct gpmc_device_timings dev_t; + const int t_cer = 15; + const int t_avdp = 12; + const int t_cez = 20; /* max of t_cez, t_oez */ + const int t_wpl = 40; + const int t_wph = 30; + int min_gpmc_clk_period, t_ces, t_avds, t_avdh, t_ach, t_aavdh, t_rdyo; + + switch (freq) { + case 104: + min_gpmc_clk_period = 9600; /* 104 MHz */ + t_ces = 3; + t_avds = 4; + t_avdh = 2; + t_ach = 3; + t_aavdh = 6; + t_rdyo = 6; + break; + case 83: + min_gpmc_clk_period = 12000; /* 83 MHz */ + t_ces = 5; + t_avds = 4; + t_avdh = 2; + t_ach = 6; + t_aavdh = 6; + t_rdyo = 9; + break; + case 66: + min_gpmc_clk_period = 15000; /* 66 MHz */ + t_ces = 6; + t_avds = 5; + t_avdh = 2; + t_ach = 6; + t_aavdh = 6; + t_rdyo = 11; + break; + default: + min_gpmc_clk_period = 18500; /* 54 MHz */ + t_ces = 7; + t_avds = 7; + t_avdh = 7; + t_ach = 9; + t_aavdh = 7; + t_rdyo = 15; + break; + } + + /* Set synchronous read timings */ + memset(&dev_t, 0, sizeof(dev_t)); + + if (!s->sync_write) { + dev_t.t_avdp_w = max(t_avdp, t_cer) * 1000; + dev_t.t_wpl = t_wpl * 1000; + dev_t.t_wph = t_wph * 1000; + dev_t.t_aavdh = t_aavdh * 1000; + } + dev_t.ce_xdelay = true; + dev_t.avd_xdelay = true; + dev_t.oe_xdelay = true; + dev_t.we_xdelay = true; + dev_t.clk = min_gpmc_clk_period; + dev_t.t_bacc = dev_t.clk; + dev_t.t_ces = t_ces * 1000; + dev_t.t_avds = t_avds * 1000; + dev_t.t_avdh = t_avdh * 1000; + dev_t.t_ach = t_ach * 1000; + dev_t.cyc_iaa = (latency + 1); + dev_t.t_cez_r = t_cez * 1000; + dev_t.t_cez_w = dev_t.t_cez_r; + dev_t.cyc_aavdh_oe = 1; + dev_t.t_rdyo = t_rdyo * 1000 + min_gpmc_clk_period; + + gpmc_calc_timings(t, s, &dev_t); +} + +int gpmc_omap_onenand_set_timings(struct device *dev, int cs, int freq, + int latency, + struct gpmc_onenand_info *info) +{ + int ret; + struct gpmc_timings gpmc_t; + struct gpmc_settings gpmc_s; + + gpmc_read_settings_dt(dev->of_node, &gpmc_s); + + info->sync_read = gpmc_s.sync_read; + info->sync_write = gpmc_s.sync_write; + info->burst_len = gpmc_s.burst_len; + + if (!gpmc_s.sync_read && !gpmc_s.sync_write) + return 0; + + gpmc_omap_onenand_calc_sync_timings(&gpmc_t, &gpmc_s, freq, latency); + + ret = gpmc_cs_program_settings(cs, &gpmc_s); + if (ret < 0) + return ret; + + return gpmc_cs_set_timings(cs, &gpmc_t, &gpmc_s); +} +EXPORT_SYMBOL_GPL(gpmc_omap_onenand_set_timings); + +int gpmc_get_client_irq(unsigned irq_config) +{ + if (!gpmc_irq_domain) { + pr_warn("%s called before GPMC IRQ domain available\n", + __func__); + return 0; + } + + /* we restrict this to NAND IRQs only */ + if (irq_config >= GPMC_NR_NAND_IRQS) + return 0; + + return irq_create_mapping(gpmc_irq_domain, irq_config); +} + +static int gpmc_irq_endis(unsigned long hwirq, bool endis) +{ + u32 regval; + + /* bits GPMC_NR_NAND_IRQS to 8 are reserved */ + if (hwirq >= GPMC_NR_NAND_IRQS) + hwirq += 8 - GPMC_NR_NAND_IRQS; + + regval = gpmc_read_reg(GPMC_IRQENABLE); + if (endis) + regval |= BIT(hwirq); + else + regval &= ~BIT(hwirq); + gpmc_write_reg(GPMC_IRQENABLE, regval); + + return 0; +} + +static void gpmc_irq_disable(struct irq_data *p) +{ + gpmc_irq_endis(p->hwirq, false); +} + +static void gpmc_irq_enable(struct irq_data *p) +{ + gpmc_irq_endis(p->hwirq, true); +} + +static void gpmc_irq_mask(struct irq_data *d) +{ + gpmc_irq_endis(d->hwirq, false); +} + +static void gpmc_irq_unmask(struct irq_data *d) +{ + gpmc_irq_endis(d->hwirq, true); +} + +static void gpmc_irq_edge_config(unsigned long hwirq, bool rising_edge) +{ + u32 regval; + + /* NAND IRQs polarity is not configurable */ + if (hwirq < GPMC_NR_NAND_IRQS) + return; + + /* WAITPIN starts at BIT 8 */ + hwirq += 8 - GPMC_NR_NAND_IRQS; + + regval = gpmc_read_reg(GPMC_CONFIG); + if (rising_edge) + regval &= ~BIT(hwirq); + else + regval |= BIT(hwirq); + + gpmc_write_reg(GPMC_CONFIG, regval); +} + +static void gpmc_irq_ack(struct irq_data *d) +{ + unsigned int hwirq = d->hwirq; + + /* skip reserved bits */ + if (hwirq >= GPMC_NR_NAND_IRQS) + hwirq += 8 - GPMC_NR_NAND_IRQS; + + /* Setting bit to 1 clears (or Acks) the interrupt */ + gpmc_write_reg(GPMC_IRQSTATUS, BIT(hwirq)); +} + +static int gpmc_irq_set_type(struct irq_data *d, unsigned int trigger) +{ + /* can't set type for NAND IRQs */ + if (d->hwirq < GPMC_NR_NAND_IRQS) + return -EINVAL; + + /* We can support either rising or falling edge at a time */ + if (trigger == IRQ_TYPE_EDGE_FALLING) + gpmc_irq_edge_config(d->hwirq, false); + else if (trigger == IRQ_TYPE_EDGE_RISING) + gpmc_irq_edge_config(d->hwirq, true); + else + return -EINVAL; + + return 0; +} + +static int gpmc_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw) +{ + struct gpmc_device *gpmc = d->host_data; + + irq_set_chip_data(virq, gpmc); + if (hw < GPMC_NR_NAND_IRQS) { + irq_modify_status(virq, IRQ_NOREQUEST, IRQ_NOAUTOEN); + irq_set_chip_and_handler(virq, &gpmc->irq_chip, + handle_simple_irq); + } else { + irq_set_chip_and_handler(virq, &gpmc->irq_chip, + handle_edge_irq); + } + + return 0; +} + +static const struct irq_domain_ops gpmc_irq_domain_ops = { + .map = gpmc_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static irqreturn_t gpmc_handle_irq(int irq, void *data) +{ + int hwirq, virq; + u32 regval, regvalx; + struct gpmc_device *gpmc = data; + + regval = gpmc_read_reg(GPMC_IRQSTATUS); + regvalx = regval; + + if (!regval) + return IRQ_NONE; + + for (hwirq = 0; hwirq < gpmc->nirqs; hwirq++) { + /* skip reserved status bits */ + if (hwirq == GPMC_NR_NAND_IRQS) + regvalx >>= 8 - GPMC_NR_NAND_IRQS; + + if (regvalx & BIT(hwirq)) { + virq = irq_find_mapping(gpmc_irq_domain, hwirq); + if (!virq) { + dev_warn(gpmc->dev, + "spurious irq detected hwirq %d, virq %d\n", + hwirq, virq); + } + + generic_handle_irq(virq); + } + } + + gpmc_write_reg(GPMC_IRQSTATUS, regval); + + return IRQ_HANDLED; +} + +static int gpmc_setup_irq(struct gpmc_device *gpmc) +{ + u32 regval; + int rc; + + /* Disable interrupts */ + gpmc_write_reg(GPMC_IRQENABLE, 0); + + /* clear interrupts */ + regval = gpmc_read_reg(GPMC_IRQSTATUS); + gpmc_write_reg(GPMC_IRQSTATUS, regval); + + gpmc->irq_chip.name = "gpmc"; + gpmc->irq_chip.irq_enable = gpmc_irq_enable; + gpmc->irq_chip.irq_disable = gpmc_irq_disable; + gpmc->irq_chip.irq_ack = gpmc_irq_ack; + gpmc->irq_chip.irq_mask = gpmc_irq_mask; + gpmc->irq_chip.irq_unmask = gpmc_irq_unmask; + gpmc->irq_chip.irq_set_type = gpmc_irq_set_type; + + gpmc_irq_domain = irq_domain_add_linear(gpmc->dev->of_node, + gpmc->nirqs, + &gpmc_irq_domain_ops, + gpmc); + if (!gpmc_irq_domain) { + dev_err(gpmc->dev, "IRQ domain add failed\n"); + return -ENODEV; + } + + rc = request_irq(gpmc->irq, gpmc_handle_irq, 0, "gpmc", gpmc); + if (rc) { + dev_err(gpmc->dev, "failed to request irq %d: %d\n", + gpmc->irq, rc); + irq_domain_remove(gpmc_irq_domain); + gpmc_irq_domain = NULL; + } + + return rc; +} + +static int gpmc_free_irq(struct gpmc_device *gpmc) +{ + int hwirq; + + free_irq(gpmc->irq, gpmc); + + for (hwirq = 0; hwirq < gpmc->nirqs; hwirq++) + irq_dispose_mapping(irq_find_mapping(gpmc_irq_domain, hwirq)); + + irq_domain_remove(gpmc_irq_domain); + gpmc_irq_domain = NULL; + + return 0; +} + +static void gpmc_mem_exit(void) +{ + int cs; + + for (cs = 0; cs < gpmc_cs_num; cs++) { + if (!gpmc_cs_mem_enabled(cs)) + continue; + gpmc_cs_delete_mem(cs); + } + +} + +static void gpmc_mem_init(void) +{ + int cs; + + gpmc_mem_root.start = GPMC_MEM_START; + gpmc_mem_root.end = GPMC_MEM_END; + + /* Reserve all regions that has been set up by bootloader */ + for (cs = 0; cs < gpmc_cs_num; cs++) { + u32 base, size; + + if (!gpmc_cs_mem_enabled(cs)) + continue; + gpmc_cs_get_memconf(cs, &base, &size); + if (gpmc_cs_insert_mem(cs, base, size)) { + pr_warn("%s: disabling cs %d mapped at 0x%x-0x%x\n", + __func__, cs, base, base + size); + gpmc_cs_disable_mem(cs); + } + } +} + +static u32 gpmc_round_ps_to_sync_clk(u32 time_ps, u32 sync_clk) +{ + u32 temp; + int div; + + div = gpmc_calc_divider(sync_clk); + temp = gpmc_ps_to_ticks(time_ps); + temp = (temp + div - 1) / div; + return gpmc_ticks_to_ps(temp * div); +} + +/* XXX: can the cycles be avoided ? */ +static int gpmc_calc_sync_read_timings(struct gpmc_timings *gpmc_t, + struct gpmc_device_timings *dev_t, + bool mux) +{ + u32 temp; + + /* adv_rd_off */ + temp = dev_t->t_avdp_r; + /* XXX: mux check required ? */ + if (mux) { + /* XXX: t_avdp not to be required for sync, only added for tusb + * this indirectly necessitates requirement of t_avdp_r and + * t_avdp_w instead of having a single t_avdp + */ + temp = max_t(u32, temp, gpmc_t->clk_activation + dev_t->t_avdh); + temp = max_t(u32, gpmc_t->adv_on + gpmc_ticks_to_ps(1), temp); + } + gpmc_t->adv_rd_off = gpmc_round_ps_to_ticks(temp); + + /* oe_on */ + temp = dev_t->t_oeasu; /* XXX: remove this ? */ + if (mux) { + temp = max_t(u32, temp, gpmc_t->clk_activation + dev_t->t_ach); + temp = max_t(u32, temp, gpmc_t->adv_rd_off + + gpmc_ticks_to_ps(dev_t->cyc_aavdh_oe)); + } + gpmc_t->oe_on = gpmc_round_ps_to_ticks(temp); + + /* access */ + /* XXX: any scope for improvement ?, by combining oe_on + * and clk_activation, need to check whether + * access = clk_activation + round to sync clk ? + */ + temp = max_t(u32, dev_t->t_iaa, dev_t->cyc_iaa * gpmc_t->sync_clk); + temp += gpmc_t->clk_activation; + if (dev_t->cyc_oe) + temp = max_t(u32, temp, gpmc_t->oe_on + + gpmc_ticks_to_ps(dev_t->cyc_oe)); + gpmc_t->access = gpmc_round_ps_to_ticks(temp); + + gpmc_t->oe_off = gpmc_t->access + gpmc_ticks_to_ps(1); + gpmc_t->cs_rd_off = gpmc_t->oe_off; + + /* rd_cycle */ + temp = max_t(u32, dev_t->t_cez_r, dev_t->t_oez); + temp = gpmc_round_ps_to_sync_clk(temp, gpmc_t->sync_clk) + + gpmc_t->access; + /* XXX: barter t_ce_rdyz with t_cez_r ? */ + if (dev_t->t_ce_rdyz) + temp = max_t(u32, temp, gpmc_t->cs_rd_off + dev_t->t_ce_rdyz); + gpmc_t->rd_cycle = gpmc_round_ps_to_ticks(temp); + + return 0; +} + +static int gpmc_calc_sync_write_timings(struct gpmc_timings *gpmc_t, + struct gpmc_device_timings *dev_t, + bool mux) +{ + u32 temp; + + /* adv_wr_off */ + temp = dev_t->t_avdp_w; + if (mux) { + temp = max_t(u32, temp, + gpmc_t->clk_activation + dev_t->t_avdh); + temp = max_t(u32, gpmc_t->adv_on + gpmc_ticks_to_ps(1), temp); + } + gpmc_t->adv_wr_off = gpmc_round_ps_to_ticks(temp); + + /* wr_data_mux_bus */ + temp = max_t(u32, dev_t->t_weasu, + gpmc_t->clk_activation + dev_t->t_rdyo); + /* XXX: shouldn't mux be kept as a whole for wr_data_mux_bus ?, + * and in that case remember to handle we_on properly + */ + if (mux) { + temp = max_t(u32, temp, + gpmc_t->adv_wr_off + dev_t->t_aavdh); + temp = max_t(u32, temp, gpmc_t->adv_wr_off + + gpmc_ticks_to_ps(dev_t->cyc_aavdh_we)); + } + gpmc_t->wr_data_mux_bus = gpmc_round_ps_to_ticks(temp); + + /* we_on */ + if (gpmc_capability & GPMC_HAS_WR_DATA_MUX_BUS) + gpmc_t->we_on = gpmc_round_ps_to_ticks(dev_t->t_weasu); + else + gpmc_t->we_on = gpmc_t->wr_data_mux_bus; + + /* wr_access */ + /* XXX: gpmc_capability check reqd ? , even if not, will not harm */ + gpmc_t->wr_access = gpmc_t->access; + + /* we_off */ + temp = gpmc_t->we_on + dev_t->t_wpl; + temp = max_t(u32, temp, + gpmc_t->wr_access + gpmc_ticks_to_ps(1)); + temp = max_t(u32, temp, + gpmc_t->we_on + gpmc_ticks_to_ps(dev_t->cyc_wpl)); + gpmc_t->we_off = gpmc_round_ps_to_ticks(temp); + + gpmc_t->cs_wr_off = gpmc_round_ps_to_ticks(gpmc_t->we_off + + dev_t->t_wph); + + /* wr_cycle */ + temp = gpmc_round_ps_to_sync_clk(dev_t->t_cez_w, gpmc_t->sync_clk); + temp += gpmc_t->wr_access; + /* XXX: barter t_ce_rdyz with t_cez_w ? */ + if (dev_t->t_ce_rdyz) + temp = max_t(u32, temp, + gpmc_t->cs_wr_off + dev_t->t_ce_rdyz); + gpmc_t->wr_cycle = gpmc_round_ps_to_ticks(temp); + + return 0; +} + +static int gpmc_calc_async_read_timings(struct gpmc_timings *gpmc_t, + struct gpmc_device_timings *dev_t, + bool mux) +{ + u32 temp; + + /* adv_rd_off */ + temp = dev_t->t_avdp_r; + if (mux) + temp = max_t(u32, gpmc_t->adv_on + gpmc_ticks_to_ps(1), temp); + gpmc_t->adv_rd_off = gpmc_round_ps_to_ticks(temp); + + /* oe_on */ + temp = dev_t->t_oeasu; + if (mux) + temp = max_t(u32, temp, + gpmc_t->adv_rd_off + dev_t->t_aavdh); + gpmc_t->oe_on = gpmc_round_ps_to_ticks(temp); + + /* access */ + temp = max_t(u32, dev_t->t_iaa, /* XXX: remove t_iaa in async ? */ + gpmc_t->oe_on + dev_t->t_oe); + temp = max_t(u32, temp, + gpmc_t->cs_on + dev_t->t_ce); + temp = max_t(u32, temp, + gpmc_t->adv_on + dev_t->t_aa); + gpmc_t->access = gpmc_round_ps_to_ticks(temp); + + gpmc_t->oe_off = gpmc_t->access + gpmc_ticks_to_ps(1); + gpmc_t->cs_rd_off = gpmc_t->oe_off; + + /* rd_cycle */ + temp = max_t(u32, dev_t->t_rd_cycle, + gpmc_t->cs_rd_off + dev_t->t_cez_r); + temp = max_t(u32, temp, gpmc_t->oe_off + dev_t->t_oez); + gpmc_t->rd_cycle = gpmc_round_ps_to_ticks(temp); + + return 0; +} + +static int gpmc_calc_async_write_timings(struct gpmc_timings *gpmc_t, + struct gpmc_device_timings *dev_t, + bool mux) +{ + u32 temp; + + /* adv_wr_off */ + temp = dev_t->t_avdp_w; + if (mux) + temp = max_t(u32, gpmc_t->adv_on + gpmc_ticks_to_ps(1), temp); + gpmc_t->adv_wr_off = gpmc_round_ps_to_ticks(temp); + + /* wr_data_mux_bus */ + temp = dev_t->t_weasu; + if (mux) { + temp = max_t(u32, temp, gpmc_t->adv_wr_off + dev_t->t_aavdh); + temp = max_t(u32, temp, gpmc_t->adv_wr_off + + gpmc_ticks_to_ps(dev_t->cyc_aavdh_we)); + } + gpmc_t->wr_data_mux_bus = gpmc_round_ps_to_ticks(temp); + + /* we_on */ + if (gpmc_capability & GPMC_HAS_WR_DATA_MUX_BUS) + gpmc_t->we_on = gpmc_round_ps_to_ticks(dev_t->t_weasu); + else + gpmc_t->we_on = gpmc_t->wr_data_mux_bus; + + /* we_off */ + temp = gpmc_t->we_on + dev_t->t_wpl; + gpmc_t->we_off = gpmc_round_ps_to_ticks(temp); + + gpmc_t->cs_wr_off = gpmc_round_ps_to_ticks(gpmc_t->we_off + + dev_t->t_wph); + + /* wr_cycle */ + temp = max_t(u32, dev_t->t_wr_cycle, + gpmc_t->cs_wr_off + dev_t->t_cez_w); + gpmc_t->wr_cycle = gpmc_round_ps_to_ticks(temp); + + return 0; +} + +static int gpmc_calc_sync_common_timings(struct gpmc_timings *gpmc_t, + struct gpmc_device_timings *dev_t) +{ + u32 temp; + + gpmc_t->sync_clk = gpmc_calc_divider(dev_t->clk) * + gpmc_get_fclk_period(); + + gpmc_t->page_burst_access = gpmc_round_ps_to_sync_clk( + dev_t->t_bacc, + gpmc_t->sync_clk); + + temp = max_t(u32, dev_t->t_ces, dev_t->t_avds); + gpmc_t->clk_activation = gpmc_round_ps_to_ticks(temp); + + if (gpmc_calc_divider(gpmc_t->sync_clk) != 1) + return 0; + + if (dev_t->ce_xdelay) + gpmc_t->bool_timings.cs_extra_delay = true; + if (dev_t->avd_xdelay) + gpmc_t->bool_timings.adv_extra_delay = true; + if (dev_t->oe_xdelay) + gpmc_t->bool_timings.oe_extra_delay = true; + if (dev_t->we_xdelay) + gpmc_t->bool_timings.we_extra_delay = true; + + return 0; +} + +static int gpmc_calc_common_timings(struct gpmc_timings *gpmc_t, + struct gpmc_device_timings *dev_t, + bool sync) +{ + u32 temp; + + /* cs_on */ + gpmc_t->cs_on = gpmc_round_ps_to_ticks(dev_t->t_ceasu); + + /* adv_on */ + temp = dev_t->t_avdasu; + if (dev_t->t_ce_avd) + temp = max_t(u32, temp, + gpmc_t->cs_on + dev_t->t_ce_avd); + gpmc_t->adv_on = gpmc_round_ps_to_ticks(temp); + + if (sync) + gpmc_calc_sync_common_timings(gpmc_t, dev_t); + + return 0; +} + +/* TODO: remove this function once all peripherals are confirmed to + * work with generic timing. Simultaneously gpmc_cs_set_timings() + * has to be modified to handle timings in ps instead of ns +*/ +static void gpmc_convert_ps_to_ns(struct gpmc_timings *t) +{ + t->cs_on /= 1000; + t->cs_rd_off /= 1000; + t->cs_wr_off /= 1000; + t->adv_on /= 1000; + t->adv_rd_off /= 1000; + t->adv_wr_off /= 1000; + t->we_on /= 1000; + t->we_off /= 1000; + t->oe_on /= 1000; + t->oe_off /= 1000; + t->page_burst_access /= 1000; + t->access /= 1000; + t->rd_cycle /= 1000; + t->wr_cycle /= 1000; + t->bus_turnaround /= 1000; + t->cycle2cycle_delay /= 1000; + t->wait_monitoring /= 1000; + t->clk_activation /= 1000; + t->wr_access /= 1000; + t->wr_data_mux_bus /= 1000; +} + +int gpmc_calc_timings(struct gpmc_timings *gpmc_t, + struct gpmc_settings *gpmc_s, + struct gpmc_device_timings *dev_t) +{ + bool mux = false, sync = false; + + if (gpmc_s) { + mux = gpmc_s->mux_add_data ? true : false; + sync = (gpmc_s->sync_read || gpmc_s->sync_write); + } + + memset(gpmc_t, 0, sizeof(*gpmc_t)); + + gpmc_calc_common_timings(gpmc_t, dev_t, sync); + + if (gpmc_s && gpmc_s->sync_read) + gpmc_calc_sync_read_timings(gpmc_t, dev_t, mux); + else + gpmc_calc_async_read_timings(gpmc_t, dev_t, mux); + + if (gpmc_s && gpmc_s->sync_write) + gpmc_calc_sync_write_timings(gpmc_t, dev_t, mux); + else + gpmc_calc_async_write_timings(gpmc_t, dev_t, mux); + + /* TODO: remove, see function definition */ + gpmc_convert_ps_to_ns(gpmc_t); + + return 0; +} + +/** + * gpmc_cs_program_settings - programs non-timing related settings + * @cs: GPMC chip-select to program + * @p: pointer to GPMC settings structure + * + * Programs non-timing related settings for a GPMC chip-select, such as + * bus-width, burst configuration, etc. Function should be called once + * for each chip-select that is being used and must be called before + * calling gpmc_cs_set_timings() as timing parameters in the CONFIG1 + * register will be initialised to zero by this function. Returns 0 on + * success and appropriate negative error code on failure. + */ +int gpmc_cs_program_settings(int cs, struct gpmc_settings *p) +{ + u32 config1; + + if ((!p->device_width) || (p->device_width > GPMC_DEVWIDTH_16BIT)) { + pr_err("%s: invalid width %d!", __func__, p->device_width); + return -EINVAL; + } + + /* Address-data multiplexing not supported for NAND devices */ + if (p->device_nand && p->mux_add_data) { + pr_err("%s: invalid configuration!\n", __func__); + return -EINVAL; + } + + if ((p->mux_add_data > GPMC_MUX_AD) || + ((p->mux_add_data == GPMC_MUX_AAD) && + !(gpmc_capability & GPMC_HAS_MUX_AAD))) { + pr_err("%s: invalid multiplex configuration!\n", __func__); + return -EINVAL; + } + + /* Page/burst mode supports lengths of 4, 8 and 16 bytes */ + if (p->burst_read || p->burst_write) { + switch (p->burst_len) { + case GPMC_BURST_4: + case GPMC_BURST_8: + case GPMC_BURST_16: + break; + default: + pr_err("%s: invalid page/burst-length (%d)\n", + __func__, p->burst_len); + return -EINVAL; + } + } + + if (p->wait_pin > gpmc_nr_waitpins) { + pr_err("%s: invalid wait-pin (%d)\n", __func__, p->wait_pin); + return -EINVAL; + } + + config1 = GPMC_CONFIG1_DEVICESIZE((p->device_width - 1)); + + if (p->sync_read) + config1 |= GPMC_CONFIG1_READTYPE_SYNC; + if (p->sync_write) + config1 |= GPMC_CONFIG1_WRITETYPE_SYNC; + if (p->wait_on_read) + config1 |= GPMC_CONFIG1_WAIT_READ_MON; + if (p->wait_on_write) + config1 |= GPMC_CONFIG1_WAIT_WRITE_MON; + if (p->wait_on_read || p->wait_on_write) + config1 |= GPMC_CONFIG1_WAIT_PIN_SEL(p->wait_pin); + if (p->device_nand) + config1 |= GPMC_CONFIG1_DEVICETYPE(GPMC_DEVICETYPE_NAND); + if (p->mux_add_data) + config1 |= GPMC_CONFIG1_MUXTYPE(p->mux_add_data); + if (p->burst_read) + config1 |= GPMC_CONFIG1_READMULTIPLE_SUPP; + if (p->burst_write) + config1 |= GPMC_CONFIG1_WRITEMULTIPLE_SUPP; + if (p->burst_read || p->burst_write) { + config1 |= GPMC_CONFIG1_PAGE_LEN(p->burst_len >> 3); + config1 |= p->burst_wrap ? GPMC_CONFIG1_WRAPBURST_SUPP : 0; + } + + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, config1); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id gpmc_dt_ids[] = { + { .compatible = "ti,omap2420-gpmc" }, + { .compatible = "ti,omap2430-gpmc" }, + { .compatible = "ti,omap3430-gpmc" }, /* omap3430 & omap3630 */ + { .compatible = "ti,omap4430-gpmc" }, /* omap4430 & omap4460 & omap543x */ + { .compatible = "ti,am3352-gpmc" }, /* am335x devices */ + { } +}; + +/** + * gpmc_read_settings_dt - read gpmc settings from device-tree + * @np: pointer to device-tree node for a gpmc child device + * @p: pointer to gpmc settings structure + * + * Reads the GPMC settings for a GPMC child device from device-tree and + * stores them in the GPMC settings structure passed. The GPMC settings + * structure is initialised to zero by this function and so any + * previously stored settings will be cleared. + */ +void gpmc_read_settings_dt(struct device_node *np, struct gpmc_settings *p) +{ + memset(p, 0, sizeof(struct gpmc_settings)); + + p->sync_read = of_property_read_bool(np, "gpmc,sync-read"); + p->sync_write = of_property_read_bool(np, "gpmc,sync-write"); + of_property_read_u32(np, "gpmc,device-width", &p->device_width); + of_property_read_u32(np, "gpmc,mux-add-data", &p->mux_add_data); + + if (!of_property_read_u32(np, "gpmc,burst-length", &p->burst_len)) { + p->burst_wrap = of_property_read_bool(np, "gpmc,burst-wrap"); + p->burst_read = of_property_read_bool(np, "gpmc,burst-read"); + p->burst_write = of_property_read_bool(np, "gpmc,burst-write"); + if (!p->burst_read && !p->burst_write) + pr_warn("%s: page/burst-length set but not used!\n", + __func__); + } + + if (!of_property_read_u32(np, "gpmc,wait-pin", &p->wait_pin)) { + p->wait_on_read = of_property_read_bool(np, + "gpmc,wait-on-read"); + p->wait_on_write = of_property_read_bool(np, + "gpmc,wait-on-write"); + if (!p->wait_on_read && !p->wait_on_write) + pr_debug("%s: rd/wr wait monitoring not enabled!\n", + __func__); + } +} + +static void __maybe_unused gpmc_read_timings_dt(struct device_node *np, + struct gpmc_timings *gpmc_t) +{ + struct gpmc_bool_timings *p; + + if (!np || !gpmc_t) + return; + + memset(gpmc_t, 0, sizeof(*gpmc_t)); + + /* minimum clock period for syncronous mode */ + of_property_read_u32(np, "gpmc,sync-clk-ps", &gpmc_t->sync_clk); + + /* chip select timtings */ + of_property_read_u32(np, "gpmc,cs-on-ns", &gpmc_t->cs_on); + of_property_read_u32(np, "gpmc,cs-rd-off-ns", &gpmc_t->cs_rd_off); + of_property_read_u32(np, "gpmc,cs-wr-off-ns", &gpmc_t->cs_wr_off); + + /* ADV signal timings */ + of_property_read_u32(np, "gpmc,adv-on-ns", &gpmc_t->adv_on); + of_property_read_u32(np, "gpmc,adv-rd-off-ns", &gpmc_t->adv_rd_off); + of_property_read_u32(np, "gpmc,adv-wr-off-ns", &gpmc_t->adv_wr_off); + of_property_read_u32(np, "gpmc,adv-aad-mux-on-ns", + &gpmc_t->adv_aad_mux_on); + of_property_read_u32(np, "gpmc,adv-aad-mux-rd-off-ns", + &gpmc_t->adv_aad_mux_rd_off); + of_property_read_u32(np, "gpmc,adv-aad-mux-wr-off-ns", + &gpmc_t->adv_aad_mux_wr_off); + + /* WE signal timings */ + of_property_read_u32(np, "gpmc,we-on-ns", &gpmc_t->we_on); + of_property_read_u32(np, "gpmc,we-off-ns", &gpmc_t->we_off); + + /* OE signal timings */ + of_property_read_u32(np, "gpmc,oe-on-ns", &gpmc_t->oe_on); + of_property_read_u32(np, "gpmc,oe-off-ns", &gpmc_t->oe_off); + of_property_read_u32(np, "gpmc,oe-aad-mux-on-ns", + &gpmc_t->oe_aad_mux_on); + of_property_read_u32(np, "gpmc,oe-aad-mux-off-ns", + &gpmc_t->oe_aad_mux_off); + + /* access and cycle timings */ + of_property_read_u32(np, "gpmc,page-burst-access-ns", + &gpmc_t->page_burst_access); + of_property_read_u32(np, "gpmc,access-ns", &gpmc_t->access); + of_property_read_u32(np, "gpmc,rd-cycle-ns", &gpmc_t->rd_cycle); + of_property_read_u32(np, "gpmc,wr-cycle-ns", &gpmc_t->wr_cycle); + of_property_read_u32(np, "gpmc,bus-turnaround-ns", + &gpmc_t->bus_turnaround); + of_property_read_u32(np, "gpmc,cycle2cycle-delay-ns", + &gpmc_t->cycle2cycle_delay); + of_property_read_u32(np, "gpmc,wait-monitoring-ns", + &gpmc_t->wait_monitoring); + of_property_read_u32(np, "gpmc,clk-activation-ns", + &gpmc_t->clk_activation); + + /* only applicable to OMAP3+ */ + of_property_read_u32(np, "gpmc,wr-access-ns", &gpmc_t->wr_access); + of_property_read_u32(np, "gpmc,wr-data-mux-bus-ns", + &gpmc_t->wr_data_mux_bus); + + /* bool timing parameters */ + p = &gpmc_t->bool_timings; + + p->cycle2cyclediffcsen = + of_property_read_bool(np, "gpmc,cycle2cycle-diffcsen"); + p->cycle2cyclesamecsen = + of_property_read_bool(np, "gpmc,cycle2cycle-samecsen"); + p->we_extra_delay = of_property_read_bool(np, "gpmc,we-extra-delay"); + p->oe_extra_delay = of_property_read_bool(np, "gpmc,oe-extra-delay"); + p->adv_extra_delay = of_property_read_bool(np, "gpmc,adv-extra-delay"); + p->cs_extra_delay = of_property_read_bool(np, "gpmc,cs-extra-delay"); + p->time_para_granularity = + of_property_read_bool(np, "gpmc,time-para-granularity"); +} + +/** + * gpmc_probe_generic_child - configures the gpmc for a child device + * @pdev: pointer to gpmc platform device + * @child: pointer to device-tree node for child device + * + * Allocates and configures a GPMC chip-select for a child device. + * Returns 0 on success and appropriate negative error code on failure. + */ +static int gpmc_probe_generic_child(struct platform_device *pdev, + struct device_node *child) +{ + struct gpmc_settings gpmc_s; + struct gpmc_timings gpmc_t; + struct resource res; + unsigned long base; + const char *name; + int ret, cs; + u32 val; + struct gpio_desc *waitpin_desc = NULL; + struct gpmc_device *gpmc = platform_get_drvdata(pdev); + + if (of_property_read_u32(child, "reg", &cs) < 0) { + dev_err(&pdev->dev, "%pOF has no 'reg' property\n", + child); + return -ENODEV; + } + + if (of_address_to_resource(child, 0, &res) < 0) { + dev_err(&pdev->dev, "%pOF has malformed 'reg' property\n", + child); + return -ENODEV; + } + + /* + * Check if we have multiple instances of the same device + * on a single chip select. If so, use the already initialized + * timings. + */ + name = gpmc_cs_get_name(cs); + if (name && of_node_cmp(child->name, name) == 0) + goto no_timings; + + ret = gpmc_cs_request(cs, resource_size(&res), &base); + if (ret < 0) { + dev_err(&pdev->dev, "cannot request GPMC CS %d\n", cs); + return ret; + } + gpmc_cs_set_name(cs, child->name); + + gpmc_read_settings_dt(child, &gpmc_s); + gpmc_read_timings_dt(child, &gpmc_t); + + /* + * For some GPMC devices we still need to rely on the bootloader + * timings because the devices can be connected via FPGA. + * REVISIT: Add timing support from slls644g.pdf. + */ + if (!gpmc_t.cs_rd_off) { + WARN(1, "enable GPMC debug to configure .dts timings for CS%i\n", + cs); + gpmc_cs_show_timings(cs, + "please add GPMC bootloader timings to .dts"); + goto no_timings; + } + + /* CS must be disabled while making changes to gpmc configuration */ + gpmc_cs_disable_mem(cs); + + /* + * FIXME: gpmc_cs_request() will map the CS to an arbitary + * location in the gpmc address space. When booting with + * device-tree we want the NOR flash to be mapped to the + * location specified in the device-tree blob. So remap the + * CS to this location. Once DT migration is complete should + * just make gpmc_cs_request() map a specific address. + */ + ret = gpmc_cs_remap(cs, res.start); + if (ret < 0) { + dev_err(&pdev->dev, "cannot remap GPMC CS %d to %pa\n", + cs, &res.start); + if (res.start < GPMC_MEM_START) { + dev_info(&pdev->dev, + "GPMC CS %d start cannot be lesser than 0x%x\n", + cs, GPMC_MEM_START); + } else if (res.end > GPMC_MEM_END) { + dev_info(&pdev->dev, + "GPMC CS %d end cannot be greater than 0x%x\n", + cs, GPMC_MEM_END); + } + goto err; + } + + if (of_node_cmp(child->name, "nand") == 0) { + /* Warn about older DT blobs with no compatible property */ + if (!of_property_read_bool(child, "compatible")) { + dev_warn(&pdev->dev, + "Incompatible NAND node: missing compatible"); + ret = -EINVAL; + goto err; + } + } + + if (of_node_cmp(child->name, "onenand") == 0) { + /* Warn about older DT blobs with no compatible property */ + if (!of_property_read_bool(child, "compatible")) { + dev_warn(&pdev->dev, + "Incompatible OneNAND node: missing compatible"); + ret = -EINVAL; + goto err; + } + } + + if (of_device_is_compatible(child, "ti,omap2-nand")) { + /* NAND specific setup */ + val = 8; + of_property_read_u32(child, "nand-bus-width", &val); + switch (val) { + case 8: + gpmc_s.device_width = GPMC_DEVWIDTH_8BIT; + break; + case 16: + gpmc_s.device_width = GPMC_DEVWIDTH_16BIT; + break; + default: + dev_err(&pdev->dev, "%s: invalid 'nand-bus-width'\n", + child->name); + ret = -EINVAL; + goto err; + } + + /* disable write protect */ + gpmc_configure(GPMC_CONFIG_WP, 0); + gpmc_s.device_nand = true; + } else { + ret = of_property_read_u32(child, "bank-width", + &gpmc_s.device_width); + if (ret < 0 && !gpmc_s.device_width) { + dev_err(&pdev->dev, + "%pOF has no 'gpmc,device-width' property\n", + child); + goto err; + } + } + + /* Reserve wait pin if it is required and valid */ + if (gpmc_s.wait_on_read || gpmc_s.wait_on_write) { + unsigned int wait_pin = gpmc_s.wait_pin; + + waitpin_desc = gpiochip_request_own_desc(&gpmc->gpio_chip, + wait_pin, "WAITPIN"); + if (IS_ERR(waitpin_desc)) { + dev_err(&pdev->dev, "invalid wait-pin: %d\n", wait_pin); + ret = PTR_ERR(waitpin_desc); + goto err; + } + } + + gpmc_cs_show_timings(cs, "before gpmc_cs_program_settings"); + + ret = gpmc_cs_program_settings(cs, &gpmc_s); + if (ret < 0) + goto err_cs; + + ret = gpmc_cs_set_timings(cs, &gpmc_t, &gpmc_s); + if (ret) { + dev_err(&pdev->dev, "failed to set gpmc timings for: %s\n", + child->name); + goto err_cs; + } + + /* Clear limited address i.e. enable A26-A11 */ + val = gpmc_read_reg(GPMC_CONFIG); + val &= ~GPMC_CONFIG_LIMITEDADDRESS; + gpmc_write_reg(GPMC_CONFIG, val); + + /* Enable CS region */ + gpmc_cs_enable_mem(cs); + +no_timings: + + /* create platform device, NULL on error or when disabled */ + if (!of_platform_device_create(child, NULL, &pdev->dev)) + goto err_child_fail; + + /* is child a common bus? */ + if (of_match_node(of_default_bus_match_table, child)) + /* create children and other common bus children */ + if (of_platform_default_populate(child, NULL, &pdev->dev)) + goto err_child_fail; + + return 0; + +err_child_fail: + + dev_err(&pdev->dev, "failed to create gpmc child %s\n", child->name); + ret = -ENODEV; + +err_cs: + gpiochip_free_own_desc(waitpin_desc); +err: + gpmc_cs_free(cs); + + return ret; +} + +static int gpmc_probe_dt(struct platform_device *pdev) +{ + int ret; + const struct of_device_id *of_id = + of_match_device(gpmc_dt_ids, &pdev->dev); + + if (!of_id) + return 0; + + ret = of_property_read_u32(pdev->dev.of_node, "gpmc,num-cs", + &gpmc_cs_num); + if (ret < 0) { + pr_err("%s: number of chip-selects not defined\n", __func__); + return ret; + } else if (gpmc_cs_num < 1) { + pr_err("%s: all chip-selects are disabled\n", __func__); + return -EINVAL; + } else if (gpmc_cs_num > GPMC_CS_NUM) { + pr_err("%s: number of supported chip-selects cannot be > %d\n", + __func__, GPMC_CS_NUM); + return -EINVAL; + } + + ret = of_property_read_u32(pdev->dev.of_node, "gpmc,num-waitpins", + &gpmc_nr_waitpins); + if (ret < 0) { + pr_err("%s: number of wait pins not found!\n", __func__); + return ret; + } + + return 0; +} + +static void gpmc_probe_dt_children(struct platform_device *pdev) +{ + int ret; + struct device_node *child; + + for_each_available_child_of_node(pdev->dev.of_node, child) { + + if (!child->name) + continue; + + ret = gpmc_probe_generic_child(pdev, child); + if (ret) { + dev_err(&pdev->dev, "failed to probe DT child '%s': %d\n", + child->name, ret); + } + } +} +#else +void gpmc_read_settings_dt(struct device_node *np, struct gpmc_settings *p) +{ + memset(p, 0, sizeof(*p)); +} +static int gpmc_probe_dt(struct platform_device *pdev) +{ + return 0; +} + +static void gpmc_probe_dt_children(struct platform_device *pdev) +{ +} +#endif /* CONFIG_OF */ + +static int gpmc_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + return 1; /* we're input only */ +} + +static int gpmc_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + return 0; /* we're input only */ +} + +static int gpmc_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + return -EINVAL; /* we're input only */ +} + +static void gpmc_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ +} + +static int gpmc_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + u32 reg; + + offset += 8; + + reg = gpmc_read_reg(GPMC_STATUS) & BIT(offset); + + return !!reg; +} + +static int gpmc_gpio_init(struct gpmc_device *gpmc) +{ + int ret; + + gpmc->gpio_chip.parent = gpmc->dev; + gpmc->gpio_chip.owner = THIS_MODULE; + gpmc->gpio_chip.label = DEVICE_NAME; + gpmc->gpio_chip.ngpio = gpmc_nr_waitpins; + gpmc->gpio_chip.get_direction = gpmc_gpio_get_direction; + gpmc->gpio_chip.direction_input = gpmc_gpio_direction_input; + gpmc->gpio_chip.direction_output = gpmc_gpio_direction_output; + gpmc->gpio_chip.set = gpmc_gpio_set; + gpmc->gpio_chip.get = gpmc_gpio_get; + gpmc->gpio_chip.base = -1; + + ret = devm_gpiochip_add_data(gpmc->dev, &gpmc->gpio_chip, NULL); + if (ret < 0) { + dev_err(gpmc->dev, "could not register gpio chip: %d\n", ret); + return ret; + } + + return 0; +} + +static int gpmc_probe(struct platform_device *pdev) +{ + int rc; + u32 l; + struct resource *res; + struct gpmc_device *gpmc; + + gpmc = devm_kzalloc(&pdev->dev, sizeof(*gpmc), GFP_KERNEL); + if (!gpmc) + return -ENOMEM; + + gpmc->dev = &pdev->dev; + platform_set_drvdata(pdev, gpmc); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) + return -ENOENT; + + phys_base = res->start; + mem_size = resource_size(res); + + gpmc_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(gpmc_base)) + return PTR_ERR(gpmc_base); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get resource: irq\n"); + return -ENOENT; + } + + gpmc->irq = res->start; + + gpmc_l3_clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(gpmc_l3_clk)) { + dev_err(&pdev->dev, "Failed to get GPMC fck\n"); + return PTR_ERR(gpmc_l3_clk); + } + + if (!clk_get_rate(gpmc_l3_clk)) { + dev_err(&pdev->dev, "Invalid GPMC fck clock rate\n"); + return -EINVAL; + } + + if (pdev->dev.of_node) { + rc = gpmc_probe_dt(pdev); + if (rc) + return rc; + } else { + gpmc_cs_num = GPMC_CS_NUM; + gpmc_nr_waitpins = GPMC_NR_WAITPINS; + } + + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + l = gpmc_read_reg(GPMC_REVISION); + + /* + * FIXME: Once device-tree migration is complete the below flags + * should be populated based upon the device-tree compatible + * string. For now just use the IP revision. OMAP3+ devices have + * the wr_access and wr_data_mux_bus register fields. OMAP4+ + * devices support the addr-addr-data multiplex protocol. + * + * GPMC IP revisions: + * - OMAP24xx = 2.0 + * - OMAP3xxx = 5.0 + * - OMAP44xx/54xx/AM335x = 6.0 + */ + if (GPMC_REVISION_MAJOR(l) > 0x4) + gpmc_capability = GPMC_HAS_WR_ACCESS | GPMC_HAS_WR_DATA_MUX_BUS; + if (GPMC_REVISION_MAJOR(l) > 0x5) + gpmc_capability |= GPMC_HAS_MUX_AAD; + dev_info(gpmc->dev, "GPMC revision %d.%d\n", GPMC_REVISION_MAJOR(l), + GPMC_REVISION_MINOR(l)); + + gpmc_mem_init(); + rc = gpmc_gpio_init(gpmc); + if (rc) + goto gpio_init_failed; + + gpmc->nirqs = GPMC_NR_NAND_IRQS + gpmc_nr_waitpins; + rc = gpmc_setup_irq(gpmc); + if (rc) { + dev_err(gpmc->dev, "gpmc_setup_irq failed\n"); + goto gpio_init_failed; + } + + gpmc_probe_dt_children(pdev); + + return 0; + +gpio_init_failed: + gpmc_mem_exit(); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return rc; +} + +static int gpmc_remove(struct platform_device *pdev) +{ + struct gpmc_device *gpmc = platform_get_drvdata(pdev); + + gpmc_free_irq(gpmc); + gpmc_mem_exit(); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int gpmc_suspend(struct device *dev) +{ + omap3_gpmc_save_context(); + pm_runtime_put_sync(dev); + return 0; +} + +static int gpmc_resume(struct device *dev) +{ + pm_runtime_get_sync(dev); + omap3_gpmc_restore_context(); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(gpmc_pm_ops, gpmc_suspend, gpmc_resume); + +static struct platform_driver gpmc_driver = { + .probe = gpmc_probe, + .remove = gpmc_remove, + .driver = { + .name = DEVICE_NAME, + .of_match_table = of_match_ptr(gpmc_dt_ids), + .pm = &gpmc_pm_ops, + }, +}; + +static __init int gpmc_init(void) +{ + return platform_driver_register(&gpmc_driver); +} +postcore_initcall(gpmc_init); + +static struct omap3_gpmc_regs gpmc_context; + +void omap3_gpmc_save_context(void) +{ + int i; + + if (!gpmc_base) + return; + + gpmc_context.sysconfig = gpmc_read_reg(GPMC_SYSCONFIG); + gpmc_context.irqenable = gpmc_read_reg(GPMC_IRQENABLE); + gpmc_context.timeout_ctrl = gpmc_read_reg(GPMC_TIMEOUT_CONTROL); + gpmc_context.config = gpmc_read_reg(GPMC_CONFIG); + gpmc_context.prefetch_config1 = gpmc_read_reg(GPMC_PREFETCH_CONFIG1); + gpmc_context.prefetch_config2 = gpmc_read_reg(GPMC_PREFETCH_CONFIG2); + gpmc_context.prefetch_control = gpmc_read_reg(GPMC_PREFETCH_CONTROL); + for (i = 0; i < gpmc_cs_num; i++) { + gpmc_context.cs_context[i].is_valid = gpmc_cs_mem_enabled(i); + if (gpmc_context.cs_context[i].is_valid) { + gpmc_context.cs_context[i].config1 = + gpmc_cs_read_reg(i, GPMC_CS_CONFIG1); + gpmc_context.cs_context[i].config2 = + gpmc_cs_read_reg(i, GPMC_CS_CONFIG2); + gpmc_context.cs_context[i].config3 = + gpmc_cs_read_reg(i, GPMC_CS_CONFIG3); + gpmc_context.cs_context[i].config4 = + gpmc_cs_read_reg(i, GPMC_CS_CONFIG4); + gpmc_context.cs_context[i].config5 = + gpmc_cs_read_reg(i, GPMC_CS_CONFIG5); + gpmc_context.cs_context[i].config6 = + gpmc_cs_read_reg(i, GPMC_CS_CONFIG6); + gpmc_context.cs_context[i].config7 = + gpmc_cs_read_reg(i, GPMC_CS_CONFIG7); + } + } +} + +void omap3_gpmc_restore_context(void) +{ + int i; + + if (!gpmc_base) + return; + + gpmc_write_reg(GPMC_SYSCONFIG, gpmc_context.sysconfig); + gpmc_write_reg(GPMC_IRQENABLE, gpmc_context.irqenable); + gpmc_write_reg(GPMC_TIMEOUT_CONTROL, gpmc_context.timeout_ctrl); + gpmc_write_reg(GPMC_CONFIG, gpmc_context.config); + gpmc_write_reg(GPMC_PREFETCH_CONFIG1, gpmc_context.prefetch_config1); + gpmc_write_reg(GPMC_PREFETCH_CONFIG2, gpmc_context.prefetch_config2); + gpmc_write_reg(GPMC_PREFETCH_CONTROL, gpmc_context.prefetch_control); + for (i = 0; i < gpmc_cs_num; i++) { + if (gpmc_context.cs_context[i].is_valid) { + gpmc_cs_write_reg(i, GPMC_CS_CONFIG1, + gpmc_context.cs_context[i].config1); + gpmc_cs_write_reg(i, GPMC_CS_CONFIG2, + gpmc_context.cs_context[i].config2); + gpmc_cs_write_reg(i, GPMC_CS_CONFIG3, + gpmc_context.cs_context[i].config3); + gpmc_cs_write_reg(i, GPMC_CS_CONFIG4, + gpmc_context.cs_context[i].config4); + gpmc_cs_write_reg(i, GPMC_CS_CONFIG5, + gpmc_context.cs_context[i].config5); + gpmc_cs_write_reg(i, GPMC_CS_CONFIG6, + gpmc_context.cs_context[i].config6); + gpmc_cs_write_reg(i, GPMC_CS_CONFIG7, + gpmc_context.cs_context[i].config7); + } + } +} diff --git a/drivers/memory/pl172.c b/drivers/memory/pl172.c new file mode 100644 index 000000000..ff57195b4 --- /dev/null +++ b/drivers/memory/pl172.c @@ -0,0 +1,321 @@ +/* + * Memory controller driver for ARM PrimeCell PL172 + * PrimeCell MultiPort Memory Controller (PL172) + * + * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com> + * + * Based on: + * TI AEMIF driver, Copyright (C) 2010 - 2013 Texas Instruments Inc. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/amba/bus.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/time.h> + +#define MPMC_STATIC_CFG(n) (0x200 + 0x20 * n) +#define MPMC_STATIC_CFG_MW_8BIT 0x0 +#define MPMC_STATIC_CFG_MW_16BIT 0x1 +#define MPMC_STATIC_CFG_MW_32BIT 0x2 +#define MPMC_STATIC_CFG_PM BIT(3) +#define MPMC_STATIC_CFG_PC BIT(6) +#define MPMC_STATIC_CFG_PB BIT(7) +#define MPMC_STATIC_CFG_EW BIT(8) +#define MPMC_STATIC_CFG_B BIT(19) +#define MPMC_STATIC_CFG_P BIT(20) +#define MPMC_STATIC_WAIT_WEN(n) (0x204 + 0x20 * n) +#define MPMC_STATIC_WAIT_WEN_MAX 0x0f +#define MPMC_STATIC_WAIT_OEN(n) (0x208 + 0x20 * n) +#define MPMC_STATIC_WAIT_OEN_MAX 0x0f +#define MPMC_STATIC_WAIT_RD(n) (0x20c + 0x20 * n) +#define MPMC_STATIC_WAIT_RD_MAX 0x1f +#define MPMC_STATIC_WAIT_PAGE(n) (0x210 + 0x20 * n) +#define MPMC_STATIC_WAIT_PAGE_MAX 0x1f +#define MPMC_STATIC_WAIT_WR(n) (0x214 + 0x20 * n) +#define MPMC_STATIC_WAIT_WR_MAX 0x1f +#define MPMC_STATIC_WAIT_TURN(n) (0x218 + 0x20 * n) +#define MPMC_STATIC_WAIT_TURN_MAX 0x0f + +/* Maximum number of static chip selects */ +#define PL172_MAX_CS 4 + +struct pl172_data { + void __iomem *base; + unsigned long rate; + struct clk *clk; +}; + +static int pl172_timing_prop(struct amba_device *adev, + const struct device_node *np, const char *name, + u32 reg_offset, u32 max, int start) +{ + struct pl172_data *pl172 = amba_get_drvdata(adev); + int cycles; + u32 val; + + if (!of_property_read_u32(np, name, &val)) { + cycles = DIV_ROUND_UP(val * pl172->rate, NSEC_PER_MSEC) - start; + if (cycles < 0) { + cycles = 0; + } else if (cycles > max) { + dev_err(&adev->dev, "%s timing too tight\n", name); + return -EINVAL; + } + + writel(cycles, pl172->base + reg_offset); + } + + dev_dbg(&adev->dev, "%s: %u cycle(s)\n", name, start + + readl(pl172->base + reg_offset)); + + return 0; +} + +static int pl172_setup_static(struct amba_device *adev, + struct device_node *np, u32 cs) +{ + struct pl172_data *pl172 = amba_get_drvdata(adev); + u32 cfg; + int ret; + + /* MPMC static memory configuration */ + if (!of_property_read_u32(np, "mpmc,memory-width", &cfg)) { + if (cfg == 8) { + cfg = MPMC_STATIC_CFG_MW_8BIT; + } else if (cfg == 16) { + cfg = MPMC_STATIC_CFG_MW_16BIT; + } else if (cfg == 32) { + cfg = MPMC_STATIC_CFG_MW_32BIT; + } else { + dev_err(&adev->dev, "invalid memory width cs%u\n", cs); + return -EINVAL; + } + } else { + dev_err(&adev->dev, "memory-width property required\n"); + return -EINVAL; + } + + if (of_property_read_bool(np, "mpmc,async-page-mode")) + cfg |= MPMC_STATIC_CFG_PM; + + if (of_property_read_bool(np, "mpmc,cs-active-high")) + cfg |= MPMC_STATIC_CFG_PC; + + if (of_property_read_bool(np, "mpmc,byte-lane-low")) + cfg |= MPMC_STATIC_CFG_PB; + + if (of_property_read_bool(np, "mpmc,extended-wait")) + cfg |= MPMC_STATIC_CFG_EW; + + if (amba_part(adev) == 0x172 && + of_property_read_bool(np, "mpmc,buffer-enable")) + cfg |= MPMC_STATIC_CFG_B; + + if (of_property_read_bool(np, "mpmc,write-protect")) + cfg |= MPMC_STATIC_CFG_P; + + writel(cfg, pl172->base + MPMC_STATIC_CFG(cs)); + dev_dbg(&adev->dev, "mpmc static config cs%u: 0x%08x\n", cs, cfg); + + /* MPMC static memory timing */ + ret = pl172_timing_prop(adev, np, "mpmc,write-enable-delay", + MPMC_STATIC_WAIT_WEN(cs), + MPMC_STATIC_WAIT_WEN_MAX, 1); + if (ret) + goto fail; + + ret = pl172_timing_prop(adev, np, "mpmc,output-enable-delay", + MPMC_STATIC_WAIT_OEN(cs), + MPMC_STATIC_WAIT_OEN_MAX, 0); + if (ret) + goto fail; + + ret = pl172_timing_prop(adev, np, "mpmc,read-access-delay", + MPMC_STATIC_WAIT_RD(cs), + MPMC_STATIC_WAIT_RD_MAX, 1); + if (ret) + goto fail; + + ret = pl172_timing_prop(adev, np, "mpmc,page-mode-read-delay", + MPMC_STATIC_WAIT_PAGE(cs), + MPMC_STATIC_WAIT_PAGE_MAX, 1); + if (ret) + goto fail; + + ret = pl172_timing_prop(adev, np, "mpmc,write-access-delay", + MPMC_STATIC_WAIT_WR(cs), + MPMC_STATIC_WAIT_WR_MAX, 2); + if (ret) + goto fail; + + ret = pl172_timing_prop(adev, np, "mpmc,turn-round-delay", + MPMC_STATIC_WAIT_TURN(cs), + MPMC_STATIC_WAIT_TURN_MAX, 1); + if (ret) + goto fail; + + return 0; +fail: + dev_err(&adev->dev, "failed to configure cs%u\n", cs); + return ret; +} + +static int pl172_parse_cs_config(struct amba_device *adev, + struct device_node *np) +{ + u32 cs; + + if (!of_property_read_u32(np, "mpmc,cs", &cs)) { + if (cs >= PL172_MAX_CS) { + dev_err(&adev->dev, "cs%u invalid\n", cs); + return -EINVAL; + } + + return pl172_setup_static(adev, np, cs); + } + + dev_err(&adev->dev, "cs property required\n"); + + return -EINVAL; +} + +static const char * const pl172_revisions[] = {"r1", "r2", "r2p3", "r2p4"}; +static const char * const pl175_revisions[] = {"r1"}; +static const char * const pl176_revisions[] = {"r0"}; + +static int pl172_probe(struct amba_device *adev, const struct amba_id *id) +{ + struct device_node *child_np, *np = adev->dev.of_node; + struct device *dev = &adev->dev; + static const char *rev = "?"; + struct pl172_data *pl172; + int ret; + + if (amba_part(adev) == 0x172) { + if (amba_rev(adev) < ARRAY_SIZE(pl172_revisions)) + rev = pl172_revisions[amba_rev(adev)]; + } else if (amba_part(adev) == 0x175) { + if (amba_rev(adev) < ARRAY_SIZE(pl175_revisions)) + rev = pl175_revisions[amba_rev(adev)]; + } else if (amba_part(adev) == 0x176) { + if (amba_rev(adev) < ARRAY_SIZE(pl176_revisions)) + rev = pl176_revisions[amba_rev(adev)]; + } + + dev_info(dev, "ARM PL%x revision %s\n", amba_part(adev), rev); + + pl172 = devm_kzalloc(dev, sizeof(*pl172), GFP_KERNEL); + if (!pl172) + return -ENOMEM; + + pl172->clk = devm_clk_get(dev, "mpmcclk"); + if (IS_ERR(pl172->clk)) { + dev_err(dev, "no mpmcclk provided clock\n"); + return PTR_ERR(pl172->clk); + } + + ret = clk_prepare_enable(pl172->clk); + if (ret) { + dev_err(dev, "unable to mpmcclk enable clock\n"); + return ret; + } + + pl172->rate = clk_get_rate(pl172->clk) / MSEC_PER_SEC; + if (!pl172->rate) { + dev_err(dev, "unable to get mpmcclk clock rate\n"); + ret = -EINVAL; + goto err_clk_enable; + } + + ret = amba_request_regions(adev, NULL); + if (ret) { + dev_err(dev, "unable to request AMBA regions\n"); + goto err_clk_enable; + } + + pl172->base = devm_ioremap(dev, adev->res.start, + resource_size(&adev->res)); + if (!pl172->base) { + dev_err(dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_no_ioremap; + } + + amba_set_drvdata(adev, pl172); + + /* + * Loop through each child node, which represent a chip select, and + * configure parameters and timing. If successful; populate devices + * under that node. + */ + for_each_available_child_of_node(np, child_np) { + ret = pl172_parse_cs_config(adev, child_np); + if (ret) + continue; + + of_platform_populate(child_np, NULL, NULL, dev); + } + + return 0; + +err_no_ioremap: + amba_release_regions(adev); +err_clk_enable: + clk_disable_unprepare(pl172->clk); + return ret; +} + +static int pl172_remove(struct amba_device *adev) +{ + struct pl172_data *pl172 = amba_get_drvdata(adev); + + clk_disable_unprepare(pl172->clk); + amba_release_regions(adev); + + return 0; +} + +static const struct amba_id pl172_ids[] = { + /* PrimeCell MPMC PL172, EMC found on NXP LPC18xx and LPC43xx */ + { + .id = 0x07041172, + .mask = 0x3f0fffff, + }, + /* PrimeCell MPMC PL175, EMC found on NXP LPC32xx */ + { + .id = 0x07041175, + .mask = 0x3f0fffff, + }, + /* PrimeCell MPMC PL176 */ + { + .id = 0x89041176, + .mask = 0xff0fffff, + }, + { 0, 0 }, +}; +MODULE_DEVICE_TABLE(amba, pl172_ids); + +static struct amba_driver pl172_driver = { + .drv = { + .name = "memory-pl172", + }, + .probe = pl172_probe, + .remove = pl172_remove, + .id_table = pl172_ids, +}; +module_amba_driver(pl172_driver); + +MODULE_AUTHOR("Joachim Eastwood <manabian@gmail.com>"); +MODULE_DESCRIPTION("PL172 Memory Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/memory/samsung/Kconfig b/drivers/memory/samsung/Kconfig new file mode 100644 index 000000000..79ce7ea58 --- /dev/null +++ b/drivers/memory/samsung/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +config SAMSUNG_MC + bool "Samsung Exynos Memory Controller support" if COMPILE_TEST + help + Support for the Memory Controller (MC) devices found on + Samsung Exynos SoCs. + +if SAMSUNG_MC + +config EXYNOS_SROM + bool "Exynos SROM controller driver" if COMPILE_TEST + depends on (ARM && ARCH_EXYNOS) || (COMPILE_TEST && HAS_IOMEM) + +endif diff --git a/drivers/memory/samsung/Makefile b/drivers/memory/samsung/Makefile new file mode 100644 index 000000000..00587be66 --- /dev/null +++ b/drivers/memory/samsung/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_EXYNOS_SROM) += exynos-srom.o diff --git a/drivers/memory/samsung/exynos-srom.c b/drivers/memory/samsung/exynos-srom.c new file mode 100644 index 000000000..7edd7fb54 --- /dev/null +++ b/drivers/memory/samsung/exynos-srom.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (c) 2015 Samsung Electronics Co., Ltd. +// http://www.samsung.com/ +// +// EXYNOS - SROM Controller support +// Author: Pankaj Dubey <pankaj.dubey@samsung.com> + +#include <linux/io.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include "exynos-srom.h" + +static const unsigned long exynos_srom_offsets[] = { + /* SROM side */ + EXYNOS_SROM_BW, + EXYNOS_SROM_BC0, + EXYNOS_SROM_BC1, + EXYNOS_SROM_BC2, + EXYNOS_SROM_BC3, +}; + +/** + * struct exynos_srom_reg_dump: register dump of SROM Controller registers. + * @offset: srom register offset from the controller base address. + * @value: the value of register under the offset. + */ +struct exynos_srom_reg_dump { + u32 offset; + u32 value; +}; + +/** + * struct exynos_srom: platform data for exynos srom controller driver. + * @dev: platform device pointer + * @reg_base: srom base address + * @reg_offset: exynos_srom_reg_dump pointer to hold offset and its value. + */ +struct exynos_srom { + struct device *dev; + void __iomem *reg_base; + struct exynos_srom_reg_dump *reg_offset; +}; + +static struct exynos_srom_reg_dump *exynos_srom_alloc_reg_dump( + const unsigned long *rdump, + unsigned long nr_rdump) +{ + struct exynos_srom_reg_dump *rd; + unsigned int i; + + rd = kcalloc(nr_rdump, sizeof(*rd), GFP_KERNEL); + if (!rd) + return NULL; + + for (i = 0; i < nr_rdump; ++i) + rd[i].offset = rdump[i]; + + return rd; +} + +static int exynos_srom_configure_bank(struct exynos_srom *srom, + struct device_node *np) +{ + u32 bank, width, pmc = 0; + u32 timing[6]; + u32 cs, bw; + + if (of_property_read_u32(np, "reg", &bank)) + return -EINVAL; + if (of_property_read_u32(np, "reg-io-width", &width)) + width = 1; + if (of_property_read_bool(np, "samsung,srom-page-mode")) + pmc = 1 << EXYNOS_SROM_BCX__PMC__SHIFT; + if (of_property_read_u32_array(np, "samsung,srom-timing", timing, + ARRAY_SIZE(timing))) + return -EINVAL; + + bank *= 4; /* Convert bank into shift/offset */ + + cs = 1 << EXYNOS_SROM_BW__BYTEENABLE__SHIFT; + if (width == 2) + cs |= 1 << EXYNOS_SROM_BW__DATAWIDTH__SHIFT; + + bw = readl_relaxed(srom->reg_base + EXYNOS_SROM_BW); + bw = (bw & ~(EXYNOS_SROM_BW__CS_MASK << bank)) | (cs << bank); + writel_relaxed(bw, srom->reg_base + EXYNOS_SROM_BW); + + writel_relaxed(pmc | (timing[0] << EXYNOS_SROM_BCX__TACP__SHIFT) | + (timing[1] << EXYNOS_SROM_BCX__TCAH__SHIFT) | + (timing[2] << EXYNOS_SROM_BCX__TCOH__SHIFT) | + (timing[3] << EXYNOS_SROM_BCX__TACC__SHIFT) | + (timing[4] << EXYNOS_SROM_BCX__TCOS__SHIFT) | + (timing[5] << EXYNOS_SROM_BCX__TACS__SHIFT), + srom->reg_base + EXYNOS_SROM_BC0 + bank); + + return 0; +} + +static int exynos_srom_probe(struct platform_device *pdev) +{ + struct device_node *np, *child; + struct exynos_srom *srom; + struct device *dev = &pdev->dev; + bool bad_bank_config = false; + + np = dev->of_node; + if (!np) { + dev_err(&pdev->dev, "could not find device info\n"); + return -EINVAL; + } + + srom = devm_kzalloc(&pdev->dev, + sizeof(struct exynos_srom), GFP_KERNEL); + if (!srom) + return -ENOMEM; + + srom->dev = dev; + srom->reg_base = of_iomap(np, 0); + if (!srom->reg_base) { + dev_err(&pdev->dev, "iomap of exynos srom controller failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, srom); + + srom->reg_offset = exynos_srom_alloc_reg_dump(exynos_srom_offsets, + ARRAY_SIZE(exynos_srom_offsets)); + if (!srom->reg_offset) { + iounmap(srom->reg_base); + return -ENOMEM; + } + + for_each_child_of_node(np, child) { + if (exynos_srom_configure_bank(srom, child)) { + dev_err(dev, + "Could not decode bank configuration for %s\n", + child->name); + bad_bank_config = true; + } + } + + /* + * If any bank failed to configure, we still provide suspend/resume, + * but do not probe child devices + */ + if (bad_bank_config) + return 0; + + return of_platform_populate(np, NULL, NULL, dev); +} + +#ifdef CONFIG_PM_SLEEP +static void exynos_srom_save(void __iomem *base, + struct exynos_srom_reg_dump *rd, + unsigned int num_regs) +{ + for (; num_regs > 0; --num_regs, ++rd) + rd->value = readl(base + rd->offset); +} + +static void exynos_srom_restore(void __iomem *base, + const struct exynos_srom_reg_dump *rd, + unsigned int num_regs) +{ + for (; num_regs > 0; --num_regs, ++rd) + writel(rd->value, base + rd->offset); +} + +static int exynos_srom_suspend(struct device *dev) +{ + struct exynos_srom *srom = dev_get_drvdata(dev); + + exynos_srom_save(srom->reg_base, srom->reg_offset, + ARRAY_SIZE(exynos_srom_offsets)); + return 0; +} + +static int exynos_srom_resume(struct device *dev) +{ + struct exynos_srom *srom = dev_get_drvdata(dev); + + exynos_srom_restore(srom->reg_base, srom->reg_offset, + ARRAY_SIZE(exynos_srom_offsets)); + return 0; +} +#endif + +static const struct of_device_id of_exynos_srom_ids[] = { + { + .compatible = "samsung,exynos4210-srom", + }, + {}, +}; + +static SIMPLE_DEV_PM_OPS(exynos_srom_pm_ops, exynos_srom_suspend, exynos_srom_resume); + +static struct platform_driver exynos_srom_driver = { + .probe = exynos_srom_probe, + .driver = { + .name = "exynos-srom", + .of_match_table = of_exynos_srom_ids, + .pm = &exynos_srom_pm_ops, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver(exynos_srom_driver); diff --git a/drivers/memory/samsung/exynos-srom.h b/drivers/memory/samsung/exynos-srom.h new file mode 100644 index 000000000..da612797f --- /dev/null +++ b/drivers/memory/samsung/exynos-srom.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Exynos SROMC register definitions + */ + +#ifndef __EXYNOS_SROM_H +#define __EXYNOS_SROM_H __FILE__ + +#define EXYNOS_SROMREG(x) (x) + +#define EXYNOS_SROM_BW EXYNOS_SROMREG(0x0) +#define EXYNOS_SROM_BC0 EXYNOS_SROMREG(0x4) +#define EXYNOS_SROM_BC1 EXYNOS_SROMREG(0x8) +#define EXYNOS_SROM_BC2 EXYNOS_SROMREG(0xc) +#define EXYNOS_SROM_BC3 EXYNOS_SROMREG(0x10) +#define EXYNOS_SROM_BC4 EXYNOS_SROMREG(0x14) +#define EXYNOS_SROM_BC5 EXYNOS_SROMREG(0x18) + +/* one register BW holds 4 x 4-bit packed settings for NCS0 - NCS3 */ + +#define EXYNOS_SROM_BW__DATAWIDTH__SHIFT 0 +#define EXYNOS_SROM_BW__ADDRMODE__SHIFT 1 +#define EXYNOS_SROM_BW__WAITENABLE__SHIFT 2 +#define EXYNOS_SROM_BW__BYTEENABLE__SHIFT 3 + +#define EXYNOS_SROM_BW__CS_MASK 0xf + +#define EXYNOS_SROM_BW__NCS0__SHIFT 0 +#define EXYNOS_SROM_BW__NCS1__SHIFT 4 +#define EXYNOS_SROM_BW__NCS2__SHIFT 8 +#define EXYNOS_SROM_BW__NCS3__SHIFT 12 +#define EXYNOS_SROM_BW__NCS4__SHIFT 16 +#define EXYNOS_SROM_BW__NCS5__SHIFT 20 + +/* applies to same to BCS0 - BCS3 */ + +#define EXYNOS_SROM_BCX__PMC__SHIFT 0 +#define EXYNOS_SROM_BCX__TACP__SHIFT 4 +#define EXYNOS_SROM_BCX__TCAH__SHIFT 8 +#define EXYNOS_SROM_BCX__TCOH__SHIFT 12 +#define EXYNOS_SROM_BCX__TACC__SHIFT 16 +#define EXYNOS_SROM_BCX__TCOS__SHIFT 24 +#define EXYNOS_SROM_BCX__TACS__SHIFT 28 + +#endif /* __EXYNOS_SROM_H */ diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig new file mode 100644 index 000000000..6d74e499e --- /dev/null +++ b/drivers/memory/tegra/Kconfig @@ -0,0 +1,17 @@ +config TEGRA_MC + bool "NVIDIA Tegra Memory Controller support" + default y + depends on ARCH_TEGRA + help + This driver supports the Memory Controller (MC) hardware found on + NVIDIA Tegra SoCs. + +config TEGRA124_EMC + bool "NVIDIA Tegra124 External Memory Controller driver" + default y + depends on TEGRA_MC && ARCH_TEGRA_124_SOC + help + This driver is for the External Memory Controller (EMC) found on + Tegra124 chips. The EMC controls the external DRAM on the board. + This driver is required to change memory timings / clock rate for + external memory. diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile new file mode 100644 index 000000000..94ab16ba0 --- /dev/null +++ b/drivers/memory/tegra/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +tegra-mc-y := mc.o + +tegra-mc-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20.o +tegra-mc-$(CONFIG_ARCH_TEGRA_3x_SOC) += tegra30.o +tegra-mc-$(CONFIG_ARCH_TEGRA_114_SOC) += tegra114.o +tegra-mc-$(CONFIG_ARCH_TEGRA_124_SOC) += tegra124.o +tegra-mc-$(CONFIG_ARCH_TEGRA_132_SOC) += tegra124.o +tegra-mc-$(CONFIG_ARCH_TEGRA_210_SOC) += tegra210.o + +obj-$(CONFIG_TEGRA_MC) += tegra-mc.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 new file mode 100644 index 000000000..346d8eadb --- /dev/null +++ b/drivers/memory/tegra/mc.c @@ -0,0 +1,727 @@ +/* + * Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sort.h> + +#include <soc/tegra/fuse.h> + +#include "mc.h" + +#define MC_INTSTATUS 0x000 + +#define MC_INTMASK 0x004 + +#define MC_ERR_STATUS 0x08 +#define MC_ERR_STATUS_TYPE_SHIFT 28 +#define MC_ERR_STATUS_TYPE_INVALID_SMMU_PAGE (6 << MC_ERR_STATUS_TYPE_SHIFT) +#define MC_ERR_STATUS_TYPE_MASK (0x7 << MC_ERR_STATUS_TYPE_SHIFT) +#define MC_ERR_STATUS_READABLE (1 << 27) +#define MC_ERR_STATUS_WRITABLE (1 << 26) +#define MC_ERR_STATUS_NONSECURE (1 << 25) +#define MC_ERR_STATUS_ADR_HI_SHIFT 20 +#define MC_ERR_STATUS_ADR_HI_MASK 0x3 +#define MC_ERR_STATUS_SECURITY (1 << 17) +#define MC_ERR_STATUS_RW (1 << 16) + +#define MC_ERR_ADR 0x0c + +#define MC_DECERR_EMEM_OTHERS_STATUS 0x58 +#define MC_SECURITY_VIOLATION_STATUS 0x74 + +#define MC_EMEM_ARB_CFG 0x90 +#define MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE(x) (((x) & 0x1ff) << 0) +#define MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE_MASK 0x1ff +#define MC_EMEM_ARB_MISC0 0xd8 + +#define MC_EMEM_ADR_CFG 0x54 +#define MC_EMEM_ADR_CFG_EMEM_NUMDEV BIT(0) + +static const struct of_device_id tegra_mc_of_match[] = { +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + { .compatible = "nvidia,tegra20-mc", .data = &tegra20_mc_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_3x_SOC + { .compatible = "nvidia,tegra30-mc", .data = &tegra30_mc_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_114_SOC + { .compatible = "nvidia,tegra114-mc", .data = &tegra114_mc_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_124_SOC + { .compatible = "nvidia,tegra124-mc", .data = &tegra124_mc_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_132_SOC + { .compatible = "nvidia,tegra132-mc", .data = &tegra132_mc_soc }, +#endif +#ifdef CONFIG_ARCH_TEGRA_210_SOC + { .compatible = "nvidia,tegra210-mc", .data = &tegra210_mc_soc }, +#endif + { } +}; +MODULE_DEVICE_TABLE(of, tegra_mc_of_match); + +static int terga_mc_block_dma_common(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->control) | BIT(rst->bit); + mc_writel(mc, value, rst->control); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +static bool terga_mc_dma_idling_common(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + return (mc_readl(mc, rst->status) & BIT(rst->bit)) != 0; +} + +static int terga_mc_unblock_dma_common(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->control) & ~BIT(rst->bit); + mc_writel(mc, value, rst->control); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +static int terga_mc_reset_status_common(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + return (mc_readl(mc, rst->control) & BIT(rst->bit)) != 0; +} + +const struct tegra_mc_reset_ops terga_mc_reset_ops_common = { + .block_dma = terga_mc_block_dma_common, + .dma_idling = terga_mc_dma_idling_common, + .unblock_dma = terga_mc_unblock_dma_common, + .reset_status = terga_mc_reset_status_common, +}; + +static inline struct tegra_mc *reset_to_mc(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct tegra_mc, reset); +} + +static const struct tegra_mc_reset *tegra_mc_reset_find(struct tegra_mc *mc, + unsigned long id) +{ + unsigned int i; + + for (i = 0; i < mc->soc->num_resets; i++) + if (mc->soc->resets[i].id == id) + return &mc->soc->resets[i]; + + return NULL; +} + +static int tegra_mc_hotreset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct tegra_mc *mc = reset_to_mc(rcdev); + const struct tegra_mc_reset_ops *rst_ops; + const struct tegra_mc_reset *rst; + int retries = 500; + int err; + + rst = tegra_mc_reset_find(mc, id); + if (!rst) + return -ENODEV; + + rst_ops = mc->soc->reset_ops; + if (!rst_ops) + return -ENODEV; + + if (rst_ops->block_dma) { + /* block clients DMA requests */ + err = rst_ops->block_dma(mc, rst); + if (err) { + dev_err(mc->dev, "Failed to block %s DMA: %d\n", + rst->name, err); + return err; + } + } + + if (rst_ops->dma_idling) { + /* wait for completion of the outstanding DMA requests */ + while (!rst_ops->dma_idling(mc, rst)) { + if (!retries--) { + dev_err(mc->dev, "Failed to flush %s DMA\n", + rst->name); + return -EBUSY; + } + + usleep_range(10, 100); + } + } + + if (rst_ops->hotreset_assert) { + /* clear clients DMA requests sitting before arbitration */ + err = rst_ops->hotreset_assert(mc, rst); + if (err) { + dev_err(mc->dev, "Failed to hot reset %s: %d\n", + rst->name, err); + return err; + } + } + + return 0; +} + +static int tegra_mc_hotreset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct tegra_mc *mc = reset_to_mc(rcdev); + const struct tegra_mc_reset_ops *rst_ops; + const struct tegra_mc_reset *rst; + int err; + + rst = tegra_mc_reset_find(mc, id); + if (!rst) + return -ENODEV; + + rst_ops = mc->soc->reset_ops; + if (!rst_ops) + return -ENODEV; + + if (rst_ops->hotreset_deassert) { + /* take out client from hot reset */ + err = rst_ops->hotreset_deassert(mc, rst); + if (err) { + dev_err(mc->dev, "Failed to deassert hot reset %s: %d\n", + rst->name, err); + return err; + } + } + + if (rst_ops->unblock_dma) { + /* allow new DMA requests to proceed to arbitration */ + err = rst_ops->unblock_dma(mc, rst); + if (err) { + dev_err(mc->dev, "Failed to unblock %s DMA : %d\n", + rst->name, err); + return err; + } + } + + return 0; +} + +static int tegra_mc_hotreset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct tegra_mc *mc = reset_to_mc(rcdev); + const struct tegra_mc_reset_ops *rst_ops; + const struct tegra_mc_reset *rst; + + rst = tegra_mc_reset_find(mc, id); + if (!rst) + return -ENODEV; + + rst_ops = mc->soc->reset_ops; + if (!rst_ops) + return -ENODEV; + + return rst_ops->reset_status(mc, rst); +} + +static const struct reset_control_ops tegra_mc_reset_ops = { + .assert = tegra_mc_hotreset_assert, + .deassert = tegra_mc_hotreset_deassert, + .status = tegra_mc_hotreset_status, +}; + +static int tegra_mc_reset_setup(struct tegra_mc *mc) +{ + int err; + + mc->reset.ops = &tegra_mc_reset_ops; + mc->reset.owner = THIS_MODULE; + mc->reset.of_node = mc->dev->of_node; + mc->reset.of_reset_n_cells = 1; + mc->reset.nr_resets = mc->soc->num_resets; + + err = reset_controller_register(&mc->reset); + if (err < 0) + return err; + + return 0; +} + +static int tegra_mc_setup_latency_allowance(struct tegra_mc *mc) +{ + unsigned long long tick; + unsigned int i; + u32 value; + + /* compute the number of MC clock cycles per tick */ + tick = (unsigned long long)mc->tick * clk_get_rate(mc->clk); + do_div(tick, NSEC_PER_SEC); + + value = readl(mc->regs + MC_EMEM_ARB_CFG); + value &= ~MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE_MASK; + value |= MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE(tick); + writel(value, mc->regs + MC_EMEM_ARB_CFG); + + /* write latency allowance defaults */ + for (i = 0; i < mc->soc->num_clients; i++) { + const struct tegra_mc_la *la = &mc->soc->clients[i].la; + u32 value; + + value = readl(mc->regs + la->reg); + value &= ~(la->mask << la->shift); + value |= (la->def & la->mask) << la->shift; + writel(value, mc->regs + la->reg); + } + + return 0; +} + +void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate) +{ + unsigned int i; + struct tegra_mc_timing *timing = NULL; + + for (i = 0; i < mc->num_timings; i++) { + if (mc->timings[i].rate == rate) { + timing = &mc->timings[i]; + break; + } + } + + if (!timing) { + dev_err(mc->dev, "no memory timing registered for rate %lu\n", + rate); + return; + } + + for (i = 0; i < mc->soc->num_emem_regs; ++i) + mc_writel(mc, timing->emem_data[i], mc->soc->emem_regs[i]); +} + +unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc) +{ + u8 dram_count; + + dram_count = mc_readl(mc, MC_EMEM_ADR_CFG); + dram_count &= MC_EMEM_ADR_CFG_EMEM_NUMDEV; + dram_count++; + + return dram_count; +} + +static int load_one_timing(struct tegra_mc *mc, + struct tegra_mc_timing *timing, + struct device_node *node) +{ + int err; + u32 tmp; + + err = of_property_read_u32(node, "clock-frequency", &tmp); + if (err) { + dev_err(mc->dev, + "timing %s: failed to read rate\n", node->name); + return err; + } + + timing->rate = tmp; + timing->emem_data = devm_kcalloc(mc->dev, mc->soc->num_emem_regs, + sizeof(u32), GFP_KERNEL); + if (!timing->emem_data) + return -ENOMEM; + + err = of_property_read_u32_array(node, "nvidia,emem-configuration", + timing->emem_data, + mc->soc->num_emem_regs); + if (err) { + dev_err(mc->dev, + "timing %s: failed to read EMEM configuration\n", + node->name); + return err; + } + + return 0; +} + +static int load_timings(struct tegra_mc *mc, struct device_node *node) +{ + struct device_node *child; + struct tegra_mc_timing *timing; + int child_count = of_get_child_count(node); + int i = 0, err; + + mc->timings = devm_kcalloc(mc->dev, child_count, sizeof(*timing), + GFP_KERNEL); + if (!mc->timings) + return -ENOMEM; + + mc->num_timings = child_count; + + for_each_child_of_node(node, child) { + timing = &mc->timings[i++]; + + err = load_one_timing(mc, timing, child); + if (err) { + of_node_put(child); + return err; + } + } + + return 0; +} + +static int tegra_mc_setup_timings(struct tegra_mc *mc) +{ + struct device_node *node; + u32 ram_code, node_ram_code; + int err; + + ram_code = tegra_read_ram_code(); + + mc->num_timings = 0; + + for_each_child_of_node(mc->dev->of_node, node) { + err = of_property_read_u32(node, "nvidia,ram-code", + &node_ram_code); + if (err || (node_ram_code != ram_code)) + continue; + + err = load_timings(mc, node); + of_node_put(node); + if (err) + return err; + break; + } + + if (mc->num_timings == 0) + dev_warn(mc->dev, + "no memory timings for RAM code %u registered\n", + ram_code); + + return 0; +} + +static const char *const status_names[32] = { + [ 1] = "External interrupt", + [ 6] = "EMEM address decode error", + [ 7] = "GART page fault", + [ 8] = "Security violation", + [ 9] = "EMEM arbitration error", + [10] = "Page fault", + [11] = "Invalid APB ASID update", + [12] = "VPR violation", + [13] = "Secure carveout violation", + [16] = "MTS carveout violation", +}; + +static const char *const error_names[8] = { + [2] = "EMEM decode error", + [3] = "TrustZone violation", + [4] = "Carveout violation", + [6] = "SMMU translation error", +}; + +static irqreturn_t tegra_mc_irq(int irq, void *data) +{ + struct tegra_mc *mc = data; + unsigned long status; + unsigned int bit; + + /* mask all interrupts to avoid flooding */ + status = mc_readl(mc, MC_INTSTATUS) & mc->soc->intmask; + if (!status) + return IRQ_NONE; + + for_each_set_bit(bit, &status, 32) { + const char *error = status_names[bit] ?: "unknown"; + const char *client = "unknown", *desc; + const char *direction, *secure; + phys_addr_t addr = 0; + unsigned int i; + char perm[7]; + u8 id, type; + u32 value; + + value = mc_readl(mc, MC_ERR_STATUS); + +#ifdef CONFIG_PHYS_ADDR_T_64BIT + if (mc->soc->num_address_bits > 32) { + addr = ((value >> MC_ERR_STATUS_ADR_HI_SHIFT) & + MC_ERR_STATUS_ADR_HI_MASK); + addr <<= 32; + } +#endif + + if (value & MC_ERR_STATUS_RW) + direction = "write"; + else + direction = "read"; + + if (value & MC_ERR_STATUS_SECURITY) + secure = "secure "; + else + secure = ""; + + id = value & mc->soc->client_id_mask; + + for (i = 0; i < mc->soc->num_clients; i++) { + if (mc->soc->clients[i].id == id) { + client = mc->soc->clients[i].name; + break; + } + } + + type = (value & MC_ERR_STATUS_TYPE_MASK) >> + MC_ERR_STATUS_TYPE_SHIFT; + desc = error_names[type]; + + switch (value & MC_ERR_STATUS_TYPE_MASK) { + case MC_ERR_STATUS_TYPE_INVALID_SMMU_PAGE: + perm[0] = ' '; + perm[1] = '['; + + if (value & MC_ERR_STATUS_READABLE) + perm[2] = 'R'; + else + perm[2] = '-'; + + if (value & MC_ERR_STATUS_WRITABLE) + perm[3] = 'W'; + else + perm[3] = '-'; + + if (value & MC_ERR_STATUS_NONSECURE) + perm[4] = '-'; + else + perm[4] = 'S'; + + perm[5] = ']'; + perm[6] = '\0'; + break; + + default: + perm[0] = '\0'; + break; + } + + value = mc_readl(mc, MC_ERR_ADR); + addr |= value; + + dev_err_ratelimited(mc->dev, "%s: %s%s @%pa: %s (%s%s)\n", + client, secure, direction, &addr, error, + desc, perm); + } + + /* clear interrupts */ + mc_writel(mc, status, MC_INTSTATUS); + + return IRQ_HANDLED; +} + +static __maybe_unused irqreturn_t tegra20_mc_irq(int irq, void *data) +{ + struct tegra_mc *mc = data; + unsigned long status; + unsigned int bit; + + /* mask all interrupts to avoid flooding */ + status = mc_readl(mc, MC_INTSTATUS) & mc->soc->intmask; + if (!status) + return IRQ_NONE; + + for_each_set_bit(bit, &status, 32) { + const char *direction = "read", *secure = ""; + const char *error = status_names[bit]; + const char *client, *desc; + phys_addr_t addr; + u32 value, reg; + u8 id, type; + + switch (BIT(bit)) { + case MC_INT_DECERR_EMEM: + reg = MC_DECERR_EMEM_OTHERS_STATUS; + value = mc_readl(mc, reg); + + id = value & mc->soc->client_id_mask; + desc = error_names[2]; + + if (value & BIT(31)) + direction = "write"; + break; + + case MC_INT_INVALID_GART_PAGE: + dev_err_ratelimited(mc->dev, "%s\n", error); + continue; + + case MC_INT_SECURITY_VIOLATION: + reg = MC_SECURITY_VIOLATION_STATUS; + value = mc_readl(mc, reg); + + id = value & mc->soc->client_id_mask; + type = (value & BIT(30)) ? 4 : 3; + desc = error_names[type]; + secure = "secure "; + + if (value & BIT(31)) + direction = "write"; + break; + + default: + continue; + } + + client = mc->soc->clients[id].name; + addr = mc_readl(mc, reg + sizeof(u32)); + + dev_err_ratelimited(mc->dev, "%s: %s%s @%pa: %s (%s)\n", + client, secure, direction, &addr, error, + desc); + } + + /* clear interrupts */ + mc_writel(mc, status, MC_INTSTATUS); + + return IRQ_HANDLED; +} + +static int tegra_mc_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + struct resource *res; + struct tegra_mc *mc; + void *isr; + int err; + + match = of_match_node(tegra_mc_of_match, pdev->dev.of_node); + if (!match) + return -ENODEV; + + mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); + if (!mc) + return -ENOMEM; + + platform_set_drvdata(pdev, mc); + spin_lock_init(&mc->lock); + mc->soc = match->data; + mc->dev = &pdev->dev; + + /* length of MC tick in nanoseconds */ + mc->tick = 30; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mc->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mc->regs)) + return PTR_ERR(mc->regs); + +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + if (mc->soc == &tegra20_mc_soc) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + mc->regs2 = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mc->regs2)) + return PTR_ERR(mc->regs2); + + isr = tegra20_mc_irq; + } else +#endif + { + mc->clk = devm_clk_get(&pdev->dev, "mc"); + if (IS_ERR(mc->clk)) { + dev_err(&pdev->dev, "failed to get MC clock: %ld\n", + PTR_ERR(mc->clk)); + return PTR_ERR(mc->clk); + } + + err = tegra_mc_setup_latency_allowance(mc); + if (err < 0) { + dev_err(&pdev->dev, "failed to setup latency allowance: %d\n", + err); + return err; + } + + isr = tegra_mc_irq; + + err = tegra_mc_setup_timings(mc); + if (err < 0) { + dev_err(&pdev->dev, "failed to setup timings: %d\n", + err); + return err; + } + } + + mc->irq = platform_get_irq(pdev, 0); + if (mc->irq < 0) { + dev_err(&pdev->dev, "interrupt not specified\n"); + return mc->irq; + } + + WARN(!mc->soc->client_id_mask, "Missing client ID mask for this SoC\n"); + + mc_writel(mc, mc->soc->intmask, MC_INTMASK); + + err = devm_request_irq(&pdev->dev, mc->irq, isr, IRQF_SHARED, + dev_name(&pdev->dev), mc); + if (err < 0) { + dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", mc->irq, + err); + return err; + } + + err = tegra_mc_reset_setup(mc); + if (err < 0) + dev_err(&pdev->dev, "failed to register reset controller: %d\n", + err); + + if (IS_ENABLED(CONFIG_TEGRA_IOMMU_SMMU)) { + mc->smmu = tegra_smmu_probe(&pdev->dev, mc->soc->smmu, mc); + if (IS_ERR(mc->smmu)) + dev_err(&pdev->dev, "failed to probe SMMU: %ld\n", + PTR_ERR(mc->smmu)); + } + + return 0; +} + +static struct platform_driver tegra_mc_driver = { + .driver = { + .name = "tegra-mc", + .of_match_table = tegra_mc_of_match, + .suppress_bind_attrs = true, + }, + .prevent_deferred_probe = true, + .probe = tegra_mc_probe, +}; + +static int tegra_mc_init(void) +{ + return platform_driver_register(&tegra_mc_driver); +} +arch_initcall(tegra_mc_init); + +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); +MODULE_DESCRIPTION("NVIDIA Tegra Memory Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/memory/tegra/mc.h b/drivers/memory/tegra/mc.h new file mode 100644 index 000000000..01065f12e --- /dev/null +++ b/drivers/memory/tegra/mc.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef MEMORY_TEGRA_MC_H +#define MEMORY_TEGRA_MC_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) + +static inline u32 mc_readl(struct tegra_mc *mc, unsigned long offset) +{ + if (mc->regs2 && offset >= 0x24) + return readl(mc->regs2 + offset - 0x3c); + + return readl(mc->regs + offset); +} + +static inline void mc_writel(struct tegra_mc *mc, u32 value, + unsigned long offset) +{ + if (mc->regs2 && offset >= 0x24) + return writel(value, mc->regs2 + offset - 0x3c); + + writel(value, mc->regs + offset); +} + +extern const struct tegra_mc_reset_ops terga_mc_reset_ops_common; + +#ifdef CONFIG_ARCH_TEGRA_2x_SOC +extern const struct tegra_mc_soc tegra20_mc_soc; +#endif + +#ifdef CONFIG_ARCH_TEGRA_3x_SOC +extern const struct tegra_mc_soc tegra30_mc_soc; +#endif + +#ifdef CONFIG_ARCH_TEGRA_114_SOC +extern const struct tegra_mc_soc tegra114_mc_soc; +#endif + +#ifdef CONFIG_ARCH_TEGRA_124_SOC +extern const struct tegra_mc_soc tegra124_mc_soc; +#endif + +#ifdef CONFIG_ARCH_TEGRA_132_SOC +extern const struct tegra_mc_soc tegra132_mc_soc; +#endif + +#ifdef CONFIG_ARCH_TEGRA_210_SOC +extern const struct tegra_mc_soc tegra210_mc_soc; +#endif + +#endif /* MEMORY_TEGRA_MC_H */ diff --git a/drivers/memory/tegra/tegra114.c b/drivers/memory/tegra/tegra114.c new file mode 100644 index 000000000..6560a5101 --- /dev/null +++ b/drivers/memory/tegra/tegra114.c @@ -0,0 +1,981 @@ +/* + * Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/of.h> +#include <linux/mm.h> + +#include <dt-bindings/memory/tegra114-mc.h> + +#include "mc.h" + +static const struct tegra_mc_client tegra114_mc_clients[] = { + { + .id = 0x00, + .name = "ptcr", + .swgroup = TEGRA_SWGROUP_PTC, + }, { + .id = 0x01, + .name = "display0a", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 1, + }, + .la = { + .reg = 0x2e8, + .shift = 0, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x02, + .name = "display0ab", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 2, + }, + .la = { + .reg = 0x2f4, + .shift = 0, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x03, + .name = "display0b", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 3, + }, + .la = { + .reg = 0x2e8, + .shift = 16, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x04, + .name = "display0bb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 4, + }, + .la = { + .reg = 0x2f4, + .shift = 16, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x05, + .name = "display0c", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 5, + }, + .la = { + .reg = 0x2ec, + .shift = 0, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x06, + .name = "display0cb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 6, + }, + .la = { + .reg = 0x2f8, + .shift = 0, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x09, + .name = "eppup", + .swgroup = TEGRA_SWGROUP_EPP, + .smmu = { + .reg = 0x228, + .bit = 9, + }, + .la = { + .reg = 0x300, + .shift = 0, + .mask = 0xff, + .def = 0x33, + }, + }, { + .id = 0x0a, + .name = "g2pr", + .swgroup = TEGRA_SWGROUP_G2, + .smmu = { + .reg = 0x228, + .bit = 10, + }, + .la = { + .reg = 0x308, + .shift = 0, + .mask = 0xff, + .def = 0x09, + }, + }, { + .id = 0x0b, + .name = "g2sr", + .swgroup = TEGRA_SWGROUP_G2, + .smmu = { + .reg = 0x228, + .bit = 11, + }, + .la = { + .reg = 0x308, + .shift = 16, + .mask = 0xff, + .def = 0x09, + }, + }, { + .id = 0x0f, + .name = "avpcarm7r", + .swgroup = TEGRA_SWGROUP_AVPC, + .smmu = { + .reg = 0x228, + .bit = 15, + }, + .la = { + .reg = 0x2e4, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x10, + .name = "displayhc", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 16, + }, + .la = { + .reg = 0x2f0, + .shift = 0, + .mask = 0xff, + .def = 0x68, + }, + }, { + .id = 0x11, + .name = "displayhcb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 17, + }, + .la = { + .reg = 0x2fc, + .shift = 0, + .mask = 0xff, + .def = 0x68, + }, + }, { + .id = 0x12, + .name = "fdcdrd", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x228, + .bit = 18, + }, + .la = { + .reg = 0x334, + .shift = 0, + .mask = 0xff, + .def = 0x0c, + }, + }, { + .id = 0x13, + .name = "fdcdrd2", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x228, + .bit = 19, + }, + .la = { + .reg = 0x33c, + .shift = 0, + .mask = 0xff, + .def = 0x0c, + }, + }, { + .id = 0x14, + .name = "g2dr", + .swgroup = TEGRA_SWGROUP_G2, + .smmu = { + .reg = 0x228, + .bit = 20, + }, + .la = { + .reg = 0x30c, + .shift = 0, + .mask = 0xff, + .def = 0x0a, + }, + }, { + .id = 0x15, + .name = "hdar", + .swgroup = TEGRA_SWGROUP_HDA, + .smmu = { + .reg = 0x228, + .bit = 21, + }, + .la = { + .reg = 0x318, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x16, + .name = "host1xdmar", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x228, + .bit = 22, + }, + .la = { + .reg = 0x310, + .shift = 0, + .mask = 0xff, + .def = 0x10, + }, + }, { + .id = 0x17, + .name = "host1xr", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x228, + .bit = 23, + }, + .la = { + .reg = 0x310, + .shift = 16, + .mask = 0xff, + .def = 0xa5, + }, + }, { + .id = 0x18, + .name = "idxsrd", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x228, + .bit = 24, + }, + .la = { + .reg = 0x334, + .shift = 16, + .mask = 0xff, + .def = 0x0b, + }, + }, { + .id = 0x1c, + .name = "msencsrd", + .swgroup = TEGRA_SWGROUP_MSENC, + .smmu = { + .reg = 0x228, + .bit = 28, + }, + .la = { + .reg = 0x328, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x1d, + .name = "ppcsahbdmar", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x228, + .bit = 29, + }, + .la = { + .reg = 0x344, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x1e, + .name = "ppcsahbslvr", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x228, + .bit = 30, + }, + .la = { + .reg = 0x344, + .shift = 16, + .mask = 0xff, + .def = 0xe8, + }, + }, { + .id = 0x20, + .name = "texl2srd", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x22c, + .bit = 0, + }, + .la = { + .reg = 0x338, + .shift = 0, + .mask = 0xff, + .def = 0x0c, + }, + }, { + .id = 0x22, + .name = "vdebsevr", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 2, + }, + .la = { + .reg = 0x354, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x23, + .name = "vdember", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 3, + }, + .la = { + .reg = 0x354, + .shift = 16, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x24, + .name = "vdemcer", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 4, + }, + .la = { + .reg = 0x358, + .shift = 0, + .mask = 0xff, + .def = 0xb8, + }, + }, { + .id = 0x25, + .name = "vdetper", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 5, + }, + .la = { + .reg = 0x358, + .shift = 16, + .mask = 0xff, + .def = 0xee, + }, + }, { + .id = 0x26, + .name = "mpcorelpr", + .swgroup = TEGRA_SWGROUP_MPCORELP, + .la = { + .reg = 0x324, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x27, + .name = "mpcorer", + .swgroup = TEGRA_SWGROUP_MPCORE, + .la = { + .reg = 0x320, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x28, + .name = "eppu", + .swgroup = TEGRA_SWGROUP_EPP, + .smmu = { + .reg = 0x22c, + .bit = 8, + }, + .la = { + .reg = 0x300, + .shift = 16, + .mask = 0xff, + .def = 0x33, + }, + }, { + .id = 0x29, + .name = "eppv", + .swgroup = TEGRA_SWGROUP_EPP, + .smmu = { + .reg = 0x22c, + .bit = 9, + }, + .la = { + .reg = 0x304, + .shift = 0, + .mask = 0xff, + .def = 0x6c, + }, + }, { + .id = 0x2a, + .name = "eppy", + .swgroup = TEGRA_SWGROUP_EPP, + .smmu = { + .reg = 0x22c, + .bit = 10, + }, + .la = { + .reg = 0x304, + .shift = 16, + .mask = 0xff, + .def = 0x6c, + }, + }, { + .id = 0x2b, + .name = "msencswr", + .swgroup = TEGRA_SWGROUP_MSENC, + .smmu = { + .reg = 0x22c, + .bit = 11, + }, + .la = { + .reg = 0x328, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x2c, + .name = "viwsb", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x22c, + .bit = 12, + }, + .la = { + .reg = 0x364, + .shift = 0, + .mask = 0xff, + .def = 0x47, + }, + }, { + .id = 0x2d, + .name = "viwu", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x22c, + .bit = 13, + }, + .la = { + .reg = 0x368, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x2e, + .name = "viwv", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x22c, + .bit = 14, + }, + .la = { + .reg = 0x368, + .shift = 16, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x2f, + .name = "viwy", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x22c, + .bit = 15, + }, + .la = { + .reg = 0x36c, + .shift = 0, + .mask = 0xff, + .def = 0x47, + }, + }, { + .id = 0x30, + .name = "g2dw", + .swgroup = TEGRA_SWGROUP_G2, + .smmu = { + .reg = 0x22c, + .bit = 16, + }, + .la = { + .reg = 0x30c, + .shift = 16, + .mask = 0xff, + .def = 0x9, + }, + }, { + .id = 0x32, + .name = "avpcarm7w", + .swgroup = TEGRA_SWGROUP_AVPC, + .smmu = { + .reg = 0x22c, + .bit = 18, + }, + .la = { + .reg = 0x2e4, + .shift = 16, + .mask = 0xff, + .def = 0x0e, + }, + }, { + .id = 0x33, + .name = "fdcdwr", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x22c, + .bit = 19, + }, + .la = { + .reg = 0x338, + .shift = 16, + .mask = 0xff, + .def = 0x10, + }, + }, { + .id = 0x34, + .name = "fdcwr2", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x22c, + .bit = 20, + }, + .la = { + .reg = 0x340, + .shift = 0, + .mask = 0xff, + .def = 0x10, + }, + }, { + .id = 0x35, + .name = "hdaw", + .swgroup = TEGRA_SWGROUP_HDA, + .smmu = { + .reg = 0x22c, + .bit = 21, + }, + .la = { + .reg = 0x318, + .shift = 16, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x36, + .name = "host1xw", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x22c, + .bit = 22, + }, + .la = { + .reg = 0x314, + .shift = 0, + .mask = 0xff, + .def = 0x25, + }, + }, { + .id = 0x37, + .name = "ispw", + .swgroup = TEGRA_SWGROUP_ISP, + .smmu = { + .reg = 0x22c, + .bit = 23, + }, + .la = { + .reg = 0x31c, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x38, + .name = "mpcorelpw", + .swgroup = TEGRA_SWGROUP_MPCORELP, + .la = { + .reg = 0x324, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x39, + .name = "mpcorew", + .swgroup = TEGRA_SWGROUP_MPCORE, + .la = { + .reg = 0x320, + .shift = 16, + .mask = 0xff, + .def = 0x0e, + }, + }, { + .id = 0x3b, + .name = "ppcsahbdmaw", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x22c, + .bit = 27, + }, + .la = { + .reg = 0x348, + .shift = 0, + .mask = 0xff, + .def = 0xa5, + }, + }, { + .id = 0x3c, + .name = "ppcsahbslvw", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x22c, + .bit = 28, + }, + .la = { + .reg = 0x348, + .shift = 16, + .mask = 0xff, + .def = 0xe8, + }, + }, { + .id = 0x3e, + .name = "vdebsevw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 30, + }, + .la = { + .reg = 0x35c, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x3f, + .name = "vdedbgw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 31, + }, + .la = { + .reg = 0x35c, + .shift = 16, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x40, + .name = "vdembew", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x230, + .bit = 0, + }, + .la = { + .reg = 0x360, + .shift = 0, + .mask = 0xff, + .def = 0x89, + }, + }, { + .id = 0x41, + .name = "vdetpmw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x230, + .bit = 1, + }, + .la = { + .reg = 0x360, + .shift = 16, + .mask = 0xff, + .def = 0x59, + }, + }, { + .id = 0x4a, + .name = "xusb_hostr", + .swgroup = TEGRA_SWGROUP_XUSB_HOST, + .smmu = { + .reg = 0x230, + .bit = 10, + }, + .la = { + .reg = 0x37c, + .shift = 0, + .mask = 0xff, + .def = 0xa5, + }, + }, { + .id = 0x4b, + .name = "xusb_hostw", + .swgroup = TEGRA_SWGROUP_XUSB_HOST, + .smmu = { + .reg = 0x230, + .bit = 11, + }, + .la = { + .reg = 0x37c, + .shift = 16, + .mask = 0xff, + .def = 0xa5, + }, + }, { + .id = 0x4c, + .name = "xusb_devr", + .swgroup = TEGRA_SWGROUP_XUSB_DEV, + .smmu = { + .reg = 0x230, + .bit = 12, + }, + .la = { + .reg = 0x380, + .shift = 0, + .mask = 0xff, + .def = 0xa5, + }, + }, { + .id = 0x4d, + .name = "xusb_devw", + .swgroup = TEGRA_SWGROUP_XUSB_DEV, + .smmu = { + .reg = 0x230, + .bit = 13, + }, + .la = { + .reg = 0x380, + .shift = 16, + .mask = 0xff, + .def = 0xa5, + }, + }, { + .id = 0x4e, + .name = "fdcdwr3", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x230, + .bit = 14, + }, + .la = { + .reg = 0x388, + .shift = 0, + .mask = 0xff, + .def = 0x10, + }, + }, { + .id = 0x4f, + .name = "fdcdrd3", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x230, + .bit = 15, + }, + .la = { + .reg = 0x384, + .shift = 0, + .mask = 0xff, + .def = 0x0c, + }, + }, { + .id = 0x50, + .name = "fdcwr4", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x230, + .bit = 16, + }, + .la = { + .reg = 0x388, + .shift = 16, + .mask = 0xff, + .def = 0x10, + }, + }, { + .id = 0x51, + .name = "fdcrd4", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x230, + .bit = 17, + }, + .la = { + .reg = 0x384, + .shift = 16, + .mask = 0xff, + .def = 0x0c, + }, + }, { + .id = 0x52, + .name = "emucifr", + .swgroup = TEGRA_SWGROUP_EMUCIF, + .la = { + .reg = 0x38c, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x53, + .name = "emucifw", + .swgroup = TEGRA_SWGROUP_EMUCIF, + .la = { + .reg = 0x38c, + .shift = 16, + .mask = 0xff, + .def = 0x0e, + }, + }, { + .id = 0x54, + .name = "tsecsrd", + .swgroup = TEGRA_SWGROUP_TSEC, + .smmu = { + .reg = 0x230, + .bit = 20, + }, + .la = { + .reg = 0x390, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x55, + .name = "tsecswr", + .swgroup = TEGRA_SWGROUP_TSEC, + .smmu = { + .reg = 0x230, + .bit = 21, + }, + .la = { + .reg = 0x390, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, +}; + +static const struct tegra_smmu_swgroup tegra114_swgroups[] = { + { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 }, + { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 }, + { .name = "epp", .swgroup = TEGRA_SWGROUP_EPP, .reg = 0x248 }, + { .name = "g2", .swgroup = TEGRA_SWGROUP_G2, .reg = 0x24c }, + { .name = "avpc", .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c }, + { .name = "nv", .swgroup = TEGRA_SWGROUP_NV, .reg = 0x268 }, + { .name = "hda", .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 }, + { .name = "hc", .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 }, + { .name = "msenc", .swgroup = TEGRA_SWGROUP_MSENC, .reg = 0x264 }, + { .name = "ppcs", .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 }, + { .name = "vde", .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c }, + { .name = "vi", .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 }, + { .name = "isp", .swgroup = TEGRA_SWGROUP_ISP, .reg = 0x258 }, + { .name = "xusb_host", .swgroup = TEGRA_SWGROUP_XUSB_HOST, .reg = 0x288 }, + { .name = "xusb_dev", .swgroup = TEGRA_SWGROUP_XUSB_DEV, .reg = 0x28c }, + { .name = "tsec", .swgroup = TEGRA_SWGROUP_TSEC, .reg = 0x294 }, +}; + +static const unsigned int tegra114_group_display[] = { + TEGRA_SWGROUP_DC, + TEGRA_SWGROUP_DCB, +}; + +static const struct tegra_smmu_group_soc tegra114_groups[] = { + { + .name = "display", + .swgroups = tegra114_group_display, + .num_swgroups = ARRAY_SIZE(tegra114_group_display), + }, +}; + +static const struct tegra_smmu_soc tegra114_smmu_soc = { + .clients = tegra114_mc_clients, + .num_clients = ARRAY_SIZE(tegra114_mc_clients), + .swgroups = tegra114_swgroups, + .num_swgroups = ARRAY_SIZE(tegra114_swgroups), + .groups = tegra114_groups, + .num_groups = ARRAY_SIZE(tegra114_groups), + .supports_round_robin_arbitration = false, + .supports_request_limit = false, + .num_tlb_lines = 32, + .num_asids = 4, +}; + +#define TEGRA114_MC_RESET(_name, _control, _status, _bit) \ + { \ + .name = #_name, \ + .id = TEGRA114_MC_RESET_##_name, \ + .control = _control, \ + .status = _status, \ + .bit = _bit, \ + } + +static const struct tegra_mc_reset tegra114_mc_resets[] = { + TEGRA114_MC_RESET(AVPC, 0x200, 0x204, 1), + TEGRA114_MC_RESET(DC, 0x200, 0x204, 2), + TEGRA114_MC_RESET(DCB, 0x200, 0x204, 3), + TEGRA114_MC_RESET(EPP, 0x200, 0x204, 4), + TEGRA114_MC_RESET(2D, 0x200, 0x204, 5), + TEGRA114_MC_RESET(HC, 0x200, 0x204, 6), + TEGRA114_MC_RESET(HDA, 0x200, 0x204, 7), + TEGRA114_MC_RESET(ISP, 0x200, 0x204, 8), + TEGRA114_MC_RESET(MPCORE, 0x200, 0x204, 9), + TEGRA114_MC_RESET(MPCORELP, 0x200, 0x204, 10), + TEGRA114_MC_RESET(MPE, 0x200, 0x204, 11), + TEGRA114_MC_RESET(3D, 0x200, 0x204, 12), + TEGRA114_MC_RESET(3D2, 0x200, 0x204, 13), + TEGRA114_MC_RESET(PPCS, 0x200, 0x204, 14), + TEGRA114_MC_RESET(VDE, 0x200, 0x204, 16), + TEGRA114_MC_RESET(VI, 0x200, 0x204, 17), +}; + +const struct tegra_mc_soc tegra114_mc_soc = { + .clients = tegra114_mc_clients, + .num_clients = ARRAY_SIZE(tegra114_mc_clients), + .num_address_bits = 32, + .atom_size = 32, + .client_id_mask = 0x7f, + .smmu = &tegra114_smmu_soc, + .intmask = MC_INT_INVALID_SMMU_PAGE | MC_INT_SECURITY_VIOLATION | + MC_INT_DECERR_EMEM, + .reset_ops = &terga_mc_reset_ops_common, + .resets = tegra114_mc_resets, + .num_resets = ARRAY_SIZE(tegra114_mc_resets), +}; diff --git a/drivers/memory/tegra/tegra124-emc.c b/drivers/memory/tegra/tegra124-emc.c new file mode 100644 index 000000000..392dc8dd4 --- /dev/null +++ b/drivers/memory/tegra/tegra124-emc.c @@ -0,0 +1,1175 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + * + * Author: + * Mikko Perttunen <mperttunen@nvidia.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/clk-provider.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/sort.h> +#include <linux/string.h> + +#include <soc/tegra/emc.h> +#include <soc/tegra/fuse.h> +#include <soc/tegra/mc.h> + +#define EMC_FBIO_CFG5 0x104 +#define EMC_FBIO_CFG5_DRAM_TYPE_MASK 0x3 +#define EMC_FBIO_CFG5_DRAM_TYPE_SHIFT 0 + +#define EMC_INTSTATUS 0x0 +#define EMC_INTSTATUS_CLKCHANGE_COMPLETE BIT(4) + +#define EMC_CFG 0xc +#define EMC_CFG_DRAM_CLKSTOP_PD BIT(31) +#define EMC_CFG_DRAM_CLKSTOP_SR BIT(30) +#define EMC_CFG_DRAM_ACPD BIT(29) +#define EMC_CFG_DYN_SREF BIT(28) +#define EMC_CFG_PWR_MASK ((0xF << 28) | BIT(18)) +#define EMC_CFG_DSR_VTTGEN_DRV_EN BIT(18) + +#define EMC_REFCTRL 0x20 +#define EMC_REFCTRL_DEV_SEL_SHIFT 0 +#define EMC_REFCTRL_ENABLE BIT(31) + +#define EMC_TIMING_CONTROL 0x28 +#define EMC_RC 0x2c +#define EMC_RFC 0x30 +#define EMC_RAS 0x34 +#define EMC_RP 0x38 +#define EMC_R2W 0x3c +#define EMC_W2R 0x40 +#define EMC_R2P 0x44 +#define EMC_W2P 0x48 +#define EMC_RD_RCD 0x4c +#define EMC_WR_RCD 0x50 +#define EMC_RRD 0x54 +#define EMC_REXT 0x58 +#define EMC_WDV 0x5c +#define EMC_QUSE 0x60 +#define EMC_QRST 0x64 +#define EMC_QSAFE 0x68 +#define EMC_RDV 0x6c +#define EMC_REFRESH 0x70 +#define EMC_BURST_REFRESH_NUM 0x74 +#define EMC_PDEX2WR 0x78 +#define EMC_PDEX2RD 0x7c +#define EMC_PCHG2PDEN 0x80 +#define EMC_ACT2PDEN 0x84 +#define EMC_AR2PDEN 0x88 +#define EMC_RW2PDEN 0x8c +#define EMC_TXSR 0x90 +#define EMC_TCKE 0x94 +#define EMC_TFAW 0x98 +#define EMC_TRPAB 0x9c +#define EMC_TCLKSTABLE 0xa0 +#define EMC_TCLKSTOP 0xa4 +#define EMC_TREFBW 0xa8 +#define EMC_ODT_WRITE 0xb0 +#define EMC_ODT_READ 0xb4 +#define EMC_WEXT 0xb8 +#define EMC_CTT 0xbc +#define EMC_RFC_SLR 0xc0 +#define EMC_MRS_WAIT_CNT2 0xc4 + +#define EMC_MRS_WAIT_CNT 0xc8 +#define EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT 0 +#define EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK \ + (0x3FF << EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT) +#define EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT 16 +#define EMC_MRS_WAIT_CNT_LONG_WAIT_MASK \ + (0x3FF << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT) + +#define EMC_MRS 0xcc +#define EMC_MODE_SET_DLL_RESET BIT(8) +#define EMC_MODE_SET_LONG_CNT BIT(26) +#define EMC_EMRS 0xd0 +#define EMC_REF 0xd4 +#define EMC_PRE 0xd8 + +#define EMC_SELF_REF 0xe0 +#define EMC_SELF_REF_CMD_ENABLED BIT(0) +#define EMC_SELF_REF_DEV_SEL_SHIFT 30 + +#define EMC_MRW 0xe8 + +#define EMC_MRR 0xec +#define EMC_MRR_MA_SHIFT 16 +#define LPDDR2_MR4_TEMP_SHIFT 0 + +#define EMC_XM2DQSPADCTRL3 0xf8 +#define EMC_FBIO_SPARE 0x100 + +#define EMC_FBIO_CFG6 0x114 +#define EMC_EMRS2 0x12c +#define EMC_MRW2 0x134 +#define EMC_MRW4 0x13c +#define EMC_EINPUT 0x14c +#define EMC_EINPUT_DURATION 0x150 +#define EMC_PUTERM_EXTRA 0x154 +#define EMC_TCKESR 0x158 +#define EMC_TPD 0x15c + +#define EMC_AUTO_CAL_CONFIG 0x2a4 +#define EMC_AUTO_CAL_CONFIG_AUTO_CAL_START BIT(31) +#define EMC_AUTO_CAL_INTERVAL 0x2a8 +#define EMC_AUTO_CAL_STATUS 0x2ac +#define EMC_AUTO_CAL_STATUS_ACTIVE BIT(31) +#define EMC_STATUS 0x2b4 +#define EMC_STATUS_TIMING_UPDATE_STALLED BIT(23) + +#define EMC_CFG_2 0x2b8 +#define EMC_CFG_2_MODE_SHIFT 0 +#define EMC_CFG_2_DIS_STP_OB_CLK_DURING_NON_WR BIT(6) + +#define EMC_CFG_DIG_DLL 0x2bc +#define EMC_CFG_DIG_DLL_PERIOD 0x2c0 +#define EMC_RDV_MASK 0x2cc +#define EMC_WDV_MASK 0x2d0 +#define EMC_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_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_XM2CMDPADCTRL 0x2f0 +#define EMC_XM2DQSPADCTRL 0x2f8 +#define EMC_XM2DQSPADCTRL2 0x2fc +#define EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE BIT(0) +#define EMC_XM2DQSPADCTRL2_VREF_ENABLE BIT(5) +#define EMC_XM2DQPADCTRL 0x300 +#define EMC_XM2DQPADCTRL2 0x304 +#define EMC_XM2CLKPADCTRL 0x308 +#define EMC_XM2COMPPADCTRL 0x30c +#define EMC_XM2VTTGENPADCTRL 0x310 +#define EMC_XM2VTTGENPADCTRL2 0x314 +#define EMC_XM2VTTGENPADCTRL3 0x318 +#define EMC_XM2DQSPADCTRL4 0x320 +#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_AFTER_CLKCHANGE 0x3cc +#define EMC_SEL_DPD_CTRL 0x3d8 +#define EMC_SEL_DPD_CTRL_DATA_SEL_DPD BIT(8) +#define EMC_SEL_DPD_CTRL_ODT_SEL_DPD BIT(5) +#define EMC_SEL_DPD_CTRL_RESET_SEL_DPD BIT(4) +#define EMC_SEL_DPD_CTRL_CA_SEL_DPD BIT(3) +#define EMC_SEL_DPD_CTRL_CLK_SEL_DPD BIT(2) +#define EMC_SEL_DPD_CTRL_DDR3_MASK \ + ((0xf << 2) | BIT(8)) +#define EMC_SEL_DPD_CTRL_MASK \ + ((0x3 << 2) | BIT(5) | BIT(8)) +#define EMC_PRE_REFRESH_REQ_CNT 0x3dc +#define EMC_DYN_SELF_REF_CONTROL 0x3e0 +#define EMC_TXSRDLL 0x3e4 +#define EMC_CCFIFO_ADDR 0x3e8 +#define EMC_CCFIFO_DATA 0x3ec +#define EMC_CCFIFO_STATUS 0x3f0 +#define EMC_CDB_CNTL_1 0x3f4 +#define EMC_CDB_CNTL_2 0x3f8 +#define EMC_XM2CLKPADCTRL2 0x3fc +#define EMC_AUTO_CAL_CONFIG2 0x458 +#define EMC_AUTO_CAL_CONFIG3 0x45c +#define EMC_IBDLY 0x468 +#define EMC_DLL_XFORM_ADDR0 0x46c +#define EMC_DLL_XFORM_ADDR1 0x470 +#define EMC_DLL_XFORM_ADDR2 0x474 +#define EMC_DSR_VTTGEN_DRV 0x47c +#define EMC_TXDSRVTTGEN 0x480 +#define EMC_XM2CMDPADCTRL4 0x484 +#define EMC_XM2CMDPADCTRL5 0x488 +#define EMC_DLL_XFORM_DQS8 0x4a0 +#define EMC_DLL_XFORM_DQS9 0x4a4 +#define EMC_DLL_XFORM_DQS10 0x4a8 +#define EMC_DLL_XFORM_DQS11 0x4ac +#define EMC_DLL_XFORM_DQS12 0x4b0 +#define EMC_DLL_XFORM_DQS13 0x4b4 +#define EMC_DLL_XFORM_DQS14 0x4b8 +#define EMC_DLL_XFORM_DQS15 0x4bc +#define EMC_DLL_XFORM_QUSE8 0x4c0 +#define EMC_DLL_XFORM_QUSE9 0x4c4 +#define EMC_DLL_XFORM_QUSE10 0x4c8 +#define EMC_DLL_XFORM_QUSE11 0x4cc +#define EMC_DLL_XFORM_QUSE12 0x4d0 +#define EMC_DLL_XFORM_QUSE13 0x4d4 +#define EMC_DLL_XFORM_QUSE14 0x4d8 +#define EMC_DLL_XFORM_QUSE15 0x4dc +#define EMC_DLL_XFORM_DQ4 0x4e0 +#define EMC_DLL_XFORM_DQ5 0x4e4 +#define EMC_DLL_XFORM_DQ6 0x4e8 +#define EMC_DLL_XFORM_DQ7 0x4ec +#define EMC_DLI_TRIM_TXDQS8 0x520 +#define EMC_DLI_TRIM_TXDQS9 0x524 +#define EMC_DLI_TRIM_TXDQS10 0x528 +#define EMC_DLI_TRIM_TXDQS11 0x52c +#define EMC_DLI_TRIM_TXDQS12 0x530 +#define EMC_DLI_TRIM_TXDQS13 0x534 +#define EMC_DLI_TRIM_TXDQS14 0x538 +#define EMC_DLI_TRIM_TXDQS15 0x53c +#define EMC_CDB_CNTL_3 0x540 +#define EMC_XM2DQSPADCTRL5 0x544 +#define EMC_XM2DQSPADCTRL6 0x548 +#define EMC_XM2DQPADCTRL3 0x54c +#define EMC_DLL_XFORM_ADDR3 0x550 +#define EMC_DLL_XFORM_ADDR4 0x554 +#define EMC_DLL_XFORM_ADDR5 0x558 +#define EMC_CFG_PIPE 0x560 +#define EMC_QPOP 0x564 +#define EMC_QUSE_WIDTH 0x568 +#define EMC_PUTERM_WIDTH 0x56c +#define EMC_BGBIAS_CTL0 0x570 +#define EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_RX BIT(3) +#define EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_VTTGEN BIT(2) +#define EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD BIT(1) +#define EMC_PUTERM_ADJ 0x574 + +#define DRAM_DEV_SEL_ALL 0 +#define DRAM_DEV_SEL_0 (2 << 30) +#define DRAM_DEV_SEL_1 (1 << 30) + +#define EMC_CFG_POWER_FEATURES_MASK \ + (EMC_CFG_DYN_SREF | EMC_CFG_DRAM_ACPD | EMC_CFG_DRAM_CLKSTOP_SR | \ + EMC_CFG_DRAM_CLKSTOP_PD | EMC_CFG_DSR_VTTGEN_DRV_EN) +#define EMC_REFCTRL_DEV_SEL(n) (((n > 1) ? 0 : 2) << EMC_REFCTRL_DEV_SEL_SHIFT) +#define EMC_DRAM_DEV_SEL(n) ((n > 1) ? DRAM_DEV_SEL_ALL : DRAM_DEV_SEL_0) + +/* Maximum amount of time in us. to wait for changes to become effective */ +#define EMC_STATUS_UPDATE_TIMEOUT 1000 + +enum emc_dram_type { + DRAM_TYPE_DDR3 = 0, + DRAM_TYPE_DDR1 = 1, + DRAM_TYPE_LPDDR3 = 2, + DRAM_TYPE_DDR2 = 3 +}; + +enum emc_dll_change { + DLL_CHANGE_NONE, + DLL_CHANGE_ON, + DLL_CHANGE_OFF +}; + +static const unsigned long emc_burst_regs[] = { + EMC_RC, + EMC_RFC, + EMC_RFC_SLR, + EMC_RAS, + EMC_RP, + EMC_R2W, + EMC_W2R, + EMC_R2P, + EMC_W2P, + EMC_RD_RCD, + EMC_WR_RCD, + EMC_RRD, + EMC_REXT, + EMC_WEXT, + EMC_WDV, + EMC_WDV_MASK, + EMC_QUSE, + EMC_QUSE_WIDTH, + EMC_IBDLY, + EMC_EINPUT, + EMC_EINPUT_DURATION, + EMC_PUTERM_EXTRA, + EMC_PUTERM_WIDTH, + EMC_PUTERM_ADJ, + EMC_CDB_CNTL_1, + EMC_CDB_CNTL_2, + EMC_CDB_CNTL_3, + EMC_QRST, + EMC_QSAFE, + EMC_RDV, + EMC_RDV_MASK, + EMC_REFRESH, + EMC_BURST_REFRESH_NUM, + EMC_PRE_REFRESH_REQ_CNT, + EMC_PDEX2WR, + EMC_PDEX2RD, + EMC_PCHG2PDEN, + EMC_ACT2PDEN, + EMC_AR2PDEN, + EMC_RW2PDEN, + EMC_TXSR, + EMC_TXSRDLL, + EMC_TCKE, + EMC_TCKESR, + EMC_TPD, + EMC_TFAW, + EMC_TRPAB, + EMC_TCLKSTABLE, + EMC_TCLKSTOP, + EMC_TREFBW, + EMC_FBIO_CFG6, + EMC_ODT_WRITE, + EMC_ODT_READ, + EMC_FBIO_CFG5, + EMC_CFG_DIG_DLL, + EMC_CFG_DIG_DLL_PERIOD, + EMC_DLL_XFORM_DQS0, + EMC_DLL_XFORM_DQS1, + EMC_DLL_XFORM_DQS2, + EMC_DLL_XFORM_DQS3, + EMC_DLL_XFORM_DQS4, + EMC_DLL_XFORM_DQS5, + EMC_DLL_XFORM_DQS6, + EMC_DLL_XFORM_DQS7, + EMC_DLL_XFORM_DQS8, + EMC_DLL_XFORM_DQS9, + EMC_DLL_XFORM_DQS10, + EMC_DLL_XFORM_DQS11, + EMC_DLL_XFORM_DQS12, + EMC_DLL_XFORM_DQS13, + EMC_DLL_XFORM_DQS14, + EMC_DLL_XFORM_DQS15, + EMC_DLL_XFORM_QUSE0, + EMC_DLL_XFORM_QUSE1, + EMC_DLL_XFORM_QUSE2, + EMC_DLL_XFORM_QUSE3, + EMC_DLL_XFORM_QUSE4, + EMC_DLL_XFORM_QUSE5, + EMC_DLL_XFORM_QUSE6, + EMC_DLL_XFORM_QUSE7, + EMC_DLL_XFORM_ADDR0, + EMC_DLL_XFORM_ADDR1, + EMC_DLL_XFORM_ADDR2, + EMC_DLL_XFORM_ADDR3, + EMC_DLL_XFORM_ADDR4, + EMC_DLL_XFORM_ADDR5, + EMC_DLL_XFORM_QUSE8, + EMC_DLL_XFORM_QUSE9, + EMC_DLL_XFORM_QUSE10, + EMC_DLL_XFORM_QUSE11, + EMC_DLL_XFORM_QUSE12, + EMC_DLL_XFORM_QUSE13, + EMC_DLL_XFORM_QUSE14, + EMC_DLL_XFORM_QUSE15, + EMC_DLI_TRIM_TXDQS0, + EMC_DLI_TRIM_TXDQS1, + EMC_DLI_TRIM_TXDQS2, + EMC_DLI_TRIM_TXDQS3, + EMC_DLI_TRIM_TXDQS4, + EMC_DLI_TRIM_TXDQS5, + EMC_DLI_TRIM_TXDQS6, + EMC_DLI_TRIM_TXDQS7, + EMC_DLI_TRIM_TXDQS8, + EMC_DLI_TRIM_TXDQS9, + EMC_DLI_TRIM_TXDQS10, + EMC_DLI_TRIM_TXDQS11, + EMC_DLI_TRIM_TXDQS12, + EMC_DLI_TRIM_TXDQS13, + EMC_DLI_TRIM_TXDQS14, + EMC_DLI_TRIM_TXDQS15, + EMC_DLL_XFORM_DQ0, + EMC_DLL_XFORM_DQ1, + EMC_DLL_XFORM_DQ2, + EMC_DLL_XFORM_DQ3, + EMC_DLL_XFORM_DQ4, + EMC_DLL_XFORM_DQ5, + EMC_DLL_XFORM_DQ6, + EMC_DLL_XFORM_DQ7, + EMC_XM2CMDPADCTRL, + EMC_XM2CMDPADCTRL4, + EMC_XM2CMDPADCTRL5, + EMC_XM2DQPADCTRL2, + EMC_XM2DQPADCTRL3, + EMC_XM2CLKPADCTRL, + EMC_XM2CLKPADCTRL2, + EMC_XM2COMPPADCTRL, + EMC_XM2VTTGENPADCTRL, + EMC_XM2VTTGENPADCTRL2, + EMC_XM2VTTGENPADCTRL3, + EMC_XM2DQSPADCTRL3, + EMC_XM2DQSPADCTRL4, + EMC_XM2DQSPADCTRL5, + EMC_XM2DQSPADCTRL6, + EMC_DSR_VTTGEN_DRV, + EMC_TXDSRVTTGEN, + EMC_FBIO_SPARE, + EMC_ZCAL_WAIT_CNT, + EMC_MRS_WAIT_CNT2, + EMC_CTT, + EMC_CTT_DURATION, + EMC_CFG_PIPE, + EMC_DYN_SELF_REF_CONTROL, + EMC_QPOP +}; + +struct emc_timing { + unsigned long rate; + + u32 emc_burst_data[ARRAY_SIZE(emc_burst_regs)]; + + u32 emc_auto_cal_config; + u32 emc_auto_cal_config2; + u32 emc_auto_cal_config3; + u32 emc_auto_cal_interval; + u32 emc_bgbias_ctl0; + u32 emc_cfg; + u32 emc_cfg_2; + u32 emc_ctt_term_ctrl; + u32 emc_mode_1; + u32 emc_mode_2; + u32 emc_mode_4; + u32 emc_mode_reset; + u32 emc_mrs_wait_cnt; + u32 emc_sel_dpd_ctrl; + u32 emc_xm2dqspadctrl2; + u32 emc_zcal_cnt_long; + u32 emc_zcal_interval; +}; + +struct tegra_emc { + struct device *dev; + + struct tegra_mc *mc; + + void __iomem *regs; + + enum emc_dram_type dram_type; + unsigned int dram_num; + + struct emc_timing last_timing; + struct emc_timing *timings; + unsigned int num_timings; +}; + +/* Timing change sequence functions */ + +static void emc_ccfifo_writel(struct tegra_emc *emc, u32 value, + unsigned long offset) +{ + writel(value, emc->regs + EMC_CCFIFO_DATA); + writel(offset, emc->regs + EMC_CCFIFO_ADDR); +} + +static void emc_seq_update_timing(struct tegra_emc *emc) +{ + unsigned int i; + u32 value; + + writel(1, emc->regs + EMC_TIMING_CONTROL); + + for (i = 0; i < EMC_STATUS_UPDATE_TIMEOUT; ++i) { + value = readl(emc->regs + EMC_STATUS); + if ((value & EMC_STATUS_TIMING_UPDATE_STALLED) == 0) + return; + udelay(1); + } + + dev_err(emc->dev, "timing update timed out\n"); +} + +static void emc_seq_disable_auto_cal(struct tegra_emc *emc) +{ + unsigned int i; + u32 value; + + writel(0, emc->regs + EMC_AUTO_CAL_INTERVAL); + + for (i = 0; i < EMC_STATUS_UPDATE_TIMEOUT; ++i) { + value = readl(emc->regs + EMC_AUTO_CAL_STATUS); + if ((value & EMC_AUTO_CAL_STATUS_ACTIVE) == 0) + return; + udelay(1); + } + + dev_err(emc->dev, "auto cal disable timed out\n"); +} + +static void emc_seq_wait_clkchange(struct tegra_emc *emc) +{ + unsigned int i; + u32 value; + + for (i = 0; i < EMC_STATUS_UPDATE_TIMEOUT; ++i) { + value = readl(emc->regs + EMC_INTSTATUS); + if (value & EMC_INTSTATUS_CLKCHANGE_COMPLETE) + return; + udelay(1); + } + + dev_err(emc->dev, "clock change timed out\n"); +} + +static struct emc_timing *tegra_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; +} + +int tegra_emc_prepare_timing_change(struct tegra_emc *emc, + unsigned long rate) +{ + struct emc_timing *timing = tegra_emc_find_timing(emc, rate); + struct emc_timing *last = &emc->last_timing; + enum emc_dll_change dll_change; + unsigned int pre_wait = 0; + u32 val, val2, mask; + bool update = false; + unsigned int i; + + if (!timing) + return -ENOENT; + + if ((last->emc_mode_1 & 0x1) == (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; + + /* Clear CLKCHANGE_COMPLETE interrupts */ + writel(EMC_INTSTATUS_CLKCHANGE_COMPLETE, emc->regs + EMC_INTSTATUS); + + /* Disable dynamic self-refresh */ + val = readl(emc->regs + EMC_CFG); + if (val & EMC_CFG_PWR_MASK) { + val &= ~EMC_CFG_POWER_FEATURES_MASK; + writel(val, emc->regs + EMC_CFG); + + pre_wait = 5; + } + + /* Disable SEL_DPD_CTRL for clock change */ + if (emc->dram_type == DRAM_TYPE_DDR3) + mask = EMC_SEL_DPD_CTRL_DDR3_MASK; + else + mask = EMC_SEL_DPD_CTRL_MASK; + + val = readl(emc->regs + EMC_SEL_DPD_CTRL); + if (val & mask) { + val &= ~mask; + writel(val, emc->regs + EMC_SEL_DPD_CTRL); + } + + /* Prepare DQ/DQS for clock change */ + val = readl(emc->regs + EMC_BGBIAS_CTL0); + val2 = last->emc_bgbias_ctl0; + if (!(timing->emc_bgbias_ctl0 & + EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_RX) && + (val & EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_RX)) { + val2 &= ~EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_RX; + update = true; + } + + if ((val & EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD) || + (val & EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_VTTGEN)) { + update = true; + } + + if (update) { + writel(val2, emc->regs + EMC_BGBIAS_CTL0); + if (pre_wait < 5) + pre_wait = 5; + } + + update = false; + val = readl(emc->regs + EMC_XM2DQSPADCTRL2); + if (timing->emc_xm2dqspadctrl2 & EMC_XM2DQSPADCTRL2_VREF_ENABLE && + !(val & EMC_XM2DQSPADCTRL2_VREF_ENABLE)) { + val |= EMC_XM2DQSPADCTRL2_VREF_ENABLE; + update = true; + } + + if (timing->emc_xm2dqspadctrl2 & EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE && + !(val & EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE)) { + val |= EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE; + update = true; + } + + if (update) { + writel(val, emc->regs + EMC_XM2DQSPADCTRL2); + if (pre_wait < 30) + pre_wait = 30; + } + + /* Wait to settle */ + if (pre_wait) { + emc_seq_update_timing(emc); + udelay(pre_wait); + } + + /* Program CTT_TERM control */ + if (last->emc_ctt_term_ctrl != timing->emc_ctt_term_ctrl) { + emc_seq_disable_auto_cal(emc); + writel(timing->emc_ctt_term_ctrl, + emc->regs + EMC_CTT_TERM_CTRL); + emc_seq_update_timing(emc); + } + + /* Program burst shadow registers */ + for (i = 0; i < ARRAY_SIZE(timing->emc_burst_data); ++i) + writel(timing->emc_burst_data[i], + emc->regs + emc_burst_regs[i]); + + writel(timing->emc_xm2dqspadctrl2, emc->regs + EMC_XM2DQSPADCTRL2); + writel(timing->emc_zcal_interval, emc->regs + EMC_ZCAL_INTERVAL); + + tegra_mc_write_emem_configuration(emc->mc, timing->rate); + + val = timing->emc_cfg & ~EMC_CFG_POWER_FEATURES_MASK; + emc_ccfifo_writel(emc, val, EMC_CFG); + + /* Program AUTO_CAL_CONFIG */ + if (timing->emc_auto_cal_config2 != last->emc_auto_cal_config2) + emc_ccfifo_writel(emc, timing->emc_auto_cal_config2, + EMC_AUTO_CAL_CONFIG2); + + if (timing->emc_auto_cal_config3 != last->emc_auto_cal_config3) + emc_ccfifo_writel(emc, timing->emc_auto_cal_config3, + EMC_AUTO_CAL_CONFIG3); + + if (timing->emc_auto_cal_config != last->emc_auto_cal_config) { + val = timing->emc_auto_cal_config; + val &= EMC_AUTO_CAL_CONFIG_AUTO_CAL_START; + emc_ccfifo_writel(emc, val, EMC_AUTO_CAL_CONFIG); + } + + /* DDR3: predict MRS long wait count */ + if (emc->dram_type == DRAM_TYPE_DDR3 && + dll_change == DLL_CHANGE_ON) { + u32 cnt = 512; + + if (timing->emc_zcal_interval != 0 && + last->emc_zcal_interval == 0) + cnt -= emc->dram_num * 256; + + val = (timing->emc_mrs_wait_cnt + & EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK) + >> EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT; + if (cnt < val) + cnt = val; + + val = timing->emc_mrs_wait_cnt + & ~EMC_MRS_WAIT_CNT_LONG_WAIT_MASK; + val |= (cnt << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT) + & EMC_MRS_WAIT_CNT_LONG_WAIT_MASK; + + writel(val, emc->regs + EMC_MRS_WAIT_CNT); + } + + val = timing->emc_cfg_2; + val &= ~EMC_CFG_2_DIS_STP_OB_CLK_DURING_NON_WR; + emc_ccfifo_writel(emc, val, EMC_CFG_2); + + /* DDR3: Turn off DLL and enter self-refresh */ + if (emc->dram_type == DRAM_TYPE_DDR3 && dll_change == DLL_CHANGE_OFF) + emc_ccfifo_writel(emc, timing->emc_mode_1, EMC_EMRS); + + /* Disable refresh controller */ + emc_ccfifo_writel(emc, EMC_REFCTRL_DEV_SEL(emc->dram_num), + EMC_REFCTRL); + if (emc->dram_type == DRAM_TYPE_DDR3) + emc_ccfifo_writel(emc, EMC_DRAM_DEV_SEL(emc->dram_num) | + EMC_SELF_REF_CMD_ENABLED, + EMC_SELF_REF); + + /* Flow control marker */ + emc_ccfifo_writel(emc, 1, EMC_STALL_THEN_EXE_AFTER_CLKCHANGE); + + /* DDR3: Exit self-refresh */ + if (emc->dram_type == DRAM_TYPE_DDR3) + emc_ccfifo_writel(emc, EMC_DRAM_DEV_SEL(emc->dram_num), + EMC_SELF_REF); + emc_ccfifo_writel(emc, EMC_REFCTRL_DEV_SEL(emc->dram_num) | + EMC_REFCTRL_ENABLE, + EMC_REFCTRL); + + /* Set DRAM mode registers */ + if (emc->dram_type == DRAM_TYPE_DDR3) { + if (timing->emc_mode_1 != last->emc_mode_1) + emc_ccfifo_writel(emc, timing->emc_mode_1, EMC_EMRS); + if (timing->emc_mode_2 != last->emc_mode_2) + emc_ccfifo_writel(emc, timing->emc_mode_2, EMC_EMRS2); + + if ((timing->emc_mode_reset != last->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; + } + emc_ccfifo_writel(emc, val, EMC_MRS); + } + } else { + if (timing->emc_mode_2 != last->emc_mode_2) + emc_ccfifo_writel(emc, timing->emc_mode_2, EMC_MRW2); + if (timing->emc_mode_1 != last->emc_mode_1) + emc_ccfifo_writel(emc, timing->emc_mode_1, EMC_MRW); + if (timing->emc_mode_4 != last->emc_mode_4) + emc_ccfifo_writel(emc, timing->emc_mode_4, EMC_MRW4); + } + + /* Issue ZCAL command if turning ZCAL on */ + if (timing->emc_zcal_interval != 0 && last->emc_zcal_interval == 0) { + emc_ccfifo_writel(emc, EMC_ZQ_CAL_LONG_CMD_DEV0, EMC_ZQ_CAL); + if (emc->dram_num > 1) + emc_ccfifo_writel(emc, EMC_ZQ_CAL_LONG_CMD_DEV1, + EMC_ZQ_CAL); + } + + /* Write to RO register to remove stall after change */ + emc_ccfifo_writel(emc, 0, EMC_CCFIFO_STATUS); + + if (timing->emc_cfg_2 & EMC_CFG_2_DIS_STP_OB_CLK_DURING_NON_WR) + emc_ccfifo_writel(emc, timing->emc_cfg_2, EMC_CFG_2); + + /* Disable AUTO_CAL for clock change */ + emc_seq_disable_auto_cal(emc); + + /* Read register to wait until programming has settled */ + readl(emc->regs + EMC_INTSTATUS); + + return 0; +} + +void tegra_emc_complete_timing_change(struct tegra_emc *emc, + unsigned long rate) +{ + struct emc_timing *timing = tegra_emc_find_timing(emc, rate); + struct emc_timing *last = &emc->last_timing; + u32 val; + + if (!timing) + return; + + /* Wait until the state machine has settled */ + emc_seq_wait_clkchange(emc); + + /* Restore AUTO_CAL */ + if (timing->emc_ctt_term_ctrl != last->emc_ctt_term_ctrl) + writel(timing->emc_auto_cal_interval, + emc->regs + EMC_AUTO_CAL_INTERVAL); + + /* Restore dynamic self-refresh */ + if (timing->emc_cfg & EMC_CFG_PWR_MASK) + writel(timing->emc_cfg, emc->regs + EMC_CFG); + + /* Set ZCAL wait count */ + writel(timing->emc_zcal_cnt_long, emc->regs + EMC_ZCAL_WAIT_CNT); + + /* LPDDR3: Turn off BGBIAS if low frequency */ + if (emc->dram_type == DRAM_TYPE_LPDDR3 && + timing->emc_bgbias_ctl0 & + EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_RX) { + val = timing->emc_bgbias_ctl0; + val |= EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_VTTGEN; + val |= EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD; + writel(val, emc->regs + EMC_BGBIAS_CTL0); + } else { + if (emc->dram_type == DRAM_TYPE_DDR3 && + readl(emc->regs + EMC_BGBIAS_CTL0) != + timing->emc_bgbias_ctl0) { + writel(timing->emc_bgbias_ctl0, + emc->regs + EMC_BGBIAS_CTL0); + } + + writel(timing->emc_auto_cal_interval, + emc->regs + EMC_AUTO_CAL_INTERVAL); + } + + /* Wait for timing to settle */ + udelay(2); + + /* Reprogram SEL_DPD_CTRL */ + writel(timing->emc_sel_dpd_ctrl, emc->regs + EMC_SEL_DPD_CTRL); + emc_seq_update_timing(emc); + + emc->last_timing = *timing; +} + +/* Initialization and deinitialization */ + +static void emc_read_current_timing(struct tegra_emc *emc, + struct emc_timing *timing) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(emc_burst_regs); ++i) + timing->emc_burst_data[i] = + readl(emc->regs + emc_burst_regs[i]); + + timing->emc_cfg = readl(emc->regs + EMC_CFG); + + timing->emc_auto_cal_interval = 0; + timing->emc_zcal_cnt_long = 0; + timing->emc_mode_1 = 0; + timing->emc_mode_2 = 0; + timing->emc_mode_4 = 0; + timing->emc_mode_reset = 0; +} + +static int emc_init(struct tegra_emc *emc) +{ + emc->dram_type = readl(emc->regs + EMC_FBIO_CFG5); + emc->dram_type &= EMC_FBIO_CFG5_DRAM_TYPE_MASK; + emc->dram_type >>= EMC_FBIO_CFG5_DRAM_TYPE_SHIFT; + + emc->dram_num = tegra_mc_get_emem_device_count(emc->mc); + + emc_read_current_timing(emc, &emc->last_timing); + + return 0; +} + +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 %s: failed to read rate: %d\n", + node->name, err); + return err; + } + + timing->rate = value; + + err = of_property_read_u32_array(node, "nvidia,emc-configuration", + timing->emc_burst_data, + ARRAY_SIZE(timing->emc_burst_data)); + if (err) { + dev_err(emc->dev, + "timing %s: failed to read emc burst data: %d\n", + node->name, err); + return err; + } + +#define EMC_READ_PROP(prop, dtprop) { \ + err = of_property_read_u32(node, dtprop, &timing->prop); \ + if (err) { \ + dev_err(emc->dev, "timing %s: failed to read " #prop ": %d\n", \ + node->name, err); \ + return err; \ + } \ +} + + EMC_READ_PROP(emc_auto_cal_config, "nvidia,emc-auto-cal-config") + EMC_READ_PROP(emc_auto_cal_config2, "nvidia,emc-auto-cal-config2") + EMC_READ_PROP(emc_auto_cal_config3, "nvidia,emc-auto-cal-config3") + EMC_READ_PROP(emc_auto_cal_interval, "nvidia,emc-auto-cal-interval") + EMC_READ_PROP(emc_bgbias_ctl0, "nvidia,emc-bgbias-ctl0") + EMC_READ_PROP(emc_cfg, "nvidia,emc-cfg") + EMC_READ_PROP(emc_cfg_2, "nvidia,emc-cfg-2") + EMC_READ_PROP(emc_ctt_term_ctrl, "nvidia,emc-ctt-term-ctrl") + EMC_READ_PROP(emc_mode_1, "nvidia,emc-mode-1") + EMC_READ_PROP(emc_mode_2, "nvidia,emc-mode-2") + EMC_READ_PROP(emc_mode_4, "nvidia,emc-mode-4") + EMC_READ_PROP(emc_mode_reset, "nvidia,emc-mode-reset") + EMC_READ_PROP(emc_mrs_wait_cnt, "nvidia,emc-mrs-wait-cnt") + EMC_READ_PROP(emc_sel_dpd_ctrl, "nvidia,emc-sel-dpd-ctrl") + EMC_READ_PROP(emc_xm2dqspadctrl2, "nvidia,emc-xm2dqspadctrl2") + EMC_READ_PROP(emc_zcal_cnt_long, "nvidia,emc-zcal-cnt-long") + EMC_READ_PROP(emc_zcal_interval, "nvidia,emc-zcal-interval") + +#undef EMC_READ_PROP + + 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; + else if (a->rate == b->rate) + return 0; + else + return 1; +} + +static int tegra_emc_load_timings_from_dt(struct tegra_emc *emc, + struct device_node *node) +{ + int child_count = of_get_child_count(node); + struct device_node *child; + struct emc_timing *timing; + unsigned int i = 0; + int err; + + emc->timings = devm_kcalloc(emc->dev, child_count, sizeof(*timing), + GFP_KERNEL); + if (!emc->timings) + return -ENOMEM; + + emc->num_timings = child_count; + + for_each_child_of_node(node, child) { + timing = &emc->timings[i++]; + + 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); + + return 0; +} + +static const struct of_device_id tegra_emc_of_match[] = { + { .compatible = "nvidia,tegra124-emc" }, + {} +}; + +static struct device_node * +tegra_emc_find_node_by_ram_code(struct device_node *node, u32 ram_code) +{ + struct device_node *np; + int err; + + for_each_child_of_node(node, np) { + u32 value; + + err = of_property_read_u32(np, "nvidia,ram-code", &value); + if (err || (value != ram_code)) + continue; + + return np; + } + + return NULL; +} + +/* Debugfs entry */ + +static int emc_debug_rate_get(void *data, u64 *rate) +{ + struct clk *c = data; + + *rate = clk_get_rate(c); + + return 0; +} + +static int emc_debug_rate_set(void *data, u64 rate) +{ + struct clk *c = data; + + return clk_set_rate(c, rate); +} + +DEFINE_SIMPLE_ATTRIBUTE(emc_debug_rate_fops, emc_debug_rate_get, + emc_debug_rate_set, "%lld\n"); + +static int emc_debug_supported_rates_show(struct seq_file *s, void *data) +{ + struct tegra_emc *emc = s->private; + const char *prefix = ""; + unsigned int i; + + for (i = 0; i < emc->num_timings; i++) { + struct emc_timing *timing = &emc->timings[i]; + + seq_printf(s, "%s%lu", prefix, timing->rate); + + prefix = " "; + } + + seq_puts(s, "\n"); + + return 0; +} + +static int emc_debug_supported_rates_open(struct inode *inode, + struct file *file) +{ + return single_open(file, emc_debug_supported_rates_show, + inode->i_private); +} + +static const struct file_operations emc_debug_supported_rates_fops = { + .open = emc_debug_supported_rates_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void emc_debugfs_init(struct device *dev, struct tegra_emc *emc) +{ + struct dentry *root, *file; + struct clk *clk; + + root = debugfs_create_dir("emc", NULL); + if (!root) { + dev_err(dev, "failed to create debugfs directory\n"); + return; + } + + clk = clk_get_sys("tegra-clk-debug", "emc"); + if (IS_ERR(clk)) { + dev_err(dev, "failed to get debug clock: %ld\n", PTR_ERR(clk)); + return; + } + + file = debugfs_create_file("rate", S_IRUGO | S_IWUSR, root, clk, + &emc_debug_rate_fops); + if (!file) + dev_err(dev, "failed to create debugfs entry\n"); + + file = debugfs_create_file("supported_rates", S_IRUGO, root, emc, + &emc_debug_supported_rates_fops); + if (!file) + dev_err(dev, "failed to create debugfs entry\n"); +} + +static int tegra_emc_probe(struct platform_device *pdev) +{ + struct platform_device *mc; + struct device_node *np; + struct tegra_emc *emc; + struct resource *res; + u32 ram_code; + int err; + + emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL); + if (!emc) + return -ENOMEM; + + emc->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + emc->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(emc->regs)) + return PTR_ERR(emc->regs); + + np = of_parse_phandle(pdev->dev.of_node, "nvidia,memory-controller", 0); + if (!np) { + dev_err(&pdev->dev, "could not get memory controller\n"); + return -ENOENT; + } + + mc = of_find_device_by_node(np); + of_node_put(np); + if (!mc) + return -ENOENT; + + emc->mc = platform_get_drvdata(mc); + if (!emc->mc) + return -EPROBE_DEFER; + + ram_code = tegra_read_ram_code(); + + np = tegra_emc_find_node_by_ram_code(pdev->dev.of_node, ram_code); + if (!np) { + dev_err(&pdev->dev, + "no memory timings for RAM code %u found in DT\n", + ram_code); + return -ENOENT; + } + + err = tegra_emc_load_timings_from_dt(emc, np); + of_node_put(np); + if (err) + return err; + + if (emc->num_timings == 0) { + dev_err(&pdev->dev, + "no memory timings for RAM code %u registered\n", + ram_code); + return -ENOENT; + } + + err = emc_init(emc); + if (err) { + dev_err(&pdev->dev, "EMC initialization failed: %d\n", err); + return err; + } + + platform_set_drvdata(pdev, emc); + + if (IS_ENABLED(CONFIG_DEBUG_FS)) + emc_debugfs_init(&pdev->dev, emc); + + return 0; +}; + +static struct platform_driver tegra_emc_driver = { + .probe = tegra_emc_probe, + .driver = { + .name = "tegra-emc", + .of_match_table = tegra_emc_of_match, + .suppress_bind_attrs = true, + }, +}; + +static int tegra_emc_init(void) +{ + return platform_driver_register(&tegra_emc_driver); +} +subsys_initcall(tegra_emc_init); diff --git a/drivers/memory/tegra/tegra124.c b/drivers/memory/tegra/tegra124.c new file mode 100644 index 000000000..b561a1fe7 --- /dev/null +++ b/drivers/memory/tegra/tegra124.c @@ -0,0 +1,1111 @@ +/* + * Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/of.h> +#include <linux/mm.h> + +#include <dt-bindings/memory/tegra124-mc.h> + +#include "mc.h" + +#define MC_EMEM_ARB_CFG 0x90 +#define MC_EMEM_ARB_OUTSTANDING_REQ 0x94 +#define MC_EMEM_ARB_TIMING_RCD 0x98 +#define MC_EMEM_ARB_TIMING_RP 0x9c +#define MC_EMEM_ARB_TIMING_RC 0xa0 +#define MC_EMEM_ARB_TIMING_RAS 0xa4 +#define MC_EMEM_ARB_TIMING_FAW 0xa8 +#define MC_EMEM_ARB_TIMING_RRD 0xac +#define MC_EMEM_ARB_TIMING_RAP2PRE 0xb0 +#define MC_EMEM_ARB_TIMING_WAP2PRE 0xb4 +#define MC_EMEM_ARB_TIMING_R2R 0xb8 +#define MC_EMEM_ARB_TIMING_W2W 0xbc +#define MC_EMEM_ARB_TIMING_R2W 0xc0 +#define MC_EMEM_ARB_TIMING_W2R 0xc4 +#define MC_EMEM_ARB_DA_TURNS 0xd0 +#define MC_EMEM_ARB_DA_COVERS 0xd4 +#define MC_EMEM_ARB_MISC0 0xd8 +#define MC_EMEM_ARB_MISC1 0xdc +#define MC_EMEM_ARB_RING1_THROTTLE 0xe0 + +static const unsigned long tegra124_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_MISC1, + MC_EMEM_ARB_RING1_THROTTLE +}; + +static const struct tegra_mc_client tegra124_mc_clients[] = { + { + .id = 0x00, + .name = "ptcr", + .swgroup = TEGRA_SWGROUP_PTC, + }, { + .id = 0x01, + .name = "display0a", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 1, + }, + .la = { + .reg = 0x2e8, + .shift = 0, + .mask = 0xff, + .def = 0xc2, + }, + }, { + .id = 0x02, + .name = "display0ab", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 2, + }, + .la = { + .reg = 0x2f4, + .shift = 0, + .mask = 0xff, + .def = 0xc6, + }, + }, { + .id = 0x03, + .name = "display0b", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 3, + }, + .la = { + .reg = 0x2e8, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x04, + .name = "display0bb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 4, + }, + .la = { + .reg = 0x2f4, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x05, + .name = "display0c", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 5, + }, + .la = { + .reg = 0x2ec, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x06, + .name = "display0cb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 6, + }, + .la = { + .reg = 0x2f8, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x0e, + .name = "afir", + .swgroup = TEGRA_SWGROUP_AFI, + .smmu = { + .reg = 0x228, + .bit = 14, + }, + .la = { + .reg = 0x2e0, + .shift = 0, + .mask = 0xff, + .def = 0x13, + }, + }, { + .id = 0x0f, + .name = "avpcarm7r", + .swgroup = TEGRA_SWGROUP_AVPC, + .smmu = { + .reg = 0x228, + .bit = 15, + }, + .la = { + .reg = 0x2e4, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x10, + .name = "displayhc", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 16, + }, + .la = { + .reg = 0x2f0, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x11, + .name = "displayhcb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 17, + }, + .la = { + .reg = 0x2fc, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x15, + .name = "hdar", + .swgroup = TEGRA_SWGROUP_HDA, + .smmu = { + .reg = 0x228, + .bit = 21, + }, + .la = { + .reg = 0x318, + .shift = 0, + .mask = 0xff, + .def = 0x24, + }, + }, { + .id = 0x16, + .name = "host1xdmar", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x228, + .bit = 22, + }, + .la = { + .reg = 0x310, + .shift = 0, + .mask = 0xff, + .def = 0x1e, + }, + }, { + .id = 0x17, + .name = "host1xr", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x228, + .bit = 23, + }, + .la = { + .reg = 0x310, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x1c, + .name = "msencsrd", + .swgroup = TEGRA_SWGROUP_MSENC, + .smmu = { + .reg = 0x228, + .bit = 28, + }, + .la = { + .reg = 0x328, + .shift = 0, + .mask = 0xff, + .def = 0x23, + }, + }, { + .id = 0x1d, + .name = "ppcsahbdmar", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x228, + .bit = 29, + }, + .la = { + .reg = 0x344, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x1e, + .name = "ppcsahbslvr", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x228, + .bit = 30, + }, + .la = { + .reg = 0x344, + .shift = 16, + .mask = 0xff, + .def = 0x1a, + }, + }, { + .id = 0x1f, + .name = "satar", + .swgroup = TEGRA_SWGROUP_SATA, + .smmu = { + .reg = 0x228, + .bit = 31, + }, + .la = { + .reg = 0x350, + .shift = 0, + .mask = 0xff, + .def = 0x65, + }, + }, { + .id = 0x22, + .name = "vdebsevr", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 2, + }, + .la = { + .reg = 0x354, + .shift = 0, + .mask = 0xff, + .def = 0x4f, + }, + }, { + .id = 0x23, + .name = "vdember", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 3, + }, + .la = { + .reg = 0x354, + .shift = 16, + .mask = 0xff, + .def = 0x3d, + }, + }, { + .id = 0x24, + .name = "vdemcer", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 4, + }, + .la = { + .reg = 0x358, + .shift = 0, + .mask = 0xff, + .def = 0x66, + }, + }, { + .id = 0x25, + .name = "vdetper", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 5, + }, + .la = { + .reg = 0x358, + .shift = 16, + .mask = 0xff, + .def = 0xa5, + }, + }, { + .id = 0x26, + .name = "mpcorelpr", + .swgroup = TEGRA_SWGROUP_MPCORELP, + .la = { + .reg = 0x324, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x27, + .name = "mpcorer", + .swgroup = TEGRA_SWGROUP_MPCORE, + .la = { + .reg = 0x320, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x2b, + .name = "msencswr", + .swgroup = TEGRA_SWGROUP_MSENC, + .smmu = { + .reg = 0x22c, + .bit = 11, + }, + .la = { + .reg = 0x328, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x31, + .name = "afiw", + .swgroup = TEGRA_SWGROUP_AFI, + .smmu = { + .reg = 0x22c, + .bit = 17, + }, + .la = { + .reg = 0x2e0, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x32, + .name = "avpcarm7w", + .swgroup = TEGRA_SWGROUP_AVPC, + .smmu = { + .reg = 0x22c, + .bit = 18, + }, + .la = { + .reg = 0x2e4, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x35, + .name = "hdaw", + .swgroup = TEGRA_SWGROUP_HDA, + .smmu = { + .reg = 0x22c, + .bit = 21, + }, + .la = { + .reg = 0x318, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x36, + .name = "host1xw", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x22c, + .bit = 22, + }, + .la = { + .reg = 0x314, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x38, + .name = "mpcorelpw", + .swgroup = TEGRA_SWGROUP_MPCORELP, + .la = { + .reg = 0x324, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x39, + .name = "mpcorew", + .swgroup = TEGRA_SWGROUP_MPCORE, + .la = { + .reg = 0x320, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3b, + .name = "ppcsahbdmaw", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x22c, + .bit = 27, + }, + .la = { + .reg = 0x348, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3c, + .name = "ppcsahbslvw", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x22c, + .bit = 28, + }, + .la = { + .reg = 0x348, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3d, + .name = "sataw", + .swgroup = TEGRA_SWGROUP_SATA, + .smmu = { + .reg = 0x22c, + .bit = 29, + }, + .la = { + .reg = 0x350, + .shift = 16, + .mask = 0xff, + .def = 0x65, + }, + }, { + .id = 0x3e, + .name = "vdebsevw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 30, + }, + .la = { + .reg = 0x35c, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3f, + .name = "vdedbgw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 31, + }, + .la = { + .reg = 0x35c, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x40, + .name = "vdembew", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x230, + .bit = 0, + }, + .la = { + .reg = 0x360, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x41, + .name = "vdetpmw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x230, + .bit = 1, + }, + .la = { + .reg = 0x360, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x44, + .name = "ispra", + .swgroup = TEGRA_SWGROUP_ISP2, + .smmu = { + .reg = 0x230, + .bit = 4, + }, + .la = { + .reg = 0x370, + .shift = 0, + .mask = 0xff, + .def = 0x18, + }, + }, { + .id = 0x46, + .name = "ispwa", + .swgroup = TEGRA_SWGROUP_ISP2, + .smmu = { + .reg = 0x230, + .bit = 6, + }, + .la = { + .reg = 0x374, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x47, + .name = "ispwb", + .swgroup = TEGRA_SWGROUP_ISP2, + .smmu = { + .reg = 0x230, + .bit = 7, + }, + .la = { + .reg = 0x374, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x4a, + .name = "xusb_hostr", + .swgroup = TEGRA_SWGROUP_XUSB_HOST, + .smmu = { + .reg = 0x230, + .bit = 10, + }, + .la = { + .reg = 0x37c, + .shift = 0, + .mask = 0xff, + .def = 0x39, + }, + }, { + .id = 0x4b, + .name = "xusb_hostw", + .swgroup = TEGRA_SWGROUP_XUSB_HOST, + .smmu = { + .reg = 0x230, + .bit = 11, + }, + .la = { + .reg = 0x37c, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x4c, + .name = "xusb_devr", + .swgroup = TEGRA_SWGROUP_XUSB_DEV, + .smmu = { + .reg = 0x230, + .bit = 12, + }, + .la = { + .reg = 0x380, + .shift = 0, + .mask = 0xff, + .def = 0x39, + }, + }, { + .id = 0x4d, + .name = "xusb_devw", + .swgroup = TEGRA_SWGROUP_XUSB_DEV, + .smmu = { + .reg = 0x230, + .bit = 13, + }, + .la = { + .reg = 0x380, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x4e, + .name = "isprab", + .swgroup = TEGRA_SWGROUP_ISP2B, + .smmu = { + .reg = 0x230, + .bit = 14, + }, + .la = { + .reg = 0x384, + .shift = 0, + .mask = 0xff, + .def = 0x18, + }, + }, { + .id = 0x50, + .name = "ispwab", + .swgroup = TEGRA_SWGROUP_ISP2B, + .smmu = { + .reg = 0x230, + .bit = 16, + }, + .la = { + .reg = 0x388, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x51, + .name = "ispwbb", + .swgroup = TEGRA_SWGROUP_ISP2B, + .smmu = { + .reg = 0x230, + .bit = 17, + }, + .la = { + .reg = 0x388, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x54, + .name = "tsecsrd", + .swgroup = TEGRA_SWGROUP_TSEC, + .smmu = { + .reg = 0x230, + .bit = 20, + }, + .la = { + .reg = 0x390, + .shift = 0, + .mask = 0xff, + .def = 0x9b, + }, + }, { + .id = 0x55, + .name = "tsecswr", + .swgroup = TEGRA_SWGROUP_TSEC, + .smmu = { + .reg = 0x230, + .bit = 21, + }, + .la = { + .reg = 0x390, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x56, + .name = "a9avpscr", + .swgroup = TEGRA_SWGROUP_A9AVP, + .smmu = { + .reg = 0x230, + .bit = 22, + }, + .la = { + .reg = 0x3a4, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x57, + .name = "a9avpscw", + .swgroup = TEGRA_SWGROUP_A9AVP, + .smmu = { + .reg = 0x230, + .bit = 23, + }, + .la = { + .reg = 0x3a4, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x58, + .name = "gpusrd", + .swgroup = TEGRA_SWGROUP_GPU, + .smmu = { + /* read-only */ + .reg = 0x230, + .bit = 24, + }, + .la = { + .reg = 0x3c8, + .shift = 0, + .mask = 0xff, + .def = 0x1a, + }, + }, { + .id = 0x59, + .name = "gpuswr", + .swgroup = TEGRA_SWGROUP_GPU, + .smmu = { + /* read-only */ + .reg = 0x230, + .bit = 25, + }, + .la = { + .reg = 0x3c8, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x5a, + .name = "displayt", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x230, + .bit = 26, + }, + .la = { + .reg = 0x2f0, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x60, + .name = "sdmmcra", + .swgroup = TEGRA_SWGROUP_SDMMC1A, + .smmu = { + .reg = 0x234, + .bit = 0, + }, + .la = { + .reg = 0x3b8, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x61, + .name = "sdmmcraa", + .swgroup = TEGRA_SWGROUP_SDMMC2A, + .smmu = { + .reg = 0x234, + .bit = 1, + }, + .la = { + .reg = 0x3bc, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x62, + .name = "sdmmcr", + .swgroup = TEGRA_SWGROUP_SDMMC3A, + .smmu = { + .reg = 0x234, + .bit = 2, + }, + .la = { + .reg = 0x3c0, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x63, + .swgroup = TEGRA_SWGROUP_SDMMC4A, + .name = "sdmmcrab", + .smmu = { + .reg = 0x234, + .bit = 3, + }, + .la = { + .reg = 0x3c4, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x64, + .name = "sdmmcwa", + .swgroup = TEGRA_SWGROUP_SDMMC1A, + .smmu = { + .reg = 0x234, + .bit = 4, + }, + .la = { + .reg = 0x3b8, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x65, + .name = "sdmmcwaa", + .swgroup = TEGRA_SWGROUP_SDMMC2A, + .smmu = { + .reg = 0x234, + .bit = 5, + }, + .la = { + .reg = 0x3bc, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x66, + .name = "sdmmcw", + .swgroup = TEGRA_SWGROUP_SDMMC3A, + .smmu = { + .reg = 0x234, + .bit = 6, + }, + .la = { + .reg = 0x3c0, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x67, + .name = "sdmmcwab", + .swgroup = TEGRA_SWGROUP_SDMMC4A, + .smmu = { + .reg = 0x234, + .bit = 7, + }, + .la = { + .reg = 0x3c4, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x6c, + .name = "vicsrd", + .swgroup = TEGRA_SWGROUP_VIC, + .smmu = { + .reg = 0x234, + .bit = 12, + }, + .la = { + .reg = 0x394, + .shift = 0, + .mask = 0xff, + .def = 0x1a, + }, + }, { + .id = 0x6d, + .name = "vicswr", + .swgroup = TEGRA_SWGROUP_VIC, + .smmu = { + .reg = 0x234, + .bit = 13, + }, + .la = { + .reg = 0x394, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x72, + .name = "viw", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x234, + .bit = 18, + }, + .la = { + .reg = 0x398, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x73, + .name = "displayd", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x234, + .bit = 19, + }, + .la = { + .reg = 0x3c8, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, +}; + +static const struct tegra_smmu_swgroup tegra124_swgroups[] = { + { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 }, + { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 }, + { .name = "afi", .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 }, + { .name = "avpc", .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c }, + { .name = "hda", .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 }, + { .name = "hc", .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 }, + { .name = "msenc", .swgroup = TEGRA_SWGROUP_MSENC, .reg = 0x264 }, + { .name = "ppcs", .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 }, + { .name = "sata", .swgroup = TEGRA_SWGROUP_SATA, .reg = 0x274 }, + { .name = "vde", .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c }, + { .name = "isp2", .swgroup = TEGRA_SWGROUP_ISP2, .reg = 0x258 }, + { .name = "xusb_host", .swgroup = TEGRA_SWGROUP_XUSB_HOST, .reg = 0x288 }, + { .name = "xusb_dev", .swgroup = TEGRA_SWGROUP_XUSB_DEV, .reg = 0x28c }, + { .name = "isp2b", .swgroup = TEGRA_SWGROUP_ISP2B, .reg = 0xaa4 }, + { .name = "tsec", .swgroup = TEGRA_SWGROUP_TSEC, .reg = 0x294 }, + { .name = "a9avp", .swgroup = TEGRA_SWGROUP_A9AVP, .reg = 0x290 }, + { .name = "gpu", .swgroup = TEGRA_SWGROUP_GPU, .reg = 0xaac }, + { .name = "sdmmc1a", .swgroup = TEGRA_SWGROUP_SDMMC1A, .reg = 0xa94 }, + { .name = "sdmmc2a", .swgroup = TEGRA_SWGROUP_SDMMC2A, .reg = 0xa98 }, + { .name = "sdmmc3a", .swgroup = TEGRA_SWGROUP_SDMMC3A, .reg = 0xa9c }, + { .name = "sdmmc4a", .swgroup = TEGRA_SWGROUP_SDMMC4A, .reg = 0xaa0 }, + { .name = "vic", .swgroup = TEGRA_SWGROUP_VIC, .reg = 0x284 }, + { .name = "vi", .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 }, +}; + +static const unsigned int tegra124_group_display[] = { + TEGRA_SWGROUP_DC, + TEGRA_SWGROUP_DCB, +}; + +static const struct tegra_smmu_group_soc tegra124_groups[] = { + { + .name = "display", + .swgroups = tegra124_group_display, + .num_swgroups = ARRAY_SIZE(tegra124_group_display), + }, +}; + +#define TEGRA124_MC_RESET(_name, _control, _status, _bit) \ + { \ + .name = #_name, \ + .id = TEGRA124_MC_RESET_##_name, \ + .control = _control, \ + .status = _status, \ + .bit = _bit, \ + } + +static const struct tegra_mc_reset tegra124_mc_resets[] = { + TEGRA124_MC_RESET(AFI, 0x200, 0x204, 0), + TEGRA124_MC_RESET(AVPC, 0x200, 0x204, 1), + TEGRA124_MC_RESET(DC, 0x200, 0x204, 2), + TEGRA124_MC_RESET(DCB, 0x200, 0x204, 3), + TEGRA124_MC_RESET(HC, 0x200, 0x204, 6), + TEGRA124_MC_RESET(HDA, 0x200, 0x204, 7), + TEGRA124_MC_RESET(ISP2, 0x200, 0x204, 8), + TEGRA124_MC_RESET(MPCORE, 0x200, 0x204, 9), + TEGRA124_MC_RESET(MPCORELP, 0x200, 0x204, 10), + TEGRA124_MC_RESET(MSENC, 0x200, 0x204, 11), + TEGRA124_MC_RESET(PPCS, 0x200, 0x204, 14), + TEGRA124_MC_RESET(SATA, 0x200, 0x204, 15), + TEGRA124_MC_RESET(VDE, 0x200, 0x204, 16), + TEGRA124_MC_RESET(VI, 0x200, 0x204, 17), + TEGRA124_MC_RESET(VIC, 0x200, 0x204, 18), + TEGRA124_MC_RESET(XUSB_HOST, 0x200, 0x204, 19), + TEGRA124_MC_RESET(XUSB_DEV, 0x200, 0x204, 20), + TEGRA124_MC_RESET(TSEC, 0x200, 0x204, 21), + TEGRA124_MC_RESET(SDMMC1, 0x200, 0x204, 22), + TEGRA124_MC_RESET(SDMMC2, 0x200, 0x204, 23), + TEGRA124_MC_RESET(SDMMC3, 0x200, 0x204, 25), + TEGRA124_MC_RESET(SDMMC4, 0x970, 0x974, 0), + TEGRA124_MC_RESET(ISP2B, 0x970, 0x974, 1), + TEGRA124_MC_RESET(GPU, 0x970, 0x974, 2), +}; + +#ifdef CONFIG_ARCH_TEGRA_124_SOC +static const struct tegra_smmu_soc tegra124_smmu_soc = { + .clients = tegra124_mc_clients, + .num_clients = ARRAY_SIZE(tegra124_mc_clients), + .swgroups = tegra124_swgroups, + .num_swgroups = ARRAY_SIZE(tegra124_swgroups), + .groups = tegra124_groups, + .num_groups = ARRAY_SIZE(tegra124_groups), + .supports_round_robin_arbitration = true, + .supports_request_limit = true, + .num_tlb_lines = 32, + .num_asids = 128, +}; + +const struct tegra_mc_soc tegra124_mc_soc = { + .clients = tegra124_mc_clients, + .num_clients = ARRAY_SIZE(tegra124_mc_clients), + .num_address_bits = 34, + .atom_size = 32, + .client_id_mask = 0x7f, + .smmu = &tegra124_smmu_soc, + .emem_regs = tegra124_mc_emem_regs, + .num_emem_regs = ARRAY_SIZE(tegra124_mc_emem_regs), + .intmask = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR | + MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE | + MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM, + .reset_ops = &terga_mc_reset_ops_common, + .resets = tegra124_mc_resets, + .num_resets = ARRAY_SIZE(tegra124_mc_resets), +}; +#endif /* CONFIG_ARCH_TEGRA_124_SOC */ + +#ifdef CONFIG_ARCH_TEGRA_132_SOC +static const struct tegra_smmu_soc tegra132_smmu_soc = { + .clients = tegra124_mc_clients, + .num_clients = ARRAY_SIZE(tegra124_mc_clients), + .swgroups = tegra124_swgroups, + .num_swgroups = ARRAY_SIZE(tegra124_swgroups), + .groups = tegra124_groups, + .num_groups = ARRAY_SIZE(tegra124_groups), + .supports_round_robin_arbitration = true, + .supports_request_limit = true, + .num_tlb_lines = 32, + .num_asids = 128, +}; + +const struct tegra_mc_soc tegra132_mc_soc = { + .clients = tegra124_mc_clients, + .num_clients = ARRAY_SIZE(tegra124_mc_clients), + .num_address_bits = 34, + .atom_size = 32, + .client_id_mask = 0x7f, + .smmu = &tegra132_smmu_soc, + .intmask = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR | + MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE | + MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM, + .reset_ops = &terga_mc_reset_ops_common, + .resets = tegra124_mc_resets, + .num_resets = ARRAY_SIZE(tegra124_mc_resets), +}; +#endif /* CONFIG_ARCH_TEGRA_132_SOC */ diff --git a/drivers/memory/tegra/tegra186.c b/drivers/memory/tegra/tegra186.c new file mode 100644 index 000000000..ffda903c4 --- /dev/null +++ b/drivers/memory/tegra/tegra186.c @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2017 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> + +#include <dt-bindings/memory/tegra186-mc.h> + +struct tegra_mc { + struct device *dev; + void __iomem *regs; +}; + +struct tegra_mc_client { + const char *name; + unsigned int sid; + struct { + unsigned int override; + unsigned int security; + } regs; +}; + +static const struct tegra_mc_client tegra186_mc_clients[] = { + { + .name = "ptcr", + .sid = TEGRA186_SID_PASSTHROUGH, + .regs = { + .override = 0x000, + .security = 0x004, + }, + }, { + .name = "afir", + .sid = TEGRA186_SID_AFI, + .regs = { + .override = 0x070, + .security = 0x074, + }, + }, { + .name = "hdar", + .sid = TEGRA186_SID_HDA, + .regs = { + .override = 0x0a8, + .security = 0x0ac, + }, + }, { + .name = "host1xdmar", + .sid = TEGRA186_SID_HOST1X, + .regs = { + .override = 0x0b0, + .security = 0x0b4, + }, + }, { + .name = "nvencsrd", + .sid = TEGRA186_SID_NVENC, + .regs = { + .override = 0x0e0, + .security = 0x0e4, + }, + }, { + .name = "satar", + .sid = TEGRA186_SID_SATA, + .regs = { + .override = 0x0f8, + .security = 0x0fc, + }, + }, { + .name = "mpcorer", + .sid = TEGRA186_SID_PASSTHROUGH, + .regs = { + .override = 0x138, + .security = 0x13c, + }, + }, { + .name = "nvencswr", + .sid = TEGRA186_SID_NVENC, + .regs = { + .override = 0x158, + .security = 0x15c, + }, + }, { + .name = "afiw", + .sid = TEGRA186_SID_AFI, + .regs = { + .override = 0x188, + .security = 0x18c, + }, + }, { + .name = "hdaw", + .sid = TEGRA186_SID_HDA, + .regs = { + .override = 0x1a8, + .security = 0x1ac, + }, + }, { + .name = "mpcorew", + .sid = TEGRA186_SID_PASSTHROUGH, + .regs = { + .override = 0x1c8, + .security = 0x1cc, + }, + }, { + .name = "sataw", + .sid = TEGRA186_SID_SATA, + .regs = { + .override = 0x1e8, + .security = 0x1ec, + }, + }, { + .name = "ispra", + .sid = TEGRA186_SID_ISP, + .regs = { + .override = 0x220, + .security = 0x224, + }, + }, { + .name = "ispwa", + .sid = TEGRA186_SID_ISP, + .regs = { + .override = 0x230, + .security = 0x234, + }, + }, { + .name = "ispwb", + .sid = TEGRA186_SID_ISP, + .regs = { + .override = 0x238, + .security = 0x23c, + }, + }, { + .name = "xusb_hostr", + .sid = TEGRA186_SID_XUSB_HOST, + .regs = { + .override = 0x250, + .security = 0x254, + }, + }, { + .name = "xusb_hostw", + .sid = TEGRA186_SID_XUSB_HOST, + .regs = { + .override = 0x258, + .security = 0x25c, + }, + }, { + .name = "xusb_devr", + .sid = TEGRA186_SID_XUSB_DEV, + .regs = { + .override = 0x260, + .security = 0x264, + }, + }, { + .name = "xusb_devw", + .sid = TEGRA186_SID_XUSB_DEV, + .regs = { + .override = 0x268, + .security = 0x26c, + }, + }, { + .name = "tsecsrd", + .sid = TEGRA186_SID_TSEC, + .regs = { + .override = 0x2a0, + .security = 0x2a4, + }, + }, { + .name = "tsecswr", + .sid = TEGRA186_SID_TSEC, + .regs = { + .override = 0x2a8, + .security = 0x2ac, + }, + }, { + .name = "gpusrd", + .sid = TEGRA186_SID_GPU, + .regs = { + .override = 0x2c0, + .security = 0x2c4, + }, + }, { + .name = "gpuswr", + .sid = TEGRA186_SID_GPU, + .regs = { + .override = 0x2c8, + .security = 0x2cc, + }, + }, { + .name = "sdmmcra", + .sid = TEGRA186_SID_SDMMC1, + .regs = { + .override = 0x300, + .security = 0x304, + }, + }, { + .name = "sdmmcraa", + .sid = TEGRA186_SID_SDMMC2, + .regs = { + .override = 0x308, + .security = 0x30c, + }, + }, { + .name = "sdmmcr", + .sid = TEGRA186_SID_SDMMC3, + .regs = { + .override = 0x310, + .security = 0x314, + }, + }, { + .name = "sdmmcrab", + .sid = TEGRA186_SID_SDMMC4, + .regs = { + .override = 0x318, + .security = 0x31c, + }, + }, { + .name = "sdmmcwa", + .sid = TEGRA186_SID_SDMMC1, + .regs = { + .override = 0x320, + .security = 0x324, + }, + }, { + .name = "sdmmcwaa", + .sid = TEGRA186_SID_SDMMC2, + .regs = { + .override = 0x328, + .security = 0x32c, + }, + }, { + .name = "sdmmcw", + .sid = TEGRA186_SID_SDMMC3, + .regs = { + .override = 0x330, + .security = 0x334, + }, + }, { + .name = "sdmmcwab", + .sid = TEGRA186_SID_SDMMC4, + .regs = { + .override = 0x338, + .security = 0x33c, + }, + }, { + .name = "vicsrd", + .sid = TEGRA186_SID_VIC, + .regs = { + .override = 0x360, + .security = 0x364, + }, + }, { + .name = "vicswr", + .sid = TEGRA186_SID_VIC, + .regs = { + .override = 0x368, + .security = 0x36c, + }, + }, { + .name = "viw", + .sid = TEGRA186_SID_VI, + .regs = { + .override = 0x390, + .security = 0x394, + }, + }, { + .name = "nvdecsrd", + .sid = TEGRA186_SID_NVDEC, + .regs = { + .override = 0x3c0, + .security = 0x3c4, + }, + }, { + .name = "nvdecswr", + .sid = TEGRA186_SID_NVDEC, + .regs = { + .override = 0x3c8, + .security = 0x3cc, + }, + }, { + .name = "aper", + .sid = TEGRA186_SID_APE, + .regs = { + .override = 0x3d0, + .security = 0x3d4, + }, + }, { + .name = "apew", + .sid = TEGRA186_SID_APE, + .regs = { + .override = 0x3d8, + .security = 0x3dc, + }, + }, { + .name = "nvjpgsrd", + .sid = TEGRA186_SID_NVJPG, + .regs = { + .override = 0x3f0, + .security = 0x3f4, + }, + }, { + .name = "nvjpgswr", + .sid = TEGRA186_SID_NVJPG, + .regs = { + .override = 0x3f8, + .security = 0x3fc, + }, + }, { + .name = "sesrd", + .sid = TEGRA186_SID_SE, + .regs = { + .override = 0x400, + .security = 0x404, + }, + }, { + .name = "seswr", + .sid = TEGRA186_SID_SE, + .regs = { + .override = 0x408, + .security = 0x40c, + }, + }, { + .name = "etrr", + .sid = TEGRA186_SID_ETR, + .regs = { + .override = 0x420, + .security = 0x424, + }, + }, { + .name = "etrw", + .sid = TEGRA186_SID_ETR, + .regs = { + .override = 0x428, + .security = 0x42c, + }, + }, { + .name = "tsecsrdb", + .sid = TEGRA186_SID_TSECB, + .regs = { + .override = 0x430, + .security = 0x434, + }, + }, { + .name = "tsecswrb", + .sid = TEGRA186_SID_TSECB, + .regs = { + .override = 0x438, + .security = 0x43c, + }, + }, { + .name = "gpusrd2", + .sid = TEGRA186_SID_GPU, + .regs = { + .override = 0x440, + .security = 0x444, + }, + }, { + .name = "gpuswr2", + .sid = TEGRA186_SID_GPU, + .regs = { + .override = 0x448, + .security = 0x44c, + }, + }, { + .name = "axisr", + .sid = TEGRA186_SID_GPCDMA_0, + .regs = { + .override = 0x460, + .security = 0x464, + }, + }, { + .name = "axisw", + .sid = TEGRA186_SID_GPCDMA_0, + .regs = { + .override = 0x468, + .security = 0x46c, + }, + }, { + .name = "eqosr", + .sid = TEGRA186_SID_EQOS, + .regs = { + .override = 0x470, + .security = 0x474, + }, + }, { + .name = "eqosw", + .sid = TEGRA186_SID_EQOS, + .regs = { + .override = 0x478, + .security = 0x47c, + }, + }, { + .name = "ufshcr", + .sid = TEGRA186_SID_UFSHC, + .regs = { + .override = 0x480, + .security = 0x484, + }, + }, { + .name = "ufshcw", + .sid = TEGRA186_SID_UFSHC, + .regs = { + .override = 0x488, + .security = 0x48c, + }, + }, { + .name = "nvdisplayr", + .sid = TEGRA186_SID_NVDISPLAY, + .regs = { + .override = 0x490, + .security = 0x494, + }, + }, { + .name = "bpmpr", + .sid = TEGRA186_SID_BPMP, + .regs = { + .override = 0x498, + .security = 0x49c, + }, + }, { + .name = "bpmpw", + .sid = TEGRA186_SID_BPMP, + .regs = { + .override = 0x4a0, + .security = 0x4a4, + }, + }, { + .name = "bpmpdmar", + .sid = TEGRA186_SID_BPMP, + .regs = { + .override = 0x4a8, + .security = 0x4ac, + }, + }, { + .name = "bpmpdmaw", + .sid = TEGRA186_SID_BPMP, + .regs = { + .override = 0x4b0, + .security = 0x4b4, + }, + }, { + .name = "aonr", + .sid = TEGRA186_SID_AON, + .regs = { + .override = 0x4b8, + .security = 0x4bc, + }, + }, { + .name = "aonw", + .sid = TEGRA186_SID_AON, + .regs = { + .override = 0x4c0, + .security = 0x4c4, + }, + }, { + .name = "aondmar", + .sid = TEGRA186_SID_AON, + .regs = { + .override = 0x4c8, + .security = 0x4cc, + }, + }, { + .name = "aondmaw", + .sid = TEGRA186_SID_AON, + .regs = { + .override = 0x4d0, + .security = 0x4d4, + }, + }, { + .name = "scer", + .sid = TEGRA186_SID_SCE, + .regs = { + .override = 0x4d8, + .security = 0x4dc, + }, + }, { + .name = "scew", + .sid = TEGRA186_SID_SCE, + .regs = { + .override = 0x4e0, + .security = 0x4e4, + }, + }, { + .name = "scedmar", + .sid = TEGRA186_SID_SCE, + .regs = { + .override = 0x4e8, + .security = 0x4ec, + }, + }, { + .name = "scedmaw", + .sid = TEGRA186_SID_SCE, + .regs = { + .override = 0x4f0, + .security = 0x4f4, + }, + }, { + .name = "apedmar", + .sid = TEGRA186_SID_APE, + .regs = { + .override = 0x4f8, + .security = 0x4fc, + }, + }, { + .name = "apedmaw", + .sid = TEGRA186_SID_APE, + .regs = { + .override = 0x500, + .security = 0x504, + }, + }, { + .name = "nvdisplayr1", + .sid = TEGRA186_SID_NVDISPLAY, + .regs = { + .override = 0x508, + .security = 0x50c, + }, + }, { + .name = "vicsrd1", + .sid = TEGRA186_SID_VIC, + .regs = { + .override = 0x510, + .security = 0x514, + }, + }, { + .name = "nvdecsrd1", + .sid = TEGRA186_SID_NVDEC, + .regs = { + .override = 0x518, + .security = 0x51c, + }, + }, +}; + +static int tegra186_mc_probe(struct platform_device *pdev) +{ + struct resource *res; + struct tegra_mc *mc; + unsigned int i; + int err = 0; + + mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL); + if (!mc) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mc->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mc->regs)) + return PTR_ERR(mc->regs); + + mc->dev = &pdev->dev; + + for (i = 0; i < ARRAY_SIZE(tegra186_mc_clients); i++) { + const struct tegra_mc_client *client = &tegra186_mc_clients[i]; + u32 override, security; + + override = readl(mc->regs + client->regs.override); + security = readl(mc->regs + client->regs.security); + + dev_dbg(&pdev->dev, "client %s: override: %x security: %x\n", + client->name, override, security); + + dev_dbg(&pdev->dev, "setting SID %u for %s\n", client->sid, + client->name); + writel(client->sid, mc->regs + client->regs.override); + + override = readl(mc->regs + client->regs.override); + security = readl(mc->regs + client->regs.security); + + dev_dbg(&pdev->dev, "client %s: override: %x security: %x\n", + client->name, override, security); + } + + platform_set_drvdata(pdev, mc); + + return err; +} + +static const struct of_device_id tegra186_mc_of_match[] = { + { .compatible = "nvidia,tegra186-mc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tegra186_mc_of_match); + +static struct platform_driver tegra186_mc_driver = { + .driver = { + .name = "tegra186-mc", + .of_match_table = tegra186_mc_of_match, + .suppress_bind_attrs = true, + }, + .prevent_deferred_probe = true, + .probe = tegra186_mc_probe, +}; +module_platform_driver(tegra186_mc_driver); + +MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>"); +MODULE_DESCRIPTION("NVIDIA Tegra186 Memory Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/memory/tegra/tegra20.c b/drivers/memory/tegra/tegra20.c new file mode 100644 index 000000000..7119e5324 --- /dev/null +++ b/drivers/memory/tegra/tegra20.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2012 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <dt-bindings/memory/tegra20-mc.h> + +#include "mc.h" + +static const struct tegra_mc_client tegra20_mc_clients[] = { + { + .id = 0x00, + .name = "display0a", + }, { + .id = 0x01, + .name = "display0ab", + }, { + .id = 0x02, + .name = "display0b", + }, { + .id = 0x03, + .name = "display0bb", + }, { + .id = 0x04, + .name = "display0c", + }, { + .id = 0x05, + .name = "display0cb", + }, { + .id = 0x06, + .name = "display1b", + }, { + .id = 0x07, + .name = "display1bb", + }, { + .id = 0x08, + .name = "eppup", + }, { + .id = 0x09, + .name = "g2pr", + }, { + .id = 0x0a, + .name = "g2sr", + }, { + .id = 0x0b, + .name = "mpeunifbr", + }, { + .id = 0x0c, + .name = "viruv", + }, { + .id = 0x0d, + .name = "avpcarm7r", + }, { + .id = 0x0e, + .name = "displayhc", + }, { + .id = 0x0f, + .name = "displayhcb", + }, { + .id = 0x10, + .name = "fdcdrd", + }, { + .id = 0x11, + .name = "g2dr", + }, { + .id = 0x12, + .name = "host1xdmar", + }, { + .id = 0x13, + .name = "host1xr", + }, { + .id = 0x14, + .name = "idxsrd", + }, { + .id = 0x15, + .name = "mpcorer", + }, { + .id = 0x16, + .name = "mpe_ipred", + }, { + .id = 0x17, + .name = "mpeamemrd", + }, { + .id = 0x18, + .name = "mpecsrd", + }, { + .id = 0x19, + .name = "ppcsahbdmar", + }, { + .id = 0x1a, + .name = "ppcsahbslvr", + }, { + .id = 0x1b, + .name = "texsrd", + }, { + .id = 0x1c, + .name = "vdebsevr", + }, { + .id = 0x1d, + .name = "vdember", + }, { + .id = 0x1e, + .name = "vdemcer", + }, { + .id = 0x1f, + .name = "vdetper", + }, { + .id = 0x20, + .name = "eppu", + }, { + .id = 0x21, + .name = "eppv", + }, { + .id = 0x22, + .name = "eppy", + }, { + .id = 0x23, + .name = "mpeunifbw", + }, { + .id = 0x24, + .name = "viwsb", + }, { + .id = 0x25, + .name = "viwu", + }, { + .id = 0x26, + .name = "viwv", + }, { + .id = 0x27, + .name = "viwy", + }, { + .id = 0x28, + .name = "g2dw", + }, { + .id = 0x29, + .name = "avpcarm7w", + }, { + .id = 0x2a, + .name = "fdcdwr", + }, { + .id = 0x2b, + .name = "host1xw", + }, { + .id = 0x2c, + .name = "ispw", + }, { + .id = 0x2d, + .name = "mpcorew", + }, { + .id = 0x2e, + .name = "mpecswr", + }, { + .id = 0x2f, + .name = "ppcsahbdmaw", + }, { + .id = 0x30, + .name = "ppcsahbslvw", + }, { + .id = 0x31, + .name = "vdebsevw", + }, { + .id = 0x32, + .name = "vdembew", + }, { + .id = 0x33, + .name = "vdetpmw", + }, +}; + +#define TEGRA20_MC_RESET(_name, _control, _status, _reset, _bit) \ + { \ + .name = #_name, \ + .id = TEGRA20_MC_RESET_##_name, \ + .control = _control, \ + .status = _status, \ + .reset = _reset, \ + .bit = _bit, \ + } + +static const struct tegra_mc_reset tegra20_mc_resets[] = { + TEGRA20_MC_RESET(AVPC, 0x100, 0x140, 0x104, 0), + TEGRA20_MC_RESET(DC, 0x100, 0x144, 0x104, 1), + TEGRA20_MC_RESET(DCB, 0x100, 0x148, 0x104, 2), + TEGRA20_MC_RESET(EPP, 0x100, 0x14c, 0x104, 3), + TEGRA20_MC_RESET(2D, 0x100, 0x150, 0x104, 4), + TEGRA20_MC_RESET(HC, 0x100, 0x154, 0x104, 5), + TEGRA20_MC_RESET(ISP, 0x100, 0x158, 0x104, 6), + TEGRA20_MC_RESET(MPCORE, 0x100, 0x15c, 0x104, 7), + TEGRA20_MC_RESET(MPEA, 0x100, 0x160, 0x104, 8), + TEGRA20_MC_RESET(MPEB, 0x100, 0x164, 0x104, 9), + TEGRA20_MC_RESET(MPEC, 0x100, 0x168, 0x104, 10), + TEGRA20_MC_RESET(3D, 0x100, 0x16c, 0x104, 11), + TEGRA20_MC_RESET(PPCS, 0x100, 0x170, 0x104, 12), + TEGRA20_MC_RESET(VDE, 0x100, 0x174, 0x104, 13), + TEGRA20_MC_RESET(VI, 0x100, 0x178, 0x104, 14), +}; + +static int terga20_mc_hotreset_assert(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->reset); + mc_writel(mc, value & ~BIT(rst->bit), rst->reset); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +static int terga20_mc_hotreset_deassert(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->reset); + mc_writel(mc, value | BIT(rst->bit), rst->reset); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +static int terga20_mc_block_dma(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->control) & ~BIT(rst->bit); + mc_writel(mc, value, rst->control); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +static bool terga20_mc_dma_idling(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + return mc_readl(mc, rst->status) == 0; +} + +static int terga20_mc_reset_status(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + return (mc_readl(mc, rst->reset) & BIT(rst->bit)) == 0; +} + +static int terga20_mc_unblock_dma(struct tegra_mc *mc, + const struct tegra_mc_reset *rst) +{ + unsigned long flags; + u32 value; + + spin_lock_irqsave(&mc->lock, flags); + + value = mc_readl(mc, rst->control) | BIT(rst->bit); + mc_writel(mc, value, rst->control); + + spin_unlock_irqrestore(&mc->lock, flags); + + return 0; +} + +const struct tegra_mc_reset_ops terga20_mc_reset_ops = { + .hotreset_assert = terga20_mc_hotreset_assert, + .hotreset_deassert = terga20_mc_hotreset_deassert, + .block_dma = terga20_mc_block_dma, + .dma_idling = terga20_mc_dma_idling, + .unblock_dma = terga20_mc_unblock_dma, + .reset_status = terga20_mc_reset_status, +}; + +const struct tegra_mc_soc tegra20_mc_soc = { + .clients = tegra20_mc_clients, + .num_clients = ARRAY_SIZE(tegra20_mc_clients), + .num_address_bits = 32, + .client_id_mask = 0x3f, + .intmask = MC_INT_SECURITY_VIOLATION | MC_INT_INVALID_GART_PAGE | + MC_INT_DECERR_EMEM, + .reset_ops = &terga20_mc_reset_ops, + .resets = tegra20_mc_resets, + .num_resets = ARRAY_SIZE(tegra20_mc_resets), +}; diff --git a/drivers/memory/tegra/tegra210.c b/drivers/memory/tegra/tegra210.c new file mode 100644 index 000000000..d00a77160 --- /dev/null +++ b/drivers/memory/tegra/tegra210.c @@ -0,0 +1,1138 @@ +/* + * Copyright (C) 2015 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <dt-bindings/memory/tegra210-mc.h> + +#include "mc.h" + +static const struct tegra_mc_client tegra210_mc_clients[] = { + { + .id = 0x00, + .name = "ptcr", + .swgroup = TEGRA_SWGROUP_PTC, + }, { + .id = 0x01, + .name = "display0a", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 1, + }, + .la = { + .reg = 0x2e8, + .shift = 0, + .mask = 0xff, + .def = 0xc2, + }, + }, { + .id = 0x02, + .name = "display0ab", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 2, + }, + .la = { + .reg = 0x2f4, + .shift = 0, + .mask = 0xff, + .def = 0xc6, + }, + }, { + .id = 0x03, + .name = "display0b", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 3, + }, + .la = { + .reg = 0x2e8, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x04, + .name = "display0bb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 4, + }, + .la = { + .reg = 0x2f4, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x05, + .name = "display0c", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 5, + }, + .la = { + .reg = 0x2ec, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x06, + .name = "display0cb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 6, + }, + .la = { + .reg = 0x2f8, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x0e, + .name = "afir", + .swgroup = TEGRA_SWGROUP_AFI, + .smmu = { + .reg = 0x228, + .bit = 14, + }, + .la = { + .reg = 0x2e0, + .shift = 0, + .mask = 0xff, + .def = 0x13, + }, + }, { + .id = 0x0f, + .name = "avpcarm7r", + .swgroup = TEGRA_SWGROUP_AVPC, + .smmu = { + .reg = 0x228, + .bit = 15, + }, + .la = { + .reg = 0x2e4, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x10, + .name = "displayhc", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 16, + }, + .la = { + .reg = 0x2f0, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x11, + .name = "displayhcb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 17, + }, + .la = { + .reg = 0x2fc, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x15, + .name = "hdar", + .swgroup = TEGRA_SWGROUP_HDA, + .smmu = { + .reg = 0x228, + .bit = 21, + }, + .la = { + .reg = 0x318, + .shift = 0, + .mask = 0xff, + .def = 0x24, + }, + }, { + .id = 0x16, + .name = "host1xdmar", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x228, + .bit = 22, + }, + .la = { + .reg = 0x310, + .shift = 0, + .mask = 0xff, + .def = 0x1e, + }, + }, { + .id = 0x17, + .name = "host1xr", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x228, + .bit = 23, + }, + .la = { + .reg = 0x310, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x1c, + .name = "nvencsrd", + .swgroup = TEGRA_SWGROUP_NVENC, + .smmu = { + .reg = 0x228, + .bit = 28, + }, + .la = { + .reg = 0x328, + .shift = 0, + .mask = 0xff, + .def = 0x23, + }, + }, { + .id = 0x1d, + .name = "ppcsahbdmar", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x228, + .bit = 29, + }, + .la = { + .reg = 0x344, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x1e, + .name = "ppcsahbslvr", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x228, + .bit = 30, + }, + .la = { + .reg = 0x344, + .shift = 16, + .mask = 0xff, + .def = 0x1a, + }, + }, { + .id = 0x1f, + .name = "satar", + .swgroup = TEGRA_SWGROUP_SATA, + .smmu = { + .reg = 0x228, + .bit = 31, + }, + .la = { + .reg = 0x350, + .shift = 0, + .mask = 0xff, + .def = 0x65, + }, + }, { + .id = 0x27, + .name = "mpcorer", + .swgroup = TEGRA_SWGROUP_MPCORE, + .la = { + .reg = 0x320, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x2b, + .name = "nvencswr", + .swgroup = TEGRA_SWGROUP_NVENC, + .smmu = { + .reg = 0x22c, + .bit = 11, + }, + .la = { + .reg = 0x328, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x31, + .name = "afiw", + .swgroup = TEGRA_SWGROUP_AFI, + .smmu = { + .reg = 0x22c, + .bit = 17, + }, + .la = { + .reg = 0x2e0, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x32, + .name = "avpcarm7w", + .swgroup = TEGRA_SWGROUP_AVPC, + .smmu = { + .reg = 0x22c, + .bit = 18, + }, + .la = { + .reg = 0x2e4, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x35, + .name = "hdaw", + .swgroup = TEGRA_SWGROUP_HDA, + .smmu = { + .reg = 0x22c, + .bit = 21, + }, + .la = { + .reg = 0x318, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x36, + .name = "host1xw", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x22c, + .bit = 22, + }, + .la = { + .reg = 0x314, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x39, + .name = "mpcorew", + .swgroup = TEGRA_SWGROUP_MPCORE, + .la = { + .reg = 0x320, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3b, + .name = "ppcsahbdmaw", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x22c, + .bit = 27, + }, + .la = { + .reg = 0x348, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3c, + .name = "ppcsahbslvw", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x22c, + .bit = 28, + }, + .la = { + .reg = 0x348, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x3d, + .name = "sataw", + .swgroup = TEGRA_SWGROUP_SATA, + .smmu = { + .reg = 0x22c, + .bit = 29, + }, + .la = { + .reg = 0x350, + .shift = 16, + .mask = 0xff, + .def = 0x65, + }, + }, { + .id = 0x44, + .name = "ispra", + .swgroup = TEGRA_SWGROUP_ISP2, + .smmu = { + .reg = 0x230, + .bit = 4, + }, + .la = { + .reg = 0x370, + .shift = 0, + .mask = 0xff, + .def = 0x18, + }, + }, { + .id = 0x46, + .name = "ispwa", + .swgroup = TEGRA_SWGROUP_ISP2, + .smmu = { + .reg = 0x230, + .bit = 6, + }, + .la = { + .reg = 0x374, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x47, + .name = "ispwb", + .swgroup = TEGRA_SWGROUP_ISP2, + .smmu = { + .reg = 0x230, + .bit = 7, + }, + .la = { + .reg = 0x374, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x4a, + .name = "xusb_hostr", + .swgroup = TEGRA_SWGROUP_XUSB_HOST, + .smmu = { + .reg = 0x230, + .bit = 10, + }, + .la = { + .reg = 0x37c, + .shift = 0, + .mask = 0xff, + .def = 0x39, + }, + }, { + .id = 0x4b, + .name = "xusb_hostw", + .swgroup = TEGRA_SWGROUP_XUSB_HOST, + .smmu = { + .reg = 0x230, + .bit = 11, + }, + .la = { + .reg = 0x37c, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x4c, + .name = "xusb_devr", + .swgroup = TEGRA_SWGROUP_XUSB_DEV, + .smmu = { + .reg = 0x230, + .bit = 12, + }, + .la = { + .reg = 0x380, + .shift = 0, + .mask = 0xff, + .def = 0x39, + }, + }, { + .id = 0x4d, + .name = "xusb_devw", + .swgroup = TEGRA_SWGROUP_XUSB_DEV, + .smmu = { + .reg = 0x230, + .bit = 13, + }, + .la = { + .reg = 0x380, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x4e, + .name = "isprab", + .swgroup = TEGRA_SWGROUP_ISP2B, + .smmu = { + .reg = 0x230, + .bit = 14, + }, + .la = { + .reg = 0x384, + .shift = 0, + .mask = 0xff, + .def = 0x18, + }, + }, { + .id = 0x50, + .name = "ispwab", + .swgroup = TEGRA_SWGROUP_ISP2B, + .smmu = { + .reg = 0x230, + .bit = 16, + }, + .la = { + .reg = 0x388, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x51, + .name = "ispwbb", + .swgroup = TEGRA_SWGROUP_ISP2B, + .smmu = { + .reg = 0x230, + .bit = 17, + }, + .la = { + .reg = 0x388, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x54, + .name = "tsecsrd", + .swgroup = TEGRA_SWGROUP_TSEC, + .smmu = { + .reg = 0x230, + .bit = 20, + }, + .la = { + .reg = 0x390, + .shift = 0, + .mask = 0xff, + .def = 0x9b, + }, + }, { + .id = 0x55, + .name = "tsecswr", + .swgroup = TEGRA_SWGROUP_TSEC, + .smmu = { + .reg = 0x230, + .bit = 21, + }, + .la = { + .reg = 0x390, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x56, + .name = "a9avpscr", + .swgroup = TEGRA_SWGROUP_A9AVP, + .smmu = { + .reg = 0x230, + .bit = 22, + }, + .la = { + .reg = 0x3a4, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x57, + .name = "a9avpscw", + .swgroup = TEGRA_SWGROUP_A9AVP, + .smmu = { + .reg = 0x230, + .bit = 23, + }, + .la = { + .reg = 0x3a4, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x58, + .name = "gpusrd", + .swgroup = TEGRA_SWGROUP_GPU, + .smmu = { + /* read-only */ + .reg = 0x230, + .bit = 24, + }, + .la = { + .reg = 0x3c8, + .shift = 0, + .mask = 0xff, + .def = 0x1a, + }, + }, { + .id = 0x59, + .name = "gpuswr", + .swgroup = TEGRA_SWGROUP_GPU, + .smmu = { + /* read-only */ + .reg = 0x230, + .bit = 25, + }, + .la = { + .reg = 0x3c8, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x5a, + .name = "displayt", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x230, + .bit = 26, + }, + .la = { + .reg = 0x2f0, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x60, + .name = "sdmmcra", + .swgroup = TEGRA_SWGROUP_SDMMC1A, + .smmu = { + .reg = 0x234, + .bit = 0, + }, + .la = { + .reg = 0x3b8, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x61, + .name = "sdmmcraa", + .swgroup = TEGRA_SWGROUP_SDMMC2A, + .smmu = { + .reg = 0x234, + .bit = 1, + }, + .la = { + .reg = 0x3bc, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x62, + .name = "sdmmcr", + .swgroup = TEGRA_SWGROUP_SDMMC3A, + .smmu = { + .reg = 0x234, + .bit = 2, + }, + .la = { + .reg = 0x3c0, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x63, + .swgroup = TEGRA_SWGROUP_SDMMC4A, + .name = "sdmmcrab", + .smmu = { + .reg = 0x234, + .bit = 3, + }, + .la = { + .reg = 0x3c4, + .shift = 0, + .mask = 0xff, + .def = 0x49, + }, + }, { + .id = 0x64, + .name = "sdmmcwa", + .swgroup = TEGRA_SWGROUP_SDMMC1A, + .smmu = { + .reg = 0x234, + .bit = 4, + }, + .la = { + .reg = 0x3b8, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x65, + .name = "sdmmcwaa", + .swgroup = TEGRA_SWGROUP_SDMMC2A, + .smmu = { + .reg = 0x234, + .bit = 5, + }, + .la = { + .reg = 0x3bc, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x66, + .name = "sdmmcw", + .swgroup = TEGRA_SWGROUP_SDMMC3A, + .smmu = { + .reg = 0x234, + .bit = 6, + }, + .la = { + .reg = 0x3c0, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x67, + .name = "sdmmcwab", + .swgroup = TEGRA_SWGROUP_SDMMC4A, + .smmu = { + .reg = 0x234, + .bit = 7, + }, + .la = { + .reg = 0x3c4, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x6c, + .name = "vicsrd", + .swgroup = TEGRA_SWGROUP_VIC, + .smmu = { + .reg = 0x234, + .bit = 12, + }, + .la = { + .reg = 0x394, + .shift = 0, + .mask = 0xff, + .def = 0x1a, + }, + }, { + .id = 0x6d, + .name = "vicswr", + .swgroup = TEGRA_SWGROUP_VIC, + .smmu = { + .reg = 0x234, + .bit = 13, + }, + .la = { + .reg = 0x394, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x72, + .name = "viw", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x234, + .bit = 18, + }, + .la = { + .reg = 0x398, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x73, + .name = "displayd", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x234, + .bit = 19, + }, + .la = { + .reg = 0x3c8, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x78, + .name = "nvdecsrd", + .swgroup = TEGRA_SWGROUP_NVDEC, + .smmu = { + .reg = 0x234, + .bit = 24, + }, + .la = { + .reg = 0x3d8, + .shift = 0, + .mask = 0xff, + .def = 0x23, + }, + }, { + .id = 0x79, + .name = "nvdecswr", + .swgroup = TEGRA_SWGROUP_NVDEC, + .smmu = { + .reg = 0x234, + .bit = 25, + }, + .la = { + .reg = 0x3d8, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x7a, + .name = "aper", + .swgroup = TEGRA_SWGROUP_APE, + .smmu = { + .reg = 0x234, + .bit = 26, + }, + .la = { + .reg = 0x3dc, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x7b, + .name = "apew", + .swgroup = TEGRA_SWGROUP_APE, + .smmu = { + .reg = 0x234, + .bit = 27, + }, + .la = { + .reg = 0x3dc, + .shift = 0, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x7e, + .name = "nvjpgsrd", + .swgroup = TEGRA_SWGROUP_NVJPG, + .smmu = { + .reg = 0x234, + .bit = 30, + }, + .la = { + .reg = 0x3e4, + .shift = 0, + .mask = 0xff, + .def = 0x23, + }, + }, { + .id = 0x7f, + .name = "nvjpgswr", + .swgroup = TEGRA_SWGROUP_NVJPG, + .smmu = { + .reg = 0x234, + .bit = 31, + }, + .la = { + .reg = 0x3e4, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x80, + .name = "sesrd", + .swgroup = TEGRA_SWGROUP_SE, + .smmu = { + .reg = 0xb98, + .bit = 0, + }, + .la = { + .reg = 0x3e0, + .shift = 0, + .mask = 0xff, + .def = 0x2e, + }, + }, { + .id = 0x81, + .name = "seswr", + .swgroup = TEGRA_SWGROUP_SE, + .smmu = { + .reg = 0xb98, + .bit = 1, + }, + .la = { + .reg = 0xb98, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x82, + .name = "axiapr", + .swgroup = TEGRA_SWGROUP_AXIAP, + .smmu = { + .reg = 0xb98, + .bit = 2, + }, + .la = { + .reg = 0x3a0, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x83, + .name = "axiapw", + .swgroup = TEGRA_SWGROUP_AXIAP, + .smmu = { + .reg = 0xb98, + .bit = 3, + }, + .la = { + .reg = 0x3a0, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x84, + .name = "etrr", + .swgroup = TEGRA_SWGROUP_ETR, + .smmu = { + .reg = 0xb98, + .bit = 4, + }, + .la = { + .reg = 0x3ec, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x85, + .name = "etrw", + .swgroup = TEGRA_SWGROUP_ETR, + .smmu = { + .reg = 0xb98, + .bit = 5, + }, + .la = { + .reg = 0x3ec, + .shift = 16, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x86, + .name = "tsecsrdb", + .swgroup = TEGRA_SWGROUP_TSECB, + .smmu = { + .reg = 0xb98, + .bit = 6, + }, + .la = { + .reg = 0x3f0, + .shift = 0, + .mask = 0xff, + .def = 0x9b, + }, + }, { + .id = 0x87, + .name = "tsecswrb", + .swgroup = TEGRA_SWGROUP_TSECB, + .smmu = { + .reg = 0xb98, + .bit = 7, + }, + .la = { + .reg = 0x3f0, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x88, + .name = "gpusrd2", + .swgroup = TEGRA_SWGROUP_GPU, + .smmu = { + /* read-only */ + .reg = 0xb98, + .bit = 8, + }, + .la = { + .reg = 0x3e8, + .shift = 0, + .mask = 0xff, + .def = 0x1a, + }, + }, { + .id = 0x89, + .name = "gpuswr2", + .swgroup = TEGRA_SWGROUP_GPU, + .smmu = { + /* read-only */ + .reg = 0xb98, + .bit = 9, + }, + .la = { + .reg = 0x3e8, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, +}; + +static const struct tegra_smmu_swgroup tegra210_swgroups[] = { + { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 }, + { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 }, + { .name = "afi", .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 }, + { .name = "avpc", .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c }, + { .name = "hda", .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 }, + { .name = "hc", .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 }, + { .name = "nvenc", .swgroup = TEGRA_SWGROUP_NVENC, .reg = 0x264 }, + { .name = "ppcs", .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 }, + { .name = "sata", .swgroup = TEGRA_SWGROUP_SATA, .reg = 0x274 }, + { .name = "isp2", .swgroup = TEGRA_SWGROUP_ISP2, .reg = 0x258 }, + { .name = "xusb_host", .swgroup = TEGRA_SWGROUP_XUSB_HOST, .reg = 0x288 }, + { .name = "xusb_dev", .swgroup = TEGRA_SWGROUP_XUSB_DEV, .reg = 0x28c }, + { .name = "isp2b", .swgroup = TEGRA_SWGROUP_ISP2B, .reg = 0xaa4 }, + { .name = "tsec", .swgroup = TEGRA_SWGROUP_TSEC, .reg = 0x294 }, + { .name = "a9avp", .swgroup = TEGRA_SWGROUP_A9AVP, .reg = 0x290 }, + { .name = "gpu", .swgroup = TEGRA_SWGROUP_GPU, .reg = 0xaac }, + { .name = "sdmmc1a", .swgroup = TEGRA_SWGROUP_SDMMC1A, .reg = 0xa94 }, + { .name = "sdmmc2a", .swgroup = TEGRA_SWGROUP_SDMMC2A, .reg = 0xa98 }, + { .name = "sdmmc3a", .swgroup = TEGRA_SWGROUP_SDMMC3A, .reg = 0xa9c }, + { .name = "sdmmc4a", .swgroup = TEGRA_SWGROUP_SDMMC4A, .reg = 0xaa0 }, + { .name = "vic", .swgroup = TEGRA_SWGROUP_VIC, .reg = 0x284 }, + { .name = "vi", .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 }, + { .name = "nvdec", .swgroup = TEGRA_SWGROUP_NVDEC, .reg = 0xab4 }, + { .name = "ape", .swgroup = TEGRA_SWGROUP_APE, .reg = 0xab8 }, + { .name = "nvjpg", .swgroup = TEGRA_SWGROUP_NVJPG, .reg = 0xac0 }, + { .name = "se", .swgroup = TEGRA_SWGROUP_SE, .reg = 0xabc }, + { .name = "axiap", .swgroup = TEGRA_SWGROUP_AXIAP, .reg = 0xacc }, + { .name = "etr", .swgroup = TEGRA_SWGROUP_ETR, .reg = 0xad0 }, + { .name = "tsecb", .swgroup = TEGRA_SWGROUP_TSECB, .reg = 0xad4 }, +}; + +static const unsigned int tegra210_group_display[] = { + TEGRA_SWGROUP_DC, + TEGRA_SWGROUP_DCB, +}; + +static const struct tegra_smmu_group_soc tegra210_groups[] = { + { + .name = "display", + .swgroups = tegra210_group_display, + .num_swgroups = ARRAY_SIZE(tegra210_group_display), + }, +}; + +static const struct tegra_smmu_soc tegra210_smmu_soc = { + .clients = tegra210_mc_clients, + .num_clients = ARRAY_SIZE(tegra210_mc_clients), + .swgroups = tegra210_swgroups, + .num_swgroups = ARRAY_SIZE(tegra210_swgroups), + .groups = tegra210_groups, + .num_groups = ARRAY_SIZE(tegra210_groups), + .supports_round_robin_arbitration = true, + .supports_request_limit = true, + .num_tlb_lines = 32, + .num_asids = 128, +}; + +#define TEGRA210_MC_RESET(_name, _control, _status, _bit) \ + { \ + .name = #_name, \ + .id = TEGRA210_MC_RESET_##_name, \ + .control = _control, \ + .status = _status, \ + .bit = _bit, \ + } + +static const struct tegra_mc_reset tegra210_mc_resets[] = { + TEGRA210_MC_RESET(AFI, 0x200, 0x204, 0), + TEGRA210_MC_RESET(AVPC, 0x200, 0x204, 1), + TEGRA210_MC_RESET(DC, 0x200, 0x204, 2), + TEGRA210_MC_RESET(DCB, 0x200, 0x204, 3), + TEGRA210_MC_RESET(HC, 0x200, 0x204, 6), + TEGRA210_MC_RESET(HDA, 0x200, 0x204, 7), + TEGRA210_MC_RESET(ISP2, 0x200, 0x204, 8), + TEGRA210_MC_RESET(MPCORE, 0x200, 0x204, 9), + TEGRA210_MC_RESET(NVENC, 0x200, 0x204, 11), + TEGRA210_MC_RESET(PPCS, 0x200, 0x204, 14), + TEGRA210_MC_RESET(SATA, 0x200, 0x204, 15), + TEGRA210_MC_RESET(VI, 0x200, 0x204, 17), + TEGRA210_MC_RESET(VIC, 0x200, 0x204, 18), + TEGRA210_MC_RESET(XUSB_HOST, 0x200, 0x204, 19), + TEGRA210_MC_RESET(XUSB_DEV, 0x200, 0x204, 20), + TEGRA210_MC_RESET(A9AVP, 0x200, 0x204, 21), + TEGRA210_MC_RESET(TSEC, 0x200, 0x204, 22), + TEGRA210_MC_RESET(SDMMC1, 0x200, 0x204, 29), + TEGRA210_MC_RESET(SDMMC2, 0x200, 0x204, 30), + TEGRA210_MC_RESET(SDMMC3, 0x200, 0x204, 31), + TEGRA210_MC_RESET(SDMMC4, 0x970, 0x974, 0), + TEGRA210_MC_RESET(ISP2B, 0x970, 0x974, 1), + TEGRA210_MC_RESET(GPU, 0x970, 0x974, 2), + TEGRA210_MC_RESET(NVDEC, 0x970, 0x974, 5), + TEGRA210_MC_RESET(APE, 0x970, 0x974, 6), + TEGRA210_MC_RESET(SE, 0x970, 0x974, 7), + TEGRA210_MC_RESET(NVJPG, 0x970, 0x974, 8), + TEGRA210_MC_RESET(AXIAP, 0x970, 0x974, 11), + TEGRA210_MC_RESET(ETR, 0x970, 0x974, 12), + TEGRA210_MC_RESET(TSECB, 0x970, 0x974, 13), +}; + +const struct tegra_mc_soc tegra210_mc_soc = { + .clients = tegra210_mc_clients, + .num_clients = ARRAY_SIZE(tegra210_mc_clients), + .num_address_bits = 34, + .atom_size = 64, + .client_id_mask = 0xff, + .smmu = &tegra210_smmu_soc, + .intmask = MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR | + MC_INT_INVALID_APB_ASID_UPDATE | MC_INT_INVALID_SMMU_PAGE | + MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM, + .reset_ops = &terga_mc_reset_ops_common, + .resets = tegra210_mc_resets, + .num_resets = ARRAY_SIZE(tegra210_mc_resets), +}; diff --git a/drivers/memory/tegra/tegra30.c b/drivers/memory/tegra/tegra30.c new file mode 100644 index 000000000..bee5314ed --- /dev/null +++ b/drivers/memory/tegra/tegra30.c @@ -0,0 +1,1005 @@ +/* + * Copyright (C) 2014 NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/of.h> +#include <linux/mm.h> + +#include <dt-bindings/memory/tegra30-mc.h> + +#include "mc.h" + +static const struct tegra_mc_client tegra30_mc_clients[] = { + { + .id = 0x00, + .name = "ptcr", + .swgroup = TEGRA_SWGROUP_PTC, + }, { + .id = 0x01, + .name = "display0a", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 1, + }, + .la = { + .reg = 0x2e8, + .shift = 0, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x02, + .name = "display0ab", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 2, + }, + .la = { + .reg = 0x2f4, + .shift = 0, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x03, + .name = "display0b", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 3, + }, + .la = { + .reg = 0x2e8, + .shift = 16, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x04, + .name = "display0bb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 4, + }, + .la = { + .reg = 0x2f4, + .shift = 16, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x05, + .name = "display0c", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 5, + }, + .la = { + .reg = 0x2ec, + .shift = 0, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x06, + .name = "display0cb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 6, + }, + .la = { + .reg = 0x2f8, + .shift = 0, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x07, + .name = "display1b", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 7, + }, + .la = { + .reg = 0x2ec, + .shift = 16, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x08, + .name = "display1bb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 8, + }, + .la = { + .reg = 0x2f8, + .shift = 16, + .mask = 0xff, + .def = 0x4e, + }, + }, { + .id = 0x09, + .name = "eppup", + .swgroup = TEGRA_SWGROUP_EPP, + .smmu = { + .reg = 0x228, + .bit = 9, + }, + .la = { + .reg = 0x300, + .shift = 0, + .mask = 0xff, + .def = 0x17, + }, + }, { + .id = 0x0a, + .name = "g2pr", + .swgroup = TEGRA_SWGROUP_G2, + .smmu = { + .reg = 0x228, + .bit = 10, + }, + .la = { + .reg = 0x308, + .shift = 0, + .mask = 0xff, + .def = 0x09, + }, + }, { + .id = 0x0b, + .name = "g2sr", + .swgroup = TEGRA_SWGROUP_G2, + .smmu = { + .reg = 0x228, + .bit = 11, + }, + .la = { + .reg = 0x308, + .shift = 16, + .mask = 0xff, + .def = 0x09, + }, + }, { + .id = 0x0c, + .name = "mpeunifbr", + .swgroup = TEGRA_SWGROUP_MPE, + .smmu = { + .reg = 0x228, + .bit = 12, + }, + .la = { + .reg = 0x328, + .shift = 0, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x0d, + .name = "viruv", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x228, + .bit = 13, + }, + .la = { + .reg = 0x364, + .shift = 0, + .mask = 0xff, + .def = 0x2c, + }, + }, { + .id = 0x0e, + .name = "afir", + .swgroup = TEGRA_SWGROUP_AFI, + .smmu = { + .reg = 0x228, + .bit = 14, + }, + .la = { + .reg = 0x2e0, + .shift = 0, + .mask = 0xff, + .def = 0x10, + }, + }, { + .id = 0x0f, + .name = "avpcarm7r", + .swgroup = TEGRA_SWGROUP_AVPC, + .smmu = { + .reg = 0x228, + .bit = 15, + }, + .la = { + .reg = 0x2e4, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x10, + .name = "displayhc", + .swgroup = TEGRA_SWGROUP_DC, + .smmu = { + .reg = 0x228, + .bit = 16, + }, + .la = { + .reg = 0x2f0, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x11, + .name = "displayhcb", + .swgroup = TEGRA_SWGROUP_DCB, + .smmu = { + .reg = 0x228, + .bit = 17, + }, + .la = { + .reg = 0x2fc, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x12, + .name = "fdcdrd", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x228, + .bit = 18, + }, + .la = { + .reg = 0x334, + .shift = 0, + .mask = 0xff, + .def = 0x0a, + }, + }, { + .id = 0x13, + .name = "fdcdrd2", + .swgroup = TEGRA_SWGROUP_NV2, + .smmu = { + .reg = 0x228, + .bit = 19, + }, + .la = { + .reg = 0x33c, + .shift = 0, + .mask = 0xff, + .def = 0x0a, + }, + }, { + .id = 0x14, + .name = "g2dr", + .swgroup = TEGRA_SWGROUP_G2, + .smmu = { + .reg = 0x228, + .bit = 20, + }, + .la = { + .reg = 0x30c, + .shift = 0, + .mask = 0xff, + .def = 0x0a, + }, + }, { + .id = 0x15, + .name = "hdar", + .swgroup = TEGRA_SWGROUP_HDA, + .smmu = { + .reg = 0x228, + .bit = 21, + }, + .la = { + .reg = 0x318, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x16, + .name = "host1xdmar", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x228, + .bit = 22, + }, + .la = { + .reg = 0x310, + .shift = 0, + .mask = 0xff, + .def = 0x05, + }, + }, { + .id = 0x17, + .name = "host1xr", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x228, + .bit = 23, + }, + .la = { + .reg = 0x310, + .shift = 16, + .mask = 0xff, + .def = 0x50, + }, + }, { + .id = 0x18, + .name = "idxsrd", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x228, + .bit = 24, + }, + .la = { + .reg = 0x334, + .shift = 16, + .mask = 0xff, + .def = 0x13, + }, + }, { + .id = 0x19, + .name = "idxsrd2", + .swgroup = TEGRA_SWGROUP_NV2, + .smmu = { + .reg = 0x228, + .bit = 25, + }, + .la = { + .reg = 0x33c, + .shift = 16, + .mask = 0xff, + .def = 0x13, + }, + }, { + .id = 0x1a, + .name = "mpe_ipred", + .swgroup = TEGRA_SWGROUP_MPE, + .smmu = { + .reg = 0x228, + .bit = 26, + }, + .la = { + .reg = 0x328, + .shift = 16, + .mask = 0xff, + .def = 0x80, + }, + }, { + .id = 0x1b, + .name = "mpeamemrd", + .swgroup = TEGRA_SWGROUP_MPE, + .smmu = { + .reg = 0x228, + .bit = 27, + }, + .la = { + .reg = 0x32c, + .shift = 0, + .mask = 0xff, + .def = 0x42, + }, + }, { + .id = 0x1c, + .name = "mpecsrd", + .swgroup = TEGRA_SWGROUP_MPE, + .smmu = { + .reg = 0x228, + .bit = 28, + }, + .la = { + .reg = 0x32c, + .shift = 16, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x1d, + .name = "ppcsahbdmar", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x228, + .bit = 29, + }, + .la = { + .reg = 0x344, + .shift = 0, + .mask = 0xff, + .def = 0x10, + }, + }, { + .id = 0x1e, + .name = "ppcsahbslvr", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x228, + .bit = 30, + }, + .la = { + .reg = 0x344, + .shift = 16, + .mask = 0xff, + .def = 0x12, + }, + }, { + .id = 0x1f, + .name = "satar", + .swgroup = TEGRA_SWGROUP_SATA, + .smmu = { + .reg = 0x228, + .bit = 31, + }, + .la = { + .reg = 0x350, + .shift = 0, + .mask = 0xff, + .def = 0x33, + }, + }, { + .id = 0x20, + .name = "texsrd", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x22c, + .bit = 0, + }, + .la = { + .reg = 0x338, + .shift = 0, + .mask = 0xff, + .def = 0x13, + }, + }, { + .id = 0x21, + .name = "texsrd2", + .swgroup = TEGRA_SWGROUP_NV2, + .smmu = { + .reg = 0x22c, + .bit = 1, + }, + .la = { + .reg = 0x340, + .shift = 0, + .mask = 0xff, + .def = 0x13, + }, + }, { + .id = 0x22, + .name = "vdebsevr", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 2, + }, + .la = { + .reg = 0x354, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x23, + .name = "vdember", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 3, + }, + .la = { + .reg = 0x354, + .shift = 16, + .mask = 0xff, + .def = 0xd0, + }, + }, { + .id = 0x24, + .name = "vdemcer", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 4, + }, + .la = { + .reg = 0x358, + .shift = 0, + .mask = 0xff, + .def = 0x2a, + }, + }, { + .id = 0x25, + .name = "vdetper", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 5, + }, + .la = { + .reg = 0x358, + .shift = 16, + .mask = 0xff, + .def = 0x74, + }, + }, { + .id = 0x26, + .name = "mpcorelpr", + .swgroup = TEGRA_SWGROUP_MPCORELP, + .la = { + .reg = 0x324, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x27, + .name = "mpcorer", + .swgroup = TEGRA_SWGROUP_MPCORE, + .la = { + .reg = 0x320, + .shift = 0, + .mask = 0xff, + .def = 0x04, + }, + }, { + .id = 0x28, + .name = "eppu", + .swgroup = TEGRA_SWGROUP_EPP, + .smmu = { + .reg = 0x22c, + .bit = 8, + }, + .la = { + .reg = 0x300, + .shift = 16, + .mask = 0xff, + .def = 0x6c, + }, + }, { + .id = 0x29, + .name = "eppv", + .swgroup = TEGRA_SWGROUP_EPP, + .smmu = { + .reg = 0x22c, + .bit = 9, + }, + .la = { + .reg = 0x304, + .shift = 0, + .mask = 0xff, + .def = 0x6c, + }, + }, { + .id = 0x2a, + .name = "eppy", + .swgroup = TEGRA_SWGROUP_EPP, + .smmu = { + .reg = 0x22c, + .bit = 10, + }, + .la = { + .reg = 0x304, + .shift = 16, + .mask = 0xff, + .def = 0x6c, + }, + }, { + .id = 0x2b, + .name = "mpeunifbw", + .swgroup = TEGRA_SWGROUP_MPE, + .smmu = { + .reg = 0x22c, + .bit = 11, + }, + .la = { + .reg = 0x330, + .shift = 0, + .mask = 0xff, + .def = 0x13, + }, + }, { + .id = 0x2c, + .name = "viwsb", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x22c, + .bit = 12, + }, + .la = { + .reg = 0x364, + .shift = 16, + .mask = 0xff, + .def = 0x12, + }, + }, { + .id = 0x2d, + .name = "viwu", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x22c, + .bit = 13, + }, + .la = { + .reg = 0x368, + .shift = 0, + .mask = 0xff, + .def = 0xb2, + }, + }, { + .id = 0x2e, + .name = "viwv", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x22c, + .bit = 14, + }, + .la = { + .reg = 0x368, + .shift = 16, + .mask = 0xff, + .def = 0xb2, + }, + }, { + .id = 0x2f, + .name = "viwy", + .swgroup = TEGRA_SWGROUP_VI, + .smmu = { + .reg = 0x22c, + .bit = 15, + }, + .la = { + .reg = 0x36c, + .shift = 0, + .mask = 0xff, + .def = 0x12, + }, + }, { + .id = 0x30, + .name = "g2dw", + .swgroup = TEGRA_SWGROUP_G2, + .smmu = { + .reg = 0x22c, + .bit = 16, + }, + .la = { + .reg = 0x30c, + .shift = 16, + .mask = 0xff, + .def = 0x9, + }, + }, { + .id = 0x31, + .name = "afiw", + .swgroup = TEGRA_SWGROUP_AFI, + .smmu = { + .reg = 0x22c, + .bit = 17, + }, + .la = { + .reg = 0x2e0, + .shift = 16, + .mask = 0xff, + .def = 0x0c, + }, + }, { + .id = 0x32, + .name = "avpcarm7w", + .swgroup = TEGRA_SWGROUP_AVPC, + .smmu = { + .reg = 0x22c, + .bit = 18, + }, + .la = { + .reg = 0x2e4, + .shift = 16, + .mask = 0xff, + .def = 0x0e, + }, + }, { + .id = 0x33, + .name = "fdcdwr", + .swgroup = TEGRA_SWGROUP_NV, + .smmu = { + .reg = 0x22c, + .bit = 19, + }, + .la = { + .reg = 0x338, + .shift = 16, + .mask = 0xff, + .def = 0x0a, + }, + }, { + .id = 0x34, + .name = "fdcwr2", + .swgroup = TEGRA_SWGROUP_NV2, + .smmu = { + .reg = 0x22c, + .bit = 20, + }, + .la = { + .reg = 0x340, + .shift = 16, + .mask = 0xff, + .def = 0x0a, + }, + }, { + .id = 0x35, + .name = "hdaw", + .swgroup = TEGRA_SWGROUP_HDA, + .smmu = { + .reg = 0x22c, + .bit = 21, + }, + .la = { + .reg = 0x318, + .shift = 16, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x36, + .name = "host1xw", + .swgroup = TEGRA_SWGROUP_HC, + .smmu = { + .reg = 0x22c, + .bit = 22, + }, + .la = { + .reg = 0x314, + .shift = 0, + .mask = 0xff, + .def = 0x10, + }, + }, { + .id = 0x37, + .name = "ispw", + .swgroup = TEGRA_SWGROUP_ISP, + .smmu = { + .reg = 0x22c, + .bit = 23, + }, + .la = { + .reg = 0x31c, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x38, + .name = "mpcorelpw", + .swgroup = TEGRA_SWGROUP_MPCORELP, + .la = { + .reg = 0x324, + .shift = 16, + .mask = 0xff, + .def = 0x0e, + }, + }, { + .id = 0x39, + .name = "mpcorew", + .swgroup = TEGRA_SWGROUP_MPCORE, + .la = { + .reg = 0x320, + .shift = 16, + .mask = 0xff, + .def = 0x0e, + }, + }, { + .id = 0x3a, + .name = "mpecswr", + .swgroup = TEGRA_SWGROUP_MPE, + .smmu = { + .reg = 0x22c, + .bit = 26, + }, + .la = { + .reg = 0x330, + .shift = 16, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x3b, + .name = "ppcsahbdmaw", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x22c, + .bit = 27, + }, + .la = { + .reg = 0x348, + .shift = 0, + .mask = 0xff, + .def = 0x10, + }, + }, { + .id = 0x3c, + .name = "ppcsahbslvw", + .swgroup = TEGRA_SWGROUP_PPCS, + .smmu = { + .reg = 0x22c, + .bit = 28, + }, + .la = { + .reg = 0x348, + .shift = 16, + .mask = 0xff, + .def = 0x06, + }, + }, { + .id = 0x3d, + .name = "sataw", + .swgroup = TEGRA_SWGROUP_SATA, + .smmu = { + .reg = 0x22c, + .bit = 29, + }, + .la = { + .reg = 0x350, + .shift = 16, + .mask = 0xff, + .def = 0x33, + }, + }, { + .id = 0x3e, + .name = "vdebsevw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 30, + }, + .la = { + .reg = 0x35c, + .shift = 0, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x3f, + .name = "vdedbgw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x22c, + .bit = 31, + }, + .la = { + .reg = 0x35c, + .shift = 16, + .mask = 0xff, + .def = 0xff, + }, + }, { + .id = 0x40, + .name = "vdembew", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x230, + .bit = 0, + }, + .la = { + .reg = 0x360, + .shift = 0, + .mask = 0xff, + .def = 0x42, + }, + }, { + .id = 0x41, + .name = "vdetpmw", + .swgroup = TEGRA_SWGROUP_VDE, + .smmu = { + .reg = 0x230, + .bit = 1, + }, + .la = { + .reg = 0x360, + .shift = 16, + .mask = 0xff, + .def = 0x2a, + }, + }, +}; + +static const struct tegra_smmu_swgroup tegra30_swgroups[] = { + { .name = "dc", .swgroup = TEGRA_SWGROUP_DC, .reg = 0x240 }, + { .name = "dcb", .swgroup = TEGRA_SWGROUP_DCB, .reg = 0x244 }, + { .name = "epp", .swgroup = TEGRA_SWGROUP_EPP, .reg = 0x248 }, + { .name = "g2", .swgroup = TEGRA_SWGROUP_G2, .reg = 0x24c }, + { .name = "mpe", .swgroup = TEGRA_SWGROUP_MPE, .reg = 0x264 }, + { .name = "vi", .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 }, + { .name = "afi", .swgroup = TEGRA_SWGROUP_AFI, .reg = 0x238 }, + { .name = "avpc", .swgroup = TEGRA_SWGROUP_AVPC, .reg = 0x23c }, + { .name = "nv", .swgroup = TEGRA_SWGROUP_NV, .reg = 0x268 }, + { .name = "nv2", .swgroup = TEGRA_SWGROUP_NV2, .reg = 0x26c }, + { .name = "hda", .swgroup = TEGRA_SWGROUP_HDA, .reg = 0x254 }, + { .name = "hc", .swgroup = TEGRA_SWGROUP_HC, .reg = 0x250 }, + { .name = "ppcs", .swgroup = TEGRA_SWGROUP_PPCS, .reg = 0x270 }, + { .name = "sata", .swgroup = TEGRA_SWGROUP_SATA, .reg = 0x278 }, + { .name = "vde", .swgroup = TEGRA_SWGROUP_VDE, .reg = 0x27c }, + { .name = "isp", .swgroup = TEGRA_SWGROUP_ISP, .reg = 0x258 }, +}; + +static const unsigned int tegra30_group_display[] = { + TEGRA_SWGROUP_DC, + TEGRA_SWGROUP_DCB, +}; + +static const struct tegra_smmu_group_soc tegra30_groups[] = { + { + .name = "display", + .swgroups = tegra30_group_display, + .num_swgroups = ARRAY_SIZE(tegra30_group_display), + }, +}; + +static const struct tegra_smmu_soc tegra30_smmu_soc = { + .clients = tegra30_mc_clients, + .num_clients = ARRAY_SIZE(tegra30_mc_clients), + .swgroups = tegra30_swgroups, + .num_swgroups = ARRAY_SIZE(tegra30_swgroups), + .groups = tegra30_groups, + .num_groups = ARRAY_SIZE(tegra30_groups), + .supports_round_robin_arbitration = false, + .supports_request_limit = false, + .num_tlb_lines = 16, + .num_asids = 4, +}; + +#define TEGRA30_MC_RESET(_name, _control, _status, _bit) \ + { \ + .name = #_name, \ + .id = TEGRA30_MC_RESET_##_name, \ + .control = _control, \ + .status = _status, \ + .bit = _bit, \ + } + +static const struct tegra_mc_reset tegra30_mc_resets[] = { + TEGRA30_MC_RESET(AFI, 0x200, 0x204, 0), + TEGRA30_MC_RESET(AVPC, 0x200, 0x204, 1), + TEGRA30_MC_RESET(DC, 0x200, 0x204, 2), + TEGRA30_MC_RESET(DCB, 0x200, 0x204, 3), + TEGRA30_MC_RESET(EPP, 0x200, 0x204, 4), + TEGRA30_MC_RESET(2D, 0x200, 0x204, 5), + TEGRA30_MC_RESET(HC, 0x200, 0x204, 6), + TEGRA30_MC_RESET(HDA, 0x200, 0x204, 7), + TEGRA30_MC_RESET(ISP, 0x200, 0x204, 8), + TEGRA30_MC_RESET(MPCORE, 0x200, 0x204, 9), + TEGRA30_MC_RESET(MPCORELP, 0x200, 0x204, 10), + TEGRA30_MC_RESET(MPE, 0x200, 0x204, 11), + TEGRA30_MC_RESET(3D, 0x200, 0x204, 12), + TEGRA30_MC_RESET(3D2, 0x200, 0x204, 13), + TEGRA30_MC_RESET(PPCS, 0x200, 0x204, 14), + TEGRA30_MC_RESET(SATA, 0x200, 0x204, 15), + TEGRA30_MC_RESET(VDE, 0x200, 0x204, 16), + TEGRA30_MC_RESET(VI, 0x200, 0x204, 17), +}; + +const struct tegra_mc_soc tegra30_mc_soc = { + .clients = tegra30_mc_clients, + .num_clients = ARRAY_SIZE(tegra30_mc_clients), + .num_address_bits = 32, + .atom_size = 16, + .client_id_mask = 0x7f, + .smmu = &tegra30_smmu_soc, + .intmask = MC_INT_INVALID_SMMU_PAGE | MC_INT_SECURITY_VIOLATION | + MC_INT_DECERR_EMEM, + .reset_ops = &terga_mc_reset_ops_common, + .resets = tegra30_mc_resets, + .num_resets = ARRAY_SIZE(tegra30_mc_resets), +}; diff --git a/drivers/memory/ti-aemif.c b/drivers/memory/ti-aemif.c new file mode 100644 index 000000000..be3d97864 --- /dev/null +++ b/drivers/memory/ti-aemif.c @@ -0,0 +1,458 @@ +/* + * TI AEMIF driver + * + * Copyright (C) 2010 - 2013 Texas Instruments Incorporated. http://www.ti.com/ + * + * Authors: + * Murali Karicheri <m-karicheri2@ti.com> + * Ivan Khoronzhuk <ivan.khoronzhuk@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/platform_data/ti-aemif.h> + +#define TA_SHIFT 2 +#define RHOLD_SHIFT 4 +#define RSTROBE_SHIFT 7 +#define RSETUP_SHIFT 13 +#define WHOLD_SHIFT 17 +#define WSTROBE_SHIFT 20 +#define WSETUP_SHIFT 26 +#define EW_SHIFT 30 +#define SS_SHIFT 31 + +#define TA(x) ((x) << TA_SHIFT) +#define RHOLD(x) ((x) << RHOLD_SHIFT) +#define RSTROBE(x) ((x) << RSTROBE_SHIFT) +#define RSETUP(x) ((x) << RSETUP_SHIFT) +#define WHOLD(x) ((x) << WHOLD_SHIFT) +#define WSTROBE(x) ((x) << WSTROBE_SHIFT) +#define WSETUP(x) ((x) << WSETUP_SHIFT) +#define EW(x) ((x) << EW_SHIFT) +#define SS(x) ((x) << SS_SHIFT) + +#define ASIZE_MAX 0x1 +#define TA_MAX 0x3 +#define RHOLD_MAX 0x7 +#define RSTROBE_MAX 0x3f +#define RSETUP_MAX 0xf +#define WHOLD_MAX 0x7 +#define WSTROBE_MAX 0x3f +#define WSETUP_MAX 0xf +#define EW_MAX 0x1 +#define SS_MAX 0x1 +#define NUM_CS 4 + +#define TA_VAL(x) (((x) & TA(TA_MAX)) >> TA_SHIFT) +#define RHOLD_VAL(x) (((x) & RHOLD(RHOLD_MAX)) >> RHOLD_SHIFT) +#define RSTROBE_VAL(x) (((x) & RSTROBE(RSTROBE_MAX)) >> RSTROBE_SHIFT) +#define RSETUP_VAL(x) (((x) & RSETUP(RSETUP_MAX)) >> RSETUP_SHIFT) +#define WHOLD_VAL(x) (((x) & WHOLD(WHOLD_MAX)) >> WHOLD_SHIFT) +#define WSTROBE_VAL(x) (((x) & WSTROBE(WSTROBE_MAX)) >> WSTROBE_SHIFT) +#define WSETUP_VAL(x) (((x) & WSETUP(WSETUP_MAX)) >> WSETUP_SHIFT) +#define EW_VAL(x) (((x) & EW(EW_MAX)) >> EW_SHIFT) +#define SS_VAL(x) (((x) & SS(SS_MAX)) >> SS_SHIFT) + +#define NRCSR_OFFSET 0x00 +#define AWCCR_OFFSET 0x04 +#define A1CR_OFFSET 0x10 + +#define ACR_ASIZE_MASK 0x3 +#define ACR_EW_MASK BIT(30) +#define ACR_SS_MASK BIT(31) +#define ASIZE_16BIT 1 + +#define CONFIG_MASK (TA(TA_MAX) | \ + RHOLD(RHOLD_MAX) | \ + RSTROBE(RSTROBE_MAX) | \ + RSETUP(RSETUP_MAX) | \ + WHOLD(WHOLD_MAX) | \ + WSTROBE(WSTROBE_MAX) | \ + WSETUP(WSETUP_MAX) | \ + EW(EW_MAX) | SS(SS_MAX) | \ + ASIZE_MAX) + +/** + * struct aemif_cs_data: structure to hold cs parameters + * @cs: chip-select number + * @wstrobe: write strobe width, ns + * @rstrobe: read strobe width, ns + * @wsetup: write setup width, ns + * @whold: write hold width, ns + * @rsetup: read setup width, ns + * @rhold: read hold width, ns + * @ta: minimum turn around time, ns + * @enable_ss: enable/disable select strobe mode + * @enable_ew: enable/disable extended wait mode + * @asize: width of the asynchronous device's data bus + */ +struct aemif_cs_data { + u8 cs; + u16 wstrobe; + u16 rstrobe; + u8 wsetup; + u8 whold; + u8 rsetup; + u8 rhold; + u8 ta; + u8 enable_ss; + u8 enable_ew; + u8 asize; +}; + +/** + * struct aemif_device: structure to hold device data + * @base: base address of AEMIF registers + * @clk: source clock + * @clk_rate: clock's rate in kHz + * @num_cs: number of assigned chip-selects + * @cs_offset: start number of cs nodes + * @cs_data: array of chip-select settings + */ +struct aemif_device { + void __iomem *base; + struct clk *clk; + unsigned long clk_rate; + u8 num_cs; + int cs_offset; + struct aemif_cs_data cs_data[NUM_CS]; +}; + +/** + * aemif_calc_rate - calculate timing data. + * @pdev: platform device to calculate for + * @wanted: The cycle time needed in nanoseconds. + * @clk: The input clock rate in kHz. + * @max: The maximum divider value that can be programmed. + * + * On success, returns the calculated timing value minus 1 for easy + * programming into AEMIF timing registers, else negative errno. + */ +static int aemif_calc_rate(struct platform_device *pdev, int wanted, + unsigned long clk, int max) +{ + int result; + + result = DIV_ROUND_UP((wanted * clk), NSEC_PER_MSEC) - 1; + + dev_dbg(&pdev->dev, "%s: result %d from %ld, %d\n", __func__, result, + clk, wanted); + + /* It is generally OK to have a more relaxed timing than requested... */ + if (result < 0) + result = 0; + + /* ... But configuring tighter timings is not an option. */ + else if (result > max) + result = -EINVAL; + + return result; +} + +/** + * aemif_config_abus - configure async bus parameters + * @pdev: platform device to configure for + * @csnum: aemif chip select number + * + * This function programs the given timing values (in real clock) into the + * AEMIF registers taking the AEMIF clock into account. + * + * This function does not use any locking while programming the AEMIF + * because it is expected that there is only one user of a given + * chip-select. + * + * Returns 0 on success, else negative errno. + */ +static int aemif_config_abus(struct platform_device *pdev, int csnum) +{ + struct aemif_device *aemif = platform_get_drvdata(pdev); + struct aemif_cs_data *data = &aemif->cs_data[csnum]; + int ta, rhold, rstrobe, rsetup, whold, wstrobe, wsetup; + unsigned long clk_rate = aemif->clk_rate; + unsigned offset; + u32 set, val; + + offset = A1CR_OFFSET + (data->cs - aemif->cs_offset) * 4; + + ta = aemif_calc_rate(pdev, data->ta, clk_rate, TA_MAX); + rhold = aemif_calc_rate(pdev, data->rhold, clk_rate, RHOLD_MAX); + rstrobe = aemif_calc_rate(pdev, data->rstrobe, clk_rate, RSTROBE_MAX); + rsetup = aemif_calc_rate(pdev, data->rsetup, clk_rate, RSETUP_MAX); + whold = aemif_calc_rate(pdev, data->whold, clk_rate, WHOLD_MAX); + wstrobe = aemif_calc_rate(pdev, data->wstrobe, clk_rate, WSTROBE_MAX); + wsetup = aemif_calc_rate(pdev, data->wsetup, clk_rate, WSETUP_MAX); + + if (ta < 0 || rhold < 0 || rstrobe < 0 || rsetup < 0 || + whold < 0 || wstrobe < 0 || wsetup < 0) { + dev_err(&pdev->dev, "%s: cannot get suitable timings\n", + __func__); + return -EINVAL; + } + + set = TA(ta) | RHOLD(rhold) | RSTROBE(rstrobe) | RSETUP(rsetup) | + WHOLD(whold) | WSTROBE(wstrobe) | WSETUP(wsetup); + + set |= (data->asize & ACR_ASIZE_MASK); + if (data->enable_ew) + set |= ACR_EW_MASK; + if (data->enable_ss) + set |= ACR_SS_MASK; + + val = readl(aemif->base + offset); + val &= ~CONFIG_MASK; + val |= set; + writel(val, aemif->base + offset); + + return 0; +} + +static inline int aemif_cycles_to_nsec(int val, unsigned long clk_rate) +{ + return ((val + 1) * NSEC_PER_MSEC) / clk_rate; +} + +/** + * aemif_get_hw_params - function to read hw register values + * @pdev: platform device to read for + * @csnum: aemif chip select number + * + * This function reads the defaults from the registers and update + * the timing values. Required for get/set commands and also for + * the case when driver needs to use defaults in hardware. + */ +static void aemif_get_hw_params(struct platform_device *pdev, int csnum) +{ + struct aemif_device *aemif = platform_get_drvdata(pdev); + struct aemif_cs_data *data = &aemif->cs_data[csnum]; + unsigned long clk_rate = aemif->clk_rate; + u32 val, offset; + + offset = A1CR_OFFSET + (data->cs - aemif->cs_offset) * 4; + val = readl(aemif->base + offset); + + data->ta = aemif_cycles_to_nsec(TA_VAL(val), clk_rate); + data->rhold = aemif_cycles_to_nsec(RHOLD_VAL(val), clk_rate); + data->rstrobe = aemif_cycles_to_nsec(RSTROBE_VAL(val), clk_rate); + data->rsetup = aemif_cycles_to_nsec(RSETUP_VAL(val), clk_rate); + data->whold = aemif_cycles_to_nsec(WHOLD_VAL(val), clk_rate); + data->wstrobe = aemif_cycles_to_nsec(WSTROBE_VAL(val), clk_rate); + data->wsetup = aemif_cycles_to_nsec(WSETUP_VAL(val), clk_rate); + data->enable_ew = EW_VAL(val); + data->enable_ss = SS_VAL(val); + data->asize = val & ASIZE_MAX; +} + +/** + * of_aemif_parse_abus_config - parse CS configuration from DT + * @pdev: platform device to parse for + * @np: device node ptr + * + * This function update the emif async bus configuration based on the values + * configured in a cs device binding node. + */ +static int of_aemif_parse_abus_config(struct platform_device *pdev, + struct device_node *np) +{ + struct aemif_device *aemif = platform_get_drvdata(pdev); + struct aemif_cs_data *data; + u32 cs; + u32 val; + + if (of_property_read_u32(np, "ti,cs-chipselect", &cs)) { + dev_dbg(&pdev->dev, "cs property is required"); + return -EINVAL; + } + + if (cs - aemif->cs_offset >= NUM_CS || cs < aemif->cs_offset) { + dev_dbg(&pdev->dev, "cs number is incorrect %d", cs); + return -EINVAL; + } + + if (aemif->num_cs >= NUM_CS) { + dev_dbg(&pdev->dev, "cs count is more than %d", NUM_CS); + return -EINVAL; + } + + data = &aemif->cs_data[aemif->num_cs]; + data->cs = cs; + + /* read the current value in the hw register */ + aemif_get_hw_params(pdev, aemif->num_cs++); + + /* override the values from device node */ + if (!of_property_read_u32(np, "ti,cs-min-turnaround-ns", &val)) + data->ta = val; + + if (!of_property_read_u32(np, "ti,cs-read-hold-ns", &val)) + data->rhold = val; + + if (!of_property_read_u32(np, "ti,cs-read-strobe-ns", &val)) + data->rstrobe = val; + + if (!of_property_read_u32(np, "ti,cs-read-setup-ns", &val)) + data->rsetup = val; + + if (!of_property_read_u32(np, "ti,cs-write-hold-ns", &val)) + data->whold = val; + + if (!of_property_read_u32(np, "ti,cs-write-strobe-ns", &val)) + data->wstrobe = val; + + if (!of_property_read_u32(np, "ti,cs-write-setup-ns", &val)) + data->wsetup = val; + + if (!of_property_read_u32(np, "ti,cs-bus-width", &val)) + if (val == 16) + data->asize = 1; + data->enable_ew = of_property_read_bool(np, "ti,cs-extended-wait-mode"); + data->enable_ss = of_property_read_bool(np, "ti,cs-select-strobe-mode"); + return 0; +} + +static const struct of_device_id aemif_of_match[] = { + { .compatible = "ti,davinci-aemif", }, + { .compatible = "ti,da850-aemif", }, + {}, +}; +MODULE_DEVICE_TABLE(of, aemif_of_match); + +static int aemif_probe(struct platform_device *pdev) +{ + int i; + int ret = -ENODEV; + struct resource *res; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child_np; + struct aemif_device *aemif; + struct aemif_platform_data *pdata; + struct of_dev_auxdata *dev_lookup; + + aemif = devm_kzalloc(dev, sizeof(*aemif), GFP_KERNEL); + if (!aemif) + return -ENOMEM; + + pdata = dev_get_platdata(&pdev->dev); + dev_lookup = pdata ? pdata->dev_lookup : NULL; + + platform_set_drvdata(pdev, aemif); + + aemif->clk = devm_clk_get(dev, NULL); + if (IS_ERR(aemif->clk)) { + dev_err(dev, "cannot get clock 'aemif'\n"); + return PTR_ERR(aemif->clk); + } + + ret = clk_prepare_enable(aemif->clk); + if (ret) + return ret; + + aemif->clk_rate = clk_get_rate(aemif->clk) / MSEC_PER_SEC; + + if (np && of_device_is_compatible(np, "ti,da850-aemif")) + aemif->cs_offset = 2; + else if (pdata) + aemif->cs_offset = pdata->cs_offset; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + aemif->base = devm_ioremap_resource(dev, res); + if (IS_ERR(aemif->base)) { + ret = PTR_ERR(aemif->base); + goto error; + } + + if (np) { + /* + * For every controller device node, there is a cs device node + * that describe the bus configuration parameters. This + * functions iterate over these nodes and update the cs data + * array. + */ + for_each_available_child_of_node(np, child_np) { + ret = of_aemif_parse_abus_config(pdev, child_np); + if (ret < 0) { + of_node_put(child_np); + goto error; + } + } + } else if (pdata && pdata->num_abus_data > 0) { + for (i = 0; i < pdata->num_abus_data; i++, aemif->num_cs++) { + aemif->cs_data[i].cs = pdata->abus_data[i].cs; + aemif_get_hw_params(pdev, i); + } + } + + for (i = 0; i < aemif->num_cs; i++) { + ret = aemif_config_abus(pdev, i); + if (ret < 0) { + dev_err(dev, "Error configuring chip select %d\n", + aemif->cs_data[i].cs); + goto error; + } + } + + /* + * Create a child devices explicitly from here to guarantee that the + * child will be probed after the AEMIF timing parameters are set. + */ + if (np) { + for_each_available_child_of_node(np, child_np) { + ret = of_platform_populate(child_np, NULL, + dev_lookup, dev); + if (ret < 0) { + of_node_put(child_np); + goto error; + } + } + } else if (pdata) { + for (i = 0; i < pdata->num_sub_devices; i++) { + pdata->sub_devices[i].dev.parent = dev; + ret = platform_device_register(&pdata->sub_devices[i]); + if (ret) { + dev_warn(dev, "Error register sub device %s\n", + pdata->sub_devices[i].name); + } + } + } + + return 0; +error: + clk_disable_unprepare(aemif->clk); + return ret; +} + +static int aemif_remove(struct platform_device *pdev) +{ + struct aemif_device *aemif = platform_get_drvdata(pdev); + + clk_disable_unprepare(aemif->clk); + return 0; +} + +static struct platform_driver aemif_driver = { + .probe = aemif_probe, + .remove = aemif_remove, + .driver = { + .name = "ti-aemif", + .of_match_table = of_match_ptr(aemif_of_match), + }, +}; + +module_platform_driver(aemif_driver); + +MODULE_AUTHOR("Murali Karicheri <m-karicheri2@ti.com>"); +MODULE_AUTHOR("Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>"); +MODULE_DESCRIPTION("Texas Instruments AEMIF driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" KBUILD_MODNAME); diff --git a/drivers/memory/ti-emif-pm.c b/drivers/memory/ti-emif-pm.c new file mode 100644 index 000000000..2250d03ea --- /dev/null +++ b/drivers/memory/ti-emif-pm.c @@ -0,0 +1,356 @@ +/* + * TI AM33XX SRAM EMIF Driver + * + * Copyright (C) 2016-2017 Texas Instruments Inc. + * Dave Gerlach + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/err.h> +#include <linux/genalloc.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/sram.h> +#include <linux/ti-emif-sram.h> + +#include "emif.h" + +#define TI_EMIF_SRAM_SYMBOL_OFFSET(sym) ((unsigned long)(sym) - \ + (unsigned long)&ti_emif_sram) + +#define EMIF_POWER_MGMT_WAIT_SELF_REFRESH_8192_CYCLES 0x00a0 + +struct ti_emif_data { + phys_addr_t ti_emif_sram_phys; + phys_addr_t ti_emif_sram_data_phys; + unsigned long ti_emif_sram_virt; + unsigned long ti_emif_sram_data_virt; + struct gen_pool *sram_pool_code; + struct gen_pool *sram_pool_data; + struct ti_emif_pm_data pm_data; + struct ti_emif_pm_functions pm_functions; +}; + +static struct ti_emif_data *emif_instance; + +static u32 sram_suspend_address(struct ti_emif_data *emif_data, + unsigned long addr) +{ + return (emif_data->ti_emif_sram_virt + + TI_EMIF_SRAM_SYMBOL_OFFSET(addr)); +} + +static phys_addr_t sram_resume_address(struct ti_emif_data *emif_data, + unsigned long addr) +{ + return ((unsigned long)emif_data->ti_emif_sram_phys + + TI_EMIF_SRAM_SYMBOL_OFFSET(addr)); +} + +static void ti_emif_free_sram(struct ti_emif_data *emif_data) +{ + gen_pool_free(emif_data->sram_pool_code, emif_data->ti_emif_sram_virt, + ti_emif_sram_sz); + gen_pool_free(emif_data->sram_pool_data, + emif_data->ti_emif_sram_data_virt, + sizeof(struct emif_regs_amx3)); +} + +static int ti_emif_alloc_sram(struct device *dev, + struct ti_emif_data *emif_data) +{ + struct device_node *np = dev->of_node; + int ret; + + emif_data->sram_pool_code = of_gen_pool_get(np, "sram", 0); + if (!emif_data->sram_pool_code) { + dev_err(dev, "Unable to get sram pool for ocmcram code\n"); + return -ENODEV; + } + + emif_data->ti_emif_sram_virt = + gen_pool_alloc(emif_data->sram_pool_code, + ti_emif_sram_sz); + if (!emif_data->ti_emif_sram_virt) { + dev_err(dev, "Unable to allocate code memory from ocmcram\n"); + return -ENOMEM; + } + + /* Save physical address to calculate resume offset during pm init */ + emif_data->ti_emif_sram_phys = + gen_pool_virt_to_phys(emif_data->sram_pool_code, + emif_data->ti_emif_sram_virt); + + /* Get sram pool for data section and allocate space */ + emif_data->sram_pool_data = of_gen_pool_get(np, "sram", 1); + if (!emif_data->sram_pool_data) { + dev_err(dev, "Unable to get sram pool for ocmcram data\n"); + ret = -ENODEV; + goto err_free_sram_code; + } + + emif_data->ti_emif_sram_data_virt = + gen_pool_alloc(emif_data->sram_pool_data, + sizeof(struct emif_regs_amx3)); + if (!emif_data->ti_emif_sram_data_virt) { + dev_err(dev, "Unable to allocate data memory from ocmcram\n"); + ret = -ENOMEM; + goto err_free_sram_code; + } + + /* Save physical address to calculate resume offset during pm init */ + emif_data->ti_emif_sram_data_phys = + gen_pool_virt_to_phys(emif_data->sram_pool_data, + emif_data->ti_emif_sram_data_virt); + /* + * These functions are called during suspend path while MMU is + * still on so add virtual base to offset for absolute address + */ + emif_data->pm_functions.save_context = + sram_suspend_address(emif_data, + (unsigned long)ti_emif_save_context); + emif_data->pm_functions.enter_sr = + sram_suspend_address(emif_data, + (unsigned long)ti_emif_enter_sr); + emif_data->pm_functions.abort_sr = + sram_suspend_address(emif_data, + (unsigned long)ti_emif_abort_sr); + + /* + * These are called during resume path when MMU is not enabled + * so physical address is used instead + */ + emif_data->pm_functions.restore_context = + sram_resume_address(emif_data, + (unsigned long)ti_emif_restore_context); + emif_data->pm_functions.exit_sr = + sram_resume_address(emif_data, + (unsigned long)ti_emif_exit_sr); + + emif_data->pm_data.regs_virt = + (struct emif_regs_amx3 *)emif_data->ti_emif_sram_data_virt; + emif_data->pm_data.regs_phys = emif_data->ti_emif_sram_data_phys; + + return 0; + +err_free_sram_code: + gen_pool_free(emif_data->sram_pool_code, emif_data->ti_emif_sram_virt, + ti_emif_sram_sz); + return ret; +} + +static int ti_emif_push_sram(struct device *dev, struct ti_emif_data *emif_data) +{ + void *copy_addr; + u32 data_addr; + + copy_addr = sram_exec_copy(emif_data->sram_pool_code, + (void *)emif_data->ti_emif_sram_virt, + &ti_emif_sram, ti_emif_sram_sz); + if (!copy_addr) { + dev_err(dev, "Cannot copy emif code to sram\n"); + return -ENODEV; + } + + data_addr = sram_suspend_address(emif_data, + (unsigned long)&ti_emif_pm_sram_data); + copy_addr = sram_exec_copy(emif_data->sram_pool_code, + (void *)data_addr, + &emif_data->pm_data, + sizeof(emif_data->pm_data)); + if (!copy_addr) { + dev_err(dev, "Cannot copy emif data to code sram\n"); + return -ENODEV; + } + + return 0; +} + +/* + * Due to Usage Note 3.1.2 "DDR3: JEDEC Compliance for Maximum + * Self-Refresh Command Limit" found in AM335x Silicon Errata + * (Document SPRZ360F Revised November 2013) we must configure + * the self refresh delay timer to 0xA (8192 cycles) to avoid + * generating too many refresh command from the EMIF. + */ +static void ti_emif_configure_sr_delay(struct ti_emif_data *emif_data) +{ + writel(EMIF_POWER_MGMT_WAIT_SELF_REFRESH_8192_CYCLES, + (emif_data->pm_data.ti_emif_base_addr_virt + + EMIF_POWER_MANAGEMENT_CONTROL)); + + writel(EMIF_POWER_MGMT_WAIT_SELF_REFRESH_8192_CYCLES, + (emif_data->pm_data.ti_emif_base_addr_virt + + EMIF_POWER_MANAGEMENT_CTRL_SHDW)); +} + +/** + * ti_emif_copy_pm_function_table - copy mapping of pm funcs in sram + * @sram_pool: pointer to struct gen_pool where dst resides + * @dst: void * to address that table should be copied + * + * Returns 0 if success other error code if table is not available + */ +int ti_emif_copy_pm_function_table(struct gen_pool *sram_pool, void *dst) +{ + void *copy_addr; + + if (!emif_instance) + return -ENODEV; + + copy_addr = sram_exec_copy(sram_pool, dst, + &emif_instance->pm_functions, + sizeof(emif_instance->pm_functions)); + if (!copy_addr) + return -ENODEV; + + return 0; +} +EXPORT_SYMBOL_GPL(ti_emif_copy_pm_function_table); + +/** + * ti_emif_get_mem_type - return type for memory type in use + * + * Returns memory type value read from EMIF or error code if fails + */ +int ti_emif_get_mem_type(void) +{ + unsigned long temp; + + if (!emif_instance) + return -ENODEV; + + temp = readl(emif_instance->pm_data.ti_emif_base_addr_virt + + EMIF_SDRAM_CONFIG); + + temp = (temp & SDRAM_TYPE_MASK) >> SDRAM_TYPE_SHIFT; + return temp; +} +EXPORT_SYMBOL_GPL(ti_emif_get_mem_type); + +static const struct of_device_id ti_emif_of_match[] = { + { .compatible = "ti,emif-am3352", .data = + (void *)EMIF_SRAM_AM33_REG_LAYOUT, }, + { .compatible = "ti,emif-am4372", .data = + (void *)EMIF_SRAM_AM43_REG_LAYOUT, }, + {}, +}; +MODULE_DEVICE_TABLE(of, ti_emif_of_match); + +#ifdef CONFIG_PM_SLEEP +static int ti_emif_resume(struct device *dev) +{ + unsigned long tmp = + __raw_readl((void *)emif_instance->ti_emif_sram_virt); + + /* + * Check to see if what we are copying is already present in the + * first byte at the destination, only copy if it is not which + * indicates we have lost context and sram no longer contains + * the PM code + */ + if (tmp != ti_emif_sram) + ti_emif_push_sram(dev, emif_instance); + + return 0; +} + +static int ti_emif_suspend(struct device *dev) +{ + /* + * The contents will be present in DDR hence no need to + * explicitly save + */ + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static int ti_emif_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + struct device *dev = &pdev->dev; + const struct of_device_id *match; + struct ti_emif_data *emif_data; + + emif_data = devm_kzalloc(dev, sizeof(*emif_data), GFP_KERNEL); + if (!emif_data) + return -ENOMEM; + + match = of_match_device(ti_emif_of_match, &pdev->dev); + if (!match) + return -ENODEV; + + emif_data->pm_data.ti_emif_sram_config = (unsigned long)match->data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + emif_data->pm_data.ti_emif_base_addr_virt = devm_ioremap_resource(dev, + res); + if (IS_ERR(emif_data->pm_data.ti_emif_base_addr_virt)) { + ret = PTR_ERR(emif_data->pm_data.ti_emif_base_addr_virt); + return ret; + } + + emif_data->pm_data.ti_emif_base_addr_phys = res->start; + + ti_emif_configure_sr_delay(emif_data); + + ret = ti_emif_alloc_sram(dev, emif_data); + if (ret) + return ret; + + ret = ti_emif_push_sram(dev, emif_data); + if (ret) + goto fail_free_sram; + + emif_instance = emif_data; + + return 0; + +fail_free_sram: + ti_emif_free_sram(emif_data); + + return ret; +} + +static int ti_emif_remove(struct platform_device *pdev) +{ + struct ti_emif_data *emif_data = emif_instance; + + emif_instance = NULL; + + ti_emif_free_sram(emif_data); + + return 0; +} + +static const struct dev_pm_ops ti_emif_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ti_emif_suspend, ti_emif_resume) +}; + +static struct platform_driver ti_emif_driver = { + .probe = ti_emif_probe, + .remove = ti_emif_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = of_match_ptr(ti_emif_of_match), + .pm = &ti_emif_pm_ops, + }, +}; +module_platform_driver(ti_emif_driver); + +MODULE_AUTHOR("Dave Gerlach <d-gerlach@ti.com>"); +MODULE_DESCRIPTION("Texas Instruments SRAM EMIF driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/memory/ti-emif-sram-pm.S b/drivers/memory/ti-emif-sram-pm.S new file mode 100644 index 000000000..a5369181e --- /dev/null +++ b/drivers/memory/ti-emif-sram-pm.S @@ -0,0 +1,334 @@ +/* + * Low level PM code for TI EMIF + * + * Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/ + * Dave Gerlach + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <generated/ti-emif-asm-offsets.h> +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/memory.h> + +#include "emif.h" + +#define EMIF_POWER_MGMT_WAIT_SELF_REFRESH_8192_CYCLES 0x00a0 +#define EMIF_POWER_MGMT_SR_TIMER_MASK 0x00f0 +#define EMIF_POWER_MGMT_SELF_REFRESH_MODE 0x0200 +#define EMIF_POWER_MGMT_SELF_REFRESH_MODE_MASK 0x0700 + +#define EMIF_SDCFG_TYPE_DDR2 0x2 << SDRAM_TYPE_SHIFT +#define EMIF_STATUS_READY 0x4 + +#define AM43XX_EMIF_PHY_CTRL_REG_COUNT 0x120 + +#define EMIF_AM437X_REGISTERS 0x1 + + .arm + .align 3 + +ENTRY(ti_emif_sram) + +/* + * void ti_emif_save_context(void) + * + * Used during suspend to save the context of all required EMIF registers + * to local memory if the EMIF is going to lose context during the sleep + * transition. Operates on the VIRTUAL address of the EMIF. + */ +ENTRY(ti_emif_save_context) + stmfd sp!, {r4 - r11, lr} @ save registers on stack + + adr r4, ti_emif_pm_sram_data + ldr r0, [r4, #EMIF_PM_BASE_ADDR_VIRT_OFFSET] + ldr r2, [r4, #EMIF_PM_REGS_VIRT_OFFSET] + + /* Save EMIF configuration */ + ldr r1, [r0, #EMIF_SDRAM_CONFIG] + str r1, [r2, #EMIF_SDCFG_VAL_OFFSET] + + ldr r1, [r0, #EMIF_SDRAM_REFRESH_CONTROL] + str r1, [r2, #EMIF_REF_CTRL_VAL_OFFSET] + + ldr r1, [r0, #EMIF_SDRAM_TIMING_1] + str r1, [r2, #EMIF_TIMING1_VAL_OFFSET] + + ldr r1, [r0, #EMIF_SDRAM_TIMING_2] + str r1, [r2, #EMIF_TIMING2_VAL_OFFSET] + + ldr r1, [r0, #EMIF_SDRAM_TIMING_3] + str r1, [r2, #EMIF_TIMING3_VAL_OFFSET] + + ldr r1, [r0, #EMIF_POWER_MANAGEMENT_CONTROL] + str r1, [r2, #EMIF_PMCR_VAL_OFFSET] + + ldr r1, [r0, #EMIF_POWER_MANAGEMENT_CTRL_SHDW] + str r1, [r2, #EMIF_PMCR_SHDW_VAL_OFFSET] + + ldr r1, [r0, #EMIF_SDRAM_OUTPUT_IMPEDANCE_CALIBRATION_CONFIG] + str r1, [r2, #EMIF_ZQCFG_VAL_OFFSET] + + ldr r1, [r0, #EMIF_DDR_PHY_CTRL_1] + str r1, [r2, #EMIF_DDR_PHY_CTLR_1_OFFSET] + + ldr r1, [r0, #EMIF_COS_CONFIG] + str r1, [r2, #EMIF_COS_CONFIG_OFFSET] + + ldr r1, [r0, #EMIF_PRIORITY_TO_CLASS_OF_SERVICE_MAPPING] + str r1, [r2, #EMIF_PRIORITY_TO_COS_MAPPING_OFFSET] + + ldr r1, [r0, #EMIF_CONNECTION_ID_TO_CLASS_OF_SERVICE_1_MAPPING] + str r1, [r2, #EMIF_CONNECT_ID_SERV_1_MAP_OFFSET] + + ldr r1, [r0, #EMIF_CONNECTION_ID_TO_CLASS_OF_SERVICE_2_MAPPING] + str r1, [r2, #EMIF_CONNECT_ID_SERV_2_MAP_OFFSET] + + ldr r1, [r0, #EMIF_OCP_CONFIG] + str r1, [r2, #EMIF_OCP_CONFIG_VAL_OFFSET] + + ldr r5, [r4, #EMIF_PM_CONFIG_OFFSET] + cmp r5, #EMIF_SRAM_AM43_REG_LAYOUT + bne emif_skip_save_extra_regs + + ldr r1, [r0, #EMIF_READ_WRITE_LEVELING_RAMP_CONTROL] + str r1, [r2, #EMIF_RD_WR_LEVEL_RAMP_CTRL_OFFSET] + + ldr r1, [r0, #EMIF_READ_WRITE_EXECUTION_THRESHOLD] + str r1, [r2, #EMIF_RD_WR_EXEC_THRESH_OFFSET] + + ldr r1, [r0, #EMIF_LPDDR2_NVM_TIMING] + str r1, [r2, #EMIF_LPDDR2_NVM_TIM_OFFSET] + + ldr r1, [r0, #EMIF_LPDDR2_NVM_TIMING_SHDW] + str r1, [r2, #EMIF_LPDDR2_NVM_TIM_SHDW_OFFSET] + + ldr r1, [r0, #EMIF_DLL_CALIB_CTRL] + str r1, [r2, #EMIF_DLL_CALIB_CTRL_VAL_OFFSET] + + ldr r1, [r0, #EMIF_DLL_CALIB_CTRL_SHDW] + str r1, [r2, #EMIF_DLL_CALIB_CTRL_VAL_SHDW_OFFSET] + + /* Loop and save entire block of emif phy regs */ + mov r5, #0x0 + add r4, r2, #EMIF_EXT_PHY_CTRL_VALS_OFFSET + add r3, r0, #EMIF_EXT_PHY_CTRL_1 +ddr_phy_ctrl_save: + ldr r1, [r3, r5] + str r1, [r4, r5] + add r5, r5, #0x4 + cmp r5, #AM43XX_EMIF_PHY_CTRL_REG_COUNT + bne ddr_phy_ctrl_save + +emif_skip_save_extra_regs: + ldmfd sp!, {r4 - r11, pc} @ restore regs and return +ENDPROC(ti_emif_save_context) + +/* + * void ti_emif_restore_context(void) + * + * Used during resume to restore the context of all required EMIF registers + * from local memory after the EMIF has lost context during a sleep transition. + * Operates on the PHYSICAL address of the EMIF. + */ +ENTRY(ti_emif_restore_context) + adr r4, ti_emif_pm_sram_data + ldr r0, [r4, #EMIF_PM_BASE_ADDR_PHYS_OFFSET] + ldr r2, [r4, #EMIF_PM_REGS_PHYS_OFFSET] + + /* Config EMIF Timings */ + ldr r1, [r2, #EMIF_DDR_PHY_CTLR_1_OFFSET] + str r1, [r0, #EMIF_DDR_PHY_CTRL_1] + str r1, [r0, #EMIF_DDR_PHY_CTRL_1_SHDW] + + ldr r1, [r2, #EMIF_TIMING1_VAL_OFFSET] + str r1, [r0, #EMIF_SDRAM_TIMING_1] + str r1, [r0, #EMIF_SDRAM_TIMING_1_SHDW] + + ldr r1, [r2, #EMIF_TIMING2_VAL_OFFSET] + str r1, [r0, #EMIF_SDRAM_TIMING_2] + str r1, [r0, #EMIF_SDRAM_TIMING_2_SHDW] + + ldr r1, [r2, #EMIF_TIMING3_VAL_OFFSET] + str r1, [r0, #EMIF_SDRAM_TIMING_3] + str r1, [r0, #EMIF_SDRAM_TIMING_3_SHDW] + + ldr r1, [r2, #EMIF_REF_CTRL_VAL_OFFSET] + str r1, [r0, #EMIF_SDRAM_REFRESH_CONTROL] + str r1, [r0, #EMIF_SDRAM_REFRESH_CTRL_SHDW] + + ldr r1, [r2, #EMIF_PMCR_VAL_OFFSET] + str r1, [r0, #EMIF_POWER_MANAGEMENT_CONTROL] + + ldr r1, [r2, #EMIF_PMCR_SHDW_VAL_OFFSET] + str r1, [r0, #EMIF_POWER_MANAGEMENT_CTRL_SHDW] + + ldr r1, [r2, #EMIF_COS_CONFIG_OFFSET] + str r1, [r0, #EMIF_COS_CONFIG] + + ldr r1, [r2, #EMIF_PRIORITY_TO_COS_MAPPING_OFFSET] + str r1, [r0, #EMIF_PRIORITY_TO_CLASS_OF_SERVICE_MAPPING] + + ldr r1, [r2, #EMIF_CONNECT_ID_SERV_1_MAP_OFFSET] + str r1, [r0, #EMIF_CONNECTION_ID_TO_CLASS_OF_SERVICE_1_MAPPING] + + ldr r1, [r2, #EMIF_CONNECT_ID_SERV_2_MAP_OFFSET] + str r1, [r0, #EMIF_CONNECTION_ID_TO_CLASS_OF_SERVICE_2_MAPPING] + + ldr r1, [r2, #EMIF_OCP_CONFIG_VAL_OFFSET] + str r1, [r0, #EMIF_OCP_CONFIG] + + ldr r5, [r4, #EMIF_PM_CONFIG_OFFSET] + cmp r5, #EMIF_SRAM_AM43_REG_LAYOUT + bne emif_skip_restore_extra_regs + + ldr r1, [r2, #EMIF_RD_WR_LEVEL_RAMP_CTRL_OFFSET] + str r1, [r0, #EMIF_READ_WRITE_LEVELING_RAMP_CONTROL] + + ldr r1, [r2, #EMIF_RD_WR_EXEC_THRESH_OFFSET] + str r1, [r0, #EMIF_READ_WRITE_EXECUTION_THRESHOLD] + + ldr r1, [r2, #EMIF_LPDDR2_NVM_TIM_OFFSET] + str r1, [r0, #EMIF_LPDDR2_NVM_TIMING] + + ldr r1, [r2, #EMIF_LPDDR2_NVM_TIM_SHDW_OFFSET] + str r1, [r0, #EMIF_LPDDR2_NVM_TIMING_SHDW] + + ldr r1, [r2, #EMIF_DLL_CALIB_CTRL_VAL_OFFSET] + str r1, [r0, #EMIF_DLL_CALIB_CTRL] + + ldr r1, [r2, #EMIF_DLL_CALIB_CTRL_VAL_SHDW_OFFSET] + str r1, [r0, #EMIF_DLL_CALIB_CTRL_SHDW] + + ldr r1, [r2, #EMIF_ZQCFG_VAL_OFFSET] + str r1, [r0, #EMIF_SDRAM_OUTPUT_IMPEDANCE_CALIBRATION_CONFIG] + + /* Loop and restore entire block of emif phy regs */ + mov r5, #0x0 + /* Load ti_emif_regs_amx3 + EMIF_EXT_PHY_CTRL_VALS_OFFSET for address + * to phy register save space + */ + add r3, r2, #EMIF_EXT_PHY_CTRL_VALS_OFFSET + add r4, r0, #EMIF_EXT_PHY_CTRL_1 +ddr_phy_ctrl_restore: + ldr r1, [r3, r5] + str r1, [r4, r5] + add r5, r5, #0x4 + cmp r5, #AM43XX_EMIF_PHY_CTRL_REG_COUNT + bne ddr_phy_ctrl_restore + +emif_skip_restore_extra_regs: + /* + * Output impedence calib needed only for DDR3 + * but since the initial state of this will be + * disabled for DDR2 no harm in restoring the + * old configuration + */ + ldr r1, [r2, #EMIF_ZQCFG_VAL_OFFSET] + str r1, [r0, #EMIF_SDRAM_OUTPUT_IMPEDANCE_CALIBRATION_CONFIG] + + /* Write to sdcfg last for DDR2 only */ + ldr r1, [r2, #EMIF_SDCFG_VAL_OFFSET] + and r2, r1, #SDRAM_TYPE_MASK + cmp r2, #EMIF_SDCFG_TYPE_DDR2 + streq r1, [r0, #EMIF_SDRAM_CONFIG] + + mov pc, lr +ENDPROC(ti_emif_restore_context) + +/* + * void ti_emif_enter_sr(void) + * + * Programs the EMIF to tell the SDRAM to enter into self-refresh + * mode during a sleep transition. Operates on the VIRTUAL address + * of the EMIF. + */ +ENTRY(ti_emif_enter_sr) + stmfd sp!, {r4 - r11, lr} @ save registers on stack + + adr r4, ti_emif_pm_sram_data + ldr r0, [r4, #EMIF_PM_BASE_ADDR_VIRT_OFFSET] + ldr r2, [r4, #EMIF_PM_REGS_VIRT_OFFSET] + + ldr r1, [r0, #EMIF_POWER_MANAGEMENT_CONTROL] + bic r1, r1, #EMIF_POWER_MGMT_SELF_REFRESH_MODE_MASK + orr r1, r1, #EMIF_POWER_MGMT_SELF_REFRESH_MODE + str r1, [r0, #EMIF_POWER_MANAGEMENT_CONTROL] + + ldmfd sp!, {r4 - r11, pc} @ restore regs and return +ENDPROC(ti_emif_enter_sr) + +/* + * void ti_emif_exit_sr(void) + * + * Programs the EMIF to tell the SDRAM to exit self-refresh mode + * after a sleep transition. Operates on the PHYSICAL address of + * the EMIF. + */ +ENTRY(ti_emif_exit_sr) + adr r4, ti_emif_pm_sram_data + ldr r0, [r4, #EMIF_PM_BASE_ADDR_PHYS_OFFSET] + ldr r2, [r4, #EMIF_PM_REGS_PHYS_OFFSET] + + /* + * Toggle EMIF to exit refresh mode: + * if EMIF lost context, PWR_MGT_CTRL is currently 0, writing disable + * (0x0), wont do diddly squat! so do a toggle from SR(0x2) to disable + * (0x0) here. + * *If* EMIF did not lose context, nothing broken as we write the same + * value(0x2) to reg before we write a disable (0x0). + */ + ldr r1, [r2, #EMIF_PMCR_VAL_OFFSET] + bic r1, r1, #EMIF_POWER_MGMT_SELF_REFRESH_MODE_MASK + orr r1, r1, #EMIF_POWER_MGMT_SELF_REFRESH_MODE + str r1, [r0, #EMIF_POWER_MANAGEMENT_CONTROL] + bic r1, r1, #EMIF_POWER_MGMT_SELF_REFRESH_MODE_MASK + str r1, [r0, #EMIF_POWER_MANAGEMENT_CONTROL] + + /* Wait for EMIF to become ready */ +1: ldr r1, [r0, #EMIF_STATUS] + tst r1, #EMIF_STATUS_READY + beq 1b + + mov pc, lr +ENDPROC(ti_emif_exit_sr) + +/* + * void ti_emif_abort_sr(void) + * + * Disables self-refresh after a failed transition to a low-power + * state so the kernel can jump back to DDR and follow abort path. + * Operates on the VIRTUAL address of the EMIF. + */ +ENTRY(ti_emif_abort_sr) + stmfd sp!, {r4 - r11, lr} @ save registers on stack + + adr r4, ti_emif_pm_sram_data + ldr r0, [r4, #EMIF_PM_BASE_ADDR_VIRT_OFFSET] + ldr r2, [r4, #EMIF_PM_REGS_VIRT_OFFSET] + + ldr r1, [r2, #EMIF_PMCR_VAL_OFFSET] + bic r1, r1, #EMIF_POWER_MGMT_SELF_REFRESH_MODE_MASK + str r1, [r0, #EMIF_POWER_MANAGEMENT_CONTROL] + + /* Wait for EMIF to become ready */ +1: ldr r1, [r0, #EMIF_STATUS] + tst r1, #EMIF_STATUS_READY + beq 1b + + ldmfd sp!, {r4 - r11, pc} @ restore regs and return +ENDPROC(ti_emif_abort_sr) + + .align 3 +ENTRY(ti_emif_pm_sram_data) + .space EMIF_PM_DATA_SIZE +ENTRY(ti_emif_sram_sz) + .word . - ti_emif_save_context |