Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
/ti-emif-asm-offsets.h
+237
View File
@@ -0,0 +1,237 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Memory devices
#
menuconfig MEMORY
bool "Memory Controller drivers"
help
This option allows to enable specific memory controller drivers,
useful mostly on embedded systems. These could be controllers
for DRAM (SDR, DDR), ROM, SRAM and others. The drivers features
vary from memory tuning and frequency scaling to enabling
access to attached peripherals through memory bus.
if MEMORY
config DDR
bool
help
Data from JEDEC specs for DDR SDRAM memories,
particularly the AC timing parameters and addressing
information. This data is useful for drivers handling
DDR SDRAM controllers.
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_EBI
bool "Atmel EBI driver"
default ARCH_AT91
depends on ARCH_AT91 || COMPILE_TEST
depends on 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 BRCMSTB_DPFE
tristate "Broadcom STB DPFE driver"
default ARCH_BRCMSTB
depends on ARCH_BRCMSTB || COMPILE_TEST
help
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.
config BRCMSTB_MEMC
tristate "Broadcom STB MEMC driver"
default ARCH_BRCMSTB
depends on ARCH_BRCMSTB || COMPILE_TEST
help
This driver provides a way to configure the Broadcom STB memory
controller and specifically control the Self Refresh Power Down
(SRPD) inactivity timeout.
config TI_AEMIF
tristate "Texas Instruments AEMIF driver"
depends on ARCH_DAVINCI || ARCH_KEYSTONE || COMPILE_TEST
depends on 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 || COMPILE_TEST
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
tristate "Texas Instruments OMAP SoC GPMC driver"
depends on OF_ADDRESS
depends on ARCH_OMAP2PLUS || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST
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 || (ARM && CPU_V7 && COMPILE_TEST)
depends on 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 FPGA_DFL_EMIF
tristate "FPGA DFL EMIF Driver"
depends on FPGA_DFL && HAS_IOMEM
help
This driver is for the EMIF private feature implemented under
FPGA Device Feature List (DFL) framework. It is used to expose
memory interface status information as well as memory clearing
control.
config MVEBU_DEVBUS
bool "Marvell EBU Device Bus Controller"
default PLAT_ORION
depends on PLAT_ORION || COMPILE_TEST
depends on 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 || COMPILE_TEST
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 "Freescale IFC driver"
depends on FSL_SOC || ARCH_LAYERSCAPE || SOC_LS1021A || COMPILE_TEST
depends on HAS_IOMEM
config JZ4780_NEMC
bool "Ingenic JZ4780 SoC NEMC driver"
depends on MIPS || 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
tristate "MediaTek SoC Memory Controller driver" if COMPILE_TEST
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 || COMPILE_TEST
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.
config PL353_SMC
tristate "ARM PL35X Static Memory Controller(SMC) driver"
default ARM
depends on ARM || COMPILE_TEST
depends on ARM_AMBA
help
This driver is for the ARM PL351/PL353 Static Memory
Controller(SMC) module.
config RENESAS_RPCIF
tristate "Renesas RPC-IF driver"
depends on ARCH_RENESAS || COMPILE_TEST
select REGMAP_MMIO
select RESET_CONTROLLER
help
This supports Renesas R-Car Gen3 or RZ/G2 RPC-IF which provides
either SPI host or HyperFlash. You'll have to select individual
components under the corresponding menu.
config STM32_FMC2_EBI
tristate "Support for FMC2 External Bus Interface on STM32MP SoCs"
depends on ARCH_STM32 || COMPILE_TEST
select MFD_SYSCON
help
Select this option to enable the STM32 FMC2 External Bus Interface
controller. This driver configures the transactions with external
devices (like SRAM, ethernet adapters, FPGAs, LCD displays, ...) on
SOCs containing the FMC2 External Bus Interface.
config STM32_OMM
tristate "STM32 Octo Memory Manager"
depends on SPI_STM32_OSPI || COMPILE_TEST
help
This driver manages the muxing between the 2 OSPI busses and
the 2 output ports. There are 4 possible muxing configurations:
- direct mode (no multiplexing): OSPI1 output is on port 1 and OSPI2
output is on port 2
- OSPI1 and OSPI2 are multiplexed over the same output port 1
- swapped mode (no multiplexing), OSPI1 output is on port 2,
OSPI2 output is on port 1
- OSPI1 and OSPI2 are multiplexed over the same output port 2
It also manages :
- the split of the memory area shared between the 2 OSPI instances.
- chip select selection override.
- the time between 2 transactions in multiplexed mode.
source "drivers/memory/samsung/Kconfig"
source "drivers/memory/tegra/Kconfig"
endif
+41
View File
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for memory devices
#
obj-$(CONFIG_DDR) += jedec_ddr_data.o
ifeq ($(CONFIG_DDR),y)
obj-$(CONFIG_OF) += of_memory.o
endif
obj-$(CONFIG_ARM_PL172_MPMC) += pl172.o
obj-$(CONFIG_ATMEL_EBI) += atmel-ebi.o
obj-$(CONFIG_BRCMSTB_DPFE) += brcmstb_dpfe.o
obj-$(CONFIG_BRCMSTB_MEMC) += brcmstb_memc.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_PL353_SMC) += pl353-smc.o
obj-$(CONFIG_RENESAS_RPCIF) += renesas-rpc-if.o
obj-$(CONFIG_STM32_FMC2_EBI) += stm32-fmc2-ebi.o
obj-$(CONFIG_STM32_OMM) += stm32_omm.o
obj-$(CONFIG_SAMSUNG_MC) += samsung/
obj-$(CONFIG_TEGRA_MC) += tegra/
obj-$(CONFIG_TI_EMIF_SRAM) += ti-emif-sram.o
obj-$(CONFIG_FPGA_DFL_EMIF) += dfl-emif.o
ti-emif-sram-objs := ti-emif-pm.o ti-emif-sram-pm.o
$(obj)/ti-emif-sram-pm.o: $(obj)/ti-emif-asm-offsets.h
$(obj)/ti-emif-asm-offsets.h: $(obj)/emif-asm-offsets.s FORCE
$(call filechk,offsets,__TI_EMIF_ASM_OFFSETS_H__)
targets += emif-asm-offsets.s
clean-files += ti-emif-asm-offsets.h
+638
View File
@@ -0,0 +1,638 @@
// SPDX-License-Identifier: GPL-2.0
/*
* EBI driver for Atmel chips
* inspired by the fsl weim bus driver
*
* Copyright (C) 2013 Jean-Jacques Hiblot <jjhiblot@traphandler.com>
*/
#include <linux/cleanup.h>
#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.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <soc/at91/atmel-sfr.h>
#define AT91_EBI_NUM_CS 8
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[] __counted_by(numcs);
};
struct atmel_ebi_caps {
unsigned int available_cs;
unsigned int ebi_csa_offs;
const char *regmap_name;
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 *regmap;
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_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, struct_size(ebid, configs, numcs),
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_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->regmap,
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,
.regmap_name = "atmel,matrix",
.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,
.regmap_name = "atmel,matrix",
.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,
.regmap_name = "atmel,matrix",
.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,
.regmap_name = "atmel,matrix",
.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,
.regmap_name = "atmel,matrix",
.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,
.regmap_name = "atmel,matrix",
.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,
.regmap_name = "atmel,matrix",
.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 atmel_ebi_caps sam9x60_ebi_caps = {
.available_cs = 0x3f,
.ebi_csa_offs = AT91_SFR_CCFG_EBICSA,
.regmap_name = "microchip,sfr",
.get_config = at91sam9_ebi_get_config,
.xlate_config = atmel_ebi_xslate_smc_config,
.apply_config = at91sam9_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,
},
{
.compatible = "microchip,sam9x60-ebi",
.data = &sam9x60_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 *np = dev->of_node;
struct atmel_ebi *ebi;
int ret, reg_cells;
struct clk *clk;
u32 val;
ebi = devm_kzalloc(dev, sizeof(*ebi), GFP_KERNEL);
if (!ebi)
return -ENOMEM;
platform_set_drvdata(pdev, ebi);
INIT_LIST_HEAD(&ebi->devs);
ebi->caps = device_get_match_data(dev);
if (!ebi->caps)
return -EINVAL;
ebi->dev = dev;
clk = devm_clk_get(dev, NULL);
if (IS_ERR(clk))
return PTR_ERR(clk);
ebi->clk = clk;
struct device_node *smc_np __free(device_node) =
of_parse_phandle(dev->of_node, "atmel,smc", 0);
ebi->smc.regmap = syscon_node_to_regmap(smc_np);
if (IS_ERR(ebi->smc.regmap))
return PTR_ERR(ebi->smc.regmap);
ebi->smc.layout = atmel_hsmc_get_reg_layout(smc_np);
if (IS_ERR(ebi->smc.layout))
return PTR_ERR(ebi->smc.layout);
ebi->smc.clk = of_clk_get(smc_np, 0);
if (IS_ERR(ebi->smc.clk)) {
if (PTR_ERR(ebi->smc.clk) != -ENOENT)
return PTR_ERR(ebi->smc.clk);
ebi->smc.clk = NULL;
}
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 it.
*/
if (ebi->caps->ebi_csa_offs) {
ebi->regmap =
syscon_regmap_lookup_by_phandle(np,
ebi->caps->regmap_name);
if (IS_ERR(ebi->regmap))
return PTR_ERR(ebi->regmap);
}
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_scoped(np, child) {
if (!of_property_present(child, "reg"))
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)
return ret;
}
}
return of_platform_populate(np, NULL, NULL, dev);
}
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 = {
.probe = atmel_ebi_probe,
.driver = {
.name = "atmel-ebi",
.of_match_table = atmel_ebi_id_table,
.pm = &atmel_ebi_pm_ops,
},
};
builtin_platform_driver(atmel_ebi_driver);
+945
View File
@@ -0,0 +1,945 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* DDR PHY Front End (DPFE) driver for Broadcom set top box SoCs
*
* Copyright (c) 2017 Broadcom
*/
/*
* 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 firmware 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.h>
#include <linux/platform_device.h>
#define DRVNAME "brcmstb-dpfe"
/* 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
#define DRAM_INFO_MR4_SHIFT 24 /* We need to look at byte 3 */
/* 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 (API v2) */
#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
#define DRAM_VENDOR_SHIFT 24 /* We need to look at byte 3 */
/* DRAM Information Offsets & Masks (API v3) */
#define DRAM_DDR_INFO_MR4 0x0
#define DRAM_DDR_INFO_MR5 0x4
#define DRAM_DDR_INFO_MR6 0x8
#define DRAM_DDR_INFO_MR7 0xc
#define DRAM_DDR_INFO_MR8 0x10
#define DRAM_DDR_INFO_ERROR 0x14
#define DRAM_DDR_INFO_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 1000
enum dpfe_msg_fields {
MSG_HEADER,
MSG_COMMAND,
MSG_ARG_COUNT,
MSG_ARG0,
MSG_FIELD_MAX = 16 /* Max number of arguments */
};
enum dpfe_commands {
DPFE_CMD_GET_INFO,
DPFE_CMD_GET_REFRESH,
DPFE_CMD_GET_VENDOR,
DPFE_CMD_MAX /* Last entry */
};
/*
* 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;
};
/* API version and corresponding commands */
struct dpfe_api {
int version;
const char *fw_name;
const struct attribute_group **sysfs_attrs;
u32 command[DPFE_CMD_MAX][MSG_FIELD_MAX];
};
/* Things we need for as long as we are active. */
struct brcmstb_dpfe_priv {
void __iomem *regs;
void __iomem *dmem;
void __iomem *imem;
struct device *dev;
const struct dpfe_api *dpfe_api;
struct mutex lock;
};
/*
* Forward declaration of our sysfs attribute functions, so we can declare the
* attribute data structures early.
*/
static ssize_t show_info(struct device *, struct device_attribute *, char *);
static ssize_t show_refresh(struct device *, struct device_attribute *, char *);
static ssize_t store_refresh(struct device *, struct device_attribute *,
const char *, size_t);
static ssize_t show_vendor(struct device *, struct device_attribute *, char *);
static ssize_t show_dram(struct device *, struct device_attribute *, char *);
/*
* Declare our attributes early, so they can be referenced in the API data
* structure. We need to do this, because the attributes depend on the API
* version.
*/
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 DEVICE_ATTR(dpfe_dram, 0444, show_dram, NULL);
/* API v2 sysfs attributes */
static struct attribute *dpfe_v2_attrs[] = {
&dev_attr_dpfe_info.attr,
&dev_attr_dpfe_refresh.attr,
&dev_attr_dpfe_vendor.attr,
NULL
};
ATTRIBUTE_GROUPS(dpfe_v2);
/* API v3 sysfs attributes */
static struct attribute *dpfe_v3_attrs[] = {
&dev_attr_dpfe_info.attr,
&dev_attr_dpfe_dram.attr,
NULL
};
ATTRIBUTE_GROUPS(dpfe_v3);
/*
* Old API v2 firmware commands, as defined in the rev 0.61 specification, we
* use a version set to 1 to denote that it is not compatible with the new API
* v2 and onwards.
*/
static const struct dpfe_api dpfe_api_old_v2 = {
.version = 1,
.fw_name = "dpfe.bin",
.sysfs_attrs = dpfe_v2_groups,
.command = {
[DPFE_CMD_GET_INFO] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 1,
[MSG_ARG_COUNT] = 1,
[MSG_ARG0] = 1,
},
[DPFE_CMD_GET_REFRESH] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 2,
[MSG_ARG_COUNT] = 1,
[MSG_ARG0] = 1,
},
[DPFE_CMD_GET_VENDOR] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 2,
[MSG_ARG_COUNT] = 1,
[MSG_ARG0] = 2,
},
}
};
/*
* API v2 firmware commands, as defined in the rev 0.8 specification, named new
* v2 here
*/
static const struct dpfe_api dpfe_api_new_v2 = {
.version = 2,
.fw_name = NULL, /* We expect the firmware to have been downloaded! */
.sysfs_attrs = dpfe_v2_groups,
.command = {
[DPFE_CMD_GET_INFO] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 0x101,
},
[DPFE_CMD_GET_REFRESH] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 0x201,
},
[DPFE_CMD_GET_VENDOR] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 0x202,
},
}
};
/* API v3 firmware commands */
static const struct dpfe_api dpfe_api_v3 = {
.version = 3,
.fw_name = NULL, /* We expect the firmware to have been downloaded! */
.sysfs_attrs = dpfe_v3_groups,
.command = {
[DPFE_CMD_GET_INFO] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 0x0101,
[MSG_ARG_COUNT] = 1,
[MSG_ARG0] = 1,
},
[DPFE_CMD_GET_REFRESH] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 0x0202,
[MSG_ARG_COUNT] = 0,
},
/* There's no GET_VENDOR command in API v3. */
},
};
static const char *get_error_text(unsigned int i)
{
static const char * const error_text[] = {
"Success", "Header code incorrect",
"Unknown command or argument", "Incorrect checksum",
"Malformed command", "Timed out", "Unknown error",
};
if (unlikely(i >= ARRAY_SIZE(error_text)))
i = ARRAY_SIZE(error_text) - 1;
return error_text[i];
}
static bool is_dcpu_enabled(struct brcmstb_dpfe_priv *priv)
{
u32 val;
mutex_lock(&priv->lock);
val = readl_relaxed(priv->regs + REG_DCPU_RESET);
mutex_unlock(&priv->lock);
return !(val & DCPU_RESET_MASK);
}
static void __disable_dcpu(struct brcmstb_dpfe_priv *priv)
{
u32 val;
if (!is_dcpu_enabled(priv))
return;
mutex_lock(&priv->lock);
/* Put DCPU in reset if it's running. */
val = readl_relaxed(priv->regs + REG_DCPU_RESET);
val |= (1 << DCPU_RESET_SHIFT);
writel_relaxed(val, priv->regs + REG_DCPU_RESET);
mutex_unlock(&priv->lock);
}
static void __enable_dcpu(struct brcmstb_dpfe_priv *priv)
{
void __iomem *regs = priv->regs;
u32 val;
mutex_lock(&priv->lock);
/* 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);
mutex_unlock(&priv->lock);
}
static unsigned int get_msg_chksum(const u32 msg[], unsigned int max)
{
unsigned int sum = 0;
unsigned int i;
/* Don't include the last field in the checksum. */
for (i = 0; i < max; i++)
sum += msg[i];
return sum;
}
static void __iomem *get_msg_ptr(struct brcmstb_dpfe_priv *priv, u32 response,
char *buf, ssize_t *size)
{
unsigned int msg_type;
unsigned int offset;
void __iomem *ptr = NULL;
/* There is no need to use this function for API v3 or later. */
if (unlikely(priv->dpfe_api->version >= 3))
return 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 void __finalize_command(struct brcmstb_dpfe_priv *priv)
{
unsigned int release_mbox;
/*
* It depends on the API version which MBOX register we have to write to
* signal we are done.
*/
release_mbox = (priv->dpfe_api->version < 2)
? REG_TO_HOST_MBOX : REG_TO_DCPU_MBOX;
writel_relaxed(0, priv->regs + release_mbox);
}
static int __send_command(struct brcmstb_dpfe_priv *priv, unsigned int cmd,
u32 result[])
{
void __iomem *regs = priv->regs;
unsigned int i, chksum, chksum_idx;
const u32 *msg;
int ret = 0;
u32 resp;
if (cmd >= DPFE_CMD_MAX)
return -1;
msg = priv->dpfe_api->command[cmd];
mutex_lock(&priv->lock);
/* Wait for DCPU to become ready */
for (i = 0; i < DELAY_LOOP_MAX; i++) {
resp = readl_relaxed(regs + REG_TO_HOST_MBOX);
if (resp == 0)
break;
msleep(1);
}
if (resp != 0) {
mutex_unlock(&priv->lock);
return -ffs(DCPU_RET_ERR_TIMEDOUT);
}
/* Compute checksum over the message */
chksum_idx = msg[MSG_ARG_COUNT] + MSG_ARG_COUNT + 1;
chksum = get_msg_chksum(msg, chksum_idx);
/* Write command and arguments to message area */
for (i = 0; i < MSG_FIELD_MAX; i++) {
if (i == chksum_idx)
writel_relaxed(chksum, regs + DCPU_MSG_RAM(i));
else
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;
msleep(1);
}
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));
chksum_idx = result[MSG_ARG_COUNT] + MSG_ARG_COUNT + 1;
}
/* Tell DCPU we are done */
__finalize_command(priv);
mutex_unlock(&priv->lock);
if (ret)
return ret;
/* Verify response */
chksum = get_msg_chksum(result, chksum_idx);
if (chksum != result[chksum_idx])
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 brcmstb_dpfe_priv *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 brcmstb_dpfe_priv *priv)
{
const struct dpfe_firmware_header *header;
unsigned int dmem_size, imem_size;
struct device *dev = priv->dev;
bool is_big_endian = false;
const struct firmware *fw;
const u32 *dmem, *imem;
struct init_data init;
const void *fw_blob;
int ret;
/*
* Skip downloading the firmware if the DCPU is already running and
* responding to commands.
*/
if (is_dcpu_enabled(priv)) {
u32 response[MSG_FIELD_MAX];
ret = __send_command(priv, DPFE_CMD_GET_INFO, response);
if (!ret)
return 0;
}
/*
* If the firmware filename is NULL it means the boot firmware has to
* download the DCPU firmware for us. If that didn't work, we have to
* bail, since downloading it ourselves wouldn't work either.
*/
if (!priv->dpfe_api->fw_name)
return -ENODEV;
ret = firmware_request_nowarn(&fw, priv->dpfe_api->fw_name, dev);
/*
* Defer the firmware download if the firmware file couldn't be found.
* The root file system may not be available yet.
*/
if (ret)
return (ret == -ENOENT) ? -EPROBE_DEFER : ret;
ret = __verify_firmware(&init, fw);
if (ret) {
ret = -EFAULT;
goto release_fw;
}
__disable_dcpu(priv);
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)
goto release_fw;
ret = __write_firmware(priv->imem, imem, imem_size, is_big_endian);
if (ret)
goto release_fw;
ret = __verify_fw_checksum(&init, priv, header, init.chksum);
if (ret)
goto release_fw;
__enable_dcpu(priv);
release_fw:
release_firmware(fw);
return ret;
}
static ssize_t generic_show(unsigned int command, u32 response[],
struct brcmstb_dpfe_priv *priv, char *buf)
{
int ret;
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", get_error_text(-ret));
return 0;
}
static ssize_t show_info(struct device *dev, struct device_attribute *devattr,
char *buf)
{
u32 response[MSG_FIELD_MAX];
struct brcmstb_dpfe_priv *priv;
unsigned int info;
ssize_t ret;
priv = dev_get_drvdata(dev);
ret = generic_show(DPFE_CMD_GET_INFO, response, priv, 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 brcmstb_dpfe_priv *priv;
u8 refresh, sr_abort, ppre, thermal_offs, tuf;
u32 mr4;
ssize_t ret;
priv = dev_get_drvdata(dev);
ret = generic_show(DPFE_CMD_GET_REFRESH, response, priv, buf);
if (ret)
return ret;
info = get_msg_ptr(priv, response[MSG_ARG0], buf, &ret);
if (!info)
return ret;
mr4 = (readl_relaxed(info + DRAM_INFO_MR4) >> DRAM_INFO_MR4_SHIFT) &
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 brcmstb_dpfe_priv *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 brcmstb_dpfe_priv *priv;
void __iomem *info;
ssize_t ret;
u32 mr5, mr6, mr7, mr8, err;
priv = dev_get_drvdata(dev);
ret = generic_show(DPFE_CMD_GET_VENDOR, response, priv, buf);
if (ret)
return ret;
info = get_msg_ptr(priv, response[MSG_ARG0], buf, &ret);
if (!info)
return ret;
mr5 = (readl_relaxed(info + DRAM_VENDOR_MR5) >> DRAM_VENDOR_SHIFT) &
DRAM_VENDOR_MASK;
mr6 = (readl_relaxed(info + DRAM_VENDOR_MR6) >> DRAM_VENDOR_SHIFT) &
DRAM_VENDOR_MASK;
mr7 = (readl_relaxed(info + DRAM_VENDOR_MR7) >> DRAM_VENDOR_SHIFT) &
DRAM_VENDOR_MASK;
mr8 = (readl_relaxed(info + DRAM_VENDOR_MR8) >> DRAM_VENDOR_SHIFT) &
DRAM_VENDOR_MASK;
err = readl_relaxed(info + DRAM_VENDOR_ERROR) & DRAM_VENDOR_MASK;
return sprintf(buf, "%#x %#x %#x %#x %#x\n", mr5, mr6, mr7, mr8, err);
}
static ssize_t show_dram(struct device *dev, struct device_attribute *devattr,
char *buf)
{
u32 response[MSG_FIELD_MAX];
struct brcmstb_dpfe_priv *priv;
ssize_t ret;
u32 mr4, mr5, mr6, mr7, mr8, err;
priv = dev_get_drvdata(dev);
ret = generic_show(DPFE_CMD_GET_REFRESH, response, priv, buf);
if (ret)
return ret;
mr4 = response[MSG_ARG0 + 0] & DRAM_INFO_MR4_MASK;
mr5 = response[MSG_ARG0 + 1] & DRAM_DDR_INFO_MASK;
mr6 = response[MSG_ARG0 + 2] & DRAM_DDR_INFO_MASK;
mr7 = response[MSG_ARG0 + 3] & DRAM_DDR_INFO_MASK;
mr8 = response[MSG_ARG0 + 4] & DRAM_DDR_INFO_MASK;
err = response[MSG_ARG0 + 5] & DRAM_DDR_INFO_MASK;
return sprintf(buf, "%#x %#x %#x %#x %#x %#x\n", mr4, mr5, mr6, mr7,
mr8, err);
}
static int brcmstb_dpfe_resume(struct platform_device *pdev)
{
struct brcmstb_dpfe_priv *priv = platform_get_drvdata(pdev);
return brcmstb_dpfe_download_firmware(priv);
}
static int brcmstb_dpfe_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct brcmstb_dpfe_priv *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
mutex_init(&priv->lock);
platform_set_drvdata(pdev, priv);
priv->regs = devm_platform_ioremap_resource_byname(pdev, "dpfe-cpu");
if (IS_ERR(priv->regs)) {
dev_err(dev, "couldn't map DCPU registers\n");
return -ENODEV;
}
priv->dmem = devm_platform_ioremap_resource_byname(pdev, "dpfe-dmem");
if (IS_ERR(priv->dmem)) {
dev_err(dev, "Couldn't map DCPU data memory\n");
return -ENOENT;
}
priv->imem = devm_platform_ioremap_resource_byname(pdev, "dpfe-imem");
if (IS_ERR(priv->imem)) {
dev_err(dev, "Couldn't map DCPU instruction memory\n");
return -ENOENT;
}
priv->dpfe_api = of_device_get_match_data(dev);
if (unlikely(!priv->dpfe_api)) {
/*
* It should be impossible to end up here, but to be safe we
* check anyway.
*/
dev_err(dev, "Couldn't determine API\n");
return -ENOENT;
}
ret = brcmstb_dpfe_download_firmware(priv);
if (ret)
return dev_err_probe(dev, ret, "Couldn't download firmware\n");
ret = sysfs_create_groups(&pdev->dev.kobj, priv->dpfe_api->sysfs_attrs);
if (!ret)
dev_info(dev, "registered with API v%d.\n",
priv->dpfe_api->version);
return ret;
}
static void brcmstb_dpfe_remove(struct platform_device *pdev)
{
struct brcmstb_dpfe_priv *priv = dev_get_drvdata(&pdev->dev);
sysfs_remove_groups(&pdev->dev.kobj, priv->dpfe_api->sysfs_attrs);
}
static const struct of_device_id brcmstb_dpfe_of_match[] = {
/* Use legacy API v2 for a select number of chips */
{ .compatible = "brcm,bcm7268-dpfe-cpu", .data = &dpfe_api_old_v2 },
{ .compatible = "brcm,bcm7271-dpfe-cpu", .data = &dpfe_api_old_v2 },
{ .compatible = "brcm,bcm7278-dpfe-cpu", .data = &dpfe_api_old_v2 },
{ .compatible = "brcm,bcm7211-dpfe-cpu", .data = &dpfe_api_new_v2 },
/* API v3 is the default going forward */
{ .compatible = "brcm,dpfe-cpu", .data = &dpfe_api_v3 },
{}
};
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");
+247
View File
@@ -0,0 +1,247 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* DDR Self-Refresh Power Down (SRPD) support for Broadcom STB SoCs
*
*/
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#define REG_MEMC_CNTRLR_CONFIG 0x00
#define CNTRLR_CONFIG_LPDDR4_SHIFT 5
#define CNTRLR_CONFIG_LPDDR5_SHIFT 6
#define CNTRLR_CONFIG_MASK 0xf
#define REG_MEMC_SRPD_CFG_21 0x20
#define REG_MEMC_SRPD_CFG_20 0x34
#define REG_MEMC_SRPD_CFG_1x 0x3c
#define INACT_COUNT_SHIFT 0
#define INACT_COUNT_MASK 0xffff
#define SRPD_EN_SHIFT 16
struct brcmstb_memc_data {
u32 srpd_offset;
};
struct brcmstb_memc {
struct device *dev;
void __iomem *ddr_ctrl;
unsigned int timeout_cycles;
u32 frequency;
u32 srpd_offset;
};
static int brcmstb_memc_uses_lpddr45(struct brcmstb_memc *memc)
{
void __iomem *config = memc->ddr_ctrl + REG_MEMC_CNTRLR_CONFIG;
u32 reg;
reg = readl_relaxed(config) & CNTRLR_CONFIG_MASK;
return reg == CNTRLR_CONFIG_LPDDR4_SHIFT ||
reg == CNTRLR_CONFIG_LPDDR5_SHIFT;
}
static int brcmstb_memc_srpd_config(struct brcmstb_memc *memc,
unsigned int cycles)
{
void __iomem *cfg = memc->ddr_ctrl + memc->srpd_offset;
u32 val;
/* Max timeout supported in HW */
if (cycles > INACT_COUNT_MASK)
return -EINVAL;
memc->timeout_cycles = cycles;
val = (cycles << INACT_COUNT_SHIFT) & INACT_COUNT_MASK;
if (cycles)
val |= BIT(SRPD_EN_SHIFT);
writel_relaxed(val, cfg);
/* Ensure the write is committed to the controller */
(void)readl_relaxed(cfg);
return 0;
}
static ssize_t frequency_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct brcmstb_memc *memc = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", memc->frequency);
}
static ssize_t srpd_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct brcmstb_memc *memc = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", memc->timeout_cycles);
}
static ssize_t srpd_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct brcmstb_memc *memc = dev_get_drvdata(dev);
unsigned int val;
int ret;
/*
* Cannot change the inactivity timeout on LPDDR4 chips because the
* dynamic tuning process will also get affected by the inactivity
* timeout, thus making it non functional.
*/
if (brcmstb_memc_uses_lpddr45(memc))
return -EOPNOTSUPP;
ret = kstrtouint(buf, 10, &val);
if (ret < 0)
return ret;
ret = brcmstb_memc_srpd_config(memc, val);
if (ret)
return ret;
return count;
}
static DEVICE_ATTR_RO(frequency);
static DEVICE_ATTR_RW(srpd);
static struct attribute *dev_attrs[] = {
&dev_attr_frequency.attr,
&dev_attr_srpd.attr,
NULL,
};
static struct attribute_group dev_attr_group = {
.attrs = dev_attrs,
};
static int brcmstb_memc_probe(struct platform_device *pdev)
{
const struct brcmstb_memc_data *memc_data;
struct device *dev = &pdev->dev;
struct brcmstb_memc *memc;
int ret;
memc = devm_kzalloc(dev, sizeof(*memc), GFP_KERNEL);
if (!memc)
return -ENOMEM;
dev_set_drvdata(dev, memc);
memc_data = device_get_match_data(dev);
memc->srpd_offset = memc_data->srpd_offset;
memc->ddr_ctrl = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(memc->ddr_ctrl))
return PTR_ERR(memc->ddr_ctrl);
of_property_read_u32(pdev->dev.of_node, "clock-frequency",
&memc->frequency);
ret = sysfs_create_group(&dev->kobj, &dev_attr_group);
if (ret)
return ret;
return 0;
}
static void brcmstb_memc_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
sysfs_remove_group(&dev->kobj, &dev_attr_group);
}
enum brcmstb_memc_hwtype {
BRCMSTB_MEMC_V21,
BRCMSTB_MEMC_V20,
BRCMSTB_MEMC_V1X,
};
static const struct brcmstb_memc_data brcmstb_memc_versions[] = {
{ .srpd_offset = REG_MEMC_SRPD_CFG_21 },
{ .srpd_offset = REG_MEMC_SRPD_CFG_20 },
{ .srpd_offset = REG_MEMC_SRPD_CFG_1x },
};
static const struct of_device_id brcmstb_memc_of_match[] = {
{
.compatible = "brcm,brcmstb-memc-ddr-rev-b.1.x",
.data = &brcmstb_memc_versions[BRCMSTB_MEMC_V1X]
},
{
.compatible = "brcm,brcmstb-memc-ddr-rev-b.2.0",
.data = &brcmstb_memc_versions[BRCMSTB_MEMC_V20]
},
{
.compatible = "brcm,brcmstb-memc-ddr-rev-b.2.1",
.data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21]
},
/* default to the V21 offset */
{
.compatible = "brcm,brcmstb-memc-ddr",
.data = &brcmstb_memc_versions[BRCMSTB_MEMC_V21]
},
{}
};
MODULE_DEVICE_TABLE(of, brcmstb_memc_of_match);
static int brcmstb_memc_suspend(struct device *dev)
{
struct brcmstb_memc *memc = dev_get_drvdata(dev);
void __iomem *cfg = memc->ddr_ctrl + memc->srpd_offset;
u32 val;
if (memc->timeout_cycles == 0)
return 0;
/*
* Disable SRPD prior to suspending the system since that can
* cause issues with other memory clients managed by the ARM
* trusted firmware to access memory.
*/
val = readl_relaxed(cfg);
val &= ~BIT(SRPD_EN_SHIFT);
writel_relaxed(val, cfg);
/* Ensure the write is committed to the controller */
(void)readl_relaxed(cfg);
return 0;
}
static int brcmstb_memc_resume(struct device *dev)
{
struct brcmstb_memc *memc = dev_get_drvdata(dev);
if (memc->timeout_cycles == 0)
return 0;
return brcmstb_memc_srpd_config(memc, memc->timeout_cycles);
}
static DEFINE_SIMPLE_DEV_PM_OPS(brcmstb_memc_pm_ops, brcmstb_memc_suspend,
brcmstb_memc_resume);
static struct platform_driver brcmstb_memc_driver = {
.probe = brcmstb_memc_probe,
.remove = brcmstb_memc_remove,
.driver = {
.name = "brcmstb_memc",
.of_match_table = brcmstb_memc_of_match,
.pm = pm_ptr(&brcmstb_memc_pm_ops),
},
};
module_platform_driver(brcmstb_memc_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Broadcom");
MODULE_DESCRIPTION("DDR SRPD driver for Broadcom STB chips");
+165
View File
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* TI da8xx DDR2/mDDR controller driver
*
* Copyright (C) 2016 BayLibre SAS
*
* Author:
* Bartosz Golaszewski <bgolaszewski@baylibre.com>
*/
#include <linux/module.h>
#include <linux/of.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 resource *res;
void __iomem *ddrctl;
struct device *dev;
u32 reg;
dev = &pdev->dev;
setting = da8xx_ddrctl_get_board_settings();
if (!setting) {
dev_err(dev, "no settings defined for this board\n");
return -EINVAL;
}
ddrctl = devm_platform_get_and_ioremap_resource(pdev, 0, &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");
+259
View File
@@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DFL device driver for EMIF private feature
*
* Copyright (C) 2020 Intel Corporation, Inc.
*
*/
#include <linux/bitfield.h>
#include <linux/dfl.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#define FME_FEATURE_ID_EMIF 0x9
#define EMIF_STAT 0x8
#define EMIF_STAT_INIT_DONE_SFT 0
#define EMIF_STAT_CALC_FAIL_SFT 8
#define EMIF_STAT_CLEAR_BUSY_SFT 16
#define EMIF_CTRL 0x10
#define EMIF_CTRL_CLEAR_EN_SFT 0
#define EMIF_CTRL_CLEAR_EN_MSK GENMASK_ULL(7, 0)
#define EMIF_POLL_INVL 10000 /* us */
#define EMIF_POLL_TIMEOUT 5000000 /* us */
/*
* The Capability Register replaces the Control Register (at the same
* offset) for EMIF feature revisions > 0. The bitmask that indicates
* the presence of memory channels exists in both the Capability Register
* and Control Register definitions. These can be thought of as a C union.
* The Capability Register definitions are used to check for the existence
* of a memory channel, and the Control Register definitions are used for
* managing the memory-clear functionality in revision 0.
*/
#define EMIF_CAPABILITY_BASE 0x10
#define EMIF_CAPABILITY_CHN_MSK_V0 GENMASK_ULL(3, 0)
#define EMIF_CAPABILITY_CHN_MSK GENMASK_ULL(7, 0)
struct dfl_emif {
struct device *dev;
void __iomem *base;
spinlock_t lock; /* Serialises access to EMIF_CTRL reg */
};
struct emif_attr {
struct device_attribute attr;
u32 shift;
u32 index;
};
#define to_emif_attr(dev_attr) \
container_of(dev_attr, struct emif_attr, attr)
static ssize_t emif_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct emif_attr *eattr = to_emif_attr(attr);
struct dfl_emif *de = dev_get_drvdata(dev);
u64 val;
val = readq(de->base + EMIF_STAT);
return sysfs_emit(buf, "%u\n",
!!(val & BIT_ULL(eattr->shift + eattr->index)));
}
static ssize_t emif_clear_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct emif_attr *eattr = to_emif_attr(attr);
struct dfl_emif *de = dev_get_drvdata(dev);
u64 clear_busy_msk, clear_en_msk, val;
void __iomem *base = de->base;
if (!sysfs_streq(buf, "1"))
return -EINVAL;
clear_busy_msk = BIT_ULL(EMIF_STAT_CLEAR_BUSY_SFT + eattr->index);
clear_en_msk = BIT_ULL(EMIF_CTRL_CLEAR_EN_SFT + eattr->index);
spin_lock(&de->lock);
/* The CLEAR_EN field is WO, but other fields are RW */
val = readq(base + EMIF_CTRL);
val &= ~EMIF_CTRL_CLEAR_EN_MSK;
val |= clear_en_msk;
writeq(val, base + EMIF_CTRL);
spin_unlock(&de->lock);
if (readq_poll_timeout(base + EMIF_STAT, val,
!(val & clear_busy_msk),
EMIF_POLL_INVL, EMIF_POLL_TIMEOUT)) {
dev_err(de->dev, "timeout, fail to clear\n");
return -ETIMEDOUT;
}
return count;
}
#define emif_state_attr(_name, _shift, _index) \
static struct emif_attr emif_attr_##inf##_index##_##_name = \
{ .attr = __ATTR(inf##_index##_##_name, 0444, \
emif_state_show, NULL), \
.shift = (_shift), .index = (_index) }
#define emif_clear_attr(_index) \
static struct emif_attr emif_attr_##inf##_index##_clear = \
{ .attr = __ATTR(inf##_index##_clear, 0200, \
NULL, emif_clear_store), \
.index = (_index) }
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 0);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 1);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 2);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 3);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 4);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 5);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 6);
emif_state_attr(init_done, EMIF_STAT_INIT_DONE_SFT, 7);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 0);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 1);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 2);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 3);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 4);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 5);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 6);
emif_state_attr(cal_fail, EMIF_STAT_CALC_FAIL_SFT, 7);
emif_clear_attr(0);
emif_clear_attr(1);
emif_clear_attr(2);
emif_clear_attr(3);
emif_clear_attr(4);
emif_clear_attr(5);
emif_clear_attr(6);
emif_clear_attr(7);
static struct attribute *dfl_emif_attrs[] = {
&emif_attr_inf0_init_done.attr.attr,
&emif_attr_inf0_cal_fail.attr.attr,
&emif_attr_inf0_clear.attr.attr,
&emif_attr_inf1_init_done.attr.attr,
&emif_attr_inf1_cal_fail.attr.attr,
&emif_attr_inf1_clear.attr.attr,
&emif_attr_inf2_init_done.attr.attr,
&emif_attr_inf2_cal_fail.attr.attr,
&emif_attr_inf2_clear.attr.attr,
&emif_attr_inf3_init_done.attr.attr,
&emif_attr_inf3_cal_fail.attr.attr,
&emif_attr_inf3_clear.attr.attr,
&emif_attr_inf4_init_done.attr.attr,
&emif_attr_inf4_cal_fail.attr.attr,
&emif_attr_inf4_clear.attr.attr,
&emif_attr_inf5_init_done.attr.attr,
&emif_attr_inf5_cal_fail.attr.attr,
&emif_attr_inf5_clear.attr.attr,
&emif_attr_inf6_init_done.attr.attr,
&emif_attr_inf6_cal_fail.attr.attr,
&emif_attr_inf6_clear.attr.attr,
&emif_attr_inf7_init_done.attr.attr,
&emif_attr_inf7_cal_fail.attr.attr,
&emif_attr_inf7_clear.attr.attr,
NULL,
};
static umode_t dfl_emif_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct dfl_emif *de = dev_get_drvdata(kobj_to_dev(kobj));
struct emif_attr *eattr = container_of(attr, struct emif_attr,
attr.attr);
struct dfl_device *ddev = to_dfl_dev(de->dev);
u64 val;
/*
* This device supports up to 8 memory interfaces, but not all
* interfaces are used on different platforms. The read out value of
* CAPABILITY_CHN_MSK field (which is a bitmap) indicates which
* interfaces are available.
*/
if (ddev->revision > 0 && strstr(attr->name, "_clear"))
return 0;
if (ddev->revision == 0)
val = FIELD_GET(EMIF_CAPABILITY_CHN_MSK_V0,
readq(de->base + EMIF_CAPABILITY_BASE));
else
val = FIELD_GET(EMIF_CAPABILITY_CHN_MSK,
readq(de->base + EMIF_CAPABILITY_BASE));
return (val & BIT_ULL(eattr->index)) ? attr->mode : 0;
}
static const struct attribute_group dfl_emif_group = {
.is_visible = dfl_emif_visible,
.attrs = dfl_emif_attrs,
};
static const struct attribute_group *dfl_emif_groups[] = {
&dfl_emif_group,
NULL,
};
static int dfl_emif_probe(struct dfl_device *ddev)
{
struct device *dev = &ddev->dev;
struct dfl_emif *de;
de = devm_kzalloc(dev, sizeof(*de), GFP_KERNEL);
if (!de)
return -ENOMEM;
de->base = devm_ioremap_resource(dev, &ddev->mmio_res);
if (IS_ERR(de->base))
return PTR_ERR(de->base);
de->dev = dev;
spin_lock_init(&de->lock);
dev_set_drvdata(dev, de);
return 0;
}
static const struct dfl_device_id dfl_emif_ids[] = {
{ FME_ID, FME_FEATURE_ID_EMIF },
{ }
};
MODULE_DEVICE_TABLE(dfl, dfl_emif_ids);
static struct dfl_driver dfl_emif_driver = {
.drv = {
.name = "dfl-emif",
.dev_groups = dfl_emif_groups,
},
.id_table = dfl_emif_ids,
.probe = dfl_emif_probe,
};
module_dfl_driver(dfl_emif_driver);
MODULE_DESCRIPTION("DFL EMIF driver");
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-2.0
/*
* TI AM33XX EMIF PM Assembly Offsets
*
* Copyright (C) 2016-2017 Texas Instruments Inc.
*/
#include <linux/ti-emif-sram.h>
int main(void)
{
ti_emif_asm_offsets();
return 0;
}
File diff suppressed because it is too large Load Diff
+607
View File
@@ -0,0 +1,607 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Defines for the EMIF driver
*
* Copyright (C) 2012 Texas Instruments, Inc.
*
* Benoit Cousson (b-cousson@ti.com)
*/
#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)
/* READ_WRITE_LEVELING_CONTROL */
#define RDWRLVLFULL_START 0x80000000
/* 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_run_hw_leveling(void);
void ti_emif_enter_sr(void);
void ti_emif_exit_sr(void);
void ti_emif_abort_sr(void);
#endif /* __ASSEMBLY__ */
#endif /* __EMIF_H */
+259
View File
@@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* CoreNet Coherency Fabric error reporting
*
* Copyright 2014 Freescale Semiconductor Inc.
*/
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.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;
u32 errinten;
int ret, irq;
ccf = devm_kzalloc(&pdev->dev, sizeof(*ccf), GFP_KERNEL);
if (!ccf)
return -ENOMEM;
ccf->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ccf->regs))
return PTR_ERR(ccf->regs);
ccf->dev = &pdev->dev;
ccf->info = device_get_match_data(&pdev->dev);
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 void 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;
}
}
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");
+329
View File
@@ -0,0 +1,329 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2011 Freescale Semiconductor, Inc
*
* Freescale Integrated Flash Controller
*
* Author: Dipen Dudhat <Dipen.Dudhat@freescale.com>
*/
#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_platform.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 void fsl_ifc_ctrl_remove(struct platform_device *dev)
{
struct fsl_ifc_ctrl *ctrl = dev_get_drvdata(&dev->dev);
of_platform_depopulate(&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);
}
/*
* 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;
}
}
/* legacy dts may still use "simple-bus" compatible */
ret = of_platform_default_populate(dev->dev.of_node, NULL, &dev->dev);
if (ret)
goto err_free_nandirq;
return 0;
err_free_nandirq:
free_irq(fsl_ifc_ctrl_dev->nand_irq, fsl_ifc_ctrl_dev);
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_AUTHOR("Freescale Semiconductor");
MODULE_DESCRIPTION("Freescale Integrated Flash Controller driver");
+280
View File
@@ -0,0 +1,280 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Definitions for DDR memories based on JEDEC specs
*
* Copyright (C) 2012 Texas Instruments, Inc.
*
* Aneesh V <aneesh@ti.com>
*/
#ifndef __JEDEC_DDR_H
#define __JEDEC_DDR_H
#include <linux/types.h>
/* DDR Densities */
#define DDR_DENSITY_64Mb 1
#define DDR_DENSITY_128Mb 2
#define DDR_DENSITY_256Mb 3
#define DDR_DENSITY_512Mb 4
#define DDR_DENSITY_1Gb 5
#define DDR_DENSITY_2Gb 6
#define DDR_DENSITY_4Gb 7
#define DDR_DENSITY_8Gb 8
#define DDR_DENSITY_16Gb 9
#define DDR_DENSITY_32Gb 10
/* DDR type */
#define DDR_TYPE_DDR2 1
#define DDR_TYPE_DDR3 2
#define DDR_TYPE_LPDDR2_S4 3
#define DDR_TYPE_LPDDR2_S2 4
#define DDR_TYPE_LPDDR2_NVM 5
#define DDR_TYPE_LPDDR3 6
/* DDR IO width */
#define DDR_IO_WIDTH_4 1
#define DDR_IO_WIDTH_8 2
#define DDR_IO_WIDTH_16 3
#define DDR_IO_WIDTH_32 4
/* Number of Row bits */
#define R9 9
#define R10 10
#define R11 11
#define R12 12
#define R13 13
#define R14 14
#define R15 15
#define R16 16
/* Number of Column bits */
#define C7 7
#define C8 8
#define C9 9
#define C10 10
#define C11 11
#define C12 12
/* Number of Banks */
#define B1 0
#define B2 1
#define B4 2
#define B8 3
/* Refresh rate in nano-seconds */
#define T_REFI_15_6 15600
#define T_REFI_7_8 7800
#define T_REFI_3_9 3900
/* tRFC values */
#define T_RFC_90 90000
#define T_RFC_110 110000
#define T_RFC_130 130000
#define T_RFC_160 160000
#define T_RFC_210 210000
#define T_RFC_300 300000
#define T_RFC_350 350000
/* Mode register numbers */
#define DDR_MR0 0
#define DDR_MR1 1
#define DDR_MR2 2
#define DDR_MR3 3
#define DDR_MR4 4
#define DDR_MR5 5
#define DDR_MR6 6
#define DDR_MR7 7
#define DDR_MR8 8
#define DDR_MR9 9
#define DDR_MR10 10
#define DDR_MR11 11
#define DDR_MR16 16
#define DDR_MR17 17
#define DDR_MR18 18
/*
* LPDDR2 related defines
*/
/* MR4 register fields */
#define MR4_SDRAM_REF_RATE_SHIFT 0
#define MR4_SDRAM_REF_RATE_MASK 7
#define MR4_TUF_SHIFT 7
#define MR4_TUF_MASK (1 << 7)
/* MR4 SDRAM Refresh Rate field values */
#define SDRAM_TEMP_NOMINAL 0x3
#define SDRAM_TEMP_RESERVED_4 0x4
#define SDRAM_TEMP_HIGH_DERATE_REFRESH 0x5
#define SDRAM_TEMP_HIGH_DERATE_REFRESH_AND_TIMINGS 0x6
#define SDRAM_TEMP_VERY_HIGH_SHUTDOWN 0x7
#define NUM_DDR_ADDR_TABLE_ENTRIES 11
#define NUM_DDR_TIMING_TABLE_ENTRIES 4
#define LPDDR2_MANID_SAMSUNG 1
#define LPDDR2_MANID_QIMONDA 2
#define LPDDR2_MANID_ELPIDA 3
#define LPDDR2_MANID_ETRON 4
#define LPDDR2_MANID_NANYA 5
#define LPDDR2_MANID_HYNIX 6
#define LPDDR2_MANID_MOSEL 7
#define LPDDR2_MANID_WINBOND 8
#define LPDDR2_MANID_ESMT 9
#define LPDDR2_MANID_SPANSION 11
#define LPDDR2_MANID_SST 12
#define LPDDR2_MANID_ZMOS 13
#define LPDDR2_MANID_INTEL 14
#define LPDDR2_MANID_NUMONYX 254
#define LPDDR2_MANID_MICRON 255
#define LPDDR2_TYPE_S4 0
#define LPDDR2_TYPE_S2 1
#define LPDDR2_TYPE_NVM 2
/* Structure for DDR addressing info from the JEDEC spec */
struct lpddr2_addressing {
u32 num_banks;
u32 tREFI_ns;
u32 tRFCab_ps;
};
/*
* Structure for timings from the LPDDR2 datasheet
* All parameters are in pico seconds(ps) unless explicitly indicated
* with a suffix like tRAS_max_ns below
*/
struct lpddr2_timings {
u32 max_freq;
u32 min_freq;
u32 tRPab;
u32 tRCD;
u32 tWR;
u32 tRAS_min;
u32 tRRD;
u32 tWTR;
u32 tXP;
u32 tRTP;
u32 tCKESR;
u32 tDQSCK_max;
u32 tDQSCK_max_derated;
u32 tFAW;
u32 tZQCS;
u32 tZQCL;
u32 tZQinit;
u32 tRAS_max_ns;
};
/*
* Min value for some parameters in terms of number of tCK cycles(nCK)
* Please set to zero parameters that are not valid for a given memory
* type
*/
struct lpddr2_min_tck {
u32 tRPab;
u32 tRCD;
u32 tWR;
u32 tRASmin;
u32 tRRD;
u32 tWTR;
u32 tXP;
u32 tRTP;
u32 tCKE;
u32 tCKESR;
u32 tFAW;
};
extern const struct lpddr2_addressing
lpddr2_jedec_addressing_table[NUM_DDR_ADDR_TABLE_ENTRIES];
extern const struct lpddr2_timings
lpddr2_jedec_timings[NUM_DDR_TIMING_TABLE_ENTRIES];
extern const struct lpddr2_min_tck lpddr2_jedec_min_tck;
/* Structure of MR8 */
union lpddr2_basic_config4 {
u32 value;
struct {
unsigned int arch_type : 2;
unsigned int density : 4;
unsigned int io_width : 2;
} __packed;
};
/*
* Structure for information about LPDDR2 chip. All parameters are
* matching raw values of standard mode register bitfields or set to
* -ENOENT if info unavailable.
*/
struct lpddr2_info {
int arch_type;
int density;
int io_width;
int manufacturer_id;
int revision_id1;
int revision_id2;
};
const char *lpddr2_jedec_manufacturer(unsigned int manufacturer_id);
/*
* Structure for timings for LPDDR3 based on LPDDR2 plus additional fields.
* All parameters are in pico seconds(ps) excluding max_freq, min_freq which
* are in Hz.
*/
struct lpddr3_timings {
u32 max_freq;
u32 min_freq;
u32 tRFC;
u32 tRRD;
u32 tRPab;
u32 tRPpb;
u32 tRCD;
u32 tRC;
u32 tRAS;
u32 tWTR;
u32 tWR;
u32 tRTP;
u32 tW2W_C2C;
u32 tR2R_C2C;
u32 tWL;
u32 tDQSCK;
u32 tRL;
u32 tFAW;
u32 tXSR;
u32 tXP;
u32 tCKE;
u32 tCKESR;
u32 tMRD;
};
/*
* Min value for some parameters in terms of number of tCK cycles(nCK)
* Please set to zero parameters that are not valid for a given memory
* type
*/
struct lpddr3_min_tck {
u32 tRFC;
u32 tRRD;
u32 tRPab;
u32 tRPpb;
u32 tRCD;
u32 tRC;
u32 tRAS;
u32 tWTR;
u32 tWR;
u32 tRTP;
u32 tW2W_C2C;
u32 tR2R_C2C;
u32 tWL;
u32 tDQSCK;
u32 tRL;
u32 tFAW;
u32 tXSR;
u32 tXP;
u32 tCKE;
u32 tCKESR;
u32 tMRD;
};
#endif /* __JEDEC_DDR_H */
+174
View File
@@ -0,0 +1,174 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* DDR addressing details and AC timing parameters from JEDEC specs
*
* Copyright (C) 2012 Texas Instruments, Inc.
*
* Aneesh V <aneesh@ti.com>
*/
#include <linux/export.h>
#include "jedec_ddr.h"
/* LPDDR2 addressing details from JESD209-2 section 2.4 */
const struct lpddr2_addressing
lpddr2_jedec_addressing_table[NUM_DDR_ADDR_TABLE_ENTRIES] = {
{B4, T_REFI_15_6, T_RFC_90}, /* 64M */
{B4, T_REFI_15_6, T_RFC_90}, /* 128M */
{B4, T_REFI_7_8, T_RFC_90}, /* 256M */
{B4, T_REFI_7_8, T_RFC_90}, /* 512M */
{B8, T_REFI_7_8, T_RFC_130}, /* 1GS4 */
{B8, T_REFI_3_9, T_RFC_130}, /* 2GS4 */
{B8, T_REFI_3_9, T_RFC_130}, /* 4G */
{B8, T_REFI_3_9, T_RFC_210}, /* 8G */
{B4, T_REFI_7_8, T_RFC_130}, /* 1GS2 */
{B4, T_REFI_3_9, T_RFC_130}, /* 2GS2 */
};
EXPORT_SYMBOL_GPL(lpddr2_jedec_addressing_table);
/* LPDDR2 AC timing parameters from JESD209-2 section 12 */
const struct lpddr2_timings
lpddr2_jedec_timings[NUM_DDR_TIMING_TABLE_ENTRIES] = {
/* Speed bin 400(200 MHz) */
[0] = {
.max_freq = 200000000,
.min_freq = 10000000,
.tRPab = 21000,
.tRCD = 18000,
.tWR = 15000,
.tRAS_min = 42000,
.tRRD = 10000,
.tWTR = 10000,
.tXP = 7500,
.tRTP = 7500,
.tCKESR = 15000,
.tDQSCK_max = 5500,
.tFAW = 50000,
.tZQCS = 90000,
.tZQCL = 360000,
.tZQinit = 1000000,
.tRAS_max_ns = 70000,
.tDQSCK_max_derated = 6000,
},
/* Speed bin 533(266 MHz) */
[1] = {
.max_freq = 266666666,
.min_freq = 10000000,
.tRPab = 21000,
.tRCD = 18000,
.tWR = 15000,
.tRAS_min = 42000,
.tRRD = 10000,
.tWTR = 7500,
.tXP = 7500,
.tRTP = 7500,
.tCKESR = 15000,
.tDQSCK_max = 5500,
.tFAW = 50000,
.tZQCS = 90000,
.tZQCL = 360000,
.tZQinit = 1000000,
.tRAS_max_ns = 70000,
.tDQSCK_max_derated = 6000,
},
/* Speed bin 800(400 MHz) */
[2] = {
.max_freq = 400000000,
.min_freq = 10000000,
.tRPab = 21000,
.tRCD = 18000,
.tWR = 15000,
.tRAS_min = 42000,
.tRRD = 10000,
.tWTR = 7500,
.tXP = 7500,
.tRTP = 7500,
.tCKESR = 15000,
.tDQSCK_max = 5500,
.tFAW = 50000,
.tZQCS = 90000,
.tZQCL = 360000,
.tZQinit = 1000000,
.tRAS_max_ns = 70000,
.tDQSCK_max_derated = 6000,
},
/* Speed bin 1066(533 MHz) */
[3] = {
.max_freq = 533333333,
.min_freq = 10000000,
.tRPab = 21000,
.tRCD = 18000,
.tWR = 15000,
.tRAS_min = 42000,
.tRRD = 10000,
.tWTR = 7500,
.tXP = 7500,
.tRTP = 7500,
.tCKESR = 15000,
.tDQSCK_max = 5500,
.tFAW = 50000,
.tZQCS = 90000,
.tZQCL = 360000,
.tZQinit = 1000000,
.tRAS_max_ns = 70000,
.tDQSCK_max_derated = 5620,
},
};
EXPORT_SYMBOL_GPL(lpddr2_jedec_timings);
const struct lpddr2_min_tck lpddr2_jedec_min_tck = {
.tRPab = 3,
.tRCD = 3,
.tWR = 3,
.tRASmin = 3,
.tRRD = 2,
.tWTR = 2,
.tXP = 2,
.tRTP = 2,
.tCKE = 3,
.tCKESR = 3,
.tFAW = 8
};
EXPORT_SYMBOL_GPL(lpddr2_jedec_min_tck);
const char *lpddr2_jedec_manufacturer(unsigned int manufacturer_id)
{
switch (manufacturer_id) {
case LPDDR2_MANID_SAMSUNG:
return "Samsung";
case LPDDR2_MANID_QIMONDA:
return "Qimonda";
case LPDDR2_MANID_ELPIDA:
return "Elpida";
case LPDDR2_MANID_ETRON:
return "Etron";
case LPDDR2_MANID_NANYA:
return "Nanya";
case LPDDR2_MANID_HYNIX:
return "Hynix";
case LPDDR2_MANID_MOSEL:
return "Mosel";
case LPDDR2_MANID_WINBOND:
return "Winbond";
case LPDDR2_MANID_ESMT:
return "ESMT";
case LPDDR2_MANID_SPANSION:
return "Spansion";
case LPDDR2_MANID_SST:
return "SST";
case LPDDR2_MANID_ZMOS:
return "ZMOS";
case LPDDR2_MANID_INTEL:
return "Intel";
case LPDDR2_MANID_NUMONYX:
return "Numonyx";
case LPDDR2_MANID_MICRON:
return "Micron";
default:
break;
}
return "invalid";
}
EXPORT_SYMBOL_GPL(lpddr2_jedec_manufacturer);
+421
View File
@@ -0,0 +1,421 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* JZ4780 NAND/external memory controller (NEMC)
*
* Copyright (c) 2015 Imagination Technologies
* Author: Alex Smith <alex@alex-smith.me.uk>
*/
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/math64.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 <linux/spinlock.h>
#include <linux/jz4780-nemc.h>
#define NEMC_SMCRn(n) (0x14 + (((n) - 1) * 4))
#define NEMC_NFCSR 0x50
#define NEMC_REG_LEN 0x54
#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 jz_soc_info {
u8 tas_tah_cycles_max;
};
struct jz4780_nemc {
spinlock_t lock;
struct device *dev;
const struct jz_soc_info *soc_info;
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 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 u8 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 > nemc->soc_info->tas_tah_cycles_max) {
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 > nemc->soc_info->tas_tah_cycles_max) {
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;
nemc->soc_info = device_get_match_data(dev);
if (!nemc->soc_info)
return -EINVAL;
spin_lock_init(&nemc->lock);
nemc->dev = dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -EINVAL;
/*
* The driver currently only uses the registers up to offset
* NEMC_REG_LEN. Since the EFUSE registers are in the middle of the
* NEMC registers, we only request the registers we will use for now;
* that way the EFUSE driver can probe too.
*/
if (!devm_request_mem_region(dev, res->start, NEMC_REG_LEN, dev_name(dev))) {
dev_err(dev, "unable to request I/O memory region\n");
return -EBUSY;
}
nemc->base = devm_ioremap(dev, res->start, NEMC_REG_LEN);
if (!nemc->base) {
dev_err(dev, "failed to get I/O memory\n");
return -ENOMEM;
}
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 void jz4780_nemc_remove(struct platform_device *pdev)
{
struct jz4780_nemc *nemc = platform_get_drvdata(pdev);
clk_disable_unprepare(nemc->clk);
}
static const struct jz_soc_info jz4740_soc_info = {
.tas_tah_cycles_max = 7,
};
static const struct jz_soc_info jz4780_soc_info = {
.tas_tah_cycles_max = 15,
};
static const struct of_device_id jz4780_nemc_dt_match[] = {
{ .compatible = "ingenic,jz4740-nemc", .data = &jz4740_soc_info, },
{ .compatible = "ingenic,jz4780-nemc", .data = &jz4780_soc_info, },
{},
};
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);
+991
View File
@@ -0,0 +1,991 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015-2016 MediaTek Inc.
* Author: Yong Wu <yong.wu@mediatek.com>
*/
#include <linux/arm-smccc.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/iopoll.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 <linux/soc/mediatek/mtk_sip_svc.h>
#include <soc/mediatek/smi.h>
#include <dt-bindings/memory/mt2701-larb-port.h>
#include <dt-bindings/memory/mtk-memory-port.h>
/* SMI COMMON */
#define SMI_L1LEN 0x100
#define SMI_L1_ARB 0x200
#define SMI_BUS_SEL 0x220
#define SMI_BUS_LARB_SHIFT(larbid) ((larbid) << 1)
/* All are MMU0 defaultly. Only specialize mmu1 here. */
#define F_MMU1_LARB(larbid) (0x1 << SMI_BUS_LARB_SHIFT(larbid))
#define SMI_READ_FIFO_TH 0x230
#define SMI_M4U_TH 0x234
#define SMI_FIFO_TH1 0x238
#define SMI_FIFO_TH2 0x23c
#define SMI_DCM 0x300
#define SMI_DUMMY 0x444
/* SMI LARB */
#define SMI_LARB_SLP_CON 0xc
#define SLP_PROT_EN BIT(0)
#define SLP_PROT_RDY BIT(16)
#define SMI_LARB_CMD_THRT_CON 0x24
#define SMI_LARB_THRT_RD_NU_LMT_MSK GENMASK(7, 4)
#define SMI_LARB_THRT_RD_NU_LMT (5 << 4)
#define SMI_LARB_SW_FLAG 0x40
#define SMI_LARB_SW_FLAG_1 0x1
#define SMI_LARB_OSTDL_PORT 0x200
#define SMI_LARB_OSTDL_PORTx(id) (SMI_LARB_OSTDL_PORT + (((id) & 0x1f) << 2))
/* Below are about mmu enable registers, they are different in SoCs */
/* gen1: 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))
/* gen2: */
/* mt8167 */
#define MT8167_SMI_LARB_MMU_EN 0xfc0
/* mt8173 */
#define MT8173_SMI_LARB_MMU_EN 0xf00
/* general */
#define SMI_LARB_NONSEC_CON(id) (0x380 + ((id) * 4))
#define F_MMU_EN BIT(0)
#define BANK_SEL(id) ({ \
u32 _id = (id) & 0x3; \
(_id << 8 | _id << 10 | _id << 12 | _id << 14); \
})
#define SMI_COMMON_INIT_REGS_NR 6
#define SMI_LARB_PORT_NR_MAX 32
#define MTK_SMI_FLAG_THRT_UPDATE BIT(0)
#define MTK_SMI_FLAG_SW_FLAG BIT(1)
#define MTK_SMI_FLAG_SLEEP_CTL BIT(2)
#define MTK_SMI_FLAG_CFG_PORT_SEC_CTL BIT(3)
#define MTK_SMI_CAPS(flags, _x) (!!((flags) & (_x)))
struct mtk_smi_reg_pair {
unsigned int offset;
u32 value;
};
enum mtk_smi_type {
MTK_SMI_GEN1,
MTK_SMI_GEN2, /* gen2 smi common */
MTK_SMI_GEN2_SUB_COMM, /* gen2 smi sub common */
};
/* larbs: Require apb/smi clocks while gals is optional. */
static const char * const mtk_smi_larb_clks[] = {"apb", "smi", "gals"};
#define MTK_SMI_LARB_REQ_CLK_NR 2
#define MTK_SMI_LARB_OPT_CLK_NR 1
/*
* common: Require these four clocks in has_gals case. Otherwise, only apb/smi are required.
* sub common: Require apb/smi/gals0 clocks in has_gals case. Otherwise, only apb/smi are required.
*/
static const char * const mtk_smi_common_clks[] = {"apb", "smi", "gals0", "gals1"};
#define MTK_SMI_CLK_NR_MAX ARRAY_SIZE(mtk_smi_common_clks)
#define MTK_SMI_COM_REQ_CLK_NR 2
#define MTK_SMI_COM_GALS_REQ_CLK_NR MTK_SMI_CLK_NR_MAX
#define MTK_SMI_SUB_COM_GALS_REQ_CLK_NR 3
struct mtk_smi_common_plat {
enum mtk_smi_type type;
bool has_gals;
u32 bus_sel; /* Balance some larbs to enter mmu0 or mmu1 */
const struct mtk_smi_reg_pair *init;
};
struct mtk_smi_larb_gen {
int port_in_larb[MTK_LARB_NR_MAX + 1];
int (*config_port)(struct device *dev);
unsigned int larb_direct_to_common_mask;
unsigned int flags_general;
const u8 (*ostd)[SMI_LARB_PORT_NR_MAX];
};
struct mtk_smi {
struct device *dev;
unsigned int clk_num;
struct clk_bulk_data clks[MTK_SMI_CLK_NR_MAX];
struct clk *clk_async; /*only needed by mt2701*/
union {
void __iomem *smi_ao_base; /* only for gen1 */
void __iomem *base; /* only for gen2 */
};
struct device *smi_common_dev; /* for sub common */
const struct mtk_smi_common_plat *plat;
};
struct mtk_smi_larb { /* larb: local arbiter */
struct mtk_smi smi;
void __iomem *base;
struct device *smi_common_dev; /* common or sub-common dev */
const struct mtk_smi_larb_gen *larb_gen;
int larbid;
u32 *mmu;
unsigned char *bank;
};
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_larb_iommu *larb_mmu = data;
unsigned int i;
for (i = 0; i < MTK_LARB_NR_MAX; i++) {
if (dev == larb_mmu[i].dev) {
larb->larbid = i;
larb->mmu = &larb_mmu[i].mmu;
larb->bank = larb_mmu[i].bank;
return 0;
}
}
return -ENODEV;
}
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 int 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));
}
return 0;
}
static int mtk_smi_larb_config_port_mt8167(struct device *dev)
{
struct mtk_smi_larb *larb = dev_get_drvdata(dev);
writel(*larb->mmu, larb->base + MT8167_SMI_LARB_MMU_EN);
return 0;
}
static int mtk_smi_larb_config_port_mt8173(struct device *dev)
{
struct mtk_smi_larb *larb = dev_get_drvdata(dev);
writel(*larb->mmu, larb->base + MT8173_SMI_LARB_MMU_EN);
return 0;
}
static int mtk_smi_larb_config_port_gen2_general(struct device *dev)
{
struct mtk_smi_larb *larb = dev_get_drvdata(dev);
u32 reg, flags_general = larb->larb_gen->flags_general;
const u8 *larbostd = larb->larb_gen->ostd ? larb->larb_gen->ostd[larb->larbid] : NULL;
struct arm_smccc_res res;
int i;
if (BIT(larb->larbid) & larb->larb_gen->larb_direct_to_common_mask)
return 0;
if (MTK_SMI_CAPS(flags_general, MTK_SMI_FLAG_THRT_UPDATE)) {
reg = readl_relaxed(larb->base + SMI_LARB_CMD_THRT_CON);
reg &= ~SMI_LARB_THRT_RD_NU_LMT_MSK;
reg |= SMI_LARB_THRT_RD_NU_LMT;
writel_relaxed(reg, larb->base + SMI_LARB_CMD_THRT_CON);
}
if (MTK_SMI_CAPS(flags_general, MTK_SMI_FLAG_SW_FLAG))
writel_relaxed(SMI_LARB_SW_FLAG_1, larb->base + SMI_LARB_SW_FLAG);
for (i = 0; i < SMI_LARB_PORT_NR_MAX && larbostd && !!larbostd[i]; i++)
writel_relaxed(larbostd[i], larb->base + SMI_LARB_OSTDL_PORTx(i));
/*
* When mmu_en bits are in security world, the bank_sel still is in the
* LARB_NONSEC_CON below. And the mmu_en bits of LARB_NONSEC_CON have no
* effect in this case.
*/
if (MTK_SMI_CAPS(flags_general, MTK_SMI_FLAG_CFG_PORT_SEC_CTL)) {
arm_smccc_smc(MTK_SIP_KERNEL_IOMMU_CONTROL, IOMMU_ATF_CMD_CONFIG_SMI_LARB,
larb->larbid, *larb->mmu, 0, 0, 0, 0, &res);
if (res.a0 != 0) {
dev_err(dev, "Enable iommu fail, ret %ld\n", res.a0);
return -EINVAL;
}
}
for_each_set_bit(i, (unsigned long *)larb->mmu, 32) {
reg = readl_relaxed(larb->base + SMI_LARB_NONSEC_CON(i));
reg |= F_MMU_EN;
reg |= BANK_SEL(larb->bank[i]);
writel(reg, larb->base + SMI_LARB_NONSEC_CON(i));
}
return 0;
}
static const u8 mtk_smi_larb_mt6893_ostd[][SMI_LARB_PORT_NR_MAX] = {
[0] = {0x2, 0x6, 0x2, 0x2, 0x2, 0x28, 0x18, 0x18, 0x1, 0x1, 0x1, 0x8,
0x8, 0x1, 0x3f},
[1] = {0x2, 0x6, 0x2, 0x2, 0x2, 0x28, 0x18, 0x18, 0x1, 0x1, 0x1, 0x8,
0x8, 0x1, 0x3f},
[2] = {0x5, 0x5, 0x5, 0x5, 0x1, 0x3f},
[3] = {0x5, 0x5, 0x5, 0x5, 0x1, 0x3f},
[4] = {0x28, 0x19, 0xb, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1},
[5] = {0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x16},
[6] = {},
[7] = {0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x4, 0x1,
0x4, 0x1, 0xa, 0x6, 0x1, 0xa, 0x6, 0x1, 0x1, 0x1, 0x1, 0x5,
0x3, 0x3, 0x4},
[8] = {0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x4, 0x1,
0x4, 0x1, 0xa, 0x6, 0x1, 0xa, 0x6, 0x1, 0x1, 0x1, 0x1, 0x5,
0x3, 0x3, 0x4},
[9] = {0x9, 0x7, 0xf, 0x8, 0x1, 0x8, 0x9, 0x3, 0x3, 0x6, 0x7, 0x4,
0x9, 0x3, 0x4, 0xe, 0x1, 0x7, 0x8, 0x7, 0x7, 0x1, 0x6, 0x2,
0xf, 0x8, 0x1, 0x1, 0x1},
[10] = {},
[11] = {0x9, 0x7, 0xf, 0x8, 0x1, 0x8, 0x9, 0x3, 0x3, 0x6, 0x7, 0x4,
0x9, 0x3, 0x4, 0xe, 0x1, 0x7, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1, 0x1, 0x1},
[12] = {},
[13] = {0x2, 0xc, 0xc, 0xe, 0x6, 0x6, 0x6, 0x6, 0x6, 0x12, 0x6, 0x1},
[14] = {0x2, 0xc, 0xc, 0x28, 0x12, 0x6},
[15] = {0x28, 0x1, 0x2, 0x28, 0x1},
[16] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x2, 0x14, 0x14, 0x4, 0x4, 0x4, 0x2,
0x4, 0x2, 0x8, 0x4, 0x4},
[17] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x2, 0x14, 0x14, 0x4, 0x4, 0x4, 0x2,
0x4, 0x2, 0x8, 0x4, 0x4},
[18] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x2, 0x14, 0x14, 0x4, 0x4, 0x4, 0x2,
0x4, 0x2, 0x8, 0x4, 0x4},
[19] = {0x2, 0x2, 0x4, 0x2},
[20] = {0x9, 0x9, 0x5, 0x5, 0x1, 0x1},
};
static const u8 mtk_smi_larb_mt8186_ostd[][SMI_LARB_PORT_NR_MAX] = {
[0] = {0x2, 0x1, 0x8, 0x1,},
[1] = {0x1, 0x3, 0x1, 0x1,},
[2] = {0x6, 0x1, 0x4, 0x1,},
[3] = {},
[4] = {0xf, 0x1, 0x5, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1,},
[5] = {},
[6] = {},
[7] = {0x1, 0x3, 0x1, 0x1, 0x1, 0x3, 0x2, 0xd, 0x7, 0x5, 0x3,
0x1, 0x5,},
[8] = {0x1, 0x2, 0x2,},
[9] = {0x9, 0x7, 0xf, 0x8, 0x1, 0x8, 0x9, 0x3, 0x3, 0xb, 0x7, 0x4,
0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1, 0x1, 0x1,},
[10] = {},
[11] = {0x9, 0x7, 0xf, 0x8, 0x1, 0x8, 0x9, 0x3, 0x3, 0xb, 0x7, 0x4,
0x9, 0x1, 0x1, 0x1, 0x1, 0x1, 0x8, 0x7, 0x7, 0x1, 0x6, 0x2,
0xf, 0x8, 0x1, 0x1, 0x1,},
[12] = {},
[13] = {0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x6, 0x6, 0x6, 0x1, 0x1, 0x1,},
[14] = {0x1, 0x1, 0x1, 0x1, 0x1, 0x1,},
[15] = {},
[16] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x1, 0x14, 0x1, 0x4, 0x4, 0x4,
0x2, 0x4, 0x2, 0x8, 0x4, 0x4,},
[17] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x1, 0x14, 0x1, 0x4, 0x4, 0x4,
0x2, 0x4, 0x2, 0x8, 0x4, 0x4,},
[18] = {},
[19] = {0x1, 0x1, 0x1, 0x1,},
[20] = {0x2, 0x2, 0x2, 0x2, 0x1, 0x1,},
};
static const u8 mtk_smi_larb_mt8188_ostd[][SMI_LARB_PORT_NR_MAX] = {
[0] = {0x02, 0x18, 0x22, 0x22, 0x01, 0x02, 0x0a,},
[1] = {0x12, 0x02, 0x14, 0x14, 0x01, 0x18, 0x0a,},
[2] = {0x12, 0x12, 0x12, 0x12, 0x0a,},
[3] = {0x12, 0x12, 0x12, 0x12, 0x28, 0x28, 0x0a,},
[4] = {0x06, 0x01, 0x17, 0x06, 0x0a, 0x07, 0x07,},
[5] = {0x02, 0x01, 0x04, 0x02, 0x06, 0x01, 0x06, 0x0a,},
[6] = {0x06, 0x01, 0x06, 0x0a,},
[7] = {0x0c, 0x0c, 0x12,},
[8] = {0x0c, 0x01, 0x0a, 0x05, 0x02, 0x03, 0x01, 0x01, 0x14, 0x14,
0x0a, 0x14, 0x1e, 0x01, 0x0c, 0x0a, 0x05, 0x02, 0x02, 0x05,
0x03, 0x01, 0x1e, 0x01, 0x05,},
[9] = {0x1e, 0x01, 0x0a, 0x0a, 0x01, 0x01, 0x03, 0x1e, 0x1e, 0x10,
0x07, 0x01, 0x0a, 0x06, 0x03, 0x03, 0x0e, 0x01, 0x04, 0x28,},
[10] = {0x03, 0x20, 0x01, 0x20, 0x01, 0x01, 0x14, 0x0a, 0x0a, 0x0c,
0x0a, 0x05, 0x02, 0x03, 0x02, 0x14, 0x0a, 0x0a, 0x14, 0x14,
0x14, 0x01, 0x01, 0x14, 0x1e, 0x01, 0x05, 0x03, 0x02, 0x28,},
[11] = {0x03, 0x20, 0x01, 0x20, 0x01, 0x01, 0x14, 0x0a, 0x0a, 0x0c,
0x0a, 0x05, 0x02, 0x03, 0x02, 0x14, 0x0a, 0x0a, 0x14, 0x14,
0x14, 0x01, 0x01, 0x14, 0x1e, 0x01, 0x05, 0x03, 0x02, 0x28,},
[12] = {0x03, 0x20, 0x01, 0x20, 0x01, 0x01, 0x14, 0x0a, 0x0a, 0x0c,
0x0a, 0x05, 0x02, 0x03, 0x02, 0x14, 0x0a, 0x0a, 0x14, 0x14,
0x14, 0x01, 0x01, 0x14, 0x1e, 0x01, 0x05, 0x03, 0x02, 0x28,},
[13] = {0x07, 0x02, 0x04, 0x02, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x07, 0x02, 0x04, 0x02, 0x05, 0x05,},
[14] = {0x02, 0x02, 0x0c, 0x0c, 0x0c, 0x0c, 0x01, 0x01, 0x02, 0x02,
0x02, 0x02, 0x0c, 0x0c, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x01, 0x01,},
[15] = {0x0c, 0x0c, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x0c, 0x0c,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x02,
0x0c, 0x01, 0x01,},
[16] = {0x28, 0x28, 0x03, 0x01, 0x01, 0x03, 0x14, 0x14, 0x0a, 0x0d,
0x03, 0x05, 0x0e, 0x01, 0x01, 0x05, 0x06, 0x0d, 0x01,},
[17] = {0x28, 0x02, 0x02, 0x12, 0x02, 0x12, 0x10, 0x02, 0x02, 0x0a,
0x12, 0x02, 0x02, 0x0a, 0x16, 0x02, 0x04,},
[18] = {0x28, 0x02, 0x02, 0x12, 0x02, 0x12, 0x10, 0x02, 0x02, 0x0a,
0x12, 0x02, 0x02, 0x0a, 0x16, 0x02, 0x04,},
[19] = {0x1a, 0x0e, 0x0a, 0x0a, 0x0c, 0x0e, 0x10,},
[20] = {0x1a, 0x0e, 0x0a, 0x0a, 0x0c, 0x0e, 0x10,},
[21] = {0x01, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x04, 0x01,
0x01, 0x01, 0x04, 0x0a, 0x06, 0x01, 0x01, 0x01, 0x0a, 0x06,
0x01, 0x01, 0x05, 0x03, 0x03, 0x04, 0x01,},
[22] = {0x28, 0x19, 0x0c, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04,
0x01,},
[23] = {0x01, 0x01, 0x04, 0x01, 0x01, 0x01, 0x18, 0x01, 0x01,},
[24] = {0x12, 0x06, 0x12, 0x06,},
[25] = {0x01},
};
static const u8 mtk_smi_larb_mt8192_ostd[][SMI_LARB_PORT_NR_MAX] = {
[0] = {0x2, 0x2, 0x28, 0xa, 0xc, 0x28,},
[1] = {0x2, 0x2, 0x18, 0x18, 0x18, 0xa, 0xc, 0x28,},
[2] = {0x5, 0x5, 0x5, 0x5, 0x1,},
[3] = {},
[4] = {0x28, 0x19, 0xb, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x4, 0x1,},
[5] = {0x1, 0x1, 0x4, 0x1, 0x1, 0x1, 0x1, 0x16,},
[6] = {},
[7] = {0x1, 0x3, 0x2, 0x1, 0x1, 0x5, 0x2, 0x12, 0x13, 0x4, 0x4, 0x1,
0x4, 0x2, 0x1,},
[8] = {},
[9] = {0xa, 0x7, 0xf, 0x8, 0x1, 0x8, 0x9, 0x3, 0x3, 0x6, 0x7, 0x4,
0xa, 0x3, 0x4, 0xe, 0x1, 0x7, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1, 0x1, 0x1,},
[10] = {},
[11] = {0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1, 0xe, 0x1, 0x7, 0x8, 0x7, 0x7, 0x1, 0x6, 0x2,
0xf, 0x8, 0x1, 0x1, 0x1,},
[12] = {},
[13] = {0x2, 0xc, 0xc, 0xe, 0x6, 0x6, 0x6, 0x6, 0x6, 0x12, 0x6, 0x28,
0x2, 0xc, 0xc, 0x28, 0x12, 0x6,},
[14] = {},
[15] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x4, 0x28, 0x14, 0x4, 0x4, 0x4, 0x2,
0x4, 0x2, 0x8, 0x4, 0x4,},
[16] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x4, 0x28, 0x14, 0x4, 0x4, 0x4, 0x2,
0x4, 0x2, 0x8, 0x4, 0x4,},
[17] = {0x28, 0x14, 0x2, 0xc, 0x18, 0x4, 0x28, 0x14, 0x4, 0x4, 0x4, 0x2,
0x4, 0x2, 0x8, 0x4, 0x4,},
[18] = {0x2, 0x2, 0x4, 0x2,},
[19] = {0x9, 0x9, 0x5, 0x5, 0x1, 0x1,},
};
static const u8 mtk_smi_larb_mt8195_ostd[][SMI_LARB_PORT_NR_MAX] = {
[0] = {0x0a, 0xc, 0x22, 0x22, 0x01, 0x0a,}, /* larb0 */
[1] = {0x0a, 0xc, 0x22, 0x22, 0x01, 0x0a,}, /* larb1 */
[2] = {0x12, 0x12, 0x12, 0x12, 0x0a,}, /* ... */
[3] = {0x12, 0x12, 0x12, 0x12, 0x28, 0x28, 0x0a,},
[4] = {0x06, 0x01, 0x17, 0x06, 0x0a,},
[5] = {0x06, 0x01, 0x17, 0x06, 0x06, 0x01, 0x06, 0x0a,},
[6] = {0x06, 0x01, 0x06, 0x0a,},
[7] = {0x0c, 0x0c, 0x12,},
[8] = {0x0c, 0x0c, 0x12,},
[9] = {0x0a, 0x08, 0x04, 0x06, 0x01, 0x01, 0x10, 0x18, 0x11, 0x0a,
0x08, 0x04, 0x11, 0x06, 0x02, 0x06, 0x01, 0x11, 0x11, 0x06,},
[10] = {0x18, 0x08, 0x01, 0x01, 0x20, 0x12, 0x18, 0x06, 0x05, 0x10,
0x08, 0x08, 0x10, 0x08, 0x08, 0x18, 0x0c, 0x09, 0x0b, 0x0d,
0x0d, 0x06, 0x10, 0x10,},
[11] = {0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x01, 0x01, 0x01, 0x01,},
[12] = {0x09, 0x09, 0x05, 0x05, 0x0c, 0x18, 0x02, 0x02, 0x04, 0x02,},
[13] = {0x02, 0x02, 0x12, 0x12, 0x02, 0x02, 0x02, 0x02, 0x08, 0x01,},
[14] = {0x12, 0x12, 0x02, 0x02, 0x02, 0x02, 0x16, 0x01, 0x16, 0x01,
0x01, 0x02, 0x02, 0x08, 0x02,},
[15] = {},
[16] = {0x28, 0x02, 0x02, 0x12, 0x02, 0x12, 0x10, 0x02, 0x02, 0x0a,
0x12, 0x02, 0x0a, 0x16, 0x02, 0x04,},
[17] = {0x1a, 0x0e, 0x0a, 0x0a, 0x0c, 0x0e, 0x10,},
[18] = {0x12, 0x06, 0x12, 0x06,},
[19] = {0x01, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x04, 0x01,
0x01, 0x01, 0x04, 0x0a, 0x06, 0x01, 0x01, 0x01, 0x0a, 0x06,
0x01, 0x01, 0x05, 0x03, 0x03, 0x04, 0x01,},
[20] = {0x01, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x04, 0x01,
0x01, 0x01, 0x04, 0x0a, 0x06, 0x01, 0x01, 0x01, 0x0a, 0x06,
0x01, 0x01, 0x05, 0x03, 0x03, 0x04, 0x01,},
[21] = {0x28, 0x19, 0x0c, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04,},
[22] = {0x28, 0x19, 0x0c, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04,},
[23] = {0x18, 0x01,},
[24] = {0x01, 0x01, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x01,
0x01, 0x01,},
[25] = {0x02, 0x02, 0x02, 0x28, 0x16, 0x02, 0x02, 0x02, 0x12, 0x16,
0x02, 0x01,},
[26] = {0x02, 0x02, 0x02, 0x28, 0x16, 0x02, 0x02, 0x02, 0x12, 0x16,
0x02, 0x01,},
[27] = {0x02, 0x02, 0x02, 0x28, 0x16, 0x02, 0x02, 0x02, 0x12, 0x16,
0x02, 0x01,},
[28] = {0x1a, 0x0e, 0x0a, 0x0a, 0x0c, 0x0e, 0x10,},
};
static const struct mtk_smi_larb_gen mtk_smi_larb_mt2701 = {
.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 = {
.config_port = mtk_smi_larb_config_port_gen2_general,
.larb_direct_to_common_mask = BIT(8) | BIT(9), /* bdpsys */
};
static const struct mtk_smi_larb_gen mtk_smi_larb_mt6779 = {
.config_port = mtk_smi_larb_config_port_gen2_general,
.larb_direct_to_common_mask =
BIT(4) | BIT(6) | BIT(11) | BIT(12) | BIT(13),
/* DUMMY | IPU0 | IPU1 | CCU | MDLA */
};
static const struct mtk_smi_larb_gen mtk_smi_larb_mt6893 = {
.config_port = mtk_smi_larb_config_port_gen2_general,
.flags_general = MTK_SMI_FLAG_THRT_UPDATE | MTK_SMI_FLAG_SW_FLAG,
.ostd = mtk_smi_larb_mt6893_ostd,
};
static const struct mtk_smi_larb_gen mtk_smi_larb_mt8167 = {
/* mt8167 do not need the port in larb */
.config_port = mtk_smi_larb_config_port_mt8167,
};
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_mt8183 = {
.config_port = mtk_smi_larb_config_port_gen2_general,
.larb_direct_to_common_mask = BIT(2) | BIT(3) | BIT(7),
/* IPU0 | IPU1 | CCU */
};
static const struct mtk_smi_larb_gen mtk_smi_larb_mt8186 = {
.config_port = mtk_smi_larb_config_port_gen2_general,
.flags_general = MTK_SMI_FLAG_SLEEP_CTL,
.ostd = mtk_smi_larb_mt8186_ostd,
};
static const struct mtk_smi_larb_gen mtk_smi_larb_mt8188 = {
.config_port = mtk_smi_larb_config_port_gen2_general,
.flags_general = MTK_SMI_FLAG_THRT_UPDATE | MTK_SMI_FLAG_SW_FLAG |
MTK_SMI_FLAG_SLEEP_CTL | MTK_SMI_FLAG_CFG_PORT_SEC_CTL,
.ostd = mtk_smi_larb_mt8188_ostd,
};
static const struct mtk_smi_larb_gen mtk_smi_larb_mt8192 = {
.config_port = mtk_smi_larb_config_port_gen2_general,
.ostd = mtk_smi_larb_mt8192_ostd,
};
static const struct mtk_smi_larb_gen mtk_smi_larb_mt8195 = {
.config_port = mtk_smi_larb_config_port_gen2_general,
.flags_general = MTK_SMI_FLAG_THRT_UPDATE | MTK_SMI_FLAG_SW_FLAG |
MTK_SMI_FLAG_SLEEP_CTL,
.ostd = mtk_smi_larb_mt8195_ostd,
};
static const struct of_device_id mtk_smi_larb_of_ids[] = {
{.compatible = "mediatek,mt2701-smi-larb", .data = &mtk_smi_larb_mt2701},
{.compatible = "mediatek,mt2712-smi-larb", .data = &mtk_smi_larb_mt2712},
{.compatible = "mediatek,mt6779-smi-larb", .data = &mtk_smi_larb_mt6779},
{.compatible = "mediatek,mt6795-smi-larb", .data = &mtk_smi_larb_mt8173},
{.compatible = "mediatek,mt6893-smi-larb", .data = &mtk_smi_larb_mt6893},
{.compatible = "mediatek,mt8167-smi-larb", .data = &mtk_smi_larb_mt8167},
{.compatible = "mediatek,mt8173-smi-larb", .data = &mtk_smi_larb_mt8173},
{.compatible = "mediatek,mt8183-smi-larb", .data = &mtk_smi_larb_mt8183},
{.compatible = "mediatek,mt8186-smi-larb", .data = &mtk_smi_larb_mt8186},
{.compatible = "mediatek,mt8188-smi-larb", .data = &mtk_smi_larb_mt8188},
{.compatible = "mediatek,mt8192-smi-larb", .data = &mtk_smi_larb_mt8192},
{.compatible = "mediatek,mt8195-smi-larb", .data = &mtk_smi_larb_mt8195},
{}
};
MODULE_DEVICE_TABLE(of, mtk_smi_larb_of_ids);
static int mtk_smi_larb_sleep_ctrl_enable(struct mtk_smi_larb *larb)
{
int ret;
u32 tmp;
writel_relaxed(SLP_PROT_EN, larb->base + SMI_LARB_SLP_CON);
ret = readl_poll_timeout_atomic(larb->base + SMI_LARB_SLP_CON,
tmp, !!(tmp & SLP_PROT_RDY), 10, 1000);
if (ret) {
/* TODO: Reset this larb if it fails here. */
dev_err(larb->smi.dev, "sleep ctrl is not ready(0x%x).\n", tmp);
}
return ret;
}
static void mtk_smi_larb_sleep_ctrl_disable(struct mtk_smi_larb *larb)
{
writel_relaxed(0, larb->base + SMI_LARB_SLP_CON);
}
static int mtk_smi_device_link_common(struct device *dev, struct device **com_dev)
{
struct platform_device *smi_com_pdev;
struct device_node *smi_com_node;
struct device *smi_com_dev;
struct device_link *link;
smi_com_node = of_parse_phandle(dev->of_node, "mediatek,smi", 0);
if (!smi_com_node)
return -EINVAL;
smi_com_pdev = of_find_device_by_node(smi_com_node);
of_node_put(smi_com_node);
if (!smi_com_pdev) {
dev_err(dev, "Failed to get the smi_common device\n");
return -EINVAL;
}
/* smi common is the supplier, Make sure it is ready before */
if (!platform_get_drvdata(smi_com_pdev)) {
put_device(&smi_com_pdev->dev);
return -EPROBE_DEFER;
}
smi_com_dev = &smi_com_pdev->dev;
link = device_link_add(dev, smi_com_dev,
DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS);
if (!link) {
dev_err(dev, "Unable to link smi-common dev\n");
put_device(&smi_com_pdev->dev);
return -ENODEV;
}
*com_dev = smi_com_dev;
return 0;
}
static int mtk_smi_dts_clk_init(struct device *dev, struct mtk_smi *smi,
const char * const clks[],
unsigned int clk_nr_required,
unsigned int clk_nr_optional)
{
int i, ret;
for (i = 0; i < clk_nr_required; i++)
smi->clks[i].id = clks[i];
ret = devm_clk_bulk_get(dev, clk_nr_required, smi->clks);
if (ret)
return ret;
for (i = clk_nr_required; i < clk_nr_required + clk_nr_optional; i++)
smi->clks[i].id = clks[i];
ret = devm_clk_bulk_get_optional(dev, clk_nr_optional,
smi->clks + clk_nr_required);
smi->clk_num = clk_nr_required + clk_nr_optional;
return ret;
}
static int mtk_smi_larb_probe(struct platform_device *pdev)
{
struct mtk_smi_larb *larb;
struct device *dev = &pdev->dev;
int ret;
larb = devm_kzalloc(dev, sizeof(*larb), GFP_KERNEL);
if (!larb)
return -ENOMEM;
larb->larb_gen = of_device_get_match_data(dev);
larb->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(larb->base))
return PTR_ERR(larb->base);
ret = mtk_smi_dts_clk_init(dev, &larb->smi, mtk_smi_larb_clks,
MTK_SMI_LARB_REQ_CLK_NR, MTK_SMI_LARB_OPT_CLK_NR);
if (ret)
return ret;
larb->smi.dev = dev;
ret = mtk_smi_device_link_common(dev, &larb->smi_common_dev);
if (ret < 0)
return ret;
pm_runtime_enable(dev);
platform_set_drvdata(pdev, larb);
ret = component_add(dev, &mtk_smi_larb_component_ops);
if (ret)
goto err_pm_disable;
return 0;
err_pm_disable:
pm_runtime_disable(dev);
device_link_remove(dev, larb->smi_common_dev);
put_device(larb->smi_common_dev);
return ret;
}
static void mtk_smi_larb_remove(struct platform_device *pdev)
{
struct mtk_smi_larb *larb = platform_get_drvdata(pdev);
device_link_remove(&pdev->dev, larb->smi_common_dev);
pm_runtime_disable(&pdev->dev);
component_del(&pdev->dev, &mtk_smi_larb_component_ops);
put_device(larb->smi_common_dev);
}
static int __maybe_unused mtk_smi_larb_resume(struct device *dev)
{
struct mtk_smi_larb *larb = dev_get_drvdata(dev);
const struct mtk_smi_larb_gen *larb_gen = larb->larb_gen;
int ret;
ret = clk_bulk_prepare_enable(larb->smi.clk_num, larb->smi.clks);
if (ret)
return ret;
if (MTK_SMI_CAPS(larb->larb_gen->flags_general, MTK_SMI_FLAG_SLEEP_CTL))
mtk_smi_larb_sleep_ctrl_disable(larb);
/* Configure the basic setting for this larb */
return larb_gen->config_port(dev);
}
static int __maybe_unused mtk_smi_larb_suspend(struct device *dev)
{
struct mtk_smi_larb *larb = dev_get_drvdata(dev);
int ret;
if (MTK_SMI_CAPS(larb->larb_gen->flags_general, MTK_SMI_FLAG_SLEEP_CTL)) {
ret = mtk_smi_larb_sleep_ctrl_enable(larb);
if (ret)
return ret;
}
clk_bulk_disable_unprepare(larb->smi.clk_num, larb->smi.clks);
return 0;
}
static const struct dev_pm_ops smi_larb_pm_ops = {
SET_RUNTIME_PM_OPS(mtk_smi_larb_suspend, mtk_smi_larb_resume, NULL)
SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
};
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,
.pm = &smi_larb_pm_ops,
}
};
static const struct mtk_smi_reg_pair mtk_smi_common_mt6795_init[SMI_COMMON_INIT_REGS_NR] = {
{SMI_L1_ARB, 0x1b},
{SMI_M4U_TH, 0xce810c85},
{SMI_FIFO_TH1, 0x43214c8},
{SMI_READ_FIFO_TH, 0x191f},
};
static const struct mtk_smi_reg_pair mtk_smi_common_mt8195_init[SMI_COMMON_INIT_REGS_NR] = {
{SMI_L1LEN, 0xb},
{SMI_M4U_TH, 0xe100e10},
{SMI_FIFO_TH1, 0x506090a},
{SMI_FIFO_TH2, 0x506090a},
{SMI_DCM, 0x4f1},
{SMI_DUMMY, 0x1},
};
static const struct mtk_smi_common_plat mtk_smi_common_gen1 = {
.type = MTK_SMI_GEN1,
};
static const struct mtk_smi_common_plat mtk_smi_common_gen2 = {
.type = MTK_SMI_GEN2,
};
static const struct mtk_smi_common_plat mtk_smi_common_mt6779 = {
.type = MTK_SMI_GEN2,
.has_gals = true,
.bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(4) |
F_MMU1_LARB(5) | F_MMU1_LARB(6) | F_MMU1_LARB(7),
};
static const struct mtk_smi_common_plat mtk_smi_common_mt6795 = {
.type = MTK_SMI_GEN2,
.bus_sel = F_MMU1_LARB(0),
.init = mtk_smi_common_mt6795_init,
};
static const struct mtk_smi_common_plat mtk_smi_common_mt6893 = {
.type = MTK_SMI_GEN2,
.has_gals = true,
.bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(4) |
F_MMU1_LARB(5) | F_MMU1_LARB(7),
};
static const struct mtk_smi_common_plat mtk_smi_common_mt8183 = {
.type = MTK_SMI_GEN2,
.has_gals = true,
.bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(5) |
F_MMU1_LARB(7),
};
static const struct mtk_smi_common_plat mtk_smi_common_mt8186 = {
.type = MTK_SMI_GEN2,
.has_gals = true,
.bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(4) | F_MMU1_LARB(7),
};
static const struct mtk_smi_common_plat mtk_smi_common_mt8188_vdo = {
.type = MTK_SMI_GEN2,
.bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(5) | F_MMU1_LARB(7),
.init = mtk_smi_common_mt8195_init,
};
static const struct mtk_smi_common_plat mtk_smi_common_mt8188_vpp = {
.type = MTK_SMI_GEN2,
.bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(7),
.init = mtk_smi_common_mt8195_init,
};
static const struct mtk_smi_common_plat mtk_smi_common_mt8192 = {
.type = MTK_SMI_GEN2,
.has_gals = true,
.bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(5) |
F_MMU1_LARB(6),
};
static const struct mtk_smi_common_plat mtk_smi_common_mt8195_vdo = {
.type = MTK_SMI_GEN2,
.has_gals = true,
.bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(3) | F_MMU1_LARB(5) |
F_MMU1_LARB(7),
.init = mtk_smi_common_mt8195_init,
};
static const struct mtk_smi_common_plat mtk_smi_common_mt8195_vpp = {
.type = MTK_SMI_GEN2,
.has_gals = true,
.bus_sel = F_MMU1_LARB(1) | F_MMU1_LARB(2) | F_MMU1_LARB(7),
.init = mtk_smi_common_mt8195_init,
};
static const struct mtk_smi_common_plat mtk_smi_sub_common_mt8195 = {
.type = MTK_SMI_GEN2_SUB_COMM,
.has_gals = true,
};
static const struct mtk_smi_common_plat mtk_smi_common_mt8365 = {
.type = MTK_SMI_GEN2,
.bus_sel = F_MMU1_LARB(2) | F_MMU1_LARB(4),
};
static const struct of_device_id mtk_smi_common_of_ids[] = {
{.compatible = "mediatek,mt2701-smi-common", .data = &mtk_smi_common_gen1},
{.compatible = "mediatek,mt2712-smi-common", .data = &mtk_smi_common_gen2},
{.compatible = "mediatek,mt6779-smi-common", .data = &mtk_smi_common_mt6779},
{.compatible = "mediatek,mt6795-smi-common", .data = &mtk_smi_common_mt6795},
{.compatible = "mediatek,mt6893-smi-common", .data = &mtk_smi_common_mt6893},
{.compatible = "mediatek,mt8167-smi-common", .data = &mtk_smi_common_gen2},
{.compatible = "mediatek,mt8173-smi-common", .data = &mtk_smi_common_gen2},
{.compatible = "mediatek,mt8183-smi-common", .data = &mtk_smi_common_mt8183},
{.compatible = "mediatek,mt8186-smi-common", .data = &mtk_smi_common_mt8186},
{.compatible = "mediatek,mt8188-smi-common-vdo", .data = &mtk_smi_common_mt8188_vdo},
{.compatible = "mediatek,mt8188-smi-common-vpp", .data = &mtk_smi_common_mt8188_vpp},
{.compatible = "mediatek,mt8192-smi-common", .data = &mtk_smi_common_mt8192},
{.compatible = "mediatek,mt8195-smi-common-vdo", .data = &mtk_smi_common_mt8195_vdo},
{.compatible = "mediatek,mt8195-smi-common-vpp", .data = &mtk_smi_common_mt8195_vpp},
{.compatible = "mediatek,mt8195-smi-sub-common", .data = &mtk_smi_sub_common_mt8195},
{.compatible = "mediatek,mt8365-smi-common", .data = &mtk_smi_common_mt8365},
{}
};
MODULE_DEVICE_TABLE(of, mtk_smi_common_of_ids);
static int mtk_smi_common_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mtk_smi *common;
int ret, clk_required = MTK_SMI_COM_REQ_CLK_NR;
common = devm_kzalloc(dev, sizeof(*common), GFP_KERNEL);
if (!common)
return -ENOMEM;
common->dev = dev;
common->plat = of_device_get_match_data(dev);
if (common->plat->has_gals) {
if (common->plat->type == MTK_SMI_GEN2)
clk_required = MTK_SMI_COM_GALS_REQ_CLK_NR;
else if (common->plat->type == MTK_SMI_GEN2_SUB_COMM)
clk_required = MTK_SMI_SUB_COM_GALS_REQ_CLK_NR;
}
ret = mtk_smi_dts_clk_init(dev, common, mtk_smi_common_clks, clk_required, 0);
if (ret)
return ret;
/*
* 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.
*/
if (common->plat->type == MTK_SMI_GEN1) {
common->smi_ao_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(common->smi_ao_base))
return PTR_ERR(common->smi_ao_base);
common->clk_async = devm_clk_get_enabled(dev, "async");
if (IS_ERR(common->clk_async))
return PTR_ERR(common->clk_async);
} else {
common->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(common->base))
return PTR_ERR(common->base);
}
/* link its smi-common if this is smi-sub-common */
if (common->plat->type == MTK_SMI_GEN2_SUB_COMM) {
ret = mtk_smi_device_link_common(dev, &common->smi_common_dev);
if (ret < 0)
return ret;
}
pm_runtime_enable(dev);
platform_set_drvdata(pdev, common);
return 0;
}
static void mtk_smi_common_remove(struct platform_device *pdev)
{
struct mtk_smi *common = dev_get_drvdata(&pdev->dev);
if (common->plat->type == MTK_SMI_GEN2_SUB_COMM)
device_link_remove(&pdev->dev, common->smi_common_dev);
pm_runtime_disable(&pdev->dev);
put_device(common->smi_common_dev);
}
static int __maybe_unused mtk_smi_common_resume(struct device *dev)
{
struct mtk_smi *common = dev_get_drvdata(dev);
const struct mtk_smi_reg_pair *init = common->plat->init;
u32 bus_sel = common->plat->bus_sel; /* default is 0 */
int ret, i;
ret = clk_bulk_prepare_enable(common->clk_num, common->clks);
if (ret)
return ret;
if (common->plat->type != MTK_SMI_GEN2)
return 0;
for (i = 0; i < SMI_COMMON_INIT_REGS_NR && init && init[i].offset; i++)
writel_relaxed(init[i].value, common->base + init[i].offset);
writel(bus_sel, common->base + SMI_BUS_SEL);
return 0;
}
static int __maybe_unused mtk_smi_common_suspend(struct device *dev)
{
struct mtk_smi *common = dev_get_drvdata(dev);
clk_bulk_disable_unprepare(common->clk_num, common->clks);
return 0;
}
static const struct dev_pm_ops smi_common_pm_ops = {
SET_RUNTIME_PM_OPS(mtk_smi_common_suspend, mtk_smi_common_resume, NULL)
SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
};
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,
.pm = &smi_common_pm_ops,
}
};
static struct platform_driver * const smidrivers[] = {
&mtk_smi_common_driver,
&mtk_smi_larb_driver,
};
static int __init mtk_smi_init(void)
{
return platform_register_drivers(smidrivers, ARRAY_SIZE(smidrivers));
}
module_init(mtk_smi_init);
static void __exit mtk_smi_exit(void)
{
platform_unregister_drivers(smidrivers, ARRAY_SIZE(smidrivers));
}
module_exit(mtk_smi_exit);
MODULE_DESCRIPTION("MediaTek SMI driver");
MODULE_LICENSE("GPL v2");
+345
View File
@@ -0,0 +1,345 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Marvell EBU SoC Device Bus Controller
* (memory controller for NOR/NAND/SRAM/FPGA devices)
*
* Copyright (C) 2013-2014 Marvell
*/
#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 clk *clk;
unsigned long rate;
int err;
devbus = devm_kzalloc(&pdev->dev, sizeof(struct devbus), GFP_KERNEL);
if (!devbus)
return -ENOMEM;
devbus->dev = dev;
devbus->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(devbus->base))
return PTR_ERR(devbus->base);
clk = devm_clk_get_enabled(&pdev->dev, NULL);
if (IS_ERR(clk))
return PTR_ERR(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_AUTHOR("Ezequiel Garcia <ezequiel.garcia@free-electrons.com>");
MODULE_DESCRIPTION("Marvell EBU SoC Device Bus controller");
+398
View File
@@ -0,0 +1,398 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* OpenFirmware helpers for memory drivers
*
* Copyright (C) 2012 Texas Instruments, Inc.
* Copyright (C) 2019 Samsung Electronics Co., Ltd.
* Copyright (C) 2020 Krzysztof Kozlowski <krzk@kernel.org>
*/
#include <linux/device.h>
#include <linux/of.h>
#include <linux/gfp.h>
#include <linux/export.h>
#include "jedec_ddr.h"
#include "of_memory.h"
/**
* of_get_min_tck() - extract min timing values for ddr
* @np: pointer to ddr device tree node
* @dev: 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, "Using default min-tck values\n");
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, "Unsupported memory type\n");
}
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])) {
of_node_put(np_tim);
devm_kfree(dev, timings);
goto default_timings;
}
i++;
}
}
*nr_frequencies = arr_sz;
return timings;
default_timings:
dev_warn(dev, "Using default memory timings\n");
*nr_frequencies = ARRAY_SIZE(lpddr2_jedec_timings);
return lpddr2_jedec_timings;
}
EXPORT_SYMBOL(of_get_ddr_timings);
/**
* of_lpddr3_get_min_tck() - extract min timing values for lpddr3
* @np: pointer to ddr device tree node
* @dev: device requesting for min timing values
*
* Populates the lpddr3_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 NULL.
*/
const struct lpddr3_min_tck *of_lpddr3_get_min_tck(struct device_node *np,
struct device *dev)
{
int ret = 0;
struct lpddr3_min_tck *min;
min = devm_kzalloc(dev, sizeof(*min), GFP_KERNEL);
if (!min)
goto default_min_tck;
ret |= of_property_read_u32(np, "tRFC-min-tck", &min->tRFC);
ret |= of_property_read_u32(np, "tRRD-min-tck", &min->tRRD);
ret |= of_property_read_u32(np, "tRPab-min-tck", &min->tRPab);
ret |= of_property_read_u32(np, "tRPpb-min-tck", &min->tRPpb);
ret |= of_property_read_u32(np, "tRCD-min-tck", &min->tRCD);
ret |= of_property_read_u32(np, "tRC-min-tck", &min->tRC);
ret |= of_property_read_u32(np, "tRAS-min-tck", &min->tRAS);
ret |= of_property_read_u32(np, "tWTR-min-tck", &min->tWTR);
ret |= of_property_read_u32(np, "tWR-min-tck", &min->tWR);
ret |= of_property_read_u32(np, "tRTP-min-tck", &min->tRTP);
ret |= of_property_read_u32(np, "tW2W-C2C-min-tck", &min->tW2W_C2C);
ret |= of_property_read_u32(np, "tR2R-C2C-min-tck", &min->tR2R_C2C);
ret |= of_property_read_u32(np, "tWL-min-tck", &min->tWL);
ret |= of_property_read_u32(np, "tDQSCK-min-tck", &min->tDQSCK);
ret |= of_property_read_u32(np, "tRL-min-tck", &min->tRL);
ret |= of_property_read_u32(np, "tFAW-min-tck", &min->tFAW);
ret |= of_property_read_u32(np, "tXSR-min-tck", &min->tXSR);
ret |= of_property_read_u32(np, "tXP-min-tck", &min->tXP);
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, "tMRD-min-tck", &min->tMRD);
if (ret) {
dev_warn(dev, "Errors while parsing min-tck values\n");
devm_kfree(dev, min);
goto default_min_tck;
}
return min;
default_min_tck:
dev_warn(dev, "Using default min-tck values\n");
return NULL;
}
EXPORT_SYMBOL(of_lpddr3_get_min_tck);
static int of_lpddr3_do_get_timings(struct device_node *np,
struct lpddr3_timings *tim)
{
int ret;
ret = of_property_read_u32(np, "max-freq", &tim->max_freq);
if (ret)
/* Deprecated way of passing max-freq as 'reg' */
ret = of_property_read_u32(np, "reg", &tim->max_freq);
ret |= of_property_read_u32(np, "min-freq", &tim->min_freq);
ret |= of_property_read_u32(np, "tRFC", &tim->tRFC);
ret |= of_property_read_u32(np, "tRRD", &tim->tRRD);
ret |= of_property_read_u32(np, "tRPab", &tim->tRPab);
ret |= of_property_read_u32(np, "tRPpb", &tim->tRPpb);
ret |= of_property_read_u32(np, "tRCD", &tim->tRCD);
ret |= of_property_read_u32(np, "tRC", &tim->tRC);
ret |= of_property_read_u32(np, "tRAS", &tim->tRAS);
ret |= of_property_read_u32(np, "tWTR", &tim->tWTR);
ret |= of_property_read_u32(np, "tWR", &tim->tWR);
ret |= of_property_read_u32(np, "tRTP", &tim->tRTP);
ret |= of_property_read_u32(np, "tW2W-C2C", &tim->tW2W_C2C);
ret |= of_property_read_u32(np, "tR2R-C2C", &tim->tR2R_C2C);
ret |= of_property_read_u32(np, "tFAW", &tim->tFAW);
ret |= of_property_read_u32(np, "tXSR", &tim->tXSR);
ret |= of_property_read_u32(np, "tXP", &tim->tXP);
ret |= of_property_read_u32(np, "tCKE", &tim->tCKE);
ret |= of_property_read_u32(np, "tCKESR", &tim->tCKESR);
ret |= of_property_read_u32(np, "tMRD", &tim->tMRD);
return ret;
}
/**
* of_lpddr3_get_ddr_timings() - extracts the lpddr3 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
* @nr_frequencies: No of frequencies available for ddr
* (updated by this function)
*
* Populates lpddr3_timings structure by extracting data from device
* tree node. Returns pointer to populated structure. If any error
* while populating, returns NULL.
*/
const struct lpddr3_timings
*of_lpddr3_get_ddr_timings(struct device_node *np_ddr, struct device *dev,
u32 device_type, u32 *nr_frequencies)
{
struct lpddr3_timings *timings = NULL;
u32 arr_sz = 0, i = 0;
struct device_node *np_tim;
char *tim_compat = NULL;
switch (device_type) {
case DDR_TYPE_LPDDR3:
tim_compat = "jedec,lpddr3-timings";
break;
default:
dev_warn(dev, "Unsupported memory type\n");
}
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_lpddr3_do_get_timings(np_tim, &timings[i])) {
devm_kfree(dev, timings);
of_node_put(np_tim);
goto default_timings;
}
i++;
}
}
*nr_frequencies = arr_sz;
return timings;
default_timings:
dev_warn(dev, "Failed to get timings\n");
*nr_frequencies = 0;
return NULL;
}
EXPORT_SYMBOL(of_lpddr3_get_ddr_timings);
/**
* of_lpddr2_get_info() - extracts information about the lpddr2 chip.
* @np: Pointer to device tree node containing lpddr2 info
* @dev: Device requesting info
*
* Populates lpddr2_info structure by extracting data from device
* tree node. Returns pointer to populated structure. If error
* happened while populating, returns NULL. If property is missing
* in a device-tree, then the corresponding value is set to -ENOENT.
*/
const struct lpddr2_info
*of_lpddr2_get_info(struct device_node *np, struct device *dev)
{
struct lpddr2_info *ret_info, info = {};
struct property *prop;
const char *cp;
int err;
u32 revision_id[2];
err = of_property_read_u32_array(np, "revision-id", revision_id, 2);
if (!err) {
info.revision_id1 = revision_id[0];
info.revision_id2 = revision_id[1];
} else {
err = of_property_read_u32(np, "revision-id1", &info.revision_id1);
if (err)
info.revision_id1 = -ENOENT;
err = of_property_read_u32(np, "revision-id2", &info.revision_id2);
if (err)
info.revision_id2 = -ENOENT;
}
err = of_property_read_u32(np, "io-width", &info.io_width);
if (err)
return NULL;
info.io_width = 32 / info.io_width - 1;
err = of_property_read_u32(np, "density", &info.density);
if (err)
return NULL;
info.density = ffs(info.density) - 7;
if (of_device_is_compatible(np, "jedec,lpddr2-s4"))
info.arch_type = LPDDR2_TYPE_S4;
else if (of_device_is_compatible(np, "jedec,lpddr2-s2"))
info.arch_type = LPDDR2_TYPE_S2;
else if (of_device_is_compatible(np, "jedec,lpddr2-nvm"))
info.arch_type = LPDDR2_TYPE_NVM;
else
return NULL;
prop = of_find_property(np, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp)) {
#define OF_LPDDR2_VENDOR_CMP(compat, ID) \
if (!of_compat_cmp(cp, compat ",", strlen(compat ","))) { \
info.manufacturer_id = LPDDR2_MANID_##ID; \
break; \
}
OF_LPDDR2_VENDOR_CMP("samsung", SAMSUNG)
OF_LPDDR2_VENDOR_CMP("qimonda", QIMONDA)
OF_LPDDR2_VENDOR_CMP("elpida", ELPIDA)
OF_LPDDR2_VENDOR_CMP("etron", ETRON)
OF_LPDDR2_VENDOR_CMP("nanya", NANYA)
OF_LPDDR2_VENDOR_CMP("hynix", HYNIX)
OF_LPDDR2_VENDOR_CMP("mosel", MOSEL)
OF_LPDDR2_VENDOR_CMP("winbond", WINBOND)
OF_LPDDR2_VENDOR_CMP("esmt", ESMT)
OF_LPDDR2_VENDOR_CMP("spansion", SPANSION)
OF_LPDDR2_VENDOR_CMP("sst", SST)
OF_LPDDR2_VENDOR_CMP("zmos", ZMOS)
OF_LPDDR2_VENDOR_CMP("intel", INTEL)
OF_LPDDR2_VENDOR_CMP("numonyx", NUMONYX)
OF_LPDDR2_VENDOR_CMP("micron", MICRON)
#undef OF_LPDDR2_VENDOR_CMP
}
if (!info.manufacturer_id)
info.manufacturer_id = -ENOENT;
ret_info = devm_kzalloc(dev, sizeof(*ret_info), GFP_KERNEL);
if (ret_info)
*ret_info = info;
return ret_info;
}
EXPORT_SYMBOL(of_lpddr2_get_info);
+60
View File
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* OpenFirmware helpers for memory drivers
*
* Copyright (C) 2012 Texas Instruments, Inc.
* Copyright (C) 2020 Krzysztof Kozlowski <krzk@kernel.org>
*/
#ifndef __LINUX_MEMORY_OF_REG_H
#define __LINUX_MEMORY_OF_REG_H
#if defined(CONFIG_OF) && defined(CONFIG_DDR)
const struct lpddr2_min_tck *of_get_min_tck(struct device_node *np,
struct device *dev);
const struct lpddr2_timings *of_get_ddr_timings(struct device_node *np_ddr,
struct device *dev,
u32 device_type, u32 *nr_frequencies);
const struct lpddr3_min_tck *of_lpddr3_get_min_tck(struct device_node *np,
struct device *dev);
const struct lpddr3_timings *
of_lpddr3_get_ddr_timings(struct device_node *np_ddr,
struct device *dev, u32 device_type, u32 *nr_frequencies);
const struct lpddr2_info *of_lpddr2_get_info(struct device_node *np,
struct device *dev);
#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;
}
static inline const struct lpddr3_min_tck
*of_lpddr3_get_min_tck(struct device_node *np, struct device *dev)
{
return NULL;
}
static inline const struct lpddr3_timings
*of_lpddr3_get_ddr_timings(struct device_node *np_ddr,
struct device *dev, u32 device_type, u32 *nr_frequencies)
{
return NULL;
}
static inline const struct lpddr2_info
*of_lpddr2_get_info(struct device_node *np, struct device *dev)
{
return NULL;
}
#endif /* CONFIG_OF && CONFIG_DDR */
#endif /* __LINUX_MEMORY_OF_REG_ */
File diff suppressed because it is too large Load Diff
+300
View File
@@ -0,0 +1,300 @@
// SPDX-License-Identifier: GPL-2.0
/*
* 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.
*/
#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 void pl172_amba_release_regions(void *data)
{
struct amba_device *adev = data;
amba_release_regions(adev);
}
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_enabled(dev, "mpmcclk");
if (IS_ERR(pl172->clk))
return dev_err_probe(dev, PTR_ERR(pl172->clk),
"no mpmcclk provided clock\n");
pl172->rate = clk_get_rate(pl172->clk) / MSEC_PER_SEC;
if (!pl172->rate)
return dev_err_probe(dev, -EINVAL,
"unable to get mpmcclk clock rate\n");
ret = amba_request_regions(adev, NULL);
if (ret) {
dev_err(dev, "unable to request AMBA regions\n");
return ret;
}
ret = devm_add_action_or_reset(dev, pl172_amba_release_regions, adev);
if (ret)
return ret;
pl172->base = devm_ioremap(dev, adev->res.start,
resource_size(&adev->res));
if (!pl172->base)
return dev_err_probe(dev, -ENOMEM, "ioremap failed\n");
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;
}
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,
.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");
+134
View File
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ARM PL353 SMC driver
*
* Copyright (C) 2012 - 2018 Xilinx, Inc
* Author: Punnaiah Choudary Kalluri <punnaiah@xilinx.com>
* Author: Naga Sureshkumar Relli <nagasure@xilinx.com>
*/
#include <linux/clk.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/amba/bus.h>
/**
* struct pl353_smc_data - Private smc driver structure
* @memclk: Pointer to the peripheral clock
* @aclk: Pointer to the AXI peripheral clock
*/
struct pl353_smc_data {
struct clk *memclk;
struct clk *aclk;
};
static int __maybe_unused pl353_smc_suspend(struct device *dev)
{
struct pl353_smc_data *pl353_smc = dev_get_drvdata(dev);
clk_disable(pl353_smc->memclk);
clk_disable(pl353_smc->aclk);
return 0;
}
static int __maybe_unused pl353_smc_resume(struct device *dev)
{
struct pl353_smc_data *pl353_smc = dev_get_drvdata(dev);
int ret;
ret = clk_enable(pl353_smc->aclk);
if (ret) {
dev_err(dev, "Cannot enable axi domain clock.\n");
return ret;
}
ret = clk_enable(pl353_smc->memclk);
if (ret) {
dev_err(dev, "Cannot enable memory clock.\n");
clk_disable(pl353_smc->aclk);
return ret;
}
return ret;
}
static SIMPLE_DEV_PM_OPS(pl353_smc_dev_pm_ops, pl353_smc_suspend,
pl353_smc_resume);
static const struct of_device_id pl353_smc_supported_children[] = {
{
.compatible = "cfi-flash"
},
{
.compatible = "arm,pl353-nand-r2p1",
},
{}
};
static int pl353_smc_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device_node *of_node = adev->dev.of_node;
const struct of_device_id *match = NULL;
struct pl353_smc_data *pl353_smc;
pl353_smc = devm_kzalloc(&adev->dev, sizeof(*pl353_smc), GFP_KERNEL);
if (!pl353_smc)
return -ENOMEM;
pl353_smc->aclk = devm_clk_get_enabled(&adev->dev, "apb_pclk");
if (IS_ERR(pl353_smc->aclk))
return dev_err_probe(&adev->dev, PTR_ERR(pl353_smc->aclk),
"aclk clock not found.\n");
pl353_smc->memclk = devm_clk_get_enabled(&adev->dev, "memclk");
if (IS_ERR(pl353_smc->memclk))
return dev_err_probe(&adev->dev, PTR_ERR(pl353_smc->memclk),
"memclk clock not found.\n");
amba_set_drvdata(adev, pl353_smc);
/* Find compatible children. Only a single child is supported */
for_each_available_child_of_node_scoped(of_node, child) {
match = of_match_node(pl353_smc_supported_children, child);
if (!match) {
dev_warn(&adev->dev, "unsupported child node\n");
continue;
}
of_platform_device_create(child, NULL, &adev->dev);
break;
}
if (!match) {
dev_err(&adev->dev, "no matching children\n");
return -ENODEV;
}
return 0;
}
static const struct amba_id pl353_ids[] = {
{
.id = 0x00041353,
.mask = 0x000fffff,
},
{ 0, 0 },
};
MODULE_DEVICE_TABLE(amba, pl353_ids);
static struct amba_driver pl353_smc_driver = {
.drv = {
.name = "pl353-smc",
.pm = &pl353_smc_dev_pm_ops,
},
.id_table = pl353_ids,
.probe = pl353_smc_probe,
};
module_amba_driver(pl353_smc_driver);
MODULE_AUTHOR("Xilinx, Inc.");
MODULE_DESCRIPTION("ARM PL353 SMC Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,147 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* R-Car RPC Interface Registers Definitions
*
* Copyright (C) 2025 Renesas Electronics Corporation
*/
#ifndef __RENESAS_RPC_IF_REGS_H__
#define __RENESAS_RPC_IF_REGS_H__
#include <linux/bits.h>
#define RPCIF_CMNCR 0x0000 /* R/W */
#define RPCIF_CMNCR_MD BIT(31)
#define RPCIF_CMNCR_MOIIO3(val) (((val) & 0x3) << 22)
#define RPCIF_CMNCR_MOIIO2(val) (((val) & 0x3) << 20)
#define RPCIF_CMNCR_MOIIO1(val) (((val) & 0x3) << 18)
#define RPCIF_CMNCR_MOIIO0(val) (((val) & 0x3) << 16)
#define RPCIF_CMNCR_MOIIO(val) (RPCIF_CMNCR_MOIIO0(val) | RPCIF_CMNCR_MOIIO1(val) | \
RPCIF_CMNCR_MOIIO2(val) | RPCIF_CMNCR_MOIIO3(val))
#define RPCIF_CMNCR_IO3FV(val) (((val) & 0x3) << 14) /* documented for RZ/G2L */
#define RPCIF_CMNCR_IO2FV(val) (((val) & 0x3) << 12) /* documented for RZ/G2L */
#define RPCIF_CMNCR_IO0FV(val) (((val) & 0x3) << 8)
#define RPCIF_CMNCR_IOFV(val) (RPCIF_CMNCR_IO0FV(val) | RPCIF_CMNCR_IO2FV(val) | \
RPCIF_CMNCR_IO3FV(val))
#define RPCIF_CMNCR_BSZ(val) (((val) & 0x3) << 0)
#define RPCIF_SSLDR 0x0004 /* R/W */
#define RPCIF_SSLDR_SPNDL(d) (((d) & 0x7) << 16)
#define RPCIF_SSLDR_SLNDL(d) (((d) & 0x7) << 8)
#define RPCIF_SSLDR_SCKDL(d) (((d) & 0x7) << 0)
#define RPCIF_DRCR 0x000C /* R/W */
#define RPCIF_DRCR_SSLN BIT(24)
#define RPCIF_DRCR_RBURST(v) ((((v) - 1) & 0x1F) << 16)
#define RPCIF_DRCR_RCF BIT(9)
#define RPCIF_DRCR_RBE BIT(8)
#define RPCIF_DRCR_SSLE BIT(0)
#define RPCIF_DRCMR 0x0010 /* R/W */
#define RPCIF_DRCMR_CMD(c) (((c) & 0xFF) << 16)
#define RPCIF_DRCMR_OCMD(c) (((c) & 0xFF) << 0)
#define RPCIF_DREAR 0x0014 /* R/W */
#define RPCIF_DREAR_EAV(c) (((c) & 0xF) << 16)
#define RPCIF_DREAR_EAC(c) (((c) & 0x7) << 0)
#define RPCIF_DROPR 0x0018 /* R/W */
#define RPCIF_DRENR 0x001C /* R/W */
#define RPCIF_DRENR_CDB(o) (((u32)((o) & 0x3)) << 30)
#define RPCIF_DRENR_OCDB(o) (((o) & 0x3) << 28)
#define RPCIF_DRENR_ADB(o) (((o) & 0x3) << 24)
#define RPCIF_DRENR_OPDB(o) (((o) & 0x3) << 20)
#define RPCIF_DRENR_DRDB(o) (((o) & 0x3) << 16)
#define RPCIF_DRENR_DME BIT(15)
#define RPCIF_DRENR_CDE BIT(14)
#define RPCIF_DRENR_OCDE BIT(12)
#define RPCIF_DRENR_ADE(v) (((v) & 0xF) << 8)
#define RPCIF_DRENR_OPDE(v) (((v) & 0xF) << 4)
#define RPCIF_SMCR 0x0020 /* R/W */
#define RPCIF_SMCR_SSLKP BIT(8)
#define RPCIF_SMCR_SPIRE BIT(2)
#define RPCIF_SMCR_SPIWE BIT(1)
#define RPCIF_SMCR_SPIE BIT(0)
#define RPCIF_SMCMR 0x0024 /* R/W */
#define RPCIF_SMCMR_CMD(c) (((c) & 0xFF) << 16)
#define RPCIF_SMCMR_OCMD(c) (((c) & 0xFF) << 0)
#define RPCIF_SMADR 0x0028 /* R/W */
#define RPCIF_SMOPR 0x002C /* R/W */
#define RPCIF_SMOPR_OPD3(o) (((o) & 0xFF) << 24)
#define RPCIF_SMOPR_OPD2(o) (((o) & 0xFF) << 16)
#define RPCIF_SMOPR_OPD1(o) (((o) & 0xFF) << 8)
#define RPCIF_SMOPR_OPD0(o) (((o) & 0xFF) << 0)
#define RPCIF_SMENR 0x0030 /* R/W */
#define RPCIF_SMENR_CDB(o) (((o) & 0x3) << 30)
#define RPCIF_SMENR_OCDB(o) (((o) & 0x3) << 28)
#define RPCIF_SMENR_ADB(o) (((o) & 0x3) << 24)
#define RPCIF_SMENR_OPDB(o) (((o) & 0x3) << 20)
#define RPCIF_SMENR_SPIDB(o) (((o) & 0x3) << 16)
#define RPCIF_SMENR_DME BIT(15)
#define RPCIF_SMENR_CDE BIT(14)
#define RPCIF_SMENR_OCDE BIT(12)
#define RPCIF_SMENR_ADE(v) (((v) & 0xF) << 8)
#define RPCIF_SMENR_OPDE(v) (((v) & 0xF) << 4)
#define RPCIF_SMENR_SPIDE(v) (((v) & 0xF) << 0)
#define RPCIF_SMRDR0 0x0038 /* R */
#define RPCIF_SMRDR1 0x003C /* R */
#define RPCIF_SMWDR0 0x0040 /* W */
#define RPCIF_SMWDR1 0x0044 /* W */
#define RPCIF_CMNSR 0x0048 /* R */
#define RPCIF_CMNSR_SSLF BIT(1)
#define RPCIF_CMNSR_TEND BIT(0)
#define RPCIF_DRDMCR 0x0058 /* R/W */
#define RPCIF_DMDMCR_DMCYC(v) ((((v) - 1) & 0x1F) << 0)
#define RPCIF_DRDRENR 0x005C /* R/W */
#define RPCIF_DRDRENR_HYPE(v) (((v) & 0x7) << 12)
#define RPCIF_DRDRENR_ADDRE BIT(8)
#define RPCIF_DRDRENR_OPDRE BIT(4)
#define RPCIF_DRDRENR_DRDRE BIT(0)
#define RPCIF_SMDMCR 0x0060 /* R/W */
#define RPCIF_SMDMCR_DMCYC(v) ((((v) - 1) & 0x1F) << 0)
#define RPCIF_SMDRENR 0x0064 /* R/W */
#define RPCIF_SMDRENR_HYPE(v) (((v) & 0x7) << 12)
#define RPCIF_SMDRENR_ADDRE BIT(8)
#define RPCIF_SMDRENR_OPDRE BIT(4)
#define RPCIF_SMDRENR_SPIDRE BIT(0)
#define RPCIF_PHYADD 0x0070 /* R/W available on R-Car E3/D3/V3M and RZ/G2{E,L} */
#define RPCIF_PHYWR 0x0074 /* R/W available on R-Car E3/D3/V3M and RZ/G2{E,L} */
#define RPCIF_PHYCNT 0x007C /* R/W */
#define RPCIF_PHYCNT_CAL BIT(31)
#define RPCIF_PHYCNT_OCTA(v) (((v) & 0x3) << 22)
#define RPCIF_PHYCNT_EXDS BIT(21)
#define RPCIF_PHYCNT_OCT BIT(20)
#define RPCIF_PHYCNT_DDRCAL BIT(19)
#define RPCIF_PHYCNT_HS BIT(18)
#define RPCIF_PHYCNT_CKSEL(v) (((v) & 0x3) << 16) /* valid only for RZ/G2L */
#define RPCIF_PHYCNT_STRTIM(v) (((v) & 0x7) << 15 | ((v) & 0x8) << 24) /* valid for R-Car and RZ/G2{E,H,M,N} */
#define RPCIF_PHYCNT_WBUF2 BIT(4)
#define RPCIF_PHYCNT_WBUF BIT(2)
#define RPCIF_PHYCNT_PHYMEM(v) (((v) & 0x3) << 0)
#define RPCIF_PHYCNT_PHYMEM_MASK GENMASK(1, 0)
#define RPCIF_PHYOFFSET1 0x0080 /* R/W */
#define RPCIF_PHYOFFSET1_DDRTMG(v) (((v) & 0x3) << 28)
#define RPCIF_PHYOFFSET2 0x0084 /* R/W */
#define RPCIF_PHYOFFSET2_OCTTMG(v) (((v) & 0x7) << 8)
#define RPCIF_PHYINT 0x0088 /* R/W */
#define RPCIF_PHYINT_WPVAL BIT(1)
#endif /* __RENESAS_RPC_IF_REGS_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,105 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* RZ xSPI Interface Registers Definitions
*
* Copyright (C) 2025 Renesas Electronics Corporation
*/
#ifndef __RENESAS_XSPI_IF_REGS_H__
#define __RENESAS_XSPI_IF_REGS_H__
#include <linux/bits.h>
/* xSPI Wrapper Configuration Register */
#define XSPI_WRAPCFG 0x0000
/* xSPI Bridge Configuration Register */
#define XSPI_BMCFG 0x0008
#define XSPI_BMCFG_WRMD BIT(0)
#define XSPI_BMCFG_MWRCOMB BIT(7)
#define XSPI_BMCFG_MWRSIZE(val) (((val) & 0xff) << 8)
#define XSPI_BMCFG_PREEN BIT(16)
/* xSPI Command Map Configuration Register 0 CS0 */
#define XSPI_CMCFG0CS0 0x0010
#define XSPI_CMCFG0_FFMT(val) (((val) & 0x03) << 0)
#define XSPI_CMCFG0_ADDSIZE(val) (((val) & 0x03) << 2)
/* xSPI Command Map Configuration Register 1 CS0 */
#define XSPI_CMCFG1CS0 0x0014
#define XSPI_CMCFG1_RDCMD(val) (((val) & 0xffff) << 0)
#define XSPI_CMCFG1_RDCMD_UPPER_BYTE(val) (((val) & 0xff) << 8)
#define XSPI_CMCFG1_RDLATE(val) (((val) & 0x1f) << 16)
/* xSPI Command Map Configuration Register 2 CS0 */
#define XSPI_CMCFG2CS0 0x0018
#define XSPI_CMCFG2_WRCMD(val) (((val) & 0xffff) << 0)
#define XSPI_CMCFG2_WRCMD_UPPER(val) (((val) & 0xff) << 8)
#define XSPI_CMCFG2_WRLATE(val) (((val) & 0x1f) << 16)
/* xSPI Link I/O Configuration Register CS0 */
#define XSPI_LIOCFGCS0 0x0050
#define XSPI_LIOCFG_PRTMD(val) (((val) & 0x3ff) << 0)
#define XSPI_LIOCFG_CSMIN(val) (((val) & 0x0f) << 16)
#define XSPI_LIOCFG_CSASTEX BIT(20)
#define XSPI_LIOCFG_CSNEGEX BIT(21)
/* xSPI Bridge Map Control Register 0 */
#define XSPI_BMCTL0 0x0060
#define XSPI_BMCTL0_CS0ACC(val) (((val) & 0x03) << 0)
/* xSPI Bridge Map Control Register 1 */
#define XSPI_BMCTL1 0x0064
#define XSPI_BMCTL1_MWRPUSH BIT(8)
/* xSPI Command Manual Control Register 0 */
#define XSPI_CDCTL0 0x0070
#define XSPI_CDCTL0_TRREQ BIT(0)
#define XSPI_CDCTL0_CSSEL BIT(3)
#define XSPI_CDCTL0_TRNUM(val) (((val) & 0x03) << 4)
/* xSPI Command Manual Type Buf */
#define XSPI_CDTBUF0 0x0080
#define XSPI_CDTBUF_CMDSIZE(val) (((val) & 0x03) << 0)
#define XSPI_CDTBUF_ADDSIZE(val) (((val) & 0x07) << 2)
#define XSPI_CDTBUF_DATASIZE(val) (((val) & 0x0f) << 5)
#define XSPI_CDTBUF_LATE(val) (((val) & 0x1f) << 9)
#define XSPI_CDTBUF_TRTYPE BIT(15)
#define XSPI_CDTBUF_CMD(val) (((val) & 0xffff) << 16)
#define XSPI_CDTBUF_CMD_FIELD(val) (((val) & 0xff) << 24)
/* xSPI Command Manual Address Buff */
#define XSPI_CDABUF0 0x0084
/* xSPI Command Manual Data 0 Buf */
#define XSPI_CDD0BUF0 0x0088
/* xSPI Command Manual Data 1 Buf */
#define XSPI_CDD1BUF0 0x008c
/* xSPI Command Calibration Control Register 0 CS0 */
#define XSPI_CCCTL0CS0 0x0130
#define XSPI_CCCTL0_CAEN BIT(0)
/* xSPI Interrupt Status Register */
#define XSPI_INTS 0x0190
#define XSPI_INTS_CMDCMP BIT(0)
/* xSPI Interrupt Clear Register */
#define XSPI_INTC 0x0194
#define XSPI_INTC_CMDCMPC BIT(0)
/* xSPI Interrupt Enable Register */
#define XSPI_INTE 0x0198
#define XSPI_INTE_CMDCMPE BIT(0)
/* Maximum data size of MWRSIZE*/
#define MWRSIZE_MAX 64
/* xSPI Protocol mode */
#define PROTO_1S_2S_2S 0x48
#define PROTO_2S_2S_2S 0x49
#define PROTO_1S_4S_4S 0x090
#define PROTO_4S_4S_4S 0x092
#endif /* __RENESAS_XSPI_IF_REGS_H__ */
@@ -0,0 +1,35 @@
# 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 EXYNOS5422_DMC
tristate "Exynos5422 Dynamic Memory Controller driver"
depends on ARCH_EXYNOS || (COMPILE_TEST && HAS_IOMEM)
select DDR
depends on DEVFREQ_GOV_SIMPLE_ONDEMAND
depends on (PM_DEVFREQ && PM_DEVFREQ_EVENT)
help
This adds driver for Samsung Exynos5422 SoC DMC (Dynamic Memory
Controller). The driver provides support for Dynamic Voltage and
Frequency Scaling in DMC and DRAM. It also supports changing timings
of DRAM running with different frequency. The timings are calculated
based on DT memory information.
If unsure, say Y on devices with Samsung Exynos SoCs.
config EXYNOS_SROM
bool "Exynos SROM controller driver" if COMPILE_TEST
depends on (ARM && ARCH_EXYNOS) || (COMPILE_TEST && HAS_IOMEM)
help
This adds driver for Samsung Exynos SoC SROM controller. The driver
in basic operation mode only saves and restores SROM registers
during suspend. If however appropriate device tree configuration
is provided, the driver enables support for external memory
or external devices.
If unsure, say Y on devices with Samsung Exynos SoCs.
endif
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_EXYNOS5422_DMC) += exynos5422-dmc.o
obj-$(CONFIG_EXYNOS_SROM) += exynos-srom.o
@@ -0,0 +1,210 @@
// 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 = kzalloc_objs(*rd, nr_rdump);
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 = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(srom->reg_base)) {
dev_err(&pdev->dev, "iomap of exynos srom controller failed\n");
return PTR_ERR(srom->reg_base);
}
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)
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 %pOFn\n",
child);
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);
@@ -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 */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+469
View File
@@ -0,0 +1,469 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) STMicroelectronics 2025 - All Rights Reserved
* Author(s): Patrice Chotard <patrice.chotard@foss.st.com> for STMicroelectronics.
*/
#include <linux/bitfield.h>
#include <linux/bus/stm32_firewall_device.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#define OMM_CR 0
#define CR_MUXEN BIT(0)
#define CR_MUXENMODE_MASK GENMASK(1, 0)
#define CR_CSSEL_OVR_EN BIT(4)
#define CR_CSSEL_OVR_MASK GENMASK(6, 5)
#define CR_REQ2ACK_MASK GENMASK(23, 16)
#define OMM_CHILD_NB 2
#define OMM_CLK_NB 3
struct stm32_omm {
struct resource *mm_res;
struct clk_bulk_data clk_bulk[OMM_CLK_NB];
struct reset_control *child_reset[OMM_CHILD_NB];
void __iomem *io_base;
u32 cr;
u8 nb_child;
bool restore_omm;
};
static int stm32_omm_set_amcr(struct device *dev, bool set)
{
struct stm32_omm *omm = dev_get_drvdata(dev);
resource_size_t mm_ospi2_size = 0;
static const char * const mm_name[] = { "ospi1", "ospi2" };
struct regmap *syscfg_regmap;
struct device_node *node;
struct resource res, res1;
unsigned int syscon_args[2];
int ret, idx;
unsigned int i, amcr, read_amcr;
for (i = 0; i < omm->nb_child; i++) {
idx = of_property_match_string(dev->of_node,
"memory-region-names",
mm_name[i]);
if (idx < 0)
continue;
/* res1 only used on second loop iteration */
res1.start = res.start;
res1.end = res.end;
node = of_parse_phandle(dev->of_node, "memory-region", idx);
if (!node)
continue;
ret = of_address_to_resource(node, 0, &res);
if (ret) {
of_node_put(node);
dev_err(dev, "unable to resolve memory region\n");
return ret;
}
/* check that memory region fits inside OMM memory map area */
if (!resource_contains(omm->mm_res, &res)) {
dev_err(dev, "%s doesn't fit inside OMM memory map area\n",
mm_name[i]);
dev_err(dev, "%pR doesn't fit inside %pR\n", &res, omm->mm_res);
of_node_put(node);
return -EFAULT;
}
if (i == 1) {
mm_ospi2_size = resource_size(&res);
/* check that OMM memory region 1 doesn't overlap memory region 2 */
if (resource_overlaps(&res, &res1)) {
dev_err(dev, "OMM memory-region %s overlaps memory region %s\n",
mm_name[0], mm_name[1]);
dev_err(dev, "%pR overlaps %pR\n", &res1, &res);
of_node_put(node);
return -EFAULT;
}
}
of_node_put(node);
}
syscfg_regmap = syscon_regmap_lookup_by_phandle_args(dev->of_node, "st,syscfg-amcr",
2, syscon_args);
if (IS_ERR(syscfg_regmap))
return dev_err_probe(dev, PTR_ERR(syscfg_regmap),
"Failed to get st,syscfg-amcr property\n");
amcr = mm_ospi2_size / SZ_64M;
if (set)
regmap_update_bits(syscfg_regmap, syscon_args[0], syscon_args[1], amcr);
/* read AMCR and check coherency with memory-map areas defined in DT */
regmap_read(syscfg_regmap, syscon_args[0], &read_amcr);
read_amcr = read_amcr >> (ffs(syscon_args[1]) - 1);
if (amcr != read_amcr) {
dev_err(dev, "AMCR value not coherent with DT memory-map areas\n");
ret = -EINVAL;
}
return ret;
}
static int stm32_omm_toggle_child_clock(struct device *dev, bool enable)
{
struct stm32_omm *omm = dev_get_drvdata(dev);
int i, ret;
for (i = 0; i < omm->nb_child; i++) {
if (enable) {
ret = clk_prepare_enable(omm->clk_bulk[i + 1].clk);
if (ret) {
dev_err(dev, "Can not enable clock\n");
goto clk_error;
}
} else {
clk_disable_unprepare(omm->clk_bulk[i + 1].clk);
}
}
return 0;
clk_error:
while (i--)
clk_disable_unprepare(omm->clk_bulk[i + 1].clk);
return ret;
}
static int stm32_omm_disable_child(struct device *dev)
{
struct stm32_omm *omm = dev_get_drvdata(dev);
struct reset_control *reset;
int ret;
u8 i;
ret = stm32_omm_toggle_child_clock(dev, true);
if (ret)
return ret;
for (i = 0; i < omm->nb_child; i++) {
/* reset OSPI to ensure CR_EN bit is set to 0 */
reset = omm->child_reset[i];
ret = reset_control_acquire(reset);
if (ret) {
stm32_omm_toggle_child_clock(dev, false);
dev_err(dev, "Can not acquire reset %d\n", ret);
return ret;
}
reset_control_assert(reset);
udelay(2);
reset_control_deassert(reset);
reset_control_release(reset);
}
return stm32_omm_toggle_child_clock(dev, false);
}
static int stm32_omm_configure(struct device *dev)
{
static const char * const clocks_name[] = {"omm", "ospi1", "ospi2"};
struct stm32_omm *omm = dev_get_drvdata(dev);
unsigned long clk_rate_max = 0;
u32 mux = 0;
u32 cssel_ovr = 0;
u32 req2ack = 0;
struct reset_control *rstc;
unsigned long clk_rate;
int ret;
u8 i;
for (i = 0; i < OMM_CLK_NB; i++)
omm->clk_bulk[i].id = clocks_name[i];
/* retrieve OMM, OSPI1 and OSPI2 clocks */
ret = devm_clk_bulk_get(dev, OMM_CLK_NB, omm->clk_bulk);
if (ret)
return dev_err_probe(dev, ret, "Failed to get OMM/OSPI's clocks\n");
/* Ensure both OSPI instance are disabled before configuring OMM */
ret = stm32_omm_disable_child(dev);
if (ret)
return ret;
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
return ret;
/* parse children's clock */
for (i = 1; i <= omm->nb_child; i++) {
clk_rate = clk_get_rate(omm->clk_bulk[i].clk);
if (!clk_rate) {
dev_err(dev, "Invalid clock rate\n");
ret = -EINVAL;
goto error;
}
if (clk_rate > clk_rate_max)
clk_rate_max = clk_rate;
}
rstc = devm_reset_control_get_exclusive(dev, "omm");
if (IS_ERR(rstc)) {
ret = dev_err_probe(dev, PTR_ERR(rstc), "reset get failed\n");
goto error;
}
reset_control_assert(rstc);
udelay(2);
reset_control_deassert(rstc);
omm->cr = readl_relaxed(omm->io_base + OMM_CR);
/* optional */
ret = of_property_read_u32(dev->of_node, "st,omm-mux", &mux);
if (!ret) {
if (mux & CR_MUXEN) {
ret = of_property_read_u32(dev->of_node, "st,omm-req2ack-ns",
&req2ack);
if (!ret && req2ack) {
req2ack = DIV_ROUND_UP(req2ack, NSEC_PER_SEC / clk_rate_max) - 1;
if (req2ack > 256)
req2ack = 256;
}
req2ack = FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
omm->cr &= ~CR_REQ2ACK_MASK;
omm->cr |= FIELD_PREP(CR_REQ2ACK_MASK, req2ack);
/*
* If the mux is enabled, the 2 OSPI clocks have to be
* always enabled
*/
ret = stm32_omm_toggle_child_clock(dev, true);
if (ret)
goto error;
}
omm->cr &= ~CR_MUXENMODE_MASK;
omm->cr |= FIELD_PREP(CR_MUXENMODE_MASK, mux);
}
/* optional */
ret = of_property_read_u32(dev->of_node, "st,omm-cssel-ovr", &cssel_ovr);
if (!ret) {
omm->cr &= ~CR_CSSEL_OVR_MASK;
omm->cr |= FIELD_PREP(CR_CSSEL_OVR_MASK, cssel_ovr);
omm->cr |= CR_CSSEL_OVR_EN;
}
omm->restore_omm = true;
writel_relaxed(omm->cr, omm->io_base + OMM_CR);
ret = stm32_omm_set_amcr(dev, true);
error:
pm_runtime_put_sync_suspend(dev);
return ret;
}
static int stm32_omm_check_access(struct device_node *np)
{
struct stm32_firewall firewall;
int ret;
ret = stm32_firewall_get_firewall(np, &firewall, 1);
if (ret)
return ret;
return stm32_firewall_grant_access(&firewall);
}
static int stm32_omm_probe(struct platform_device *pdev)
{
static const char * const resets_name[] = {"ospi1", "ospi2"};
struct device *dev = &pdev->dev;
u8 child_access_granted = 0;
struct stm32_omm *omm;
int i, ret;
omm = devm_kzalloc(dev, sizeof(*omm), GFP_KERNEL);
if (!omm)
return -ENOMEM;
omm->io_base = devm_platform_ioremap_resource_byname(pdev, "regs");
if (IS_ERR(omm->io_base))
return PTR_ERR(omm->io_base);
omm->mm_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "memory_map");
if (!omm->mm_res)
return -ENODEV;
/* check child's access */
for_each_child_of_node_scoped(dev->of_node, child) {
if (omm->nb_child >= OMM_CHILD_NB) {
dev_err(dev, "Bad DT, found too much children\n");
return -E2BIG;
}
ret = stm32_omm_check_access(child);
if (ret < 0 && ret != -EACCES)
return ret;
if (!ret)
child_access_granted++;
omm->nb_child++;
}
if (omm->nb_child != OMM_CHILD_NB)
return -EINVAL;
platform_set_drvdata(pdev, omm);
devm_pm_runtime_enable(dev);
/* check if OMM's resource access is granted */
ret = stm32_omm_check_access(dev->of_node);
if (ret < 0 && ret != -EACCES)
return ret;
for (i = 0; i < omm->nb_child; i++) {
omm->child_reset[i] = devm_reset_control_get_exclusive_released(dev,
resets_name[i]);
if (IS_ERR(omm->child_reset[i]))
return dev_err_probe(dev, PTR_ERR(omm->child_reset[i]),
"Can't get %s reset\n", resets_name[i]);
}
if (!ret && child_access_granted == OMM_CHILD_NB) {
ret = stm32_omm_configure(dev);
if (ret)
return ret;
} else {
dev_dbg(dev, "Octo Memory Manager resource's access not granted\n");
/*
* AMCR can't be set, so check if current value is coherent
* with memory-map areas defined in DT
*/
ret = stm32_omm_set_amcr(dev, false);
if (ret)
return ret;
}
ret = devm_of_platform_populate(dev);
if (ret) {
if (omm->cr & CR_MUXEN)
stm32_omm_toggle_child_clock(&pdev->dev, false);
return dev_err_probe(dev, ret, "Failed to create Octo Memory Manager child\n");
}
return 0;
}
static void stm32_omm_remove(struct platform_device *pdev)
{
struct stm32_omm *omm = platform_get_drvdata(pdev);
if (omm->cr & CR_MUXEN)
stm32_omm_toggle_child_clock(&pdev->dev, false);
}
static const struct of_device_id stm32_omm_of_match[] = {
{ .compatible = "st,stm32mp25-omm", },
{}
};
MODULE_DEVICE_TABLE(of, stm32_omm_of_match);
static int __maybe_unused stm32_omm_runtime_suspend(struct device *dev)
{
struct stm32_omm *omm = dev_get_drvdata(dev);
clk_disable_unprepare(omm->clk_bulk[0].clk);
return 0;
}
static int __maybe_unused stm32_omm_runtime_resume(struct device *dev)
{
struct stm32_omm *omm = dev_get_drvdata(dev);
return clk_prepare_enable(omm->clk_bulk[0].clk);
}
static int __maybe_unused stm32_omm_suspend(struct device *dev)
{
struct stm32_omm *omm = dev_get_drvdata(dev);
if (omm->restore_omm && omm->cr & CR_MUXEN)
stm32_omm_toggle_child_clock(dev, false);
return pinctrl_pm_select_sleep_state(dev);
}
static int __maybe_unused stm32_omm_resume(struct device *dev)
{
struct stm32_omm *omm = dev_get_drvdata(dev);
int ret;
pinctrl_pm_select_default_state(dev);
if (!omm->restore_omm)
return 0;
/* Ensure both OSPI instance are disabled before configuring OMM */
ret = stm32_omm_disable_child(dev);
if (ret)
return ret;
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
return ret;
writel_relaxed(omm->cr, omm->io_base + OMM_CR);
ret = stm32_omm_set_amcr(dev, true);
pm_runtime_put_sync_suspend(dev);
if (ret)
return ret;
if (omm->cr & CR_MUXEN)
ret = stm32_omm_toggle_child_clock(dev, true);
return ret;
}
static const struct dev_pm_ops stm32_omm_pm_ops = {
SET_RUNTIME_PM_OPS(stm32_omm_runtime_suspend,
stm32_omm_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(stm32_omm_suspend, stm32_omm_resume)
};
static struct platform_driver stm32_omm_driver = {
.probe = stm32_omm_probe,
.remove = stm32_omm_remove,
.driver = {
.name = "stm32-omm",
.of_match_table = stm32_omm_of_match,
.pm = &stm32_omm_pm_ops,
},
};
module_platform_driver(stm32_omm_driver);
MODULE_DESCRIPTION("STMicroelectronics Octo Memory Manager driver");
MODULE_LICENSE("GPL");
+83
View File
@@ -0,0 +1,83 @@
# SPDX-License-Identifier: GPL-2.0-only
config TEGRA_MC
bool "NVIDIA Tegra Memory Controller support"
default ARCH_TEGRA
depends on ARCH_TEGRA || (COMPILE_TEST && COMMON_CLK)
select INTERCONNECT
help
This driver supports the Memory Controller (MC) hardware found on
NVIDIA Tegra SoCs.
if TEGRA_MC
config TEGRA20_EMC
tristate "NVIDIA Tegra20 External Memory Controller driver"
default ARCH_TEGRA_2x_SOC
depends on ARCH_TEGRA_2x_SOC || COMPILE_TEST
select DEVFREQ_GOV_SIMPLE_ONDEMAND
select PM_DEVFREQ
select DDR
select TEGRA_EMC_COMMON
help
This driver is for the External Memory Controller (EMC) found on
Tegra20 chips. The EMC controls the external DRAM on the board.
This driver is required to change memory timings / clock rate for
external memory.
config TEGRA30_EMC
tristate "NVIDIA Tegra30 External Memory Controller driver"
default ARCH_TEGRA_3x_SOC
depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST
select PM_OPP
select DDR
select TEGRA_EMC_COMMON
help
This driver is for the External Memory Controller (EMC) found on
Tegra30 chips. The EMC controls the external DRAM on the board.
This driver is required to change memory timings / clock rate for
external memory.
config TEGRA114_EMC
tristate "NVIDIA Tegra114 External Memory Controller driver"
default ARCH_TEGRA_114_SOC
depends on ARCH_TEGRA_114_SOC || COMPILE_TEST
select TEGRA124_CLK_EMC if ARCH_TEGRA
select PM_OPP
select TEGRA_EMC_COMMON
help
This driver is for the External Memory Controller (EMC) found on
Tegra114 chips. The EMC controls the external DRAM on the board.
This driver is required to change memory timings / clock rate for
external memory.
config TEGRA124_EMC
tristate "NVIDIA Tegra124 External Memory Controller driver"
default ARCH_TEGRA_124_SOC
depends on ARCH_TEGRA_124_SOC || COMPILE_TEST
select TEGRA124_CLK_EMC if ARCH_TEGRA
select PM_OPP
select TEGRA_EMC_COMMON
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.
config TEGRA210_EMC_TABLE
bool
depends on ARCH_TEGRA_210_SOC || COMPILE_TEST
config TEGRA210_EMC
tristate "NVIDIA Tegra210 External Memory Controller driver"
depends on ARCH_TEGRA_210_SOC || COMPILE_TEST
select TEGRA210_EMC_TABLE
help
This driver is for the External Memory Controller (EMC) found on
Tegra210 chips. The EMC controls the external DRAM on the board.
This driver is required to change memory timings / clock rate for
external memory.
config TEGRA_EMC_COMMON
tristate
endif
+30
View File
@@ -0,0 +1,30 @@
# 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
tegra-mc-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o
tegra-mc-$(CONFIG_ARCH_TEGRA_194_SOC) += tegra186.o tegra194.o
tegra-mc-$(CONFIG_ARCH_TEGRA_234_SOC) += tegra186.o tegra234.o
tegra-mc-$(CONFIG_ARCH_TEGRA_238_SOC) += tegra186.o tegra234.o tegra238.o
tegra-mc-$(CONFIG_ARCH_TEGRA_264_SOC) += tegra186.o tegra264.o
obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
obj-$(CONFIG_TEGRA_EMC_COMMON) += tegra-emc-common.o
obj-$(CONFIG_TEGRA20_EMC) += tegra20-emc.o
obj-$(CONFIG_TEGRA30_EMC) += tegra30-emc.o
obj-$(CONFIG_TEGRA114_EMC) += tegra114-emc.o
obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o
obj-$(CONFIG_TEGRA210_EMC_TABLE) += tegra210-emc-table.o
obj-$(CONFIG_TEGRA210_EMC) += tegra210-emc.o
obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186-emc.o
obj-$(CONFIG_ARCH_TEGRA_194_SOC) += tegra186-emc.o
obj-$(CONFIG_ARCH_TEGRA_234_SOC) += tegra186-emc.o
obj-$(CONFIG_ARCH_TEGRA_264_SOC) += tegra186-emc.o
tegra210-emc-y := tegra210-emc-core.o tegra210-emc-cc-r21021.o
File diff suppressed because it is too large Load Diff
+285
View File
@@ -0,0 +1,285 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2014-2026 NVIDIA CORPORATION. All rights reserved.
*/
#ifndef MEMORY_TEGRA_MC_H
#define MEMORY_TEGRA_MC_H
#include <linux/bits.h>
#include <linux/io.h>
#include <linux/types.h>
#include <soc/tegra/mc.h>
#define MC_INTSTATUS 0x00
/* Bit field of MC_INTSTATUS register */
#define MC_INT_DECERR_EMEM BIT(6)
#define MC_INT_INVALID_GART_PAGE BIT(7)
#define MC_INT_SECURITY_VIOLATION BIT(8)
#define MC_INT_ARBITRATION_EMEM BIT(9)
#define MC_INT_INVALID_SMMU_PAGE BIT(10)
#define MC_INT_INVALID_APB_ASID_UPDATE BIT(11)
#define MC_INT_DECERR_VPR BIT(12)
#define MC_INT_SECERR_SEC BIT(13)
#define MC_INT_DECERR_MTS BIT(16)
#define MC_INT_DECERR_GENERALIZED_CARVEOUT BIT(17)
#define MC_INT_DECERR_ROUTE_SANITY BIT(20)
#define MC_INT_DECERR_ROUTE_SANITY_GIC_MSI BIT(21)
#define MC_INTMASK 0x04
#define MC_GART_ERROR_REQ 0x30
#define MC_EMEM_ADR_CFG 0x54
#define MC_EMEM_ADR_CFG_EMEM_NUMDEV BIT(0)
#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)
#define MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE_MASK 0x1ff
#define MC_EMEM_ARB_OUTSTANDING_REQ 0x94
#define MC_EMEM_ARB_OUTSTANDING_REQ_HOLDOFF_OVERRIDE BIT(30)
#define MC_EMEM_ARB_OUTSTANDING_REQ_LIMIT_ENABLE BIT(31)
#define MC_EMEM_ARB_OUTSTANDING_REQ_MAX_MASK 0x1ff
#define MC_EMEM_ARB_TIMING_RCD 0x98
#define MC_EMEM_ARB_TIMING_RP 0x9c
#define MC_EMEM_ARB_TIMING_RC 0xa0
#define MC_EMEM_ARB_TIMING_RAS 0xa4
#define MC_EMEM_ARB_TIMING_FAW 0xa8
#define MC_EMEM_ARB_TIMING_RRD 0xac
#define MC_EMEM_ARB_TIMING_RAP2PRE 0xb0
#define MC_EMEM_ARB_TIMING_WAP2PRE 0xb4
#define MC_EMEM_ARB_TIMING_R2R 0xb8
#define MC_EMEM_ARB_TIMING_W2W 0xbc
#define MC_EMEM_ARB_TIMING_R2W 0xc0
#define MC_EMEM_ARB_TIMING_W2R 0xc4
#define MC_EMEM_ARB_MISC2 0xc8
#define MC_EMEM_ARB_DA_TURNS 0xd0
#define MC_EMEM_ARB_DA_COVERS 0xd4
#define MC_EMEM_ARB_MISC0 0xd8
#define MC_EMEM_ARB_MISC1 0xdc
#define MC_EMEM_ARB_RING1_THROTTLE 0xe0
#define MC_EMEM_ARB_OVERRIDE 0xe8
#define MC_EMEM_ARB_OVERRIDE_EACK_MASK 0x3
#define MC_TIMING_CONTROL_DBG 0xf8
#define MC_TIMING_CONTROL 0xfc
#define MC_TIMING_UPDATE BIT(0)
#define MC_GLOBAL_INTSTATUS 0xf24
/* Bit field of MC_ERR_STATUS_0 register */
#define MC_ERR_STATUS_RW BIT(16)
#define MC_ERR_STATUS_SECURITY BIT(17)
#define MC_ERR_STATUS_NONSECURE BIT(25)
#define MC_ERR_STATUS_WRITABLE BIT(26)
#define MC_ERR_STATUS_READABLE BIT(27)
#define MC_ERR_STATUS_GSC_ADR_HI_MASK 0xffff
#define MC_ERR_STATUS_GSC_ADR_HI_SHIFT 16
#define MC_ERR_STATUS_RT_ADR_HI_SHIFT 15
#define MC_ERR_STATUS_TYPE_SHIFT 28
#define MC_ERR_STATUS_TYPE_INVALID_SMMU_PAGE (0x6 << 28)
#define MC_ERR_STATUS_RT_TYPE_MASK (0xf << 28)
#define MC_ERR_STATUS_RT_TYPE_SHIFT 28
#define MC_ERR_STATUS_ADR_HI_SHIFT 20
#define MC_BROADCAST_CHANNEL ~0
/* Tegra264 specific registers */
/* Registers for MSS HUB */
#define MSS_HUB_GLOBAL_INTSTATUS_0 0x6000
#define MSS_HUBC_INTR BIT(0)
#define MSS_HUB_GLOBAL_MASK 0x7F00
#define MSS_HUB_GLOBAL_SHIFT 8
#define MSS_HUB_HUBC_INTSTATUS_0 0x6008
#define MSS_HUB_INTRSTATUS_0 0x600c
#define MSS_HUB_HUBC_INTMASK_0 0x6010
#define MSS_HUB_HUBC_SCRUB_DONE_INTMASK BIT(0)
#define MSS_HUB_HUBC_INTPRIORITY_0 0x6014
#define MSS_HUB_INTRMASK_0 0x6018
#define MSS_HUB_COALESCER_ERR_INTMASK BIT(0)
#define MSS_HUB_SMMU_BYPASS_ALLOW_ERR_INTMASK BIT(1)
#define MSS_HUB_ILLEGAL_TBUGRP_ID_INTMASK BIT(2)
#define MSS_HUB_MSI_ERR_INTMASK BIT(3)
#define MSS_HUB_POISON_RSP_INTMASK BIT(4)
#define MSS_HUB_RESTRICTED_ACCESS_ERR_INTMASK BIT(5)
#define MSS_HUB_RESERVED_PA_ERR_INTMASK BIT(6)
#define MSS_HUB_INTRPRIORITY_0 0x601c
#define MSS_HUB_SMMU_BYPASS_ALLOW_ERR_STATUS_0 0x6020
#define MSS_HUB_MSI_ERR_STATUS_0 0x6024
#define MSS_HUB_POISON_RSP_STATUS_0 0x6028
#define MSS_HUB_COALESCE_ERR_STATUS_0 0x60e0
#define MSS_HUB_COALESCE_ERR_ADR_HI_0 0x60e4
#define MSS_HUB_COALESCE_ERR_ADR_0 0x60e8
#define MSS_HUB_RESTRICTED_ACCESS_ERR_STATUS_0 0x638c
#define MSS_HUB_RESERVED_PA_ERR_STATUS_0 0x6390
#define MSS_HUB_ILLEGAL_TBUGRP_ID_ERR_STATUS_0 0x63b0
/* Registers for channels */
#define MC_CH_INTSTATUS_0 0x82d4
#define MC_CH_INTMASK_0 0x82d8
#define WCAM_ERR_INTMASK BIT(19)
#define MC_ERR_GENERALIZED_CARVEOUT_STATUS_1_0 0xbc74
/* Registers for MCF */
#define MCF_COMMON_INTSTATUS0_0_0 0xce04
#define MCF_INTSTATUS_0 0xce2c
#define MCF_INTMASK_0 0xce30
#define MCF_INTPRIORITY_0 0xce34
/* Registers for SBS */
#define MSS_SBS_INTSTATUS_0 0xec08
#define MSS_SBS_INTMASK_0 0xec0c
#define MSS_SBS_FILL_FIFO_ISO_OVERFLOW_INTMASK BIT(0)
#define MSS_SBS_FILL_FIFO_SISO_OVERFLOW_INTMASK BIT(1)
#define MSS_SBS_FILL_FIFO_NISO_OVERFLOW_INTMASK BIT(2)
/* Bit field of MC_ERR_ROUTE_SANITY_STATUS_0 register */
#define MC_ERR_ROUTE_SANITY_RW BIT(12)
#define MC_ERR_ROUTE_SANITY_SEC BIT(13)
#define ERR_GENERALIZED_APERTURE_ID_SHIFT 0
#define ERR_GENERALIZED_APERTURE_ID_MASK 0x1F
#define ERR_GENERALIZED_CARVEOUT_APERTURE_ID_SHIFT 5
#define ERR_GENERALIZED_CARVEOUT_APERTURE_ID_MASK 0x1F
static inline u32 tegra_mc_scale_percents(u64 val, unsigned int percents)
{
val = val * percents;
do_div(val, 100);
return min_t(u64, val, U32_MAX);
}
static inline struct tegra_mc *
icc_provider_to_tegra_mc(struct icc_provider *provider)
{
return container_of(provider, struct tegra_mc, provider);
}
static inline u32 mc_ch_readl(const struct tegra_mc *mc, int ch,
unsigned long offset)
{
if (!mc->bcast_ch_regs)
return 0;
if (ch == MC_BROADCAST_CHANNEL)
return readl_relaxed(mc->bcast_ch_regs + offset);
return readl_relaxed(mc->ch_regs[ch] + offset);
}
static inline void mc_ch_writel(const struct tegra_mc *mc, int ch,
u32 value, unsigned long offset)
{
if (!mc->bcast_ch_regs)
return;
if (ch == MC_BROADCAST_CHANNEL)
writel_relaxed(value, mc->bcast_ch_regs + offset);
else
writel_relaxed(value, mc->ch_regs[ch] + offset);
}
static inline u32 mc_readl(const struct tegra_mc *mc, unsigned long offset)
{
return readl_relaxed(mc->regs + offset);
}
static inline void mc_writel(const struct tegra_mc *mc, u32 value,
unsigned long offset)
{
writel_relaxed(value, mc->regs + offset);
}
extern const struct tegra_mc_reset_ops tegra_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
#ifdef CONFIG_ARCH_TEGRA_186_SOC
extern const struct tegra_mc_soc tegra186_mc_soc;
#endif
#ifdef CONFIG_ARCH_TEGRA_194_SOC
extern const struct tegra_mc_soc tegra194_mc_soc;
#endif
#ifdef CONFIG_ARCH_TEGRA_234_SOC
extern const struct tegra_mc_soc tegra234_mc_soc;
#endif
#if defined(CONFIG_ARCH_TEGRA_234_SOC) || defined(CONFIG_ARCH_TEGRA_238_SOC)
extern const struct tegra_mc_icc_ops tegra234_mc_icc_ops;
#endif
#ifdef CONFIG_ARCH_TEGRA_238_SOC
extern const struct tegra_mc_soc tegra238_mc_soc;
#endif
#ifdef CONFIG_ARCH_TEGRA_264_SOC
extern const struct tegra_mc_soc tegra264_mc_soc;
#endif
#if defined(CONFIG_ARCH_TEGRA_3x_SOC) || \
defined(CONFIG_ARCH_TEGRA_114_SOC) || \
defined(CONFIG_ARCH_TEGRA_124_SOC) || \
defined(CONFIG_ARCH_TEGRA_132_SOC) || \
defined(CONFIG_ARCH_TEGRA_210_SOC)
int tegra30_mc_probe(struct tegra_mc *mc);
extern const struct tegra_mc_ops tegra30_mc_ops;
#endif
#if defined(CONFIG_ARCH_TEGRA_186_SOC) || \
defined(CONFIG_ARCH_TEGRA_194_SOC) || \
defined(CONFIG_ARCH_TEGRA_234_SOC) || \
defined(CONFIG_ARCH_TEGRA_238_SOC) || \
defined(CONFIG_ARCH_TEGRA_264_SOC)
extern const struct tegra_mc_ops tegra186_mc_ops;
#endif
irqreturn_t tegra30_mc_handle_irq(int irq, void *data);
extern const irq_handler_t tegra30_mc_irq_handlers[1];
extern const char * const tegra_mc_status_names[32];
extern const char * const tegra20_mc_error_names[8];
/*
* These IDs are for internal use of Tegra ICC drivers. The ID numbers are
* chosen such that they don't conflict with the device-tree ICC node IDs.
*/
#define TEGRA_ICC_MC 1000
#define TEGRA_ICC_EMC 1001
#define TEGRA_ICC_EMEM 1002
#endif /* MEMORY_TEGRA_MC_H */
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm_opp.h>
#include "tegra-emc-common.h"
/**
* tegra_emc_rate_requests_init() - Initialize EMC rate request tracking
* @reqs: struct tegra_emc_rate_requests to initialize.
* @dev: EMC device.
*
* Initializes the rate request tracking state with default state
* (no active requests). Must be called before using @reqs with
* other functions.
*/
void tegra_emc_rate_requests_init(struct tegra_emc_rate_requests *reqs,
struct device *dev)
{
unsigned int i;
mutex_init(&reqs->rate_lock);
reqs->dev = dev;
for (i = 0; i < TEGRA_EMC_RATE_TYPE_MAX; i++) {
reqs->requested_rate[i].min_rate = 0;
reqs->requested_rate[i].max_rate = ULONG_MAX;
}
}
EXPORT_SYMBOL_GPL(tegra_emc_rate_requests_init);
/* Caller must hold reqs->rate_lock. */
static int tegra_emc_request_rate(struct tegra_emc_rate_requests *reqs,
unsigned long new_min_rate,
unsigned long new_max_rate,
enum tegra_emc_rate_request_type type)
{
struct tegra_emc_rate_request *req = reqs->requested_rate;
unsigned long min_rate = 0, max_rate = ULONG_MAX;
unsigned int i;
int err;
lockdep_assert_held(&reqs->rate_lock);
/* select minimum and maximum rates among the requested rates */
for (i = 0; i < TEGRA_EMC_RATE_TYPE_MAX; i++, req++) {
if (i == type) {
min_rate = max(new_min_rate, min_rate);
max_rate = min(new_max_rate, max_rate);
} else {
min_rate = max(req->min_rate, min_rate);
max_rate = min(req->max_rate, max_rate);
}
}
if (min_rate > max_rate) {
dev_err_ratelimited(reqs->dev, "%s: type %u: out of range: %lu %lu\n",
__func__, type, min_rate, max_rate);
return -ERANGE;
}
/*
* EMC rate-changes should go via OPP API because it manages voltage
* changes.
*/
err = dev_pm_opp_set_rate(reqs->dev, min_rate);
if (err)
return err;
reqs->requested_rate[type].min_rate = new_min_rate;
reqs->requested_rate[type].max_rate = new_max_rate;
return 0;
}
/**
* tegra_emc_set_min_rate() - Update minimum rate request for a request type
* @reqs: rate request tracking state
* @rate: new minimum rate in Hz requested by @type
* @type: type of request
*
* Records @rate as the new minimum rate request for @type, recalculates target
* rate based on all requests and applies new rate through the OPP API.
*
* Context: Sleeps. Requests to same @reqs are synchronized via mutex.
*
* Return:
* * %0 - success
* * %-ERANGE - request would cause minimum rate request to be higher than
* maximum rate request
* * other - setting new rate failed
*/
int tegra_emc_set_min_rate(struct tegra_emc_rate_requests *reqs,
unsigned long rate,
enum tegra_emc_rate_request_type type)
{
struct tegra_emc_rate_request *req = &reqs->requested_rate[type];
int ret;
mutex_lock(&reqs->rate_lock);
ret = tegra_emc_request_rate(reqs, rate, req->max_rate, type);
mutex_unlock(&reqs->rate_lock);
return ret;
}
EXPORT_SYMBOL_GPL(tegra_emc_set_min_rate);
/**
* tegra_emc_set_max_rate() - Update maximum rate request for a request type
* @reqs: rate request tracking state
* @rate: new maximum rate in Hz requested by @type
* @type: type of request
*
* Records @rate as the new maximum rate request for @type, recalculates target
* rate based on all requests and applies new rate through the OPP API.
*
* Context: Sleeps. Requests to same @reqs are synchronized via mutex.
*
* Return:
* * %0 - success
* * %-ERANGE - request would cause minimum rate request to be higher than
* maximum rate request
* * other - setting new rate failed
*/
int tegra_emc_set_max_rate(struct tegra_emc_rate_requests *reqs,
unsigned long rate,
enum tegra_emc_rate_request_type type)
{
struct tegra_emc_rate_request *req = &reqs->requested_rate[type];
int ret;
mutex_lock(&reqs->rate_lock);
ret = tegra_emc_request_rate(reqs, req->min_rate, rate, type);
mutex_unlock(&reqs->rate_lock);
return ret;
}
EXPORT_SYMBOL_GPL(tegra_emc_set_max_rate);
MODULE_DESCRIPTION("NVIDIA Tegra EMC common code");
MODULE_LICENSE("GPL");
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef TEGRA_EMC_COMMON_H
#define TEGRA_EMC_COMMON_H
#include <linux/device.h>
#include <linux/mutex.h>
/**
* enum tegra_emc_rate_request_type - source of rate request
* @TEGRA_EMC_RATE_DEVFREQ: rate requested by devfreq governor
* @TEGRA_EMC_RATE_DEBUG: rate requested through debugfs knobs
* @TEGRA_EMC_RATE_ICC: rate requested by ICC framework
* @TEGRA_EMC_RATE_TYPE_MAX: number of valid request types
*/
enum tegra_emc_rate_request_type {
TEGRA_EMC_RATE_DEVFREQ,
TEGRA_EMC_RATE_DEBUG,
TEGRA_EMC_RATE_ICC,
TEGRA_EMC_RATE_TYPE_MAX,
};
struct tegra_emc_rate_request {
unsigned long min_rate;
unsigned long max_rate;
};
struct tegra_emc_rate_requests {
struct tegra_emc_rate_request requested_rate[TEGRA_EMC_RATE_TYPE_MAX];
/* Protects @requested_rate. */
struct mutex rate_lock;
struct device *dev;
};
void tegra_emc_rate_requests_init(struct tegra_emc_rate_requests *reqs,
struct device *dev);
int tegra_emc_set_min_rate(struct tegra_emc_rate_requests *reqs,
unsigned long rate,
enum tegra_emc_rate_request_type type);
int tegra_emc_set_max_rate(struct tegra_emc_rate_requests *reqs,
unsigned long rate,
enum tegra_emc_rate_request_type type);
#endif /* TEGRA_EMC_COMMON_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,419 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2019-2025 NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <soc/tegra/bpmp.h>
#include "mc.h"
struct tegra186_emc_dvfs {
unsigned long latency;
unsigned long rate;
};
struct tegra186_emc {
struct tegra_bpmp *bpmp;
struct device *dev;
struct clk *clk;
struct clk *clk_dbb;
struct tegra186_emc_dvfs *dvfs;
unsigned int num_dvfs;
struct {
struct dentry *root;
unsigned long min_rate;
unsigned long max_rate;
} debugfs;
struct icc_provider provider;
};
/*
* debugfs interface
*
* The memory controller driver exposes some files in debugfs that can be used
* to control the EMC frequency. The top-level directory can be found here:
*
* /sys/kernel/debug/emc
*
* It contains the following files:
*
* - available_rates: This file contains a list of valid, space-separated
* EMC frequencies.
*
* - min_rate: Writing a value to this file sets the given frequency as the
* floor of the permitted range. If this is higher than the currently
* configured EMC frequency, this will cause the frequency to be
* increased so that it stays within the valid range.
*
* - max_rate: Similarily to the min_rate file, writing a value to this file
* sets the given frequency as the ceiling of the permitted range. If
* the value is lower than the currently configured EMC frequency, this
* will cause the frequency to be decreased so that it stays within the
* valid range.
*/
static bool tegra186_emc_validate_rate(struct tegra186_emc *emc,
unsigned long rate)
{
unsigned int i;
for (i = 0; i < emc->num_dvfs; i++)
if (rate == emc->dvfs[i].rate)
return true;
return false;
}
static int tegra186_emc_debug_available_rates_show(struct seq_file *s,
void *data)
{
struct tegra186_emc *emc = s->private;
const char *prefix = "";
unsigned int i;
for (i = 0; i < emc->num_dvfs; i++) {
seq_printf(s, "%s%lu", prefix, emc->dvfs[i].rate);
prefix = " ";
}
seq_puts(s, "\n");
return 0;
}
DEFINE_SHOW_ATTRIBUTE(tegra186_emc_debug_available_rates);
static int tegra186_emc_debug_min_rate_get(void *data, u64 *rate)
{
struct tegra186_emc *emc = data;
*rate = emc->debugfs.min_rate;
return 0;
}
static int tegra186_emc_debug_min_rate_set(void *data, u64 rate)
{
struct tegra186_emc *emc = data;
int err;
if (!tegra186_emc_validate_rate(emc, rate))
return -EINVAL;
err = clk_set_min_rate(emc->clk, rate);
if (err < 0)
return err;
emc->debugfs.min_rate = rate;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(tegra186_emc_debug_min_rate_fops,
tegra186_emc_debug_min_rate_get,
tegra186_emc_debug_min_rate_set, "%llu\n");
static int tegra186_emc_debug_max_rate_get(void *data, u64 *rate)
{
struct tegra186_emc *emc = data;
*rate = emc->debugfs.max_rate;
return 0;
}
static int tegra186_emc_debug_max_rate_set(void *data, u64 rate)
{
struct tegra186_emc *emc = data;
int err;
if (!tegra186_emc_validate_rate(emc, rate))
return -EINVAL;
err = clk_set_max_rate(emc->clk, rate);
if (err < 0)
return err;
emc->debugfs.max_rate = rate;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(tegra186_emc_debug_max_rate_fops,
tegra186_emc_debug_max_rate_get,
tegra186_emc_debug_max_rate_set, "%llu\n");
static int tegra186_emc_get_emc_dvfs_latency(struct tegra186_emc *emc)
{
struct mrq_emc_dvfs_latency_response response;
struct tegra_bpmp_message msg;
unsigned int i;
int err;
memset(&msg, 0, sizeof(msg));
msg.mrq = MRQ_EMC_DVFS_LATENCY;
msg.tx.data = NULL;
msg.tx.size = 0;
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_transfer(emc->bpmp, &msg);
if (err < 0) {
dev_err(emc->dev, "failed to EMC DVFS pairs: %d\n", err);
return err;
}
if (msg.rx.ret < 0) {
dev_err(emc->dev, "EMC DVFS MRQ failed: %d (BPMP error code)\n", msg.rx.ret);
return -EINVAL;
}
emc->debugfs.min_rate = ULONG_MAX;
emc->debugfs.max_rate = 0;
emc->num_dvfs = response.num_pairs;
emc->dvfs = devm_kmalloc_array(emc->dev, emc->num_dvfs, sizeof(*emc->dvfs), GFP_KERNEL);
if (!emc->dvfs)
return -ENOMEM;
dev_dbg(emc->dev, "%u DVFS pairs:\n", emc->num_dvfs);
for (i = 0; i < emc->num_dvfs; i++) {
emc->dvfs[i].rate = response.pairs[i].freq * 1000;
emc->dvfs[i].latency = response.pairs[i].latency;
if (emc->dvfs[i].rate < emc->debugfs.min_rate)
emc->debugfs.min_rate = emc->dvfs[i].rate;
if (emc->dvfs[i].rate > emc->debugfs.max_rate)
emc->debugfs.max_rate = emc->dvfs[i].rate;
dev_dbg(emc->dev, " %2u: %lu Hz -> %lu us\n", i,
emc->dvfs[i].rate, emc->dvfs[i].latency);
}
err = clk_set_rate_range(emc->clk, emc->debugfs.min_rate, emc->debugfs.max_rate);
if (err < 0) {
dev_err(emc->dev, "failed to set rate range [%lu-%lu] for %pC\n",
emc->debugfs.min_rate, emc->debugfs.max_rate, emc->clk);
return err;
}
emc->debugfs.root = debugfs_create_dir("emc", NULL);
debugfs_create_file("available_rates", 0444, emc->debugfs.root, emc,
&tegra186_emc_debug_available_rates_fops);
debugfs_create_file("min_rate", 0644, emc->debugfs.root, emc,
&tegra186_emc_debug_min_rate_fops);
debugfs_create_file("max_rate", 0644, emc->debugfs.root, emc,
&tegra186_emc_debug_max_rate_fops);
return 0;
}
/*
* tegra186_emc_icc_set_bw() - Set BW api for EMC provider
* @src: ICC node for External Memory Controller (EMC)
* @dst: ICC node for External Memory (DRAM)
*
* Do nothing here as info to BPMP-FW is now passed in the BW set function
* of the MC driver. BPMP-FW sets the final Freq based on the passed values.
*/
static int tegra186_emc_icc_set_bw(struct icc_node *src, struct icc_node *dst)
{
return 0;
}
static struct icc_node *
tegra186_emc_of_icc_xlate(const struct of_phandle_args *spec, void *data)
{
struct icc_provider *provider = data;
struct icc_node *node;
/* External Memory is the only possible ICC route */
list_for_each_entry(node, &provider->nodes, node_list) {
if (node->id != TEGRA_ICC_EMEM)
continue;
return node;
}
return ERR_PTR(-EPROBE_DEFER);
}
static int tegra186_emc_icc_get_init_bw(struct icc_node *node, u32 *avg, u32 *peak)
{
*avg = 0;
*peak = 0;
return 0;
}
static int tegra186_emc_interconnect_init(struct tegra186_emc *emc)
{
struct icc_node *node;
int err;
emc->provider.dev = emc->dev;
emc->provider.set = tegra186_emc_icc_set_bw;
emc->provider.data = &emc->provider;
emc->provider.aggregate = icc_std_aggregate;
emc->provider.xlate = tegra186_emc_of_icc_xlate;
emc->provider.get_bw = tegra186_emc_icc_get_init_bw;
icc_provider_init(&emc->provider);
/* create External Memory Controller node */
node = icc_node_create(TEGRA_ICC_EMC);
if (IS_ERR(node))
return PTR_ERR(node);
node->name = "External Memory Controller";
icc_node_add(node, &emc->provider);
/* link External Memory Controller to External Memory (DRAM) */
err = icc_link_create(node, TEGRA_ICC_EMEM);
if (err)
goto remove_nodes;
/* create External Memory node */
node = icc_node_create(TEGRA_ICC_EMEM);
if (IS_ERR(node)) {
err = PTR_ERR(node);
goto remove_nodes;
}
node->name = "External Memory (DRAM)";
icc_node_add(node, &emc->provider);
err = icc_provider_register(&emc->provider);
if (err)
goto remove_nodes;
return 0;
remove_nodes:
icc_nodes_remove(&emc->provider);
return dev_err_probe(emc->dev, err, "failed to initialize ICC\n");
}
static int tegra186_emc_probe(struct platform_device *pdev)
{
struct tegra_mc *mc = dev_get_drvdata(pdev->dev.parent);
struct tegra186_emc *emc;
int err;
emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL);
if (!emc)
return -ENOMEM;
emc->bpmp = tegra_bpmp_get(&pdev->dev);
if (IS_ERR(emc->bpmp))
return dev_err_probe(&pdev->dev, PTR_ERR(emc->bpmp),
"failed to get BPMP\n");
emc->clk = devm_clk_get(&pdev->dev, "emc");
if (IS_ERR(emc->clk)) {
err = dev_err_probe(&pdev->dev, PTR_ERR(emc->clk),
"failed to get EMC clock\n");
goto put_bpmp;
}
emc->clk_dbb = devm_clk_get_optional_enabled(&pdev->dev, "dbb");
if (IS_ERR(emc->clk_dbb)) {
err = dev_err_probe(&pdev->dev, PTR_ERR(emc->clk_dbb),
"failed to get DBB clock\n");
goto put_bpmp;
}
platform_set_drvdata(pdev, emc);
emc->dev = &pdev->dev;
if (tegra_bpmp_mrq_is_supported(emc->bpmp, MRQ_EMC_DVFS_LATENCY)) {
err = tegra186_emc_get_emc_dvfs_latency(emc);
if (err)
goto put_bpmp;
}
if (mc && mc->soc->icc_ops) {
if (tegra_bpmp_mrq_is_supported(emc->bpmp, MRQ_BWMGR_INT)) {
mc->bwmgr_mrq_supported = true;
/*
* MC driver probe can't get BPMP reference as it gets probed
* earlier than BPMP. So, save the BPMP ref got from the EMC
* DT node in the mc->bpmp and use it in MC's icc_set hook.
*/
mc->bpmp = emc->bpmp;
barrier();
}
/*
* Initialize the ICC even if BPMP-FW doesn't support 'MRQ_BWMGR_INT'.
* Use the flag 'mc->bwmgr_mrq_supported' within MC driver and return
* EINVAL instead of passing the request to BPMP-FW later when the BW
* request is made by client with 'icc_set_bw()' call.
*/
err = tegra186_emc_interconnect_init(emc);
if (err) {
mc->bpmp = NULL;
goto put_bpmp;
}
}
return 0;
put_bpmp:
tegra_bpmp_put(emc->bpmp);
return err;
}
static void tegra186_emc_remove(struct platform_device *pdev)
{
struct tegra_mc *mc = dev_get_drvdata(pdev->dev.parent);
struct tegra186_emc *emc = platform_get_drvdata(pdev);
debugfs_remove_recursive(emc->debugfs.root);
mc->bpmp = NULL;
tegra_bpmp_put(emc->bpmp);
}
static const struct of_device_id tegra186_emc_of_match[] = {
#if defined(CONFIG_ARCH_TEGRA_186_SOC)
{ .compatible = "nvidia,tegra186-emc" },
#endif
#if defined(CONFIG_ARCH_TEGRA_194_SOC)
{ .compatible = "nvidia,tegra194-emc" },
#endif
#if defined(CONFIG_ARCH_TEGRA_234_SOC)
{ .compatible = "nvidia,tegra234-emc" },
#endif
#if defined(CONFIG_ARCH_TEGRA_264_SOC)
{ .compatible = "nvidia,tegra264-emc" },
#endif
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, tegra186_emc_of_match);
static struct platform_driver tegra186_emc_driver = {
.driver = {
.name = "tegra186-emc",
.of_match_table = tegra186_emc_of_match,
.suppress_bind_attrs = true,
.sync_state = icc_sync_state,
},
.probe = tegra186_emc_probe,
.remove = tegra186_emc_remove,
};
module_platform_driver(tegra186_emc_driver);
MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
MODULE_DESCRIPTION("NVIDIA Tegra186 External Memory Controller driver");
+927
View File
@@ -0,0 +1,927 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2017-2026 NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/io.h>
#include <linux/iommu.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <soc/tegra/mc.h>
#if defined(CONFIG_ARCH_TEGRA_186_SOC)
#include <dt-bindings/memory/tegra186-mc.h>
#endif
#include "mc.h"
#define MC_SID_STREAMID_OVERRIDE_MASK GENMASK(7, 0)
#define MC_SID_STREAMID_SECURITY_WRITE_ACCESS_DISABLED BIT(16)
#define MC_SID_STREAMID_SECURITY_OVERRIDE BIT(8)
static int tegra186_mc_probe(struct tegra_mc *mc)
{
struct platform_device *pdev = to_platform_device(mc->dev);
struct resource *res;
unsigned int i;
char name[8];
int err;
/*
* From Tegra264, the SID region is not present in MC node and BROADCAST is first.
* The common function 'tegra_mc_probe()' already maps first region entry from DT.
* Check if the SID region is present in DT then map BROADCAST. Otherwise, consider
* the first entry mapped in mc probe as the BROADCAST region. This is done to avoid
* mapping the region twice when SID is not present and keep backward compatibility.
*/
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sid");
if (res)
mc->bcast_ch_regs = devm_platform_ioremap_resource_byname(pdev, "broadcast");
else
mc->bcast_ch_regs = mc->regs;
if (IS_ERR(mc->bcast_ch_regs)) {
if (PTR_ERR(mc->bcast_ch_regs) == -EINVAL) {
dev_warn(&pdev->dev,
"Broadcast channel is missing, please update your device-tree\n");
mc->bcast_ch_regs = NULL;
goto populate;
}
return PTR_ERR(mc->bcast_ch_regs);
}
mc->ch_regs = devm_kcalloc(mc->dev, mc->soc->num_channels, sizeof(*mc->ch_regs),
GFP_KERNEL);
if (!mc->ch_regs)
return -ENOMEM;
for (i = 0; i < mc->soc->num_channels; i++) {
snprintf(name, sizeof(name), "ch%u", i);
mc->ch_regs[i] = devm_platform_ioremap_resource_byname(pdev, name);
if (IS_ERR(mc->ch_regs[i]))
return PTR_ERR(mc->ch_regs[i]);
}
populate:
err = of_platform_populate(mc->dev->of_node, NULL, NULL, mc->dev);
if (err < 0)
return err;
return 0;
}
static void tegra186_mc_remove(struct tegra_mc *mc)
{
of_platform_depopulate(mc->dev);
}
#if IS_ENABLED(CONFIG_IOMMU_API)
static void tegra186_mc_client_sid_override(struct tegra_mc *mc,
const struct tegra_mc_client *client,
unsigned int sid)
{
u32 value, old;
if (client->regs.sid.security == 0 && client->regs.sid.override == 0)
return;
value = readl(mc->regs + client->regs.sid.security);
if ((value & MC_SID_STREAMID_SECURITY_OVERRIDE) == 0) {
/*
* If the secure firmware has locked this down the override
* for this memory client, there's nothing we can do here.
*/
if (value & MC_SID_STREAMID_SECURITY_WRITE_ACCESS_DISABLED)
return;
/*
* Otherwise, try to set the override itself. Typically the
* secure firmware will never have set this configuration.
* Instead, it will either have disabled write access to
* this field, or it will already have set an explicit
* override itself.
*/
WARN_ON((value & MC_SID_STREAMID_SECURITY_OVERRIDE) == 0);
value |= MC_SID_STREAMID_SECURITY_OVERRIDE;
writel(value, mc->regs + client->regs.sid.security);
}
value = readl(mc->regs + client->regs.sid.override);
old = value & MC_SID_STREAMID_OVERRIDE_MASK;
if (old != sid) {
dev_dbg(mc->dev, "overriding SID %x for %s with %x\n", old,
client->name, sid);
writel(sid, mc->regs + client->regs.sid.override);
}
}
#endif
static int tegra186_mc_probe_device(struct tegra_mc *mc, struct device *dev)
{
#if IS_ENABLED(CONFIG_IOMMU_API)
struct of_phandle_args args;
unsigned int i, index = 0;
u32 sid;
if (!tegra_dev_iommu_get_stream_id(dev, &sid))
return 0;
while (!of_parse_phandle_with_args(dev->of_node, "interconnects", "#interconnect-cells",
index, &args)) {
if (args.np == mc->dev->of_node && args.args_count != 0) {
for (i = 0; i < mc->soc->num_clients; i++) {
const struct tegra_mc_client *client = &mc->soc->clients[i];
if (client->id == args.args[0])
tegra186_mc_client_sid_override(
mc, client,
sid & MC_SID_STREAMID_OVERRIDE_MASK);
}
}
index++;
}
#endif
return 0;
}
static void tegra186_mc_resume(struct tegra_mc *mc)
{
#if IS_ENABLED(CONFIG_IOMMU_API)
unsigned int i;
for (i = 0; i < mc->soc->num_clients; i++) {
const struct tegra_mc_client *client = &mc->soc->clients[i];
tegra186_mc_client_sid_override(mc, client, client->sid);
}
#endif
}
const struct tegra_mc_ops tegra186_mc_ops = {
.probe = tegra186_mc_probe,
.remove = tegra186_mc_remove,
.resume = tegra186_mc_resume,
.probe_device = tegra186_mc_probe_device,
};
#if defined(CONFIG_ARCH_TEGRA_186_SOC)
static const struct tegra_mc_client tegra186_mc_clients[] = {
{
.id = TEGRA186_MEMORY_CLIENT_PTCR,
.name = "ptcr",
.sid = TEGRA186_SID_PASSTHROUGH,
.regs = {
.sid = {
.override = 0x000,
.security = 0x004,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_AFIR,
.name = "afir",
.sid = TEGRA186_SID_AFI,
.regs = {
.sid = {
.override = 0x070,
.security = 0x074,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_HDAR,
.name = "hdar",
.sid = TEGRA186_SID_HDA,
.regs = {
.sid = {
.override = 0x0a8,
.security = 0x0ac,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_HOST1XDMAR,
.name = "host1xdmar",
.sid = TEGRA186_SID_HOST1X,
.regs = {
.sid = {
.override = 0x0b0,
.security = 0x0b4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_NVENCSRD,
.name = "nvencsrd",
.sid = TEGRA186_SID_NVENC,
.regs = {
.sid = {
.override = 0x0e0,
.security = 0x0e4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SATAR,
.name = "satar",
.sid = TEGRA186_SID_SATA,
.regs = {
.sid = {
.override = 0x0f8,
.security = 0x0fc,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_MPCORER,
.name = "mpcorer",
.sid = TEGRA186_SID_PASSTHROUGH,
.regs = {
.sid = {
.override = 0x138,
.security = 0x13c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_NVENCSWR,
.name = "nvencswr",
.sid = TEGRA186_SID_NVENC,
.regs = {
.sid = {
.override = 0x158,
.security = 0x15c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_AFIW,
.name = "afiw",
.sid = TEGRA186_SID_AFI,
.regs = {
.sid = {
.override = 0x188,
.security = 0x18c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_HDAW,
.name = "hdaw",
.sid = TEGRA186_SID_HDA,
.regs = {
.sid = {
.override = 0x1a8,
.security = 0x1ac,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_MPCOREW,
.name = "mpcorew",
.sid = TEGRA186_SID_PASSTHROUGH,
.regs = {
.sid = {
.override = 0x1c8,
.security = 0x1cc,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SATAW,
.name = "sataw",
.sid = TEGRA186_SID_SATA,
.regs = {
.sid = {
.override = 0x1e8,
.security = 0x1ec,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_ISPRA,
.name = "ispra",
.sid = TEGRA186_SID_ISP,
.regs = {
.sid = {
.override = 0x220,
.security = 0x224,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_ISPWA,
.name = "ispwa",
.sid = TEGRA186_SID_ISP,
.regs = {
.sid = {
.override = 0x230,
.security = 0x234,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_ISPWB,
.name = "ispwb",
.sid = TEGRA186_SID_ISP,
.regs = {
.sid = {
.override = 0x238,
.security = 0x23c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_XUSB_HOSTR,
.name = "xusb_hostr",
.sid = TEGRA186_SID_XUSB_HOST,
.regs = {
.sid = {
.override = 0x250,
.security = 0x254,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_XUSB_HOSTW,
.name = "xusb_hostw",
.sid = TEGRA186_SID_XUSB_HOST,
.regs = {
.sid = {
.override = 0x258,
.security = 0x25c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_XUSB_DEVR,
.name = "xusb_devr",
.sid = TEGRA186_SID_XUSB_DEV,
.regs = {
.sid = {
.override = 0x260,
.security = 0x264,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_XUSB_DEVW,
.name = "xusb_devw",
.sid = TEGRA186_SID_XUSB_DEV,
.regs = {
.sid = {
.override = 0x268,
.security = 0x26c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_TSECSRD,
.name = "tsecsrd",
.sid = TEGRA186_SID_TSEC,
.regs = {
.sid = {
.override = 0x2a0,
.security = 0x2a4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_TSECSWR,
.name = "tsecswr",
.sid = TEGRA186_SID_TSEC,
.regs = {
.sid = {
.override = 0x2a8,
.security = 0x2ac,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_GPUSRD,
.name = "gpusrd",
.sid = TEGRA186_SID_GPU,
.regs = {
.sid = {
.override = 0x2c0,
.security = 0x2c4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_GPUSWR,
.name = "gpuswr",
.sid = TEGRA186_SID_GPU,
.regs = {
.sid = {
.override = 0x2c8,
.security = 0x2cc,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SDMMCRA,
.name = "sdmmcra",
.sid = TEGRA186_SID_SDMMC1,
.regs = {
.sid = {
.override = 0x300,
.security = 0x304,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SDMMCRAA,
.name = "sdmmcraa",
.sid = TEGRA186_SID_SDMMC2,
.regs = {
.sid = {
.override = 0x308,
.security = 0x30c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SDMMCR,
.name = "sdmmcr",
.sid = TEGRA186_SID_SDMMC3,
.regs = {
.sid = {
.override = 0x310,
.security = 0x314,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SDMMCRAB,
.name = "sdmmcrab",
.sid = TEGRA186_SID_SDMMC4,
.regs = {
.sid = {
.override = 0x318,
.security = 0x31c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SDMMCWA,
.name = "sdmmcwa",
.sid = TEGRA186_SID_SDMMC1,
.regs = {
.sid = {
.override = 0x320,
.security = 0x324,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SDMMCWAA,
.name = "sdmmcwaa",
.sid = TEGRA186_SID_SDMMC2,
.regs = {
.sid = {
.override = 0x328,
.security = 0x32c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SDMMCW,
.name = "sdmmcw",
.sid = TEGRA186_SID_SDMMC3,
.regs = {
.sid = {
.override = 0x330,
.security = 0x334,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SDMMCWAB,
.name = "sdmmcwab",
.sid = TEGRA186_SID_SDMMC4,
.regs = {
.sid = {
.override = 0x338,
.security = 0x33c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_VICSRD,
.name = "vicsrd",
.sid = TEGRA186_SID_VIC,
.regs = {
.sid = {
.override = 0x360,
.security = 0x364,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_VICSWR,
.name = "vicswr",
.sid = TEGRA186_SID_VIC,
.regs = {
.sid = {
.override = 0x368,
.security = 0x36c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_VIW,
.name = "viw",
.sid = TEGRA186_SID_VI,
.regs = {
.sid = {
.override = 0x390,
.security = 0x394,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_NVDECSRD,
.name = "nvdecsrd",
.sid = TEGRA186_SID_NVDEC,
.regs = {
.sid = {
.override = 0x3c0,
.security = 0x3c4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_NVDECSWR,
.name = "nvdecswr",
.sid = TEGRA186_SID_NVDEC,
.regs = {
.sid = {
.override = 0x3c8,
.security = 0x3cc,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_APER,
.name = "aper",
.sid = TEGRA186_SID_APE,
.regs = {
.sid = {
.override = 0x3d0,
.security = 0x3d4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_APEW,
.name = "apew",
.sid = TEGRA186_SID_APE,
.regs = {
.sid = {
.override = 0x3d8,
.security = 0x3dc,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_NVJPGSRD,
.name = "nvjpgsrd",
.sid = TEGRA186_SID_NVJPG,
.regs = {
.sid = {
.override = 0x3f0,
.security = 0x3f4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_NVJPGSWR,
.name = "nvjpgswr",
.sid = TEGRA186_SID_NVJPG,
.regs = {
.sid = {
.override = 0x3f8,
.security = 0x3fc,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SESRD,
.name = "sesrd",
.sid = TEGRA186_SID_SE,
.regs = {
.sid = {
.override = 0x400,
.security = 0x404,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SESWR,
.name = "seswr",
.sid = TEGRA186_SID_SE,
.regs = {
.sid = {
.override = 0x408,
.security = 0x40c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_ETRR,
.name = "etrr",
.sid = TEGRA186_SID_ETR,
.regs = {
.sid = {
.override = 0x420,
.security = 0x424,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_ETRW,
.name = "etrw",
.sid = TEGRA186_SID_ETR,
.regs = {
.sid = {
.override = 0x428,
.security = 0x42c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_TSECSRDB,
.name = "tsecsrdb",
.sid = TEGRA186_SID_TSECB,
.regs = {
.sid = {
.override = 0x430,
.security = 0x434,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_TSECSWRB,
.name = "tsecswrb",
.sid = TEGRA186_SID_TSECB,
.regs = {
.sid = {
.override = 0x438,
.security = 0x43c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_GPUSRD2,
.name = "gpusrd2",
.sid = TEGRA186_SID_GPU,
.regs = {
.sid = {
.override = 0x440,
.security = 0x444,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_GPUSWR2,
.name = "gpuswr2",
.sid = TEGRA186_SID_GPU,
.regs = {
.sid = {
.override = 0x448,
.security = 0x44c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_AXISR,
.name = "axisr",
.sid = TEGRA186_SID_GPCDMA_0,
.regs = {
.sid = {
.override = 0x460,
.security = 0x464,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_AXISW,
.name = "axisw",
.sid = TEGRA186_SID_GPCDMA_0,
.regs = {
.sid = {
.override = 0x468,
.security = 0x46c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_EQOSR,
.name = "eqosr",
.sid = TEGRA186_SID_EQOS,
.regs = {
.sid = {
.override = 0x470,
.security = 0x474,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_EQOSW,
.name = "eqosw",
.sid = TEGRA186_SID_EQOS,
.regs = {
.sid = {
.override = 0x478,
.security = 0x47c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_UFSHCR,
.name = "ufshcr",
.sid = TEGRA186_SID_UFSHC,
.regs = {
.sid = {
.override = 0x480,
.security = 0x484,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_UFSHCW,
.name = "ufshcw",
.sid = TEGRA186_SID_UFSHC,
.regs = {
.sid = {
.override = 0x488,
.security = 0x48c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_NVDISPLAYR,
.name = "nvdisplayr",
.sid = TEGRA186_SID_NVDISPLAY,
.regs = {
.sid = {
.override = 0x490,
.security = 0x494,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_BPMPR,
.name = "bpmpr",
.sid = TEGRA186_SID_BPMP,
.regs = {
.sid = {
.override = 0x498,
.security = 0x49c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_BPMPW,
.name = "bpmpw",
.sid = TEGRA186_SID_BPMP,
.regs = {
.sid = {
.override = 0x4a0,
.security = 0x4a4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_BPMPDMAR,
.name = "bpmpdmar",
.sid = TEGRA186_SID_BPMP,
.regs = {
.sid = {
.override = 0x4a8,
.security = 0x4ac,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_BPMPDMAW,
.name = "bpmpdmaw",
.sid = TEGRA186_SID_BPMP,
.regs = {
.sid = {
.override = 0x4b0,
.security = 0x4b4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_AONR,
.name = "aonr",
.sid = TEGRA186_SID_AON,
.regs = {
.sid = {
.override = 0x4b8,
.security = 0x4bc,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_AONW,
.name = "aonw",
.sid = TEGRA186_SID_AON,
.regs = {
.sid = {
.override = 0x4c0,
.security = 0x4c4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_AONDMAR,
.name = "aondmar",
.sid = TEGRA186_SID_AON,
.regs = {
.sid = {
.override = 0x4c8,
.security = 0x4cc,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_AONDMAW,
.name = "aondmaw",
.sid = TEGRA186_SID_AON,
.regs = {
.sid = {
.override = 0x4d0,
.security = 0x4d4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SCER,
.name = "scer",
.sid = TEGRA186_SID_SCE,
.regs = {
.sid = {
.override = 0x4d8,
.security = 0x4dc,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SCEW,
.name = "scew",
.sid = TEGRA186_SID_SCE,
.regs = {
.sid = {
.override = 0x4e0,
.security = 0x4e4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SCEDMAR,
.name = "scedmar",
.sid = TEGRA186_SID_SCE,
.regs = {
.sid = {
.override = 0x4e8,
.security = 0x4ec,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_SCEDMAW,
.name = "scedmaw",
.sid = TEGRA186_SID_SCE,
.regs = {
.sid = {
.override = 0x4f0,
.security = 0x4f4,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_APEDMAR,
.name = "apedmar",
.sid = TEGRA186_SID_APE,
.regs = {
.sid = {
.override = 0x4f8,
.security = 0x4fc,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_APEDMAW,
.name = "apedmaw",
.sid = TEGRA186_SID_APE,
.regs = {
.sid = {
.override = 0x500,
.security = 0x504,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_NVDISPLAYR1,
.name = "nvdisplayr1",
.sid = TEGRA186_SID_NVDISPLAY,
.regs = {
.sid = {
.override = 0x508,
.security = 0x50c,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_VICSRD1,
.name = "vicsrd1",
.sid = TEGRA186_SID_VIC,
.regs = {
.sid = {
.override = 0x510,
.security = 0x514,
},
},
}, {
.id = TEGRA186_MEMORY_CLIENT_NVDECSRD1,
.name = "nvdecsrd1",
.sid = TEGRA186_SID_NVDEC,
.regs = {
.sid = {
.override = 0x518,
.security = 0x51c,
},
},
},
};
static const struct tegra_mc_intmask tegra186_mc_intmasks[] = {
{
.reg = MC_INTMASK,
.mask = MC_INT_DECERR_GENERALIZED_CARVEOUT | MC_INT_DECERR_MTS |
MC_INT_SECERR_SEC | MC_INT_DECERR_VPR |
MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM,
},
};
const struct tegra_mc_soc tegra186_mc_soc = {
.num_clients = ARRAY_SIZE(tegra186_mc_clients),
.clients = tegra186_mc_clients,
.num_address_bits = 40,
.num_channels = 4,
.client_id_mask = 0xff,
.intmasks = tegra186_mc_intmasks,
.num_intmasks = ARRAY_SIZE(tegra186_mc_intmasks),
.ops = &tegra186_mc_ops,
.ch_intmask = 0x0000000f,
.global_intstatus_channel_shift = 0,
.regs = &tegra20_mc_regs,
.handle_irq = tegra30_mc_irq_handlers,
.num_interrupts = ARRAY_SIZE(tegra30_mc_irq_handlers),
.mc_addr_hi_mask = 0x3,
.mc_err_status_type_mask = (0x7 << 28),
};
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+796
View File
@@ -0,0 +1,796 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012-2026 NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <dt-bindings/memory/tegra20-mc.h>
#include "mc.h"
#define MC_STAT_CONTROL 0x90
#define MC_STAT_EMC_CLOCK_LIMIT 0xa0
#define MC_STAT_EMC_CLOCKS 0xa4
#define MC_STAT_EMC_CONTROL_0 0xa8
#define MC_STAT_EMC_CONTROL_1 0xac
#define MC_STAT_EMC_COUNT_0 0xb8
#define MC_STAT_EMC_COUNT_1 0xbc
#define MC_STAT_CONTROL_CLIENT_ID GENMASK(13, 8)
#define MC_STAT_CONTROL_EVENT GENMASK(23, 16)
#define MC_STAT_CONTROL_PRI_EVENT GENMASK(25, 24)
#define MC_STAT_CONTROL_FILTER_CLIENT_ENABLE GENMASK(26, 26)
#define MC_STAT_CONTROL_FILTER_PRI GENMASK(29, 28)
#define MC_STAT_CONTROL_PRI_EVENT_HP 0
#define MC_STAT_CONTROL_PRI_EVENT_TM 1
#define MC_STAT_CONTROL_PRI_EVENT_BW 2
#define MC_STAT_CONTROL_FILTER_PRI_DISABLE 0
#define MC_STAT_CONTROL_FILTER_PRI_NO 1
#define MC_STAT_CONTROL_FILTER_PRI_YES 2
#define MC_STAT_CONTROL_EVENT_QUALIFIED 0
#define MC_STAT_CONTROL_EVENT_ANY_READ 1
#define MC_STAT_CONTROL_EVENT_ANY_WRITE 2
#define MC_STAT_CONTROL_EVENT_RD_WR_CHANGE 3
#define MC_STAT_CONTROL_EVENT_SUCCESSIVE 4
#define MC_STAT_CONTROL_EVENT_ARB_BANK_AA 5
#define MC_STAT_CONTROL_EVENT_ARB_BANK_BB 6
#define MC_STAT_CONTROL_EVENT_PAGE_MISS 7
#define MC_STAT_CONTROL_EVENT_AUTO_PRECHARGE 8
#define EMC_GATHER_RST (0 << 8)
#define EMC_GATHER_CLEAR (1 << 8)
#define EMC_GATHER_DISABLE (2 << 8)
#define EMC_GATHER_ENABLE (3 << 8)
#define MC_STAT_SAMPLE_TIME_USEC 16000
/* we store collected statistics as a fixed point values */
#define MC_FX_FRAC_SCALE 100
static DEFINE_MUTEX(tegra20_mc_stat_lock);
struct tegra20_mc_stat_gather {
unsigned int pri_filter;
unsigned int pri_event;
unsigned int result;
unsigned int client;
unsigned int event;
bool client_enb;
};
struct tegra20_mc_stat {
struct tegra20_mc_stat_gather gather0;
struct tegra20_mc_stat_gather gather1;
unsigned int sample_time_usec;
const struct tegra_mc *mc;
};
struct tegra20_mc_client_stat {
unsigned int events;
unsigned int arb_high_prio;
unsigned int arb_timeout;
unsigned int arb_bandwidth;
unsigned int rd_wr_change;
unsigned int successive;
unsigned int page_miss;
unsigned int auto_precharge;
unsigned int arb_bank_aa;
unsigned int arb_bank_bb;
};
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 tegra20_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 tegra20_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 tegra20_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 tegra20_mc_dma_idling(struct tegra_mc *mc,
const struct tegra_mc_reset *rst)
{
return mc_readl(mc, rst->status) == 0;
}
static int tegra20_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 tegra20_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;
}
static const struct tegra_mc_reset_ops tegra20_mc_reset_ops = {
.hotreset_assert = tegra20_mc_hotreset_assert,
.hotreset_deassert = tegra20_mc_hotreset_deassert,
.block_dma = tegra20_mc_block_dma,
.dma_idling = tegra20_mc_dma_idling,
.unblock_dma = tegra20_mc_unblock_dma,
.reset_status = tegra20_mc_reset_status,
};
static int tegra20_mc_icc_set(struct icc_node *src, struct icc_node *dst)
{
/*
* It should be possible to tune arbitration knobs here, but the
* default values are known to work well on all devices. Hence
* nothing to do here so far.
*/
return 0;
}
static int tegra20_mc_icc_aggreate(struct icc_node *node, u32 tag, u32 avg_bw,
u32 peak_bw, u32 *agg_avg, u32 *agg_peak)
{
/*
* ISO clients need to reserve extra bandwidth up-front because
* there could be high bandwidth pressure during initial filling
* of the client's FIFO buffers. Secondly, we need to take into
* account impurities of the memory subsystem.
*/
if (tag & TEGRA_MC_ICC_TAG_ISO)
peak_bw = tegra_mc_scale_percents(peak_bw, 300);
*agg_avg += avg_bw;
*agg_peak = max(*agg_peak, peak_bw);
return 0;
}
static struct icc_node_data *
tegra20_mc_of_icc_xlate_extended(const struct of_phandle_args *spec, void *data)
{
struct tegra_mc *mc = icc_provider_to_tegra_mc(data);
unsigned int i, idx = spec->args[0];
struct icc_node_data *ndata;
struct icc_node *node;
list_for_each_entry(node, &mc->provider.nodes, node_list) {
if (node->id != idx)
continue;
ndata = kzalloc_obj(*ndata);
if (!ndata)
return ERR_PTR(-ENOMEM);
ndata->node = node;
/* these clients are isochronous by default */
if (strstarts(node->name, "display") ||
strstarts(node->name, "vi"))
ndata->tag = TEGRA_MC_ICC_TAG_ISO;
else
ndata->tag = TEGRA_MC_ICC_TAG_DEFAULT;
return ndata;
}
for (i = 0; i < mc->soc->num_clients; i++) {
if (mc->soc->clients[i].id == idx)
return ERR_PTR(-EPROBE_DEFER);
}
dev_err(mc->dev, "invalid ICC client ID %u\n", idx);
return ERR_PTR(-EINVAL);
}
static const struct tegra_mc_icc_ops tegra20_mc_icc_ops = {
.xlate_extended = tegra20_mc_of_icc_xlate_extended,
.aggregate = tegra20_mc_icc_aggreate,
.set = tegra20_mc_icc_set,
};
static u32 tegra20_mc_stat_gather_control(const struct tegra20_mc_stat_gather *g)
{
u32 control;
control = FIELD_PREP(MC_STAT_CONTROL_EVENT, g->event);
control |= FIELD_PREP(MC_STAT_CONTROL_CLIENT_ID, g->client);
control |= FIELD_PREP(MC_STAT_CONTROL_PRI_EVENT, g->pri_event);
control |= FIELD_PREP(MC_STAT_CONTROL_FILTER_PRI, g->pri_filter);
control |= FIELD_PREP(MC_STAT_CONTROL_FILTER_CLIENT_ENABLE, g->client_enb);
return control;
}
static void tegra20_mc_stat_gather(struct tegra20_mc_stat *stat)
{
u32 clocks, count0, count1, control_0, control_1;
const struct tegra_mc *mc = stat->mc;
control_0 = tegra20_mc_stat_gather_control(&stat->gather0);
control_1 = tegra20_mc_stat_gather_control(&stat->gather1);
/*
* Reset statistic gathers state, select statistics collection mode
* and set clocks counter saturation limit to maximum.
*/
mc_writel(mc, 0x00000000, MC_STAT_CONTROL);
mc_writel(mc, control_0, MC_STAT_EMC_CONTROL_0);
mc_writel(mc, control_1, MC_STAT_EMC_CONTROL_1);
mc_writel(mc, 0xffffffff, MC_STAT_EMC_CLOCK_LIMIT);
mc_writel(mc, EMC_GATHER_ENABLE, MC_STAT_CONTROL);
fsleep(stat->sample_time_usec);
mc_writel(mc, EMC_GATHER_DISABLE, MC_STAT_CONTROL);
count0 = mc_readl(mc, MC_STAT_EMC_COUNT_0);
count1 = mc_readl(mc, MC_STAT_EMC_COUNT_1);
clocks = mc_readl(mc, MC_STAT_EMC_CLOCKS);
clocks = max(clocks / 100 / MC_FX_FRAC_SCALE, 1u);
stat->gather0.result = DIV_ROUND_UP(count0, clocks);
stat->gather1.result = DIV_ROUND_UP(count1, clocks);
}
static void tegra20_mc_stat_events(const struct tegra_mc *mc,
const struct tegra_mc_client *client0,
const struct tegra_mc_client *client1,
unsigned int pri_filter,
unsigned int pri_event,
unsigned int event,
unsigned int *result0,
unsigned int *result1)
{
struct tegra20_mc_stat stat = {};
stat.gather0.client = client0 ? client0->id : 0;
stat.gather0.pri_filter = pri_filter;
stat.gather0.client_enb = !!client0;
stat.gather0.pri_event = pri_event;
stat.gather0.event = event;
stat.gather1.client = client1 ? client1->id : 0;
stat.gather1.pri_filter = pri_filter;
stat.gather1.client_enb = !!client1;
stat.gather1.pri_event = pri_event;
stat.gather1.event = event;
stat.sample_time_usec = MC_STAT_SAMPLE_TIME_USEC;
stat.mc = mc;
tegra20_mc_stat_gather(&stat);
*result0 = stat.gather0.result;
*result1 = stat.gather1.result;
}
static void tegra20_mc_collect_stats(const struct tegra_mc *mc,
struct tegra20_mc_client_stat *stats)
{
const struct tegra_mc_client *client0, *client1;
unsigned int i;
/* collect memory controller utilization percent for each client */
for (i = 0; i < mc->soc->num_clients; i += 2) {
client0 = &mc->soc->clients[i];
client1 = &mc->soc->clients[i + 1];
if (i + 1 == mc->soc->num_clients)
client1 = NULL;
tegra20_mc_stat_events(mc, client0, client1,
MC_STAT_CONTROL_FILTER_PRI_DISABLE,
MC_STAT_CONTROL_PRI_EVENT_HP,
MC_STAT_CONTROL_EVENT_QUALIFIED,
&stats[i + 0].events,
&stats[i + 1].events);
}
/* collect more info from active clients */
for (i = 0; i < mc->soc->num_clients; i++) {
unsigned int clienta, clientb = mc->soc->num_clients;
for (client0 = NULL; i < mc->soc->num_clients; i++) {
if (stats[i].events) {
client0 = &mc->soc->clients[i];
clienta = i++;
break;
}
}
for (client1 = NULL; i < mc->soc->num_clients; i++) {
if (stats[i].events) {
client1 = &mc->soc->clients[i];
clientb = i;
break;
}
}
if (!client0 && !client1)
break;
tegra20_mc_stat_events(mc, client0, client1,
MC_STAT_CONTROL_FILTER_PRI_YES,
MC_STAT_CONTROL_PRI_EVENT_HP,
MC_STAT_CONTROL_EVENT_QUALIFIED,
&stats[clienta].arb_high_prio,
&stats[clientb].arb_high_prio);
tegra20_mc_stat_events(mc, client0, client1,
MC_STAT_CONTROL_FILTER_PRI_YES,
MC_STAT_CONTROL_PRI_EVENT_TM,
MC_STAT_CONTROL_EVENT_QUALIFIED,
&stats[clienta].arb_timeout,
&stats[clientb].arb_timeout);
tegra20_mc_stat_events(mc, client0, client1,
MC_STAT_CONTROL_FILTER_PRI_YES,
MC_STAT_CONTROL_PRI_EVENT_BW,
MC_STAT_CONTROL_EVENT_QUALIFIED,
&stats[clienta].arb_bandwidth,
&stats[clientb].arb_bandwidth);
tegra20_mc_stat_events(mc, client0, client1,
MC_STAT_CONTROL_FILTER_PRI_DISABLE,
MC_STAT_CONTROL_PRI_EVENT_HP,
MC_STAT_CONTROL_EVENT_RD_WR_CHANGE,
&stats[clienta].rd_wr_change,
&stats[clientb].rd_wr_change);
tegra20_mc_stat_events(mc, client0, client1,
MC_STAT_CONTROL_FILTER_PRI_DISABLE,
MC_STAT_CONTROL_PRI_EVENT_HP,
MC_STAT_CONTROL_EVENT_SUCCESSIVE,
&stats[clienta].successive,
&stats[clientb].successive);
tegra20_mc_stat_events(mc, client0, client1,
MC_STAT_CONTROL_FILTER_PRI_DISABLE,
MC_STAT_CONTROL_PRI_EVENT_HP,
MC_STAT_CONTROL_EVENT_PAGE_MISS,
&stats[clienta].page_miss,
&stats[clientb].page_miss);
}
}
static void tegra20_mc_printf_percents(struct seq_file *s,
const char *fmt,
unsigned int percents_fx)
{
char percents_str[8];
snprintf(percents_str, ARRAY_SIZE(percents_str), "%3u.%02u%%",
percents_fx / MC_FX_FRAC_SCALE, percents_fx % MC_FX_FRAC_SCALE);
seq_printf(s, fmt, percents_str);
}
static int tegra20_mc_stats_show(struct seq_file *s, void *unused)
{
const struct tegra_mc *mc = dev_get_drvdata(s->private);
struct tegra20_mc_client_stat *stats;
unsigned int i;
stats = kzalloc_objs(*stats, mc->soc->num_clients + 1);
if (!stats)
return -ENOMEM;
mutex_lock(&tegra20_mc_stat_lock);
tegra20_mc_collect_stats(mc, stats);
mutex_unlock(&tegra20_mc_stat_lock);
seq_puts(s, "Memory client Events Timeout High priority Bandwidth ARB RW change Successive Page miss\n");
seq_puts(s, "-----------------------------------------------------------------------------------------------------\n");
for (i = 0; i < mc->soc->num_clients; i++) {
seq_printf(s, "%-14s ", mc->soc->clients[i].name);
/* An event is generated when client performs R/W request. */
tegra20_mc_printf_percents(s, "%-9s", stats[i].events);
/*
* An event is generated based on the timeout (TM) signal
* accompanying a request for arbitration.
*/
tegra20_mc_printf_percents(s, "%-10s", stats[i].arb_timeout);
/*
* An event is generated based on the high-priority (HP) signal
* accompanying a request for arbitration.
*/
tegra20_mc_printf_percents(s, "%-16s", stats[i].arb_high_prio);
/*
* An event is generated based on the bandwidth (BW) signal
* accompanying a request for arbitration.
*/
tegra20_mc_printf_percents(s, "%-16s", stats[i].arb_bandwidth);
/*
* An event is generated when the memory controller switches
* between making a read request to making a write request.
*/
tegra20_mc_printf_percents(s, "%-12s", stats[i].rd_wr_change);
/*
* An even generated when the chosen client has wins arbitration
* when it was also the winner at the previous request. If a
* client makes N requests in a row that are honored, SUCCESSIVE
* will be counted (N-1) times. Large values for this event
* imply that if we were patient enough, all of those requests
* could have been coalesced.
*/
tegra20_mc_printf_percents(s, "%-13s", stats[i].successive);
/*
* An event is generated when the memory controller detects a
* page miss for the current request.
*/
tegra20_mc_printf_percents(s, "%-12s\n", stats[i].page_miss);
}
kfree(stats);
return 0;
}
static int tegra20_mc_probe(struct tegra_mc *mc)
{
debugfs_create_devm_seqfile(mc->dev, "stats", mc->debugfs.root,
tegra20_mc_stats_show);
return 0;
}
static irqreturn_t tegra20_mc_handle_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->intmasks[0].mask;
if (!status)
return IRQ_NONE;
for_each_set_bit(bit, &status, 32) {
const char *error = tegra_mc_status_names[bit];
const char *direction = "read", *secure = "";
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 = tegra20_mc_error_names[2];
if (value & BIT(31))
direction = "write";
break;
case MC_INT_INVALID_GART_PAGE:
reg = MC_GART_ERROR_REQ;
value = mc_readl(mc, reg);
id = (value >> 1) & mc->soc->client_id_mask;
desc = tegra20_mc_error_names[2];
if (value & BIT(0))
direction = "write";
break;
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 = tegra20_mc_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 const irq_handler_t tegra20_mc_irq_handlers[] = {
tegra20_mc_handle_irq
};
static const struct tegra_mc_ops tegra20_mc_ops = {
.probe = tegra20_mc_probe,
};
static const struct tegra_mc_intmask tegra20_mc_intmasks[] = {
{
.reg = MC_INTMASK,
.mask = MC_INT_SECURITY_VIOLATION | MC_INT_INVALID_GART_PAGE |
MC_INT_DECERR_EMEM,
},
};
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,
.intmasks = tegra20_mc_intmasks,
.num_intmasks = ARRAY_SIZE(tegra20_mc_intmasks),
.reset_ops = &tegra20_mc_reset_ops,
.resets = tegra20_mc_resets,
.num_resets = ARRAY_SIZE(tegra20_mc_resets),
.icc_ops = &tegra20_mc_icc_ops,
.ops = &tegra20_mc_ops,
.regs = &tegra20_mc_regs,
.handle_irq = tegra20_mc_irq_handlers,
.num_interrupts = ARRAY_SIZE(tegra20_mc_irq_handlers),
.mc_err_status_type_mask = (0x7 << 28),
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/of_reserved_mem.h>
#include "tegra210-emc.h"
#define TEGRA_EMC_MAX_FREQS 16
static int tegra210_emc_table_device_init(struct reserved_mem *rmem,
struct device *dev)
{
struct tegra210_emc *emc = dev_get_drvdata(dev);
struct tegra210_emc_timing *timings;
unsigned int i, count = 0;
timings = memremap(rmem->base, rmem->size, MEMREMAP_WB);
if (!timings) {
dev_err(dev, "failed to map EMC table\n");
return -ENOMEM;
}
for (i = 0; i < TEGRA_EMC_MAX_FREQS; i++) {
if (timings[i].revision == 0)
break;
count++;
}
/* only the nominal and derated tables are expected */
if (emc->derated) {
dev_warn(dev, "excess EMC table '%s'\n", rmem->name);
goto out;
}
if (emc->nominal) {
if (count != emc->num_timings) {
dev_warn(dev, "%u derated vs. %u nominal entries\n",
count, emc->num_timings);
memunmap(timings);
return -EINVAL;
}
emc->derated = timings;
} else {
emc->num_timings = count;
emc->nominal = timings;
}
out:
/* keep track of which table this is */
rmem->priv = timings;
return 0;
}
static void tegra210_emc_table_device_release(struct reserved_mem *rmem,
struct device *dev)
{
struct tegra210_emc_timing *timings = rmem->priv;
struct tegra210_emc *emc = dev_get_drvdata(dev);
if ((emc->nominal && timings != emc->nominal) &&
(emc->derated && timings != emc->derated))
dev_warn(dev, "trying to release unassigned EMC table '%s'\n",
rmem->name);
memunmap(timings);
}
static int tegra210_emc_table_init(unsigned long node,
struct reserved_mem *rmem)
{
pr_debug("Tegra210 EMC table at %pa, size %lu bytes\n", &rmem->base,
(unsigned long)rmem->size);
return 0;
}
static const struct reserved_mem_ops tegra210_emc_table_ops = {
.node_init = tegra210_emc_table_init,
.device_init = tegra210_emc_table_device_init,
.device_release = tegra210_emc_table_device_release,
};
RESERVEDMEM_OF_DECLARE(tegra210_emc_table, "nvidia,tegra210-emc-table",
&tegra210_emc_table_ops);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,50 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2015-2020, NVIDIA CORPORATION. All rights reserved.
*/
#ifndef TEGRA210_MC_H
#define TEGRA210_MC_H
#include "mc.h"
/* register definitions */
#define MC_LATENCY_ALLOWANCE_AVPC_0 0x2e4
#define MC_LATENCY_ALLOWANCE_HC_0 0x310
#define MC_LATENCY_ALLOWANCE_HC_1 0x314
#define MC_LATENCY_ALLOWANCE_MPCORE_0 0x320
#define MC_LATENCY_ALLOWANCE_NVENC_0 0x328
#define MC_LATENCY_ALLOWANCE_PPCS_0 0x344
#define MC_LATENCY_ALLOWANCE_PPCS_1 0x348
#define MC_LATENCY_ALLOWANCE_ISP2_0 0x370
#define MC_LATENCY_ALLOWANCE_ISP2_1 0x374
#define MC_LATENCY_ALLOWANCE_XUSB_0 0x37c
#define MC_LATENCY_ALLOWANCE_XUSB_1 0x380
#define MC_LATENCY_ALLOWANCE_TSEC_0 0x390
#define MC_LATENCY_ALLOWANCE_VIC_0 0x394
#define MC_LATENCY_ALLOWANCE_VI2_0 0x398
#define MC_LATENCY_ALLOWANCE_GPU_0 0x3ac
#define MC_LATENCY_ALLOWANCE_SDMMCA_0 0x3b8
#define MC_LATENCY_ALLOWANCE_SDMMCAA_0 0x3bc
#define MC_LATENCY_ALLOWANCE_SDMMC_0 0x3c0
#define MC_LATENCY_ALLOWANCE_SDMMCAB_0 0x3c4
#define MC_LATENCY_ALLOWANCE_GPU2_0 0x3e8
#define MC_LATENCY_ALLOWANCE_NVDEC_0 0x3d8
#define MC_MLL_MPCORER_PTSA_RATE 0x44c
#define MC_FTOP_PTSA_RATE 0x50c
#define MC_EMEM_ARB_TIMING_RFCPB 0x6c0
#define MC_EMEM_ARB_TIMING_CCDMW 0x6c4
#define MC_EMEM_ARB_REFPB_HP_CTRL 0x6f0
#define MC_EMEM_ARB_REFPB_BANK_CTRL 0x6f4
#define MC_PTSA_GRANT_DECREMENT 0x960
#define MC_EMEM_ARB_DHYST_CTRL 0xbcc
#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0 0xbd0
#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1 0xbd4
#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2 0xbd8
#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3 0xbdc
#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4 0xbe0
#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5 0xbe4
#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6 0xbe8
#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7 0xbec
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+391
View File
@@ -0,0 +1,391 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2026, NVIDIA CORPORATION. All rights reserved.
*/
#include <soc/tegra/mc.h>
#include <dt-bindings/memory/tegra234-mc.h>
#include <dt-bindings/memory/nvidia,tegra238-mc.h>
#include <linux/interconnect.h>
#include <linux/tegra-icc.h>
#include <soc/tegra/bpmp.h>
#include "mc.h"
static const struct tegra_mc_client tegra238_mc_clients[] = {
{
.id = TEGRA234_MEMORY_CLIENT_HDAR,
.name = "hdar",
.bpmp_id = TEGRA_ICC_BPMP_HDA,
.type = TEGRA_ICC_ISO_AUDIO,
.sid = TEGRA238_SID_HDA,
.regs = {
.sid = {
.override = 0xa8,
.security = 0xac,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_HDAW,
.name = "hdaw",
.bpmp_id = TEGRA_ICC_BPMP_HDA,
.type = TEGRA_ICC_ISO_AUDIO,
.sid = TEGRA238_SID_HDA,
.regs = {
.sid = {
.override = 0x1a8,
.security = 0x1ac,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_SDMMCRAB,
.name = "sdmmcrab",
.bpmp_id = TEGRA_ICC_BPMP_SDMMC_4,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_SDMMC4A,
.regs = {
.sid = {
.override = 0x318,
.security = 0x31c,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_SDMMCWAB,
.name = "sdmmcwab",
.bpmp_id = TEGRA_ICC_BPMP_SDMMC_4,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_SDMMC4A,
.regs = {
.sid = {
.override = 0x338,
.security = 0x33c,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_APER,
.name = "aper",
.bpmp_id = TEGRA_ICC_BPMP_APE,
.type = TEGRA_ICC_ISO_AUDIO,
.sid = TEGRA238_SID_ISO_APE0,
.regs = {
.sid = {
.override = 0x3d0,
.security = 0x3d4,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_APEW,
.name = "apew",
.bpmp_id = TEGRA_ICC_BPMP_APE,
.type = TEGRA_ICC_ISO_AUDIO,
.sid = TEGRA238_SID_ISO_APE0,
.regs = {
.sid = {
.override = 0x3d8,
.security = 0x3dc,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_NVDISPLAYR,
.name = "nvdisplayr",
.bpmp_id = TEGRA_ICC_BPMP_DISPLAY,
.type = TEGRA_ICC_ISO_DISPLAY,
.sid = TEGRA238_SID_ISO_NVDISPLAY,
.regs = {
.sid = {
.override = 0x490,
.security = 0x494,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_NVDISPLAYR1,
.name = "nvdisplayr1",
.bpmp_id = TEGRA_ICC_BPMP_DISPLAY,
.type = TEGRA_ICC_ISO_DISPLAY,
.sid = TEGRA238_SID_ISO_NVDISPLAY,
.regs = {
.sid = {
.override = 0x508,
.security = 0x50c,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_BPMPR,
.name = "bpmpr",
.sid = TEGRA238_SID_BPMP,
.regs = {
.sid = {
.override = 0x498,
.security = 0x49c,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_BPMPW,
.name = "bpmpw",
.sid = TEGRA238_SID_BPMP,
.regs = {
.sid = {
.override = 0x4a0,
.security = 0x4a4,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_BPMPDMAR,
.name = "bpmpdmar",
.sid = TEGRA238_SID_BPMP,
.regs = {
.sid = {
.override = 0x4a8,
.security = 0x4ac,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_BPMPDMAW,
.name = "bpmpdmaw",
.sid = TEGRA238_SID_BPMP,
.regs = {
.sid = {
.override = 0x4b0,
.security = 0x4b4,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_APEDMAR,
.name = "apedmar",
.bpmp_id = TEGRA_ICC_BPMP_APEDMA,
.type = TEGRA_ICC_ISO_AUDIO,
.sid = TEGRA238_SID_ISO_APE1,
.regs = {
.sid = {
.override = 0x4f8,
.security = 0x4fc,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_APEDMAW,
.name = "apedmaw",
.bpmp_id = TEGRA_ICC_BPMP_APEDMA,
.type = TEGRA_ICC_ISO_AUDIO,
.sid = TEGRA238_SID_ISO_APE1,
.regs = {
.sid = {
.override = 0x500,
.security = 0x504,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_VICSRD,
.name = "vicsrd",
.bpmp_id = TEGRA_ICC_BPMP_VIC,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_VIC,
.regs = {
.sid = {
.override = 0x360,
.security = 0x364,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_VICSWR,
.name = "vicswr",
.bpmp_id = TEGRA_ICC_BPMP_VIC,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_VIC,
.regs = {
.sid = {
.override = 0x368,
.security = 0x36c,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_NVDECSRD,
.name = "nvdecsrd",
.bpmp_id = TEGRA_ICC_BPMP_NVDEC,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_NVDEC,
.regs = {
.sid = {
.override = 0x3c0,
.security = 0x3c4,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_NVDECSWR,
.name = "nvdecswr",
.bpmp_id = TEGRA_ICC_BPMP_NVDEC,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_NVDEC,
.regs = {
.sid = {
.override = 0x3c8,
.security = 0x3cc,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_NVENCSRD,
.name = "nvencsrd",
.bpmp_id = TEGRA_ICC_BPMP_NVENC,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_NVENC,
.regs = {
.sid = {
.override = 0xe0,
.security = 0xe4,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_NVENCSWR,
.name = "nvencswr",
.bpmp_id = TEGRA_ICC_BPMP_NVENC,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_NVENC,
.regs = {
.sid = {
.override = 0x158,
.security = 0x15c,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_PCIE0R,
.name = "pcie0r",
.bpmp_id = TEGRA_ICC_BPMP_PCIE_0,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_PCIE0,
.regs = {
.sid = {
.override = 0x6c0,
.security = 0x6c4,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_PCIE0W,
.name = "pcie0w",
.bpmp_id = TEGRA_ICC_BPMP_PCIE_0,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_PCIE0,
.regs = {
.sid = {
.override = 0x6c8,
.security = 0x6cc,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_PCIE1R,
.name = "pcie1r",
.bpmp_id = TEGRA_ICC_BPMP_PCIE_1,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_PCIE1,
.regs = {
.sid = {
.override = 0x6d0,
.security = 0x6d4,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_PCIE1W,
.name = "pcie1w",
.bpmp_id = TEGRA_ICC_BPMP_PCIE_1,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_PCIE1,
.regs = {
.sid = {
.override = 0x6d8,
.security = 0x6dc,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_PCIE2AR,
.name = "pcie2ar",
.bpmp_id = TEGRA_ICC_BPMP_PCIE_2,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_PCIE2,
.regs = {
.sid = {
.override = 0x6e0,
.security = 0x6e4,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_PCIE2AW,
.name = "pcie2aw",
.bpmp_id = TEGRA_ICC_BPMP_PCIE_2,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_PCIE2,
.regs = {
.sid = {
.override = 0x6e8,
.security = 0x6ec,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_PCIE3R,
.name = "pcie3r",
.bpmp_id = TEGRA_ICC_BPMP_PCIE_3,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_PCIE3,
.regs = {
.sid = {
.override = 0x6f0,
.security = 0x6f4,
},
},
}, {
.id = TEGRA234_MEMORY_CLIENT_PCIE3W,
.name = "pcie3w",
.bpmp_id = TEGRA_ICC_BPMP_PCIE_3,
.type = TEGRA_ICC_NISO,
.sid = TEGRA238_SID_PCIE3,
.regs = {
.sid = {
.override = 0x6f8,
.security = 0x6fc,
},
},
}, {
.id = TEGRA_ICC_MC_CPU_CLUSTER0,
.name = "sw_cluster0",
.bpmp_id = TEGRA_ICC_BPMP_CPU_CLUSTER0,
.type = TEGRA_ICC_NISO,
}, {
.id = TEGRA234_MEMORY_CLIENT_NVL1R,
.name = "nvl1r",
.bpmp_id = TEGRA_ICC_BPMP_GPU,
.type = TEGRA_ICC_NISO,
}, {
.id = TEGRA234_MEMORY_CLIENT_NVL1W,
.name = "nvl1w",
.bpmp_id = TEGRA_ICC_BPMP_GPU,
.type = TEGRA_ICC_NISO,
}
};
static const struct tegra_mc_intmask tegra238_mc_intmasks[] = {
{
.reg = MC_INTMASK,
.mask = MC_INT_DECERR_ROUTE_SANITY | MC_INT_DECERR_GENERALIZED_CARVEOUT |
MC_INT_DECERR_MTS | MC_INT_SECERR_SEC | MC_INT_DECERR_VPR |
MC_INT_SECURITY_VIOLATION | MC_INT_DECERR_EMEM,
},
};
const struct tegra_mc_soc tegra238_mc_soc = {
.num_clients = ARRAY_SIZE(tegra238_mc_clients),
.clients = tegra238_mc_clients,
.num_address_bits = 40,
.num_channels = 8,
.client_id_mask = 0x1ff,
.intmasks = tegra238_mc_intmasks,
.num_intmasks = ARRAY_SIZE(tegra238_mc_intmasks),
.has_addr_hi_reg = true,
.ops = &tegra186_mc_ops,
.icc_ops = &tegra234_mc_icc_ops,
.ch_intmask = 0x0000ff00,
.global_intstatus_channel_shift = 8,
.num_carveouts = 32,
.regs = &tegra20_mc_regs,
.handle_irq = tegra30_mc_irq_handlers,
.num_interrupts = ARRAY_SIZE(tegra30_mc_irq_handlers),
.mc_addr_hi_mask = 0x3,
.mc_err_status_type_mask = (0x7 << 28),
};
@@ -0,0 +1,50 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) 2025 NVIDIA CORPORATION. All rights reserved. */
#ifndef MEMORY_TEGRA_TEGRA264_BWMGR_H
#define MEMORY_TEGRA_TEGRA264_BWMGR_H
#define TEGRA264_BWMGR_ICC_PRIMARY 1
#define TEGRA264_BWMGR_DEBUG 2
#define TEGRA264_BWMGR_CPU_CLUSTER0 3
#define TEGRA264_BWMGR_CPU_CLUSTER1 4
#define TEGRA264_BWMGR_CPU_CLUSTER2 5
#define TEGRA264_BWMGR_CPU_CLUSTER3 6
#define TEGRA264_BWMGR_CPU_CLUSTER4 7
#define TEGRA264_BWMGR_CPU_CLUSTER5 8
#define TEGRA264_BWMGR_CPU_CLUSTER6 9
#define TEGRA264_BWMGR_CACTMON 10
#define TEGRA264_BWMGR_DISPLAY 11
#define TEGRA264_BWMGR_VI 12
#define TEGRA264_BWMGR_APE 13
#define TEGRA264_BWMGR_VIFAL 14
#define TEGRA264_BWMGR_GPU 15
#define TEGRA264_BWMGR_EQOS 16
#define TEGRA264_BWMGR_PCIE_0 17
#define TEGRA264_BWMGR_PCIE_1 18
#define TEGRA264_BWMGR_PCIE_2 19
#define TEGRA264_BWMGR_PCIE_3 20
#define TEGRA264_BWMGR_PCIE_4 21
#define TEGRA264_BWMGR_PCIE_5 22
#define TEGRA264_BWMGR_SDMMC_1 23
#define TEGRA264_BWMGR_SDMMC_2 24
#define TEGRA264_BWMGR_NVDEC 25
#define TEGRA264_BWMGR_NVENC 26
#define TEGRA264_BWMGR_NVJPG_0 27
#define TEGRA264_BWMGR_NVJPG_1 28
#define TEGRA264_BWMGR_OFAA 29
#define TEGRA264_BWMGR_XUSB_HOST 30
#define TEGRA264_BWMGR_XUSB_DEV 31
#define TEGRA264_BWMGR_TSEC 32
#define TEGRA264_BWMGR_VIC 33
#define TEGRA264_BWMGR_APEDMA 34
#define TEGRA264_BWMGR_SE 35
#define TEGRA264_BWMGR_ISP 36
#define TEGRA264_BWMGR_HDA 37
#define TEGRA264_BWMGR_VI2FAL 38
#define TEGRA264_BWMGR_VI2 39
#define TEGRA264_BWMGR_RCE 40
#define TEGRA264_BWMGR_PVA 41
#define TEGRA264_BWMGR_NVPMODEL 42
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+446
View File
@@ -0,0 +1,446 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 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>
*/
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/memory/ti-aemif.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.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 SSTROBE_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 SSTROBE(x) ((x) << SSTROBE_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 SSTROBE_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 SSTROBE_VAL(x) (((x) & SSTROBE(SSTROBE_MAX)) >> SSTROBE_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_SSTROBE_MASK BIT(31)
#define ASIZE_16BIT 1
#define TIMINGS_MASK (TA(TA_MAX) | \
RHOLD(RHOLD_MAX) | \
RSTROBE(RSTROBE_MAX) | \
RSETUP(RSETUP_MAX) | \
WHOLD(WHOLD_MAX) | \
WSTROBE(WSTROBE_MAX) | \
WSETUP(WSETUP_MAX))
#define CONFIG_MASK (EW(EW_MAX) | SSTROBE(SSTROBE_MAX) | ASIZE_MAX)
/**
* struct aemif_cs_data: structure to hold CS parameters
* @timings: timings configuration
* @cs: chip-select number
* @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 {
struct aemif_cs_timings timings;
u8 cs;
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
* @config_cs_lock: lock used to access CS configuration
*/
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];
struct mutex config_cs_lock;
};
/**
* aemif_check_cs_timings() - Check the validity of a CS timing configuration.
* @timings: timings configuration
*
* @return: 0 if the timing configuration is valid, negative error number otherwise.
*/
int aemif_check_cs_timings(struct aemif_cs_timings *timings)
{
if (timings->ta > TA_MAX)
return -EINVAL;
if (timings->rhold > RHOLD_MAX)
return -EINVAL;
if (timings->rstrobe > RSTROBE_MAX)
return -EINVAL;
if (timings->rsetup > RSETUP_MAX)
return -EINVAL;
if (timings->whold > WHOLD_MAX)
return -EINVAL;
if (timings->wstrobe > WSTROBE_MAX)
return -EINVAL;
if (timings->wsetup > WSETUP_MAX)
return -EINVAL;
return 0;
}
EXPORT_SYMBOL_GPL(aemif_check_cs_timings);
/**
* aemif_set_cs_timings() - Set the timing configuration of a given chip select.
* @aemif: aemif device to configure
* @cs: index of the chip select to configure
* @timings: timings configuration to set
*
* @return: 0 on success, else negative errno.
*/
int aemif_set_cs_timings(struct aemif_device *aemif, u8 cs,
struct aemif_cs_timings *timings)
{
unsigned int offset;
u32 val, set;
int ret;
if (!timings || !aemif)
return -EINVAL;
if (cs > aemif->num_cs)
return -EINVAL;
ret = aemif_check_cs_timings(timings);
if (ret)
return ret;
set = TA(timings->ta) | RHOLD(timings->rhold) | RSTROBE(timings->rstrobe) |
RSETUP(timings->rsetup) | WHOLD(timings->whold) |
WSTROBE(timings->wstrobe) | WSETUP(timings->wsetup);
offset = A1CR_OFFSET + cs * 4;
mutex_lock(&aemif->config_cs_lock);
val = readl(aemif->base + offset);
val &= ~TIMINGS_MASK;
val |= set;
writel(val, aemif->base + offset);
mutex_unlock(&aemif->config_cs_lock);
return 0;
}
EXPORT_SYMBOL_GPL(aemif_set_cs_timings);
/**
* 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.
*
* @return: the calculated timing value minus 1 for easy
* programming into AEMIF timing registers.
*/
static u32 aemif_calc_rate(struct platform_device *pdev, int wanted, unsigned long clk)
{
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;
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];
unsigned offset;
u32 set, val;
offset = A1CR_OFFSET + (data->cs - aemif->cs_offset) * 4;
set = (data->asize & ACR_ASIZE_MASK);
if (data->enable_ew)
set |= ACR_EW_MASK;
if (data->enable_ss)
set |= ACR_SSTROBE_MASK;
mutex_lock(&aemif->config_cs_lock);
val = readl(aemif->base + offset);
val &= ~CONFIG_MASK;
val |= set;
writel(val, aemif->base + offset);
mutex_unlock(&aemif->config_cs_lock);
return aemif_set_cs_timings(aemif, data->cs - aemif->cs_offset, &data->timings);
}
/**
* 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];
u32 val, offset;
offset = A1CR_OFFSET + (data->cs - aemif->cs_offset) * 4;
val = readl(aemif->base + offset);
data->timings.ta = TA_VAL(val);
data->timings.rhold = RHOLD_VAL(val);
data->timings.rstrobe = RSTROBE_VAL(val);
data->timings.rsetup = RSETUP_VAL(val);
data->timings.whold = WHOLD_VAL(val);
data->timings.wstrobe = WSTROBE_VAL(val);
data->timings.wsetup = WSETUP_VAL(val);
data->enable_ew = EW_VAL(val);
data->enable_ss = SSTROBE_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);
unsigned long clk_rate = aemif->clk_rate;
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->timings.ta = aemif_calc_rate(pdev, val, clk_rate);
if (!of_property_read_u32(np, "ti,cs-read-hold-ns", &val))
data->timings.rhold = aemif_calc_rate(pdev, val, clk_rate);
if (!of_property_read_u32(np, "ti,cs-read-strobe-ns", &val))
data->timings.rstrobe = aemif_calc_rate(pdev, val, clk_rate);
if (!of_property_read_u32(np, "ti,cs-read-setup-ns", &val))
data->timings.rsetup = aemif_calc_rate(pdev, val, clk_rate);
if (!of_property_read_u32(np, "ti,cs-write-hold-ns", &val))
data->timings.whold = aemif_calc_rate(pdev, val, clk_rate);
if (!of_property_read_u32(np, "ti,cs-write-strobe-ns", &val))
data->timings.wstrobe = aemif_calc_rate(pdev, val, clk_rate);
if (!of_property_read_u32(np, "ti,cs-write-setup-ns", &val))
data->timings.wsetup = aemif_calc_rate(pdev, val, clk_rate);
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 aemif_check_cs_timings(&data->timings);
}
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 device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct aemif_device *aemif;
aemif = devm_kzalloc(dev, sizeof(*aemif), GFP_KERNEL);
if (!aemif)
return -ENOMEM;
platform_set_drvdata(pdev, aemif);
aemif->clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(aemif->clk))
return dev_err_probe(dev, PTR_ERR(aemif->clk),
"cannot get clock 'aemif'\n");
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;
aemif->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(aemif->base))
return PTR_ERR(aemif->base);
mutex_init(&aemif->config_cs_lock);
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_scoped(np, child_np) {
ret = of_aemif_parse_abus_config(pdev, child_np);
if (ret < 0)
return ret;
}
}
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);
return ret;
}
}
/*
* 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_scoped(np, child_np) {
ret = of_platform_populate(child_np, NULL, NULL, dev);
if (ret < 0)
return ret;
}
}
return 0;
}
static struct platform_driver aemif_driver = {
.probe = aemif_probe,
.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);
+344
View File
@@ -0,0 +1,344 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* TI AM33XX SRAM EMIF Driver
*
* Copyright (C) 2016-2017 Texas Instruments Inc.
* Dave Gerlach
*/
#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_functions.run_hw_leveling =
sram_resume_address(emif_data,
(unsigned long)ti_emif_run_hw_leveling);
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 __iomem *)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;
struct ti_emif_data *emif_data;
emif_data = devm_kzalloc(dev, sizeof(*emif_data), GFP_KERNEL);
if (!emif_data)
return -ENOMEM;
emif_data->pm_data.ti_emif_sram_config = (unsigned long) device_get_match_data(&pdev->dev);
emif_data->pm_data.ti_emif_base_addr_virt = devm_platform_get_and_ioremap_resource(pdev,
0,
&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 void ti_emif_remove(struct platform_device *pdev)
{
struct ti_emif_data *emif_data = emif_instance;
emif_instance = NULL;
ti_emif_free_sram(emif_data);
}
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 = 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");
@@ -0,0 +1,368 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Low level PM code for TI EMIF
*
* Copyright (C) 2016-2017 Texas Instruments Incorporated - http://www.ti.com/
* Dave Gerlach
*/
#include <linux/linkage.h>
#include <asm/assembler.h>
#include <asm/page.h>
#include "emif.h"
#include "ti-emif-asm-offsets.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_SDCFG_TYPE_DDR3 0x3 << SDRAM_TYPE_SHIFT
#define EMIF_STATUS_READY 0x4
#define AM43XX_EMIF_PHY_CTRL_REG_COUNT 0x120
#define EMIF_AM437X_REGISTERS 0x1
.arm
.align 3
.arch armv7-a
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_run_hw_leveling(void)
*
* Used during resume to run hardware leveling again and restore the
* configuration of the EMIF PHY, only for DDR3.
*/
ENTRY(ti_emif_run_hw_leveling)
adr r4, ti_emif_pm_sram_data
ldr r0, [r4, #EMIF_PM_BASE_ADDR_PHYS_OFFSET]
ldr r3, [r0, #EMIF_READ_WRITE_LEVELING_CONTROL]
orr r3, r3, #RDWRLVLFULL_START
ldr r2, [r0, #EMIF_SDRAM_CONFIG]
and r2, r2, #SDRAM_TYPE_MASK
cmp r2, #EMIF_SDCFG_TYPE_DDR3
bne skip_hwlvl
str r3, [r0, #EMIF_READ_WRITE_LEVELING_CONTROL]
/*
* If EMIF registers are touched during initial stage of HW
* leveling sequence there will be an L3 NOC timeout error issued
* as the EMIF will not respond, which is not fatal, but it is
* avoidable. This small wait loop is enough time for this condition
* to clear, even at worst case of CPU running at max speed of 1Ghz.
*/
mov r2, #0x2000
1:
subs r2, r2, #0x1
bne 1b
/* Bit clears when operation is complete */
2: ldr r1, [r0, #EMIF_READ_WRITE_LEVELING_CONTROL]
tst r1, #RDWRLVLFULL_START
bne 2b
skip_hwlvl:
mov pc, lr
ENDPROC(ti_emif_run_hw_leveling)
/*
* 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