summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2019-12-05 20:43:31 +0100
committerLinus Torvalds <torvalds@linux-foundation.org>2019-12-05 20:43:31 +0100
commitec939e4c94bd3ef2fd4f34c15f8aaf79bd0c5ee1 (patch)
tree1d39945dbdd233d35c571a726e135fe0ae845814 /drivers
parentMerge tag 'armsoc-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc (diff)
parentMerge tag 'scmi-fix-5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/sud... (diff)
downloadlinux-ec939e4c94bd3ef2fd4f34c15f8aaf79bd0c5ee1.tar.xz
linux-ec939e4c94bd3ef2fd4f34c15f8aaf79bd0c5ee1.zip
Merge tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc
Pull ARM SoC driver updates from Olof Johansson: "Various driver updates for platforms: - A larger set of work on Tegra 2/3 around memory controller and regulator features, some fuse cleanups, etc.. - MMP platform drivers, in particular for USB PHY, and other smaller additions. - Samsung Exynos 5422 driver for DMC (dynamic memory configuration), and ASV (adaptive voltage), allowing the platform to run at more optimal operating points. - Misc refactorings and support for RZ/G2N and R8A774B1 from Renesas - Clock/reset control driver for TI/OMAP - Meson-A1 reset controller support - Qualcomm sdm845 and sda845 SoC IDs for socinfo" * tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc: (150 commits) firmware: arm_scmi: Fix doorbell ring logic for !CONFIG_64BIT soc: fsl: add RCPM driver dt-bindings: fsl: rcpm: Add 'little-endian' and update Chassis definition memory: tegra: Consolidate registers definition into common header memory: tegra: Ensure timing control debug features are disabled memory: tegra: Introduce Tegra30 EMC driver memory: tegra: Do not handle error from wait_for_completion_timeout() memory: tegra: Increase handshake timeout on Tegra20 memory: tegra: Print a brief info message about EMC timings memory: tegra: Pre-configure debug register on Tegra20 memory: tegra: Include io.h instead of iopoll.h memory: tegra: Adapt for Tegra20 clock driver changes memory: tegra: Don't set EMC rate to maximum on probe for Tegra20 memory: tegra: Add gr2d and gr3d to DRM IOMMU group memory: tegra: Set DMA mask based on supported address bits soc: at91: Add Atmel SFR SN (Serial Number) support memory: atmel-ebi: switch to SPDX license identifiers memory: atmel-ebi: move NUM_CS definition inside EBI driver soc: mediatek: Refactor bus protection control soc: mediatek: Refactor sram control ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/base/power/wakeup.c54
-rw-r--r--drivers/bus/Kconfig5
-rw-r--r--drivers/bus/hisi_lpc.c9
-rw-r--r--drivers/bus/ti-sysc.c87
-rw-r--r--drivers/firmware/arm_scmi/perf.c2
-rw-r--r--drivers/firmware/imx/imx-dsp.c2
-rw-r--r--drivers/firmware/imx/imx-scu-irq.c1
-rw-r--r--drivers/firmware/imx/imx-scu.c24
-rw-r--r--drivers/firmware/meson/meson_sm.c110
-rw-r--r--drivers/firmware/tegra/bpmp.c2
-rw-r--r--drivers/firmware/xilinx/zynqmp.c8
-rw-r--r--drivers/memory/atmel-ebi.c11
-rw-r--r--drivers/memory/brcmstb_dpfe.c164
-rw-r--r--drivers/memory/emif.c5
-rw-r--r--drivers/memory/jedec_ddr.h61
-rw-r--r--drivers/memory/of_memory.c149
-rw-r--r--drivers/memory/of_memory.h18
-rw-r--r--drivers/memory/samsung/Kconfig13
-rw-r--r--drivers/memory/samsung/Makefile1
-rw-r--r--drivers/memory/samsung/exynos5422-dmc.c1550
-rw-r--r--drivers/memory/tegra/Kconfig10
-rw-r--r--drivers/memory/tegra/Makefile1
-rw-r--r--drivers/memory/tegra/mc.c52
-rw-r--r--drivers/memory/tegra/mc.h74
-rw-r--r--drivers/memory/tegra/tegra114.c10
-rw-r--r--drivers/memory/tegra/tegra124.c30
-rw-r--r--drivers/memory/tegra/tegra20-emc.c134
-rw-r--r--drivers/memory/tegra/tegra30-emc.c1232
-rw-r--r--drivers/memory/tegra/tegra30.c34
-rw-r--r--drivers/nvmem/meson-efuse.c24
-rw-r--r--drivers/phy/marvell/Kconfig11
-rw-r--r--drivers/phy/marvell/Makefile1
-rw-r--r--drivers/phy/marvell/phy-mmp3-usb.c291
-rw-r--r--drivers/reset/Kconfig5
-rw-r--r--drivers/reset/core.c8
-rw-r--r--drivers/reset/hisilicon/reset-hi3660.c2
-rw-r--r--drivers/reset/reset-meson-audio-arb.c43
-rw-r--r--drivers/reset/reset-meson.c35
-rw-r--r--drivers/reset/reset-uniphier-glue.c4
-rw-r--r--drivers/reset/reset-zynqmp.c2
-rw-r--r--drivers/soc/amlogic/meson-gx-socinfo.c3
-rw-r--r--drivers/soc/atmel/Kconfig11
-rw-r--r--drivers/soc/atmel/Makefile1
-rw-r--r--drivers/soc/atmel/sfr.c99
-rw-r--r--drivers/soc/fsl/Kconfig10
-rw-r--r--drivers/soc/fsl/Makefile1
-rw-r--r--drivers/soc/fsl/rcpm.c151
-rw-r--r--drivers/soc/imx/soc-imx-scu.c34
-rw-r--r--drivers/soc/imx/soc-imx8.c49
-rw-r--r--drivers/soc/mediatek/mtk-scpsys.c214
-rw-r--r--drivers/soc/qcom/Kconfig14
-rw-r--r--drivers/soc/qcom/Makefile3
-rw-r--r--drivers/soc/qcom/llcc-qcom.c (renamed from drivers/soc/qcom/llcc-slice.c)130
-rw-r--r--drivers/soc/qcom/llcc-sdm845.c100
-rw-r--r--drivers/soc/qcom/rpmpd.c23
-rw-r--r--drivers/soc/qcom/smd-rpm.c18
-rw-r--r--drivers/soc/qcom/socinfo.c2
-rw-r--r--drivers/soc/renesas/Kconfig32
-rw-r--r--drivers/soc/renesas/Makefile4
-rw-r--r--drivers/soc/renesas/r8a7743-sysc.c1
-rw-r--r--drivers/soc/renesas/r8a7745-sysc.c1
-rw-r--r--drivers/soc/renesas/r8a77470-sysc.c1
-rw-r--r--drivers/soc/renesas/r8a774a1-sysc.c1
-rw-r--r--drivers/soc/renesas/r8a774b1-sysc.c37
-rw-r--r--drivers/soc/renesas/r8a774c0-sysc.c4
-rw-r--r--drivers/soc/renesas/r8a7779-sysc.c1
-rw-r--r--drivers/soc/renesas/r8a7790-sysc.c1
-rw-r--r--drivers/soc/renesas/r8a7791-sysc.c1
-rw-r--r--drivers/soc/renesas/r8a7792-sysc.c1
-rw-r--r--drivers/soc/renesas/r8a7794-sysc.c1
-rw-r--r--drivers/soc/renesas/r8a7795-sysc.c33
-rw-r--r--drivers/soc/renesas/r8a7796-sysc.c30
-rw-r--r--drivers/soc/renesas/r8a77965-sysc.c4
-rw-r--r--drivers/soc/renesas/r8a77970-sysc.c4
-rw-r--r--drivers/soc/renesas/r8a77980-sysc.c4
-rw-r--r--drivers/soc/renesas/r8a77990-sysc.c4
-rw-r--r--drivers/soc/renesas/r8a77995-sysc.c1
-rw-r--r--drivers/soc/renesas/rcar-rst.c2
-rw-r--r--drivers/soc/renesas/rcar-sysc.c26
-rw-r--r--drivers/soc/renesas/rcar-sysc.h9
-rw-r--r--drivers/soc/renesas/renesas-soc.c15
-rw-r--r--drivers/soc/samsung/Kconfig10
-rw-r--r--drivers/soc/samsung/Makefile3
-rw-r--r--drivers/soc/samsung/exynos-asv.c177
-rw-r--r--drivers/soc/samsung/exynos-asv.h71
-rw-r--r--drivers/soc/samsung/exynos-chipid.c12
-rw-r--r--drivers/soc/samsung/exynos5422-asv.c505
-rw-r--r--drivers/soc/samsung/exynos5422-asv.h31
-rw-r--r--drivers/soc/tegra/Kconfig10
-rw-r--r--drivers/soc/tegra/Makefile2
-rw-r--r--drivers/soc/tegra/fuse/fuse-tegra.c198
-rw-r--r--drivers/soc/tegra/fuse/fuse-tegra30.c154
-rw-r--r--drivers/soc/tegra/fuse/fuse.h8
-rw-r--r--drivers/soc/tegra/pmc.c232
-rw-r--r--drivers/soc/tegra/regulators-tegra20.c365
-rw-r--r--drivers/soc/tegra/regulators-tegra30.c317
-rw-r--r--drivers/soc/ti/Makefile1
-rw-r--r--drivers/soc/ti/omap_prm.c391
-rw-r--r--drivers/soc/xilinx/zynqmp_pm_domains.c10
99 files changed, 7166 insertions, 691 deletions
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
index 5817b51d2b15..70a9edb5f525 100644
--- a/drivers/base/power/wakeup.c
+++ b/drivers/base/power/wakeup.c
@@ -248,6 +248,60 @@ void wakeup_source_unregister(struct wakeup_source *ws)
EXPORT_SYMBOL_GPL(wakeup_source_unregister);
/**
+ * wakeup_sources_read_lock - Lock wakeup source list for read.
+ *
+ * Returns an index of srcu lock for struct wakeup_srcu.
+ * This index must be passed to the matching wakeup_sources_read_unlock().
+ */
+int wakeup_sources_read_lock(void)
+{
+ return srcu_read_lock(&wakeup_srcu);
+}
+EXPORT_SYMBOL_GPL(wakeup_sources_read_lock);
+
+/**
+ * wakeup_sources_read_unlock - Unlock wakeup source list.
+ * @idx: return value from corresponding wakeup_sources_read_lock()
+ */
+void wakeup_sources_read_unlock(int idx)
+{
+ srcu_read_unlock(&wakeup_srcu, idx);
+}
+EXPORT_SYMBOL_GPL(wakeup_sources_read_unlock);
+
+/**
+ * wakeup_sources_walk_start - Begin a walk on wakeup source list
+ *
+ * Returns first object of the list of wakeup sources.
+ *
+ * Note that to be safe, wakeup sources list needs to be locked by calling
+ * wakeup_source_read_lock() for this.
+ */
+struct wakeup_source *wakeup_sources_walk_start(void)
+{
+ struct list_head *ws_head = &wakeup_sources;
+
+ return list_entry_rcu(ws_head->next, struct wakeup_source, entry);
+}
+EXPORT_SYMBOL_GPL(wakeup_sources_walk_start);
+
+/**
+ * wakeup_sources_walk_next - Get next wakeup source from the list
+ * @ws: Previous wakeup source object
+ *
+ * Note that to be safe, wakeup sources list needs to be locked by calling
+ * wakeup_source_read_lock() for this.
+ */
+struct wakeup_source *wakeup_sources_walk_next(struct wakeup_source *ws)
+{
+ struct list_head *ws_head = &wakeup_sources;
+
+ return list_next_or_null_rcu(ws_head, &ws->entry,
+ struct wakeup_source, entry);
+}
+EXPORT_SYMBOL_GPL(wakeup_sources_walk_next);
+
+/**
* device_wakeup_attach - Attach a wakeup source object to a device object.
* @dev: Device to handle.
* @ws: Wakeup source object to attach to @dev.
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig
index 97ab5ad171d4..50200d1c06ea 100644
--- a/drivers/bus/Kconfig
+++ b/drivers/bus/Kconfig
@@ -41,8 +41,9 @@ config MOXTET
config HISILICON_LPC
bool "Support for ISA I/O space on HiSilicon Hip06/7"
- depends on ARM64 && (ARCH_HISI || COMPILE_TEST)
- select INDIRECT_PIO
+ depends on (ARM64 && ARCH_HISI) || (COMPILE_TEST && !ALPHA && !HEXAGON && !PARISC && !C6X)
+ depends on HAS_IOMEM
+ select INDIRECT_PIO if ARM64
help
Driver to enable I/O access to devices attached to the Low Pin
Count bus on the HiSilicon Hip06/7 SoC.
diff --git a/drivers/bus/hisi_lpc.c b/drivers/bus/hisi_lpc.c
index 20c957185af2..8101df901830 100644
--- a/drivers/bus/hisi_lpc.c
+++ b/drivers/bus/hisi_lpc.c
@@ -74,7 +74,7 @@ struct hisi_lpc_dev {
/* About 10us. This is specific for single IO operations, such as inb */
#define LPC_PEROP_WAITCNT 100
-static int wait_lpc_idle(unsigned char *mbase, unsigned int waitcnt)
+static int wait_lpc_idle(void __iomem *mbase, unsigned int waitcnt)
{
u32 status;
@@ -209,7 +209,7 @@ static u32 hisi_lpc_comm_in(void *hostdata, unsigned long pio, size_t dwidth)
struct hisi_lpc_dev *lpcdev = hostdata;
struct lpc_cycle_para iopara;
unsigned long addr;
- u32 rd_data = 0;
+ __le32 rd_data = 0;
int ret;
if (!lpcdev || !dwidth || dwidth > LPC_MAX_DWIDTH)
@@ -244,13 +244,12 @@ static void hisi_lpc_comm_out(void *hostdata, unsigned long pio,
struct lpc_cycle_para iopara;
const unsigned char *buf;
unsigned long addr;
+ __le32 _val = cpu_to_le32(val);
if (!lpcdev || !dwidth || dwidth > LPC_MAX_DWIDTH)
return;
- val = cpu_to_le32(val);
-
- buf = (const unsigned char *)&val;
+ buf = (const unsigned char *)&_val;
addr = hisi_lpc_pio_to_addr(lpcdev, pio);
iopara.opflags = FG_INCRADDR_LPC;
diff --git a/drivers/bus/ti-sysc.c b/drivers/bus/ti-sysc.c
index 2b6670daf7fc..97b85493aa43 100644
--- a/drivers/bus/ti-sysc.c
+++ b/drivers/bus/ti-sysc.c
@@ -917,6 +917,9 @@ set_midle:
return -EINVAL;
}
+ if (ddata->cfg.quirks & SYSC_QUIRK_SWSUP_MSTANDBY)
+ best_mode = SYSC_IDLE_NO;
+
reg &= ~(SYSC_IDLE_MASK << regbits->midle_shift);
reg |= best_mode << regbits->midle_shift;
sysc_write(ddata, ddata->offsets[SYSC_SYSCONFIG], reg);
@@ -978,6 +981,9 @@ static int sysc_disable_module(struct device *dev)
return ret;
}
+ if (ddata->cfg.quirks & SYSC_QUIRK_SWSUP_MSTANDBY)
+ best_mode = SYSC_IDLE_FORCE;
+
reg &= ~(SYSC_IDLE_MASK << regbits->midle_shift);
reg |= best_mode << regbits->midle_shift;
sysc_write(ddata, ddata->offsets[SYSC_SYSCONFIG], reg);
@@ -1037,8 +1043,6 @@ static int __maybe_unused sysc_runtime_resume_legacy(struct device *dev,
struct ti_sysc_platform_data *pdata;
int error;
- reset_control_deassert(ddata->rsts);
-
pdata = dev_get_platdata(ddata->dev);
if (!pdata)
return 0;
@@ -1051,6 +1055,8 @@ static int __maybe_unused sysc_runtime_resume_legacy(struct device *dev,
dev_err(dev, "%s: could not enable: %i\n",
__func__, error);
+ reset_control_deassert(ddata->rsts);
+
return 0;
}
@@ -1104,8 +1110,6 @@ static int __maybe_unused sysc_runtime_resume(struct device *dev)
sysc_clkdm_deny_idle(ddata);
- reset_control_deassert(ddata->rsts);
-
if (sysc_opt_clks_needed(ddata)) {
error = sysc_enable_opt_clocks(ddata);
if (error)
@@ -1116,6 +1120,8 @@ static int __maybe_unused sysc_runtime_resume(struct device *dev)
if (error)
goto err_opt_clocks;
+ reset_control_deassert(ddata->rsts);
+
if (ddata->legacy_mode) {
error = sysc_runtime_resume_legacy(dev, ddata);
if (error)
@@ -1251,6 +1257,10 @@ static const struct sysc_revision_quirk sysc_revision_quirks[] = {
SYSC_QUIRK("gpu", 0x50000000, 0x14, -1, -1, 0x00010201, 0xffffffff, 0),
SYSC_QUIRK("gpu", 0x50000000, 0xfe00, 0xfe10, -1, 0x40000000 , 0xffffffff,
SYSC_MODULE_QUIRK_SGX),
+ SYSC_QUIRK("usb_otg_hs", 0, 0x400, 0x404, 0x408, 0x00000050,
+ 0xffffffff, SYSC_QUIRK_SWSUP_SIDLE | SYSC_QUIRK_SWSUP_MSTANDBY),
+ SYSC_QUIRK("usb_otg_hs", 0, 0, 0x10, -1, 0x4ea2080d, 0xffffffff,
+ SYSC_QUIRK_SWSUP_SIDLE | SYSC_QUIRK_SWSUP_MSTANDBY),
SYSC_QUIRK("wdt", 0, 0, 0x10, 0x14, 0x502a0500, 0xfffff0f0,
SYSC_MODULE_QUIRK_WDT),
/* Watchdog on am3 and am4 */
@@ -1309,8 +1319,6 @@ static const struct sysc_revision_quirk sysc_revision_quirks[] = {
SYSC_QUIRK("usbhstll", 0, 0, 0x10, 0x14, 0x00000008, 0xffffffff, 0),
SYSC_QUIRK("usb_host_hs", 0, 0, 0x10, 0x14, 0x50700100, 0xffffffff, 0),
SYSC_QUIRK("usb_host_hs", 0, 0, 0x10, -1, 0x50700101, 0xffffffff, 0),
- SYSC_QUIRK("usb_otg_hs", 0, 0x400, 0x404, 0x408, 0x00000050,
- 0xffffffff, 0),
SYSC_QUIRK("vfpe", 0, 0, 0x104, -1, 0x4d001200, 0xffffffff, 0),
#endif
};
@@ -1532,37 +1540,6 @@ static int sysc_legacy_init(struct sysc *ddata)
return error;
}
-/**
- * sysc_rstctrl_reset_deassert - deassert rstctrl reset
- * @ddata: device driver data
- * @reset: reset before deassert
- *
- * A module can have both OCP softreset control and external rstctrl.
- * If more complicated rstctrl resets are needed, please handle these
- * directly from the child device driver and map only the module reset
- * for the parent interconnect target module device.
- *
- * Automatic reset of the module on init can be skipped with the
- * "ti,no-reset-on-init" device tree property.
- */
-static int sysc_rstctrl_reset_deassert(struct sysc *ddata, bool reset)
-{
- int error;
-
- if (!ddata->rsts)
- return 0;
-
- if (reset) {
- error = reset_control_assert(ddata->rsts);
- if (error)
- return error;
- }
-
- reset_control_deassert(ddata->rsts);
-
- return 0;
-}
-
/*
* Note that the caller must ensure the interconnect target module is enabled
* before calling reset. Otherwise reset will not complete.
@@ -1625,15 +1602,6 @@ static int sysc_reset(struct sysc *ddata)
static int sysc_init_module(struct sysc *ddata)
{
int error = 0;
- bool manage_clocks = true;
-
- error = sysc_rstctrl_reset_deassert(ddata, false);
- if (error)
- return error;
-
- if (ddata->cfg.quirks &
- (SYSC_QUIRK_NO_IDLE | SYSC_QUIRK_NO_IDLE_ON_INIT))
- manage_clocks = false;
error = sysc_clockdomain_init(ddata);
if (error)
@@ -1654,7 +1622,7 @@ static int sysc_init_module(struct sysc *ddata)
goto err_opt_clocks;
if (!(ddata->cfg.quirks & SYSC_QUIRK_NO_RESET_ON_INIT)) {
- error = sysc_rstctrl_reset_deassert(ddata, true);
+ error = reset_control_deassert(ddata->rsts);
if (error)
goto err_main_clocks;
}
@@ -1666,28 +1634,32 @@ static int sysc_init_module(struct sysc *ddata)
if (ddata->legacy_mode) {
error = sysc_legacy_init(ddata);
if (error)
- goto err_main_clocks;
+ goto err_reset;
}
if (!ddata->legacy_mode) {
error = sysc_enable_module(ddata->dev);
if (error)
- goto err_main_clocks;
+ goto err_reset;
}
error = sysc_reset(ddata);
if (error)
dev_err(ddata->dev, "Reset failed with %d\n", error);
- if (!ddata->legacy_mode && manage_clocks)
+ if (error && !ddata->legacy_mode)
sysc_disable_module(ddata->dev);
+err_reset:
+ if (error && !(ddata->cfg.quirks & SYSC_QUIRK_NO_RESET_ON_INIT))
+ reset_control_assert(ddata->rsts);
+
err_main_clocks:
- if (manage_clocks)
+ if (error)
sysc_disable_main_clocks(ddata);
err_opt_clocks:
/* No re-enable of clockdomain autoidle to prevent module autoidle */
- if (manage_clocks) {
+ if (error) {
sysc_disable_opt_clocks(ddata);
sysc_clkdm_allow_idle(ddata);
}
@@ -2460,10 +2432,17 @@ static int sysc_probe(struct platform_device *pdev)
goto unprepare;
}
- /* Balance reset counts */
- if (ddata->rsts)
+ /* Balance use counts as PM runtime should have enabled these all */
+ if (!(ddata->cfg.quirks & SYSC_QUIRK_NO_RESET_ON_INIT))
reset_control_assert(ddata->rsts);
+ if (!(ddata->cfg.quirks &
+ (SYSC_QUIRK_NO_IDLE | SYSC_QUIRK_NO_IDLE_ON_INIT))) {
+ sysc_disable_main_clocks(ddata);
+ sysc_disable_opt_clocks(ddata);
+ sysc_clkdm_allow_idle(ddata);
+ }
+
sysc_show_registers(ddata);
ddata->dev->type = &sysc_device_type;
diff --git a/drivers/firmware/arm_scmi/perf.c b/drivers/firmware/arm_scmi/perf.c
index 4a8012e3cb8c..601af4edad5e 100644
--- a/drivers/firmware/arm_scmi/perf.c
+++ b/drivers/firmware/arm_scmi/perf.c
@@ -323,7 +323,7 @@ static void scmi_perf_fc_ring_db(struct scmi_fc_db_info *db)
if (db->mask)
val = ioread64_hi_lo(db->addr) & db->mask;
- iowrite64_hi_lo(db->set, db->addr);
+ iowrite64_hi_lo(db->set | val, db->addr);
}
#endif
}
diff --git a/drivers/firmware/imx/imx-dsp.c b/drivers/firmware/imx/imx-dsp.c
index a43d2db5cbdb..4265e9dbed84 100644
--- a/drivers/firmware/imx/imx-dsp.c
+++ b/drivers/firmware/imx/imx-dsp.c
@@ -114,7 +114,7 @@ static int imx_dsp_probe(struct platform_device *pdev)
dev_info(dev, "NXP i.MX DSP IPC initialized\n");
- return devm_of_platform_populate(dev);
+ return 0;
out:
kfree(chan_name);
for (j = 0; j < i; j++) {
diff --git a/drivers/firmware/imx/imx-scu-irq.c b/drivers/firmware/imx/imx-scu-irq.c
index 687121f8c4d5..db655e87cdc8 100644
--- a/drivers/firmware/imx/imx-scu-irq.c
+++ b/drivers/firmware/imx/imx-scu-irq.c
@@ -8,6 +8,7 @@
#include <dt-bindings/firmware/imx/rsrc.h>
#include <linux/firmware/imx/ipc.h>
+#include <linux/firmware/imx/sci.h>
#include <linux/mailbox_client.h>
#define IMX_SC_IRQ_FUNC_ENABLE 1
diff --git a/drivers/firmware/imx/imx-scu.c b/drivers/firmware/imx/imx-scu.c
index 04a24a863d6e..03b43b7a6d1d 100644
--- a/drivers/firmware/imx/imx-scu.c
+++ b/drivers/firmware/imx/imx-scu.c
@@ -107,6 +107,12 @@ static void imx_scu_rx_callback(struct mbox_client *c, void *msg)
struct imx_sc_rpc_msg *hdr;
u32 *data = msg;
+ if (!sc_ipc->msg) {
+ dev_warn(sc_ipc->dev, "unexpected rx idx %d 0x%08x, ignore!\n",
+ sc_chan->idx, *data);
+ return;
+ }
+
if (sc_chan->idx == 0) {
hdr = msg;
sc_ipc->rx_size = hdr->size;
@@ -156,6 +162,7 @@ static int imx_scu_ipc_write(struct imx_sc_ipc *sc_ipc, void *msg)
*/
int imx_scu_call_rpc(struct imx_sc_ipc *sc_ipc, void *msg, bool have_resp)
{
+ uint8_t saved_svc, saved_func;
struct imx_sc_rpc_msg *hdr;
int ret;
@@ -165,7 +172,11 @@ int imx_scu_call_rpc(struct imx_sc_ipc *sc_ipc, void *msg, bool have_resp)
mutex_lock(&sc_ipc->lock);
reinit_completion(&sc_ipc->done);
- sc_ipc->msg = msg;
+ if (have_resp) {
+ sc_ipc->msg = msg;
+ saved_svc = ((struct imx_sc_rpc_msg *)msg)->svc;
+ saved_func = ((struct imx_sc_rpc_msg *)msg)->func;
+ }
sc_ipc->count = 0;
ret = imx_scu_ipc_write(sc_ipc, msg);
if (ret < 0) {
@@ -184,9 +195,20 @@ int imx_scu_call_rpc(struct imx_sc_ipc *sc_ipc, void *msg, bool have_resp)
/* response status is stored in hdr->func field */
hdr = msg;
ret = hdr->func;
+ /*
+ * Some special SCU firmware APIs do NOT have return value
+ * in hdr->func, but they do have response data, those special
+ * APIs are defined as void function in SCU firmware, so they
+ * should be treated as return success always.
+ */
+ if ((saved_svc == IMX_SC_RPC_SVC_MISC) &&
+ (saved_func == IMX_SC_MISC_FUNC_UNIQUE_ID ||
+ saved_func == IMX_SC_MISC_FUNC_GET_BUTTON_STATUS))
+ ret = 0;
}
out:
+ sc_ipc->msg = NULL;
mutex_unlock(&sc_ipc->lock);
dev_dbg(sc_ipc->dev, "RPC SVC done\n");
diff --git a/drivers/firmware/meson/meson_sm.c b/drivers/firmware/meson/meson_sm.c
index 8d908a8e0d20..1d5b4d74f96d 100644
--- a/drivers/firmware/meson/meson_sm.c
+++ b/drivers/firmware/meson/meson_sm.c
@@ -35,7 +35,7 @@ struct meson_sm_chip {
struct meson_sm_cmd cmd[];
};
-struct meson_sm_chip gxbb_chip = {
+static const struct meson_sm_chip gxbb_chip = {
.shmem_size = SZ_4K,
.cmd_shmem_in_base = 0x82000020,
.cmd_shmem_out_base = 0x82000021,
@@ -54,8 +54,6 @@ struct meson_sm_firmware {
void __iomem *sm_shmem_out_base;
};
-static struct meson_sm_firmware fw;
-
static u32 meson_sm_get_cmd(const struct meson_sm_chip *chip,
unsigned int cmd_index)
{
@@ -90,6 +88,7 @@ static void __iomem *meson_sm_map_shmem(u32 cmd_shmem, unsigned int size)
/**
* meson_sm_call - generic SMC32 call to the secure-monitor
*
+ * @fw: Pointer to secure-monitor firmware
* @cmd_index: Index of the SMC32 function ID
* @ret: Returned value
* @arg0: SMC32 Argument 0
@@ -100,15 +99,15 @@ static void __iomem *meson_sm_map_shmem(u32 cmd_shmem, unsigned int size)
*
* Return: 0 on success, a negative value on error
*/
-int meson_sm_call(unsigned int cmd_index, u32 *ret, u32 arg0,
- u32 arg1, u32 arg2, u32 arg3, u32 arg4)
+int meson_sm_call(struct meson_sm_firmware *fw, unsigned int cmd_index,
+ u32 *ret, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4)
{
u32 cmd, lret;
- if (!fw.chip)
+ if (!fw->chip)
return -ENOENT;
- cmd = meson_sm_get_cmd(fw.chip, cmd_index);
+ cmd = meson_sm_get_cmd(fw->chip, cmd_index);
if (!cmd)
return -EINVAL;
@@ -124,6 +123,7 @@ EXPORT_SYMBOL(meson_sm_call);
/**
* meson_sm_call_read - retrieve data from secure-monitor
*
+ * @fw: Pointer to secure-monitor firmware
* @buffer: Buffer to store the retrieved data
* @bsize: Size of the buffer
* @cmd_index: Index of the SMC32 function ID
@@ -137,22 +137,23 @@ EXPORT_SYMBOL(meson_sm_call);
* When 0 is returned there is no guarantee about the amount of
* data read and bsize bytes are copied in buffer.
*/
-int meson_sm_call_read(void *buffer, unsigned int bsize, unsigned int cmd_index,
- u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4)
+int meson_sm_call_read(struct meson_sm_firmware *fw, void *buffer,
+ unsigned int bsize, unsigned int cmd_index, u32 arg0,
+ u32 arg1, u32 arg2, u32 arg3, u32 arg4)
{
u32 size;
int ret;
- if (!fw.chip)
+ if (!fw->chip)
return -ENOENT;
- if (!fw.chip->cmd_shmem_out_base)
+ if (!fw->chip->cmd_shmem_out_base)
return -EINVAL;
- if (bsize > fw.chip->shmem_size)
+ if (bsize > fw->chip->shmem_size)
return -EINVAL;
- if (meson_sm_call(cmd_index, &size, arg0, arg1, arg2, arg3, arg4) < 0)
+ if (meson_sm_call(fw, cmd_index, &size, arg0, arg1, arg2, arg3, arg4) < 0)
return -EINVAL;
if (size > bsize)
@@ -164,7 +165,7 @@ int meson_sm_call_read(void *buffer, unsigned int bsize, unsigned int cmd_index,
size = bsize;
if (buffer)
- memcpy(buffer, fw.sm_shmem_out_base, size);
+ memcpy(buffer, fw->sm_shmem_out_base, size);
return ret;
}
@@ -173,6 +174,7 @@ EXPORT_SYMBOL(meson_sm_call_read);
/**
* meson_sm_call_write - send data to secure-monitor
*
+ * @fw: Pointer to secure-monitor firmware
* @buffer: Buffer containing data to send
* @size: Size of the data to send
* @cmd_index: Index of the SMC32 function ID
@@ -184,23 +186,24 @@ EXPORT_SYMBOL(meson_sm_call_read);
*
* Return: size of sent data on success, a negative value on error
*/
-int meson_sm_call_write(void *buffer, unsigned int size, unsigned int cmd_index,
- u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 arg4)
+int meson_sm_call_write(struct meson_sm_firmware *fw, void *buffer,
+ unsigned int size, unsigned int cmd_index, u32 arg0,
+ u32 arg1, u32 arg2, u32 arg3, u32 arg4)
{
u32 written;
- if (!fw.chip)
+ if (!fw->chip)
return -ENOENT;
- if (size > fw.chip->shmem_size)
+ if (size > fw->chip->shmem_size)
return -EINVAL;
- if (!fw.chip->cmd_shmem_in_base)
+ if (!fw->chip->cmd_shmem_in_base)
return -EINVAL;
- memcpy(fw.sm_shmem_in_base, buffer, size);
+ memcpy(fw->sm_shmem_in_base, buffer, size);
- if (meson_sm_call(cmd_index, &written, arg0, arg1, arg2, arg3, arg4) < 0)
+ if (meson_sm_call(fw, cmd_index, &written, arg0, arg1, arg2, arg3, arg4) < 0)
return -EINVAL;
if (!written)
@@ -210,6 +213,24 @@ int meson_sm_call_write(void *buffer, unsigned int size, unsigned int cmd_index,
}
EXPORT_SYMBOL(meson_sm_call_write);
+/**
+ * meson_sm_get - get pointer to meson_sm_firmware structure.
+ *
+ * @sm_node: Pointer to the secure-monitor Device Tree node.
+ *
+ * Return: NULL is the secure-monitor device is not ready.
+ */
+struct meson_sm_firmware *meson_sm_get(struct device_node *sm_node)
+{
+ struct platform_device *pdev = of_find_device_by_node(sm_node);
+
+ if (!pdev)
+ return NULL;
+
+ return platform_get_drvdata(pdev);
+}
+EXPORT_SYMBOL_GPL(meson_sm_get);
+
#define SM_CHIP_ID_LENGTH 119
#define SM_CHIP_ID_OFFSET 4
#define SM_CHIP_ID_SIZE 12
@@ -217,33 +238,25 @@ EXPORT_SYMBOL(meson_sm_call_write);
static ssize_t serial_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct meson_sm_firmware *fw;
uint8_t *id_buf;
int ret;
+ fw = platform_get_drvdata(pdev);
+
id_buf = kmalloc(SM_CHIP_ID_LENGTH, GFP_KERNEL);
if (!id_buf)
return -ENOMEM;
- ret = meson_sm_call_read(id_buf, SM_CHIP_ID_LENGTH, SM_GET_CHIP_ID,
+ ret = meson_sm_call_read(fw, id_buf, SM_CHIP_ID_LENGTH, SM_GET_CHIP_ID,
0, 0, 0, 0, 0);
if (ret < 0) {
kfree(id_buf);
return ret;
}
- ret = sprintf(buf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
- id_buf[SM_CHIP_ID_OFFSET + 0],
- id_buf[SM_CHIP_ID_OFFSET + 1],
- id_buf[SM_CHIP_ID_OFFSET + 2],
- id_buf[SM_CHIP_ID_OFFSET + 3],
- id_buf[SM_CHIP_ID_OFFSET + 4],
- id_buf[SM_CHIP_ID_OFFSET + 5],
- id_buf[SM_CHIP_ID_OFFSET + 6],
- id_buf[SM_CHIP_ID_OFFSET + 7],
- id_buf[SM_CHIP_ID_OFFSET + 8],
- id_buf[SM_CHIP_ID_OFFSET + 9],
- id_buf[SM_CHIP_ID_OFFSET + 10],
- id_buf[SM_CHIP_ID_OFFSET + 11]);
+ ret = sprintf(buf, "%12phN\n", &id_buf[SM_CHIP_ID_OFFSET]);
kfree(id_buf);
@@ -268,25 +281,34 @@ static const struct of_device_id meson_sm_ids[] = {
static int __init meson_sm_probe(struct platform_device *pdev)
{
+ struct device *dev = &pdev->dev;
const struct meson_sm_chip *chip;
+ struct meson_sm_firmware *fw;
+
+ fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL);
+ if (!fw)
+ return -ENOMEM;
- chip = of_match_device(meson_sm_ids, &pdev->dev)->data;
+ chip = of_match_device(meson_sm_ids, dev)->data;
if (chip->cmd_shmem_in_base) {
- fw.sm_shmem_in_base = meson_sm_map_shmem(chip->cmd_shmem_in_base,
- chip->shmem_size);
- if (WARN_ON(!fw.sm_shmem_in_base))
+ fw->sm_shmem_in_base = meson_sm_map_shmem(chip->cmd_shmem_in_base,
+ chip->shmem_size);
+ if (WARN_ON(!fw->sm_shmem_in_base))
goto out;
}
if (chip->cmd_shmem_out_base) {
- fw.sm_shmem_out_base = meson_sm_map_shmem(chip->cmd_shmem_out_base,
- chip->shmem_size);
- if (WARN_ON(!fw.sm_shmem_out_base))
+ fw->sm_shmem_out_base = meson_sm_map_shmem(chip->cmd_shmem_out_base,
+ chip->shmem_size);
+ if (WARN_ON(!fw->sm_shmem_out_base))
goto out_in_base;
}
- fw.chip = chip;
+ fw->chip = chip;
+
+ platform_set_drvdata(pdev, fw);
+
pr_info("secure-monitor enabled\n");
if (sysfs_create_group(&pdev->dev.kobj, &meson_sm_sysfs_attr_group))
@@ -295,7 +317,7 @@ static int __init meson_sm_probe(struct platform_device *pdev)
return 0;
out_in_base:
- iounmap(fw.sm_shmem_in_base);
+ iounmap(fw->sm_shmem_in_base);
out:
return -EINVAL;
}
diff --git a/drivers/firmware/tegra/bpmp.c b/drivers/firmware/tegra/bpmp.c
index 19c56133234b..6741fcda0c37 100644
--- a/drivers/firmware/tegra/bpmp.c
+++ b/drivers/firmware/tegra/bpmp.c
@@ -804,7 +804,7 @@ static int __maybe_unused tegra_bpmp_resume(struct device *dev)
}
static const struct dev_pm_ops tegra_bpmp_pm_ops = {
- .resume_early = tegra_bpmp_resume,
+ .resume_noirq = tegra_bpmp_resume,
};
#if IS_ENABLED(CONFIG_ARCH_TEGRA_186_SOC) || \
diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c
index fd3d83745208..75bdfaa08380 100644
--- a/drivers/firmware/xilinx/zynqmp.c
+++ b/drivers/firmware/xilinx/zynqmp.c
@@ -711,8 +711,11 @@ static int zynqmp_firmware_probe(struct platform_device *pdev)
int ret;
np = of_find_compatible_node(NULL, NULL, "xlnx,zynqmp");
- if (!np)
- return 0;
+ if (!np) {
+ np = of_find_compatible_node(NULL, NULL, "xlnx,versal");
+ if (!np)
+ return 0;
+ }
of_node_put(np);
ret = get_set_conduit_method(dev->of_node);
@@ -770,6 +773,7 @@ static int zynqmp_firmware_remove(struct platform_device *pdev)
static const struct of_device_id zynqmp_firmware_of_match[] = {
{.compatible = "xlnx,zynqmp-firmware"},
+ {.compatible = "xlnx,versal-firmware"},
{},
};
MODULE_DEVICE_TABLE(of, zynqmp_firmware_of_match);
diff --git a/drivers/memory/atmel-ebi.c b/drivers/memory/atmel-ebi.c
index 0322df9dc249..14386d0b5f57 100644
--- a/drivers/memory/atmel-ebi.c
+++ b/drivers/memory/atmel-ebi.c
@@ -1,12 +1,9 @@
+// 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>
- *
- * This file is licensed under the terms of the GNU General Public
- * License version 2. This program is licensed "as is" without any
- * warranty of any kind, whether express or implied.
*/
#include <linux/clk.h>
@@ -19,6 +16,8 @@
#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;
@@ -314,7 +313,7 @@ static int atmel_ebi_dev_setup(struct atmel_ebi *ebi, struct device_node *np,
if (ret)
return ret;
- if (cs >= AT91_MATRIX_EBI_NUM_CS ||
+ if (cs >= AT91_EBI_NUM_CS ||
!(ebi->caps->available_cs & BIT(cs))) {
dev_err(dev, "invalid reg property in %pOF\n", np);
return -EINVAL;
@@ -344,7 +343,7 @@ static int atmel_ebi_dev_setup(struct atmel_ebi *ebi, struct device_node *np,
apply = true;
i = 0;
- for_each_set_bit(cs, &cslines, AT91_MATRIX_EBI_NUM_CS) {
+ for_each_set_bit(cs, &cslines, AT91_EBI_NUM_CS) {
ebid->configs[i].cs = cs;
if (apply) {
diff --git a/drivers/memory/brcmstb_dpfe.c b/drivers/memory/brcmstb_dpfe.c
index 6827ed484750..82b415be18d1 100644
--- a/drivers/memory/brcmstb_dpfe.c
+++ b/drivers/memory/brcmstb_dpfe.c
@@ -127,7 +127,6 @@ enum dpfe_msg_fields {
MSG_COMMAND,
MSG_ARG_COUNT,
MSG_ARG0,
- MSG_CHKSUM,
MSG_FIELD_MAX = 16 /* Max number of arguments */
};
@@ -180,7 +179,7 @@ struct dpfe_api {
};
/* Things we need for as long as we are active. */
-struct private_data {
+struct brcmstb_dpfe_priv {
void __iomem *regs;
void __iomem *dmem;
void __iomem *imem;
@@ -232,9 +231,13 @@ static struct attribute *dpfe_v3_attrs[] = {
};
ATTRIBUTE_GROUPS(dpfe_v3);
-/* API v2 firmware commands */
-static const struct dpfe_api dpfe_api_v2 = {
- .version = 2,
+/*
+ * 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 = {
@@ -243,21 +246,42 @@ static const struct dpfe_api dpfe_api_v2 = {
[MSG_COMMAND] = 1,
[MSG_ARG_COUNT] = 1,
[MSG_ARG0] = 1,
- [MSG_CHKSUM] = 4,
},
[DPFE_CMD_GET_REFRESH] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 2,
[MSG_ARG_COUNT] = 1,
[MSG_ARG0] = 1,
- [MSG_CHKSUM] = 5,
},
[DPFE_CMD_GET_VENDOR] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 2,
[MSG_ARG_COUNT] = 1,
[MSG_ARG0] = 2,
- [MSG_CHKSUM] = 6,
+ },
+ }
+};
+
+/*
+ * 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,
},
}
};
@@ -273,49 +297,51 @@ static const struct dpfe_api dpfe_api_v3 = {
[MSG_COMMAND] = 0x0101,
[MSG_ARG_COUNT] = 1,
[MSG_ARG0] = 1,
- [MSG_CHKSUM] = 0x104,
},
[DPFE_CMD_GET_REFRESH] = {
[MSG_HEADER] = DPFE_MSG_TYPE_COMMAND,
[MSG_COMMAND] = 0x0202,
[MSG_ARG_COUNT] = 0,
- /*
- * This is a bit ugly. Without arguments, the checksum
- * follows right after the argument count and not at
- * offset MSG_CHKSUM.
- */
- [MSG_ARG0] = 0x203,
},
/* There's no GET_VENDOR command in API v3. */
},
};
-static bool is_dcpu_enabled(void __iomem *regs)
+static bool is_dcpu_enabled(struct brcmstb_dpfe_priv *priv)
{
u32 val;
- val = readl_relaxed(regs + REG_DCPU_RESET);
+ 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(void __iomem *regs)
+static void __disable_dcpu(struct brcmstb_dpfe_priv *priv)
{
u32 val;
- if (!is_dcpu_enabled(regs))
+ if (!is_dcpu_enabled(priv))
return;
+ mutex_lock(&priv->lock);
+
/* Put DCPU in reset if it's running. */
- val = readl_relaxed(regs + REG_DCPU_RESET);
+ val = readl_relaxed(priv->regs + REG_DCPU_RESET);
val |= (1 << DCPU_RESET_SHIFT);
- writel_relaxed(val, regs + REG_DCPU_RESET);
+ writel_relaxed(val, priv->regs + REG_DCPU_RESET);
+
+ mutex_unlock(&priv->lock);
}
-static void __enable_dcpu(void __iomem *regs)
+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);
@@ -329,6 +355,8 @@ static void __enable_dcpu(void __iomem *regs)
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)
@@ -343,7 +371,7 @@ static unsigned int get_msg_chksum(const u32 msg[], unsigned int max)
return sum;
}
-static void __iomem *get_msg_ptr(struct private_data *priv, u32 response,
+static void __iomem *get_msg_ptr(struct brcmstb_dpfe_priv *priv, u32 response,
char *buf, ssize_t *size)
{
unsigned int msg_type;
@@ -382,7 +410,7 @@ static void __iomem *get_msg_ptr(struct private_data *priv, u32 response,
return ptr;
}
-static void __finalize_command(struct private_data *priv)
+static void __finalize_command(struct brcmstb_dpfe_priv *priv)
{
unsigned int release_mbox;
@@ -390,12 +418,12 @@ static void __finalize_command(struct private_data *priv)
* It depends on the API version which MBOX register we have to write to
* to signal we are done.
*/
- release_mbox = (priv->dpfe_api->version < 3)
+ 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 private_data *priv, unsigned int cmd,
+static int __send_command(struct brcmstb_dpfe_priv *priv, unsigned int cmd,
u32 result[])
{
const u32 *msg = priv->dpfe_api->command[cmd];
@@ -421,9 +449,17 @@ static int __send_command(struct private_data *priv, unsigned int cmd,
return -ETIMEDOUT;
}
+ /* 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++)
- writel_relaxed(msg[i], regs + DCPU_MSG_RAM(i));
+ 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);
@@ -517,7 +553,7 @@ static int __verify_firmware(struct init_data *init,
/* Verify checksum by reading back the firmware from co-processor RAM. */
static int __verify_fw_checksum(struct init_data *init,
- struct private_data *priv,
+ struct brcmstb_dpfe_priv *priv,
const struct dpfe_firmware_header *header,
u32 checksum)
{
@@ -571,26 +607,23 @@ static int __write_firmware(u32 __iomem *mem, const u32 *fw,
return 0;
}
-static int brcmstb_dpfe_download_firmware(struct platform_device *pdev,
- struct init_data *init)
+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 = &pdev->dev;
+ struct device *dev = priv->dev;
bool is_big_endian = false;
- struct private_data *priv;
const struct firmware *fw;
const u32 *dmem, *imem;
+ struct init_data init;
const void *fw_blob;
int ret;
- priv = platform_get_drvdata(pdev);
-
/*
* Skip downloading the firmware if the DCPU is already running and
* responding to commands.
*/
- if (is_dcpu_enabled(priv->regs)) {
+ if (is_dcpu_enabled(priv)) {
u32 response[MSG_FIELD_MAX];
ret = __send_command(priv, DPFE_CMD_GET_INFO, response);
@@ -606,20 +639,23 @@ static int brcmstb_dpfe_download_firmware(struct platform_device *pdev,
if (!priv->dpfe_api->fw_name)
return -ENODEV;
- ret = request_firmware(&fw, priv->dpfe_api->fw_name, dev);
- /* request_firmware() prints its own error messages. */
+ 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;
+ return (ret == -ENOENT) ? -EPROBE_DEFER : ret;
- ret = __verify_firmware(init, fw);
+ ret = __verify_firmware(&init, fw);
if (ret)
return -EFAULT;
- __disable_dcpu(priv->regs);
+ __disable_dcpu(priv);
- is_big_endian = init->is_big_endian;
- dmem_size = init->dmem_len;
- imem_size = init->imem_len;
+ 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;
@@ -637,17 +673,17 @@ static int brcmstb_dpfe_download_firmware(struct platform_device *pdev,
if (ret)
return ret;
- ret = __verify_fw_checksum(init, priv, header, init->chksum);
+ ret = __verify_fw_checksum(&init, priv, header, init.chksum);
if (ret)
return ret;
- __enable_dcpu(priv->regs);
+ __enable_dcpu(priv);
return 0;
}
static ssize_t generic_show(unsigned int command, u32 response[],
- struct private_data *priv, char *buf)
+ struct brcmstb_dpfe_priv *priv, char *buf)
{
int ret;
@@ -665,7 +701,7 @@ static ssize_t show_info(struct device *dev, struct device_attribute *devattr,
char *buf)
{
u32 response[MSG_FIELD_MAX];
- struct private_data *priv;
+ struct brcmstb_dpfe_priv *priv;
unsigned int info;
ssize_t ret;
@@ -688,7 +724,7 @@ static ssize_t show_refresh(struct device *dev,
{
u32 response[MSG_FIELD_MAX];
void __iomem *info;
- struct private_data *priv;
+ struct brcmstb_dpfe_priv *priv;
u8 refresh, sr_abort, ppre, thermal_offs, tuf;
u32 mr4;
ssize_t ret;
@@ -721,7 +757,7 @@ static ssize_t store_refresh(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
u32 response[MSG_FIELD_MAX];
- struct private_data *priv;
+ struct brcmstb_dpfe_priv *priv;
void __iomem *info;
unsigned long val;
int ret;
@@ -747,7 +783,7 @@ static ssize_t show_vendor(struct device *dev, struct device_attribute *devattr,
char *buf)
{
u32 response[MSG_FIELD_MAX];
- struct private_data *priv;
+ struct brcmstb_dpfe_priv *priv;
void __iomem *info;
ssize_t ret;
u32 mr5, mr6, mr7, mr8, err;
@@ -778,7 +814,7 @@ static ssize_t show_dram(struct device *dev, struct device_attribute *devattr,
char *buf)
{
u32 response[MSG_FIELD_MAX];
- struct private_data *priv;
+ struct brcmstb_dpfe_priv *priv;
ssize_t ret;
u32 mr4, mr5, mr6, mr7, mr8, err;
@@ -800,16 +836,15 @@ static ssize_t show_dram(struct device *dev, struct device_attribute *devattr,
static int brcmstb_dpfe_resume(struct platform_device *pdev)
{
- struct init_data init;
+ struct brcmstb_dpfe_priv *priv = platform_get_drvdata(pdev);
- return brcmstb_dpfe_download_firmware(pdev, &init);
+ return brcmstb_dpfe_download_firmware(priv);
}
static int brcmstb_dpfe_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct private_data *priv;
- struct init_data init;
+ struct brcmstb_dpfe_priv *priv;
struct resource *res;
int ret;
@@ -817,6 +852,8 @@ static int brcmstb_dpfe_probe(struct platform_device *pdev)
if (!priv)
return -ENOMEM;
+ priv->dev = dev;
+
mutex_init(&priv->lock);
platform_set_drvdata(pdev, priv);
@@ -851,9 +888,10 @@ static int brcmstb_dpfe_probe(struct platform_device *pdev)
return -ENOENT;
}
- ret = brcmstb_dpfe_download_firmware(pdev, &init);
+ ret = brcmstb_dpfe_download_firmware(priv);
if (ret) {
- dev_err(dev, "Couldn't download firmware -- %d\n", ret);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Couldn't download firmware -- %d\n", ret);
return ret;
}
@@ -867,7 +905,7 @@ static int brcmstb_dpfe_probe(struct platform_device *pdev)
static int brcmstb_dpfe_remove(struct platform_device *pdev)
{
- struct private_data *priv = dev_get_drvdata(&pdev->dev);
+ struct brcmstb_dpfe_priv *priv = dev_get_drvdata(&pdev->dev);
sysfs_remove_groups(&pdev->dev.kobj, priv->dpfe_api->sysfs_attrs);
@@ -876,10 +914,10 @@ static int brcmstb_dpfe_remove(struct platform_device *pdev)
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_v2 },
- { .compatible = "brcm,bcm7271-dpfe-cpu", .data = &dpfe_api_v2 },
- { .compatible = "brcm,bcm7278-dpfe-cpu", .data = &dpfe_api_v2 },
- { .compatible = "brcm,bcm7211-dpfe-cpu", .data = &dpfe_api_v2 },
+ { .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 },
{}
diff --git a/drivers/memory/emif.c b/drivers/memory/emif.c
index 402c6bc8e621..9d9127bf2a59 100644
--- a/drivers/memory/emif.c
+++ b/drivers/memory/emif.c
@@ -1613,7 +1613,7 @@ static void emif_shutdown(struct platform_device *pdev)
static int get_emif_reg_values(struct emif_data *emif, u32 freq,
struct emif_regs *regs)
{
- u32 cs1_used, ip_rev, phy_type;
+ u32 ip_rev, phy_type;
u32 cl, type;
const struct lpddr2_timings *timings;
const struct lpddr2_min_tck *min_tck;
@@ -1621,7 +1621,6 @@ static int get_emif_reg_values(struct emif_data *emif, u32 freq,
const struct lpddr2_addressing *addressing;
struct emif_data *emif_for_calc;
struct device *dev;
- const struct emif_custom_configs *custom_configs;
dev = emif->dev;
/*
@@ -1639,12 +1638,10 @@ static int get_emif_reg_values(struct emif_data *emif, u32 freq,
device_info = emif_for_calc->plat_data->device_info;
type = device_info->type;
- cs1_used = device_info->cs1_used;
ip_rev = emif_for_calc->plat_data->ip_rev;
phy_type = emif_for_calc->plat_data->phy_type;
min_tck = emif_for_calc->plat_data->min_tck;
- custom_configs = emif_for_calc->plat_data->custom_configs;
set_ddr_clk_period(freq);
diff --git a/drivers/memory/jedec_ddr.h b/drivers/memory/jedec_ddr.h
index 4a21b5044ff8..e59ccbd982d0 100644
--- a/drivers/memory/jedec_ddr.h
+++ b/drivers/memory/jedec_ddr.h
@@ -29,6 +29,7 @@
#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
@@ -169,4 +170,64 @@ extern const struct lpddr2_timings
lpddr2_jedec_timings[NUM_DDR_TIMING_TABLE_ENTRIES];
extern const struct lpddr2_min_tck lpddr2_jedec_min_tck;
+/*
+ * 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 */
diff --git a/drivers/memory/of_memory.c b/drivers/memory/of_memory.c
index 46539b27a3fb..71f26eac7350 100644
--- a/drivers/memory/of_memory.c
+++ b/drivers/memory/of_memory.c
@@ -3,6 +3,7 @@
* OpenFirmware helpers for memory drivers
*
* Copyright (C) 2012 Texas Instruments, Inc.
+ * Copyright (C) 2019 Samsung Electronics Co., Ltd.
*/
#include <linux/device.h>
@@ -149,3 +150,151 @@ default_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
+ * @device: 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, "%s: errors while parsing min-tck values\n",
+ __func__);
+ devm_kfree(dev, min);
+ goto default_min_tck;
+ }
+
+ return min;
+
+default_min_tck:
+ dev_warn(dev, "%s: using default min-tck values\n", __func__);
+ return 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;
+
+ /* The 'reg' param required since DT has changed, used as 'max-freq' */
+ 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, "%s: un-supported memory type\n", __func__);
+ }
+
+ for_each_child_of_node(np_ddr, np_tim)
+ if (of_device_is_compatible(np_tim, tim_compat))
+ arr_sz++;
+
+ if (arr_sz)
+ timings = devm_kcalloc(dev, arr_sz, sizeof(*timings),
+ GFP_KERNEL);
+
+ if (!timings)
+ goto default_timings;
+
+ for_each_child_of_node(np_ddr, np_tim) {
+ if (of_device_is_compatible(np_tim, tim_compat)) {
+ if (of_lpddr3_do_get_timings(np_tim, &timings[i])) {
+ devm_kfree(dev, timings);
+ goto default_timings;
+ }
+ i++;
+ }
+ }
+
+ *nr_frequencies = arr_sz;
+
+ return timings;
+
+default_timings:
+ dev_warn(dev, "%s: failed to get timings\n", __func__);
+ *nr_frequencies = 0;
+ return NULL;
+}
+EXPORT_SYMBOL(of_lpddr3_get_ddr_timings);
diff --git a/drivers/memory/of_memory.h b/drivers/memory/of_memory.h
index b077cc836b0b..e39ecc4c733d 100644
--- a/drivers/memory/of_memory.h
+++ b/drivers/memory/of_memory.h
@@ -14,6 +14,11 @@ extern const struct lpddr2_min_tck *of_get_min_tck(struct device_node *np,
extern const struct lpddr2_timings
*of_get_ddr_timings(struct device_node *np_ddr, struct device *dev,
u32 device_type, u32 *nr_frequencies);
+extern const struct lpddr3_min_tck
+ *of_lpddr3_get_min_tck(struct device_node *np, struct device *dev);
+extern const struct lpddr3_timings
+ *of_lpddr3_get_ddr_timings(struct device_node *np_ddr,
+ struct device *dev, u32 device_type, u32 *nr_frequencies);
#else
static inline const struct lpddr2_min_tck
*of_get_min_tck(struct device_node *np, struct device *dev)
@@ -27,6 +32,19 @@ static inline const struct lpddr2_timings
{
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;
+}
#endif /* CONFIG_OF && CONFIG_DDR */
#endif /* __LINUX_MEMORY_OF_REG_ */
diff --git a/drivers/memory/samsung/Kconfig b/drivers/memory/samsung/Kconfig
index 79ce7ea58903..e9c3ce92350c 100644
--- a/drivers/memory/samsung/Kconfig
+++ b/drivers/memory/samsung/Kconfig
@@ -7,6 +7,19 @@ config SAMSUNG_MC
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 Exynos5422 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.
+
config EXYNOS_SROM
bool "Exynos SROM controller driver" if COMPILE_TEST
depends on (ARM && ARCH_EXYNOS) || (COMPILE_TEST && HAS_IOMEM)
diff --git a/drivers/memory/samsung/Makefile b/drivers/memory/samsung/Makefile
index 00587be66211..ea071be21c44 100644
--- a/drivers/memory/samsung/Makefile
+++ b/drivers/memory/samsung/Makefile
@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_EXYNOS5422_DMC) += exynos5422-dmc.o
obj-$(CONFIG_EXYNOS_SROM) += exynos-srom.o
diff --git a/drivers/memory/samsung/exynos5422-dmc.c b/drivers/memory/samsung/exynos5422-dmc.c
new file mode 100644
index 000000000000..47dbf6d1789f
--- /dev/null
+++ b/drivers/memory/samsung/exynos5422-dmc.c
@@ -0,0 +1,1550 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ * Author: Lukasz Luba <l.luba@partner.samsung.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/devfreq.h>
+#include <linux/devfreq-event.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_opp.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include "../jedec_ddr.h"
+#include "../of_memory.h"
+
+#define EXYNOS5_DREXI_TIMINGAREF (0x0030)
+#define EXYNOS5_DREXI_TIMINGROW0 (0x0034)
+#define EXYNOS5_DREXI_TIMINGDATA0 (0x0038)
+#define EXYNOS5_DREXI_TIMINGPOWER0 (0x003C)
+#define EXYNOS5_DREXI_TIMINGROW1 (0x00E4)
+#define EXYNOS5_DREXI_TIMINGDATA1 (0x00E8)
+#define EXYNOS5_DREXI_TIMINGPOWER1 (0x00EC)
+#define CDREX_PAUSE (0x2091c)
+#define CDREX_LPDDR3PHY_CON3 (0x20a20)
+#define CDREX_LPDDR3PHY_CLKM_SRC (0x20700)
+#define EXYNOS5_TIMING_SET_SWI BIT(28)
+#define USE_MX_MSPLL_TIMINGS (1)
+#define USE_BPLL_TIMINGS (0)
+#define EXYNOS5_AREF_NORMAL (0x2e)
+
+#define DREX_PPCCLKCON (0x0130)
+#define DREX_PEREV2CONFIG (0x013c)
+#define DREX_PMNC_PPC (0xE000)
+#define DREX_CNTENS_PPC (0xE010)
+#define DREX_CNTENC_PPC (0xE020)
+#define DREX_INTENS_PPC (0xE030)
+#define DREX_INTENC_PPC (0xE040)
+#define DREX_FLAG_PPC (0xE050)
+#define DREX_PMCNT2_PPC (0xE130)
+
+/*
+ * A value for register DREX_PMNC_PPC which should be written to reset
+ * the cycle counter CCNT (a reference wall clock). It sets zero to the
+ * CCNT counter.
+ */
+#define CC_RESET BIT(2)
+
+/*
+ * A value for register DREX_PMNC_PPC which does the reset of all performance
+ * counters to zero.
+ */
+#define PPC_COUNTER_RESET BIT(1)
+
+/*
+ * Enables all configured counters (including cycle counter). The value should
+ * be written to the register DREX_PMNC_PPC.
+ */
+#define PPC_ENABLE BIT(0)
+
+/* A value for register DREX_PPCCLKCON which enables performance events clock.
+ * Must be written before first access to the performance counters register
+ * set, otherwise it could crash.
+ */
+#define PEREV_CLK_EN BIT(0)
+
+/*
+ * Values which are used to enable counters, interrupts or configure flags of
+ * the performance counters. They configure counter 2 and cycle counter.
+ */
+#define PERF_CNT2 BIT(2)
+#define PERF_CCNT BIT(31)
+
+/*
+ * Performance event types which are used for setting the preferred event
+ * to track in the counters.
+ * There is a set of different types, the values are from range 0 to 0x6f.
+ * These settings should be written to the configuration register which manages
+ * the type of the event (register DREX_PEREV2CONFIG).
+ */
+#define READ_TRANSFER_CH0 (0x6d)
+#define READ_TRANSFER_CH1 (0x6f)
+
+#define PERF_COUNTER_START_VALUE 0xff000000
+#define PERF_EVENT_UP_DOWN_THRESHOLD 900000000ULL
+
+/**
+ * struct dmc_opp_table - Operating level desciption
+ *
+ * Covers frequency and voltage settings of the DMC operating mode.
+ */
+struct dmc_opp_table {
+ u32 freq_hz;
+ u32 volt_uv;
+};
+
+/**
+ * struct exynos5_dmc - main structure describing DMC device
+ *
+ * The main structure for the Dynamic Memory Controller which covers clocks,
+ * memory regions, HW information, parameters and current operating mode.
+ */
+struct exynos5_dmc {
+ struct device *dev;
+ struct devfreq *df;
+ struct devfreq_simple_ondemand_data gov_data;
+ void __iomem *base_drexi0;
+ void __iomem *base_drexi1;
+ struct regmap *clk_regmap;
+ struct mutex lock;
+ unsigned long curr_rate;
+ unsigned long curr_volt;
+ unsigned long bypass_rate;
+ struct dmc_opp_table *opp;
+ struct dmc_opp_table opp_bypass;
+ int opp_count;
+ u32 timings_arr_size;
+ u32 *timing_row;
+ u32 *timing_data;
+ u32 *timing_power;
+ const struct lpddr3_timings *timings;
+ const struct lpddr3_min_tck *min_tck;
+ u32 bypass_timing_row;
+ u32 bypass_timing_data;
+ u32 bypass_timing_power;
+ struct regulator *vdd_mif;
+ struct clk *fout_spll;
+ struct clk *fout_bpll;
+ struct clk *mout_spll;
+ struct clk *mout_bpll;
+ struct clk *mout_mclk_cdrex;
+ struct clk *mout_mx_mspll_ccore;
+ struct clk *mx_mspll_ccore_phy;
+ struct clk *mout_mx_mspll_ccore_phy;
+ struct devfreq_event_dev **counter;
+ int num_counters;
+ u64 last_overflow_ts[2];
+ unsigned long load;
+ unsigned long total;
+ bool in_irq_mode;
+};
+
+#define TIMING_FIELD(t_name, t_bit_beg, t_bit_end) \
+ { .name = t_name, .bit_beg = t_bit_beg, .bit_end = t_bit_end }
+
+#define TIMING_VAL2REG(timing, t_val) \
+({ \
+ u32 __val; \
+ __val = (t_val) << (timing)->bit_beg; \
+ __val; \
+})
+
+struct timing_reg {
+ char *name;
+ int bit_beg;
+ int bit_end;
+ unsigned int val;
+};
+
+static const struct timing_reg timing_row[] = {
+ TIMING_FIELD("tRFC", 24, 31),
+ TIMING_FIELD("tRRD", 20, 23),
+ TIMING_FIELD("tRP", 16, 19),
+ TIMING_FIELD("tRCD", 12, 15),
+ TIMING_FIELD("tRC", 6, 11),
+ TIMING_FIELD("tRAS", 0, 5),
+};
+
+static const struct timing_reg timing_data[] = {
+ TIMING_FIELD("tWTR", 28, 31),
+ TIMING_FIELD("tWR", 24, 27),
+ TIMING_FIELD("tRTP", 20, 23),
+ TIMING_FIELD("tW2W-C2C", 14, 14),
+ TIMING_FIELD("tR2R-C2C", 12, 12),
+ TIMING_FIELD("WL", 8, 11),
+ TIMING_FIELD("tDQSCK", 4, 7),
+ TIMING_FIELD("RL", 0, 3),
+};
+
+static const struct timing_reg timing_power[] = {
+ TIMING_FIELD("tFAW", 26, 31),
+ TIMING_FIELD("tXSR", 16, 25),
+ TIMING_FIELD("tXP", 8, 15),
+ TIMING_FIELD("tCKE", 4, 7),
+ TIMING_FIELD("tMRD", 0, 3),
+};
+
+#define TIMING_COUNT (ARRAY_SIZE(timing_row) + ARRAY_SIZE(timing_data) + \
+ ARRAY_SIZE(timing_power))
+
+static int exynos5_counters_set_event(struct exynos5_dmc *dmc)
+{
+ int i, ret;
+
+ for (i = 0; i < dmc->num_counters; i++) {
+ if (!dmc->counter[i])
+ continue;
+ ret = devfreq_event_set_event(dmc->counter[i]);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int exynos5_counters_enable_edev(struct exynos5_dmc *dmc)
+{
+ int i, ret;
+
+ for (i = 0; i < dmc->num_counters; i++) {
+ if (!dmc->counter[i])
+ continue;
+ ret = devfreq_event_enable_edev(dmc->counter[i]);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int exynos5_counters_disable_edev(struct exynos5_dmc *dmc)
+{
+ int i, ret;
+
+ for (i = 0; i < dmc->num_counters; i++) {
+ if (!dmc->counter[i])
+ continue;
+ ret = devfreq_event_disable_edev(dmc->counter[i]);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/**
+ * find_target_freq_id() - Finds requested frequency in local DMC configuration
+ * @dmc: device for which the information is checked
+ * @target_rate: requested frequency in KHz
+ *
+ * Seeks in the local DMC driver structure for the requested frequency value
+ * and returns index or error value.
+ */
+static int find_target_freq_idx(struct exynos5_dmc *dmc,
+ unsigned long target_rate)
+{
+ int i;
+
+ for (i = dmc->opp_count - 1; i >= 0; i--)
+ if (dmc->opp[i].freq_hz <= target_rate)
+ return i;
+
+ return -EINVAL;
+}
+
+/**
+ * exynos5_switch_timing_regs() - Changes bank register set for DRAM timings
+ * @dmc: device for which the new settings is going to be applied
+ * @set: boolean variable passing set value
+ *
+ * Changes the register set, which holds timing parameters.
+ * There is two register sets: 0 and 1. The register set 0
+ * is used in normal operation when the clock is provided from main PLL.
+ * The bank register set 1 is used when the main PLL frequency is going to be
+ * changed and the clock is taken from alternative, stable source.
+ * This function switches between these banks according to the
+ * currently used clock source.
+ */
+static void exynos5_switch_timing_regs(struct exynos5_dmc *dmc, bool set)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(dmc->clk_regmap, CDREX_LPDDR3PHY_CON3, &reg);
+
+ if (set)
+ reg |= EXYNOS5_TIMING_SET_SWI;
+ else
+ reg &= ~EXYNOS5_TIMING_SET_SWI;
+
+ regmap_write(dmc->clk_regmap, CDREX_LPDDR3PHY_CON3, reg);
+}
+
+/**
+ * exynos5_init_freq_table() - Initialized PM OPP framework
+ * @dmc: DMC device for which the frequencies are used for OPP init
+ * @profile: devfreq device's profile
+ *
+ * Populate the devfreq device's OPP table based on current frequency, voltage.
+ */
+static int exynos5_init_freq_table(struct exynos5_dmc *dmc,
+ struct devfreq_dev_profile *profile)
+{
+ int i, ret;
+ int idx;
+ unsigned long freq;
+
+ ret = dev_pm_opp_of_add_table(dmc->dev);
+ if (ret < 0) {
+ dev_err(dmc->dev, "Failed to get OPP table\n");
+ return ret;
+ }
+
+ dmc->opp_count = dev_pm_opp_get_opp_count(dmc->dev);
+
+ dmc->opp = devm_kmalloc_array(dmc->dev, dmc->opp_count,
+ sizeof(struct dmc_opp_table), GFP_KERNEL);
+ if (!dmc->opp)
+ goto err_opp;
+
+ idx = dmc->opp_count - 1;
+ for (i = 0, freq = ULONG_MAX; i < dmc->opp_count; i++, freq--) {
+ struct dev_pm_opp *opp;
+
+ opp = dev_pm_opp_find_freq_floor(dmc->dev, &freq);
+ if (IS_ERR(opp))
+ goto err_opp;
+
+ dmc->opp[idx - i].freq_hz = freq;
+ dmc->opp[idx - i].volt_uv = dev_pm_opp_get_voltage(opp);
+
+ dev_pm_opp_put(opp);
+ }
+
+ return 0;
+
+err_opp:
+ dev_pm_opp_of_remove_table(dmc->dev);
+
+ return -EINVAL;
+}
+
+/**
+ * exynos5_set_bypass_dram_timings() - Low-level changes of the DRAM timings
+ * @dmc: device for which the new settings is going to be applied
+ * @param: DRAM parameters which passes timing data
+ *
+ * Low-level function for changing timings for DRAM memory clocking from
+ * 'bypass' clock source (fixed frequency @400MHz).
+ * It uses timing bank registers set 1.
+ */
+static void exynos5_set_bypass_dram_timings(struct exynos5_dmc *dmc)
+{
+ writel(EXYNOS5_AREF_NORMAL,
+ dmc->base_drexi0 + EXYNOS5_DREXI_TIMINGAREF);
+
+ writel(dmc->bypass_timing_row,
+ dmc->base_drexi0 + EXYNOS5_DREXI_TIMINGROW1);
+ writel(dmc->bypass_timing_row,
+ dmc->base_drexi1 + EXYNOS5_DREXI_TIMINGROW1);
+ writel(dmc->bypass_timing_data,
+ dmc->base_drexi0 + EXYNOS5_DREXI_TIMINGDATA1);
+ writel(dmc->bypass_timing_data,
+ dmc->base_drexi1 + EXYNOS5_DREXI_TIMINGDATA1);
+ writel(dmc->bypass_timing_power,
+ dmc->base_drexi0 + EXYNOS5_DREXI_TIMINGPOWER1);
+ writel(dmc->bypass_timing_power,
+ dmc->base_drexi1 + EXYNOS5_DREXI_TIMINGPOWER1);
+}
+
+/**
+ * exynos5_dram_change_timings() - Low-level changes of the DRAM final timings
+ * @dmc: device for which the new settings is going to be applied
+ * @target_rate: target frequency of the DMC
+ *
+ * Low-level function for changing timings for DRAM memory operating from main
+ * clock source (BPLL), which can have different frequencies. Thus, each
+ * frequency must have corresponding timings register values in order to keep
+ * the needed delays.
+ * It uses timing bank registers set 0.
+ */
+static int exynos5_dram_change_timings(struct exynos5_dmc *dmc,
+ unsigned long target_rate)
+{
+ int idx;
+
+ for (idx = dmc->opp_count - 1; idx >= 0; idx--)
+ if (dmc->opp[idx].freq_hz <= target_rate)
+ break;
+
+ if (idx < 0)
+ return -EINVAL;
+
+ writel(EXYNOS5_AREF_NORMAL,
+ dmc->base_drexi0 + EXYNOS5_DREXI_TIMINGAREF);
+
+ writel(dmc->timing_row[idx],
+ dmc->base_drexi0 + EXYNOS5_DREXI_TIMINGROW0);
+ writel(dmc->timing_row[idx],
+ dmc->base_drexi1 + EXYNOS5_DREXI_TIMINGROW0);
+ writel(dmc->timing_data[idx],
+ dmc->base_drexi0 + EXYNOS5_DREXI_TIMINGDATA0);
+ writel(dmc->timing_data[idx],
+ dmc->base_drexi1 + EXYNOS5_DREXI_TIMINGDATA0);
+ writel(dmc->timing_power[idx],
+ dmc->base_drexi0 + EXYNOS5_DREXI_TIMINGPOWER0);
+ writel(dmc->timing_power[idx],
+ dmc->base_drexi1 + EXYNOS5_DREXI_TIMINGPOWER0);
+
+ return 0;
+}
+
+/**
+ * exynos5_dmc_align_target_voltage() - Sets the final voltage for the DMC
+ * @dmc: device for which it is going to be set
+ * @target_volt: new voltage which is chosen to be final
+ *
+ * Function tries to align voltage to the safe level for 'normal' mode.
+ * It checks the need of higher voltage and changes the value. The target
+ * voltage might be lower that currently set and still the system will be
+ * stable.
+ */
+static int exynos5_dmc_align_target_voltage(struct exynos5_dmc *dmc,
+ unsigned long target_volt)
+{
+ int ret = 0;
+
+ if (dmc->curr_volt <= target_volt)
+ return 0;
+
+ ret = regulator_set_voltage(dmc->vdd_mif, target_volt,
+ target_volt);
+ if (!ret)
+ dmc->curr_volt = target_volt;
+
+ return ret;
+}
+
+/**
+ * exynos5_dmc_align_bypass_voltage() - Sets the voltage for the DMC
+ * @dmc: device for which it is going to be set
+ * @target_volt: new voltage which is chosen to be final
+ *
+ * Function tries to align voltage to the safe level for the 'bypass' mode.
+ * It checks the need of higher voltage and changes the value.
+ * The target voltage must not be less than currently needed, because
+ * for current frequency the device might become unstable.
+ */
+static int exynos5_dmc_align_bypass_voltage(struct exynos5_dmc *dmc,
+ unsigned long target_volt)
+{
+ int ret = 0;
+ unsigned long bypass_volt = dmc->opp_bypass.volt_uv;
+
+ target_volt = max(bypass_volt, target_volt);
+
+ if (dmc->curr_volt >= target_volt)
+ return 0;
+
+ ret = regulator_set_voltage(dmc->vdd_mif, target_volt,
+ target_volt);
+ if (!ret)
+ dmc->curr_volt = target_volt;
+
+ return ret;
+}
+
+/**
+ * exynos5_dmc_align_bypass_dram_timings() - Chooses and sets DRAM timings
+ * @dmc: device for which it is going to be set
+ * @target_rate: new frequency which is chosen to be final
+ *
+ * Function changes the DRAM timings for the temporary 'bypass' mode.
+ */
+static int exynos5_dmc_align_bypass_dram_timings(struct exynos5_dmc *dmc,
+ unsigned long target_rate)
+{
+ int idx = find_target_freq_idx(dmc, target_rate);
+
+ if (idx < 0)
+ return -EINVAL;
+
+ exynos5_set_bypass_dram_timings(dmc);
+
+ return 0;
+}
+
+/**
+ * exynos5_dmc_switch_to_bypass_configuration() - Switching to temporary clock
+ * @dmc: DMC device for which the switching is going to happen
+ * @target_rate: new frequency which is going to be set as a final
+ * @target_volt: new voltage which is going to be set as a final
+ *
+ * Function configures DMC and clocks for operating in temporary 'bypass' mode.
+ * This mode is used only temporary but if required, changes voltage and timings
+ * for DRAM chips. It switches the main clock to stable clock source for the
+ * period of the main PLL reconfiguration.
+ */
+static int
+exynos5_dmc_switch_to_bypass_configuration(struct exynos5_dmc *dmc,
+ unsigned long target_rate,
+ unsigned long target_volt)
+{
+ int ret;
+
+ /*
+ * Having higher voltage for a particular frequency does not harm
+ * the chip. Use it for the temporary frequency change when one
+ * voltage manipulation might be avoided.
+ */
+ ret = exynos5_dmc_align_bypass_voltage(dmc, target_volt);
+ if (ret)
+ return ret;
+
+ /*
+ * Longer delays for DRAM does not cause crash, the opposite does.
+ */
+ ret = exynos5_dmc_align_bypass_dram_timings(dmc, target_rate);
+ if (ret)
+ return ret;
+
+ /*
+ * Delays are long enough, so use them for the new coming clock.
+ */
+ exynos5_switch_timing_regs(dmc, USE_MX_MSPLL_TIMINGS);
+
+ return ret;
+}
+
+/**
+ * exynos5_dmc_change_freq_and_volt() - Changes voltage and frequency of the DMC
+ * using safe procedure
+ * @dmc: device for which the frequency is going to be changed
+ * @target_rate: requested new frequency
+ * @target_volt: requested voltage which corresponds to the new frequency
+ *
+ * The DMC frequency change procedure requires a few steps.
+ * The main requirement is to change the clock source in the clk mux
+ * for the time of main clock PLL locking. The assumption is that the
+ * alternative clock source set as parent is stable.
+ * The second parent's clock frequency is fixed to 400MHz, it is named 'bypass'
+ * clock. This requires alignment in DRAM timing parameters for the new
+ * T-period. There is two bank sets for keeping DRAM
+ * timings: set 0 and set 1. The set 0 is used when main clock source is
+ * chosen. The 2nd set of regs is used for 'bypass' clock. Switching between
+ * the two bank sets is part of the process.
+ * The voltage must also be aligned to the minimum required level. There is
+ * this intermediate step with switching to 'bypass' parent clock source.
+ * if the old voltage is lower, it requires an increase of the voltage level.
+ * The complexity of the voltage manipulation is hidden in low level function.
+ * In this function there is last alignment of the voltage level at the end.
+ */
+static int
+exynos5_dmc_change_freq_and_volt(struct exynos5_dmc *dmc,
+ unsigned long target_rate,
+ unsigned long target_volt)
+{
+ int ret;
+
+ ret = exynos5_dmc_switch_to_bypass_configuration(dmc, target_rate,
+ target_volt);
+ if (ret)
+ return ret;
+
+ /*
+ * Voltage is set at least to a level needed for this frequency,
+ * so switching clock source is safe now.
+ */
+ clk_prepare_enable(dmc->fout_spll);
+ clk_prepare_enable(dmc->mout_spll);
+ clk_prepare_enable(dmc->mout_mx_mspll_ccore);
+
+ ret = clk_set_parent(dmc->mout_mclk_cdrex, dmc->mout_mx_mspll_ccore);
+ if (ret)
+ goto disable_clocks;
+
+ /*
+ * We are safe to increase the timings for current bypass frequency.
+ * Thanks to this the settings will be ready for the upcoming clock
+ * source change.
+ */
+ exynos5_dram_change_timings(dmc, target_rate);
+
+ clk_set_rate(dmc->fout_bpll, target_rate);
+
+ exynos5_switch_timing_regs(dmc, USE_BPLL_TIMINGS);
+
+ ret = clk_set_parent(dmc->mout_mclk_cdrex, dmc->mout_bpll);
+ if (ret)
+ goto disable_clocks;
+
+ /*
+ * Make sure if the voltage is not from 'bypass' settings and align to
+ * the right level for power efficiency.
+ */
+ ret = exynos5_dmc_align_target_voltage(dmc, target_volt);
+
+disable_clocks:
+ clk_disable_unprepare(dmc->mout_mx_mspll_ccore);
+ clk_disable_unprepare(dmc->mout_spll);
+ clk_disable_unprepare(dmc->fout_spll);
+
+ return ret;
+}
+
+/**
+ * exynos5_dmc_get_volt_freq() - Gets the frequency and voltage from the OPP
+ * table.
+ * @dmc: device for which the frequency is going to be changed
+ * @freq: requested frequency in KHz
+ * @target_rate: returned frequency which is the same or lower than
+ * requested
+ * @target_volt: returned voltage which corresponds to the returned
+ * frequency
+ *
+ * Function gets requested frequency and checks OPP framework for needed
+ * frequency and voltage. It populates the values 'target_rate' and
+ * 'target_volt' or returns error value when OPP framework fails.
+ */
+static int exynos5_dmc_get_volt_freq(struct exynos5_dmc *dmc,
+ unsigned long *freq,
+ unsigned long *target_rate,
+ unsigned long *target_volt, u32 flags)
+{
+ struct dev_pm_opp *opp;
+
+ opp = devfreq_recommended_opp(dmc->dev, freq, flags);
+ if (IS_ERR(opp))
+ return PTR_ERR(opp);
+
+ *target_rate = dev_pm_opp_get_freq(opp);
+ *target_volt = dev_pm_opp_get_voltage(opp);
+ dev_pm_opp_put(opp);
+
+ return 0;
+}
+
+/**
+ * exynos5_dmc_target() - Function responsible for changing frequency of DMC
+ * @dev: device for which the frequency is going to be changed
+ * @freq: requested frequency in KHz
+ * @flags: flags provided for this frequency change request
+ *
+ * An entry function provided to the devfreq framework which provides frequency
+ * change of the DMC. The function gets the possible rate from OPP table based
+ * on requested frequency. It calls the next function responsible for the
+ * frequency and voltage change. In case of failure, does not set 'curr_rate'
+ * and returns error value to the framework.
+ */
+static int exynos5_dmc_target(struct device *dev, unsigned long *freq,
+ u32 flags)
+{
+ struct exynos5_dmc *dmc = dev_get_drvdata(dev);
+ unsigned long target_rate = 0;
+ unsigned long target_volt = 0;
+ int ret;
+
+ ret = exynos5_dmc_get_volt_freq(dmc, freq, &target_rate, &target_volt,
+ flags);
+
+ if (ret)
+ return ret;
+
+ if (target_rate == dmc->curr_rate)
+ return 0;
+
+ mutex_lock(&dmc->lock);
+
+ ret = exynos5_dmc_change_freq_and_volt(dmc, target_rate, target_volt);
+
+ if (ret) {
+ mutex_unlock(&dmc->lock);
+ return ret;
+ }
+
+ dmc->curr_rate = target_rate;
+
+ mutex_unlock(&dmc->lock);
+ return 0;
+}
+
+/**
+ * exynos5_counters_get() - Gets the performance counters values.
+ * @dmc: device for which the counters are going to be checked
+ * @load_count: variable which is populated with counter value
+ * @total_count: variable which is used as 'wall clock' reference
+ *
+ * Function which provides performance counters values. It sums up counters for
+ * two DMC channels. The 'total_count' is used as a reference and max value.
+ * The ratio 'load_count/total_count' shows the busy percentage [0%, 100%].
+ */
+static int exynos5_counters_get(struct exynos5_dmc *dmc,
+ unsigned long *load_count,
+ unsigned long *total_count)
+{
+ unsigned long total = 0;
+ struct devfreq_event_data event;
+ int ret, i;
+
+ *load_count = 0;
+
+ /* Take into account only read+write counters, but stop all */
+ for (i = 0; i < dmc->num_counters; i++) {
+ if (!dmc->counter[i])
+ continue;
+
+ ret = devfreq_event_get_event(dmc->counter[i], &event);
+ if (ret < 0)
+ return ret;
+
+ *load_count += event.load_count;
+
+ if (total < event.total_count)
+ total = event.total_count;
+ }
+
+ *total_count = total;
+
+ return 0;
+}
+
+/**
+ * exynos5_dmc_start_perf_events() - Setup and start performance event counters
+ * @dmc: device for which the counters are going to be checked
+ * @beg_value: initial value for the counter
+ *
+ * Function which enables needed counters, interrupts and sets initial values
+ * then starts the counters.
+ */
+static void exynos5_dmc_start_perf_events(struct exynos5_dmc *dmc,
+ u32 beg_value)
+{
+ /* Enable interrupts for counter 2 */
+ writel(PERF_CNT2, dmc->base_drexi0 + DREX_INTENS_PPC);
+ writel(PERF_CNT2, dmc->base_drexi1 + DREX_INTENS_PPC);
+
+ /* Enable counter 2 and CCNT */
+ writel(PERF_CNT2 | PERF_CCNT, dmc->base_drexi0 + DREX_CNTENS_PPC);
+ writel(PERF_CNT2 | PERF_CCNT, dmc->base_drexi1 + DREX_CNTENS_PPC);
+
+ /* Clear overflow flag for all counters */
+ writel(PERF_CNT2 | PERF_CCNT, dmc->base_drexi0 + DREX_FLAG_PPC);
+ writel(PERF_CNT2 | PERF_CCNT, dmc->base_drexi1 + DREX_FLAG_PPC);
+
+ /* Reset all counters */
+ writel(CC_RESET | PPC_COUNTER_RESET, dmc->base_drexi0 + DREX_PMNC_PPC);
+ writel(CC_RESET | PPC_COUNTER_RESET, dmc->base_drexi1 + DREX_PMNC_PPC);
+
+ /*
+ * Set start value for the counters, the number of samples that
+ * will be gathered is calculated as: 0xffffffff - beg_value
+ */
+ writel(beg_value, dmc->base_drexi0 + DREX_PMCNT2_PPC);
+ writel(beg_value, dmc->base_drexi1 + DREX_PMCNT2_PPC);
+
+ /* Start all counters */
+ writel(PPC_ENABLE, dmc->base_drexi0 + DREX_PMNC_PPC);
+ writel(PPC_ENABLE, dmc->base_drexi1 + DREX_PMNC_PPC);
+}
+
+/**
+ * exynos5_dmc_perf_events_calc() - Calculate utilization
+ * @dmc: device for which the counters are going to be checked
+ * @diff_ts: time between last interrupt and current one
+ *
+ * Function which calculates needed utilization for the devfreq governor.
+ * It prepares values for 'busy_time' and 'total_time' based on elapsed time
+ * between interrupts, which approximates utilization.
+ */
+static void exynos5_dmc_perf_events_calc(struct exynos5_dmc *dmc, u64 diff_ts)
+{
+ /*
+ * This is a simple algorithm for managing traffic on DMC.
+ * When there is almost no load the counters overflow every 4s,
+ * no mater the DMC frequency.
+ * The high load might be approximated using linear function.
+ * Knowing that, simple calculation can provide 'busy_time' and
+ * 'total_time' to the devfreq governor which picks up target
+ * frequency.
+ * We want a fast ramp up and slow decay in frequency change function.
+ */
+ if (diff_ts < PERF_EVENT_UP_DOWN_THRESHOLD) {
+ /*
+ * Set higher utilization for the simple_ondemand governor.
+ * The governor should increase the frequency of the DMC.
+ */
+ dmc->load = 70;
+ dmc->total = 100;
+ } else {
+ /*
+ * Set low utilization for the simple_ondemand governor.
+ * The governor should decrease the frequency of the DMC.
+ */
+ dmc->load = 35;
+ dmc->total = 100;
+ }
+
+ dev_dbg(dmc->dev, "diff_ts=%llu\n", diff_ts);
+}
+
+/**
+ * exynos5_dmc_perf_events_check() - Checks the status of the counters
+ * @dmc: device for which the counters are going to be checked
+ *
+ * Function which is called from threaded IRQ to check the counters state
+ * and to call approximation for the needed utilization.
+ */
+static void exynos5_dmc_perf_events_check(struct exynos5_dmc *dmc)
+{
+ u32 val;
+ u64 diff_ts, ts;
+
+ ts = ktime_get_ns();
+
+ /* Stop all counters */
+ writel(0, dmc->base_drexi0 + DREX_PMNC_PPC);
+ writel(0, dmc->base_drexi1 + DREX_PMNC_PPC);
+
+ /* Check the source in interrupt flag registers (which channel) */
+ val = readl(dmc->base_drexi0 + DREX_FLAG_PPC);
+ if (val) {
+ diff_ts = ts - dmc->last_overflow_ts[0];
+ dmc->last_overflow_ts[0] = ts;
+ dev_dbg(dmc->dev, "drex0 0xE050 val= 0x%08x\n", val);
+ } else {
+ val = readl(dmc->base_drexi1 + DREX_FLAG_PPC);
+ diff_ts = ts - dmc->last_overflow_ts[1];
+ dmc->last_overflow_ts[1] = ts;
+ dev_dbg(dmc->dev, "drex1 0xE050 val= 0x%08x\n", val);
+ }
+
+ exynos5_dmc_perf_events_calc(dmc, diff_ts);
+
+ exynos5_dmc_start_perf_events(dmc, PERF_COUNTER_START_VALUE);
+}
+
+/**
+ * exynos5_dmc_enable_perf_events() - Enable performance events
+ * @dmc: device for which the counters are going to be checked
+ *
+ * Function which is setup needed environment and enables counters.
+ */
+static void exynos5_dmc_enable_perf_events(struct exynos5_dmc *dmc)
+{
+ u64 ts;
+
+ /* Enable Performance Event Clock */
+ writel(PEREV_CLK_EN, dmc->base_drexi0 + DREX_PPCCLKCON);
+ writel(PEREV_CLK_EN, dmc->base_drexi1 + DREX_PPCCLKCON);
+
+ /* Select read transfers as performance event2 */
+ writel(READ_TRANSFER_CH0, dmc->base_drexi0 + DREX_PEREV2CONFIG);
+ writel(READ_TRANSFER_CH1, dmc->base_drexi1 + DREX_PEREV2CONFIG);
+
+ ts = ktime_get_ns();
+ dmc->last_overflow_ts[0] = ts;
+ dmc->last_overflow_ts[1] = ts;
+
+ /* Devfreq shouldn't be faster than initialization, play safe though. */
+ dmc->load = 99;
+ dmc->total = 100;
+}
+
+/**
+ * exynos5_dmc_disable_perf_events() - Disable performance events
+ * @dmc: device for which the counters are going to be checked
+ *
+ * Function which stops, disables performance event counters and interrupts.
+ */
+static void exynos5_dmc_disable_perf_events(struct exynos5_dmc *dmc)
+{
+ /* Stop all counters */
+ writel(0, dmc->base_drexi0 + DREX_PMNC_PPC);
+ writel(0, dmc->base_drexi1 + DREX_PMNC_PPC);
+
+ /* Disable interrupts for counter 2 */
+ writel(PERF_CNT2, dmc->base_drexi0 + DREX_INTENC_PPC);
+ writel(PERF_CNT2, dmc->base_drexi1 + DREX_INTENC_PPC);
+
+ /* Disable counter 2 and CCNT */
+ writel(PERF_CNT2 | PERF_CCNT, dmc->base_drexi0 + DREX_CNTENC_PPC);
+ writel(PERF_CNT2 | PERF_CCNT, dmc->base_drexi1 + DREX_CNTENC_PPC);
+
+ /* Clear overflow flag for all counters */
+ writel(PERF_CNT2 | PERF_CCNT, dmc->base_drexi0 + DREX_FLAG_PPC);
+ writel(PERF_CNT2 | PERF_CCNT, dmc->base_drexi1 + DREX_FLAG_PPC);
+}
+
+/**
+ * exynos5_dmc_get_status() - Read current DMC performance statistics.
+ * @dev: device for which the statistics are requested
+ * @stat: structure which has statistic fields
+ *
+ * Function reads the DMC performance counters and calculates 'busy_time'
+ * and 'total_time'. To protect from overflow, the values are shifted right
+ * by 10. After read out the counters are setup to count again.
+ */
+static int exynos5_dmc_get_status(struct device *dev,
+ struct devfreq_dev_status *stat)
+{
+ struct exynos5_dmc *dmc = dev_get_drvdata(dev);
+ unsigned long load, total;
+ int ret;
+
+ if (dmc->in_irq_mode) {
+ stat->current_frequency = dmc->curr_rate;
+ stat->busy_time = dmc->load;
+ stat->total_time = dmc->total;
+ } else {
+ ret = exynos5_counters_get(dmc, &load, &total);
+ if (ret < 0)
+ return -EINVAL;
+
+ /* To protect from overflow, divide by 1024 */
+ stat->busy_time = load >> 10;
+ stat->total_time = total >> 10;
+
+ ret = exynos5_counters_set_event(dmc);
+ if (ret < 0) {
+ dev_err(dev, "could not set event counter\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * exynos5_dmc_get_cur_freq() - Function returns current DMC frequency
+ * @dev: device for which the framework checks operating frequency
+ * @freq: returned frequency value
+ *
+ * It returns the currently used frequency of the DMC. The real operating
+ * frequency might be lower when the clock source value could not be divided
+ * to the requested value.
+ */
+static int exynos5_dmc_get_cur_freq(struct device *dev, unsigned long *freq)
+{
+ struct exynos5_dmc *dmc = dev_get_drvdata(dev);
+
+ mutex_lock(&dmc->lock);
+ *freq = dmc->curr_rate;
+ mutex_unlock(&dmc->lock);
+
+ return 0;
+}
+
+/**
+ * exynos5_dmc_df_profile - Devfreq governor's profile structure
+ *
+ * It provides to the devfreq framework needed functions and polling period.
+ */
+static struct devfreq_dev_profile exynos5_dmc_df_profile = {
+ .target = exynos5_dmc_target,
+ .get_dev_status = exynos5_dmc_get_status,
+ .get_cur_freq = exynos5_dmc_get_cur_freq,
+};
+
+/**
+ * exynos5_dmc_align_initial_frequency() - Align initial frequency value
+ * @dmc: device for which the frequency is going to be set
+ * @bootloader_init_freq: initial frequency set by the bootloader in KHz
+ *
+ * The initial bootloader frequency, which is present during boot, might be
+ * different that supported frequency values in the driver. It is possible
+ * due to different PLL settings or used PLL as a source.
+ * This function provides the 'initial_freq' for the devfreq framework
+ * statistics engine which supports only registered values. Thus, some alignment
+ * must be made.
+ */
+static unsigned long
+exynos5_dmc_align_init_freq(struct exynos5_dmc *dmc,
+ unsigned long bootloader_init_freq)
+{
+ unsigned long aligned_freq;
+ int idx;
+
+ idx = find_target_freq_idx(dmc, bootloader_init_freq);
+ if (idx >= 0)
+ aligned_freq = dmc->opp[idx].freq_hz;
+ else
+ aligned_freq = dmc->opp[dmc->opp_count - 1].freq_hz;
+
+ return aligned_freq;
+}
+
+/**
+ * create_timings_aligned() - Create register values and align with standard
+ * @dmc: device for which the frequency is going to be set
+ * @idx: speed bin in the OPP table
+ * @clk_period_ps: the period of the clock, known as tCK
+ *
+ * The function calculates timings and creates a register value ready for
+ * a frequency transition. The register contains a few timings. They are
+ * shifted by a known offset. The timing value is calculated based on memory
+ * specyfication: minimal time required and minimal cycles required.
+ */
+static int create_timings_aligned(struct exynos5_dmc *dmc, u32 *reg_timing_row,
+ u32 *reg_timing_data, u32 *reg_timing_power,
+ u32 clk_period_ps)
+{
+ u32 val;
+ const struct timing_reg *reg;
+
+ if (clk_period_ps == 0)
+ return -EINVAL;
+
+ *reg_timing_row = 0;
+ *reg_timing_data = 0;
+ *reg_timing_power = 0;
+
+ val = dmc->timings->tRFC / clk_period_ps;
+ val += dmc->timings->tRFC % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tRFC);
+ reg = &timing_row[0];
+ *reg_timing_row |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tRRD / clk_period_ps;
+ val += dmc->timings->tRRD % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tRRD);
+ reg = &timing_row[1];
+ *reg_timing_row |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tRPab / clk_period_ps;
+ val += dmc->timings->tRPab % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tRPab);
+ reg = &timing_row[2];
+ *reg_timing_row |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tRCD / clk_period_ps;
+ val += dmc->timings->tRCD % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tRCD);
+ reg = &timing_row[3];
+ *reg_timing_row |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tRC / clk_period_ps;
+ val += dmc->timings->tRC % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tRC);
+ reg = &timing_row[4];
+ *reg_timing_row |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tRAS / clk_period_ps;
+ val += dmc->timings->tRAS % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tRAS);
+ reg = &timing_row[5];
+ *reg_timing_row |= TIMING_VAL2REG(reg, val);
+
+ /* data related timings */
+ val = dmc->timings->tWTR / clk_period_ps;
+ val += dmc->timings->tWTR % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tWTR);
+ reg = &timing_data[0];
+ *reg_timing_data |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tWR / clk_period_ps;
+ val += dmc->timings->tWR % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tWR);
+ reg = &timing_data[1];
+ *reg_timing_data |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tRTP / clk_period_ps;
+ val += dmc->timings->tRTP % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tRTP);
+ reg = &timing_data[2];
+ *reg_timing_data |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tW2W_C2C / clk_period_ps;
+ val += dmc->timings->tW2W_C2C % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tW2W_C2C);
+ reg = &timing_data[3];
+ *reg_timing_data |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tR2R_C2C / clk_period_ps;
+ val += dmc->timings->tR2R_C2C % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tR2R_C2C);
+ reg = &timing_data[4];
+ *reg_timing_data |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tWL / clk_period_ps;
+ val += dmc->timings->tWL % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tWL);
+ reg = &timing_data[5];
+ *reg_timing_data |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tDQSCK / clk_period_ps;
+ val += dmc->timings->tDQSCK % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tDQSCK);
+ reg = &timing_data[6];
+ *reg_timing_data |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tRL / clk_period_ps;
+ val += dmc->timings->tRL % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tRL);
+ reg = &timing_data[7];
+ *reg_timing_data |= TIMING_VAL2REG(reg, val);
+
+ /* power related timings */
+ val = dmc->timings->tFAW / clk_period_ps;
+ val += dmc->timings->tFAW % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tXP);
+ reg = &timing_power[0];
+ *reg_timing_power |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tXSR / clk_period_ps;
+ val += dmc->timings->tXSR % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tXSR);
+ reg = &timing_power[1];
+ *reg_timing_power |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tXP / clk_period_ps;
+ val += dmc->timings->tXP % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tXP);
+ reg = &timing_power[2];
+ *reg_timing_power |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tCKE / clk_period_ps;
+ val += dmc->timings->tCKE % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tCKE);
+ reg = &timing_power[3];
+ *reg_timing_power |= TIMING_VAL2REG(reg, val);
+
+ val = dmc->timings->tMRD / clk_period_ps;
+ val += dmc->timings->tMRD % clk_period_ps ? 1 : 0;
+ val = max(val, dmc->min_tck->tMRD);
+ reg = &timing_power[4];
+ *reg_timing_power |= TIMING_VAL2REG(reg, val);
+
+ return 0;
+}
+
+/**
+ * of_get_dram_timings() - helper function for parsing DT settings for DRAM
+ * @dmc: device for which the frequency is going to be set
+ *
+ * The function parses DT entries with DRAM information.
+ */
+static int of_get_dram_timings(struct exynos5_dmc *dmc)
+{
+ int ret = 0;
+ int idx;
+ struct device_node *np_ddr;
+ u32 freq_mhz, clk_period_ps;
+
+ np_ddr = of_parse_phandle(dmc->dev->of_node, "device-handle", 0);
+ if (!np_ddr) {
+ dev_warn(dmc->dev, "could not find 'device-handle' in DT\n");
+ return -EINVAL;
+ }
+
+ dmc->timing_row = devm_kmalloc_array(dmc->dev, TIMING_COUNT,
+ sizeof(u32), GFP_KERNEL);
+ if (!dmc->timing_row)
+ return -ENOMEM;
+
+ dmc->timing_data = devm_kmalloc_array(dmc->dev, TIMING_COUNT,
+ sizeof(u32), GFP_KERNEL);
+ if (!dmc->timing_data)
+ return -ENOMEM;
+
+ dmc->timing_power = devm_kmalloc_array(dmc->dev, TIMING_COUNT,
+ sizeof(u32), GFP_KERNEL);
+ if (!dmc->timing_power)
+ return -ENOMEM;
+
+ dmc->timings = of_lpddr3_get_ddr_timings(np_ddr, dmc->dev,
+ DDR_TYPE_LPDDR3,
+ &dmc->timings_arr_size);
+ if (!dmc->timings) {
+ of_node_put(np_ddr);
+ dev_warn(dmc->dev, "could not get timings from DT\n");
+ return -EINVAL;
+ }
+
+ dmc->min_tck = of_lpddr3_get_min_tck(np_ddr, dmc->dev);
+ if (!dmc->min_tck) {
+ of_node_put(np_ddr);
+ dev_warn(dmc->dev, "could not get tck from DT\n");
+ return -EINVAL;
+ }
+
+ /* Sorted array of OPPs with frequency ascending */
+ for (idx = 0; idx < dmc->opp_count; idx++) {
+ freq_mhz = dmc->opp[idx].freq_hz / 1000000;
+ clk_period_ps = 1000000 / freq_mhz;
+
+ ret = create_timings_aligned(dmc, &dmc->timing_row[idx],
+ &dmc->timing_data[idx],
+ &dmc->timing_power[idx],
+ clk_period_ps);
+ }
+
+ of_node_put(np_ddr);
+
+ /* Take the highest frequency's timings as 'bypass' */
+ dmc->bypass_timing_row = dmc->timing_row[idx - 1];
+ dmc->bypass_timing_data = dmc->timing_data[idx - 1];
+ dmc->bypass_timing_power = dmc->timing_power[idx - 1];
+
+ return ret;
+}
+
+/**
+ * exynos5_dmc_init_clks() - Initialize clocks needed for DMC operation.
+ * @dmc: DMC structure containing needed fields
+ *
+ * Get the needed clocks defined in DT device, enable and set the right parents.
+ * Read current frequency and initialize the initial rate for governor.
+ */
+static int exynos5_dmc_init_clks(struct exynos5_dmc *dmc)
+{
+ int ret;
+ unsigned long target_volt = 0;
+ unsigned long target_rate = 0;
+ unsigned int tmp;
+
+ dmc->fout_spll = devm_clk_get(dmc->dev, "fout_spll");
+ if (IS_ERR(dmc->fout_spll))
+ return PTR_ERR(dmc->fout_spll);
+
+ dmc->fout_bpll = devm_clk_get(dmc->dev, "fout_bpll");
+ if (IS_ERR(dmc->fout_bpll))
+ return PTR_ERR(dmc->fout_bpll);
+
+ dmc->mout_mclk_cdrex = devm_clk_get(dmc->dev, "mout_mclk_cdrex");
+ if (IS_ERR(dmc->mout_mclk_cdrex))
+ return PTR_ERR(dmc->mout_mclk_cdrex);
+
+ dmc->mout_bpll = devm_clk_get(dmc->dev, "mout_bpll");
+ if (IS_ERR(dmc->mout_bpll))
+ return PTR_ERR(dmc->mout_bpll);
+
+ dmc->mout_mx_mspll_ccore = devm_clk_get(dmc->dev,
+ "mout_mx_mspll_ccore");
+ if (IS_ERR(dmc->mout_mx_mspll_ccore))
+ return PTR_ERR(dmc->mout_mx_mspll_ccore);
+
+ dmc->mout_spll = devm_clk_get(dmc->dev, "ff_dout_spll2");
+ if (IS_ERR(dmc->mout_spll)) {
+ dmc->mout_spll = devm_clk_get(dmc->dev, "mout_sclk_spll");
+ if (IS_ERR(dmc->mout_spll))
+ return PTR_ERR(dmc->mout_spll);
+ }
+
+ /*
+ * Convert frequency to KHz values and set it for the governor.
+ */
+ dmc->curr_rate = clk_get_rate(dmc->mout_mclk_cdrex);
+ dmc->curr_rate = exynos5_dmc_align_init_freq(dmc, dmc->curr_rate);
+ exynos5_dmc_df_profile.initial_freq = dmc->curr_rate;
+
+ ret = exynos5_dmc_get_volt_freq(dmc, &dmc->curr_rate, &target_rate,
+ &target_volt, 0);
+ if (ret)
+ return ret;
+
+ dmc->curr_volt = target_volt;
+
+ clk_set_parent(dmc->mout_mx_mspll_ccore, dmc->mout_spll);
+
+ dmc->bypass_rate = clk_get_rate(dmc->mout_mx_mspll_ccore);
+
+ clk_prepare_enable(dmc->fout_bpll);
+ clk_prepare_enable(dmc->mout_bpll);
+
+ /*
+ * Some bootloaders do not set clock routes correctly.
+ * Stop one path in clocks to PHY.
+ */
+ regmap_read(dmc->clk_regmap, CDREX_LPDDR3PHY_CLKM_SRC, &tmp);
+ tmp &= ~(BIT(1) | BIT(0));
+ regmap_write(dmc->clk_regmap, CDREX_LPDDR3PHY_CLKM_SRC, tmp);
+
+ return 0;
+}
+
+/**
+ * exynos5_performance_counters_init() - Initializes performance DMC's counters
+ * @dmc: DMC for which it does the setup
+ *
+ * Initialization of performance counters in DMC for estimating usage.
+ * The counter's values are used for calculation of a memory bandwidth and based
+ * on that the governor changes the frequency.
+ * The counters are not used when the governor is GOVERNOR_USERSPACE.
+ */
+static int exynos5_performance_counters_init(struct exynos5_dmc *dmc)
+{
+ int counters_size;
+ int ret, i;
+
+ dmc->num_counters = devfreq_event_get_edev_count(dmc->dev);
+ if (dmc->num_counters < 0) {
+ dev_err(dmc->dev, "could not get devfreq-event counters\n");
+ return dmc->num_counters;
+ }
+
+ counters_size = sizeof(struct devfreq_event_dev) * dmc->num_counters;
+ dmc->counter = devm_kzalloc(dmc->dev, counters_size, GFP_KERNEL);
+ if (!dmc->counter)
+ return -ENOMEM;
+
+ for (i = 0; i < dmc->num_counters; i++) {
+ dmc->counter[i] =
+ devfreq_event_get_edev_by_phandle(dmc->dev, i);
+ if (IS_ERR_OR_NULL(dmc->counter[i]))
+ return -EPROBE_DEFER;
+ }
+
+ ret = exynos5_counters_enable_edev(dmc);
+ if (ret < 0) {
+ dev_err(dmc->dev, "could not enable event counter\n");
+ return ret;
+ }
+
+ ret = exynos5_counters_set_event(dmc);
+ if (ret < 0) {
+ exynos5_counters_disable_edev(dmc);
+ dev_err(dmc->dev, "could not set event counter\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * exynos5_dmc_set_pause_on_switching() - Controls a pause feature in DMC
+ * @dmc: device which is used for changing this feature
+ * @set: a boolean state passing enable/disable request
+ *
+ * There is a need of pausing DREX DMC when divider or MUX in clock tree
+ * changes its configuration. In such situation access to the memory is blocked
+ * in DMC automatically. This feature is used when clock frequency change
+ * request appears and touches clock tree.
+ */
+static inline int exynos5_dmc_set_pause_on_switching(struct exynos5_dmc *dmc)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(dmc->clk_regmap, CDREX_PAUSE, &val);
+ if (ret)
+ return ret;
+
+ val |= 1UL;
+ regmap_write(dmc->clk_regmap, CDREX_PAUSE, val);
+
+ return 0;
+}
+
+static irqreturn_t dmc_irq_thread(int irq, void *priv)
+{
+ int res;
+ struct exynos5_dmc *dmc = priv;
+
+ mutex_lock(&dmc->df->lock);
+
+ exynos5_dmc_perf_events_check(dmc);
+
+ res = update_devfreq(dmc->df);
+ if (res)
+ dev_warn(dmc->dev, "devfreq failed with %d\n", res);
+
+ mutex_unlock(&dmc->df->lock);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * exynos5_dmc_probe() - Probe function for the DMC driver
+ * @pdev: platform device for which the driver is going to be initialized
+ *
+ * Initialize basic components: clocks, regulators, performance counters, etc.
+ * Read out product version and based on the information setup
+ * internal structures for the controller (frequency and voltage) and for DRAM
+ * memory parameters: timings for each operating frequency.
+ * Register new devfreq device for controlling DVFS of the DMC.
+ */
+static int exynos5_dmc_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct exynos5_dmc *dmc;
+ struct resource *res;
+ int irq[2];
+
+ dmc = devm_kzalloc(dev, sizeof(*dmc), GFP_KERNEL);
+ if (!dmc)
+ return -ENOMEM;
+
+ mutex_init(&dmc->lock);
+
+ dmc->dev = dev;
+ platform_set_drvdata(pdev, dmc);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dmc->base_drexi0 = devm_ioremap_resource(dev, res);
+ if (IS_ERR(dmc->base_drexi0))
+ return PTR_ERR(dmc->base_drexi0);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ dmc->base_drexi1 = devm_ioremap_resource(dev, res);
+ if (IS_ERR(dmc->base_drexi1))
+ return PTR_ERR(dmc->base_drexi1);
+
+ dmc->clk_regmap = syscon_regmap_lookup_by_phandle(np,
+ "samsung,syscon-clk");
+ if (IS_ERR(dmc->clk_regmap))
+ return PTR_ERR(dmc->clk_regmap);
+
+ ret = exynos5_init_freq_table(dmc, &exynos5_dmc_df_profile);
+ if (ret) {
+ dev_warn(dev, "couldn't initialize frequency settings\n");
+ return ret;
+ }
+
+ dmc->vdd_mif = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(dmc->vdd_mif)) {
+ ret = PTR_ERR(dmc->vdd_mif);
+ return ret;
+ }
+
+ ret = exynos5_dmc_init_clks(dmc);
+ if (ret)
+ return ret;
+
+ ret = of_get_dram_timings(dmc);
+ if (ret) {
+ dev_warn(dev, "couldn't initialize timings settings\n");
+ goto remove_clocks;
+ }
+
+ ret = exynos5_dmc_set_pause_on_switching(dmc);
+ if (ret) {
+ dev_warn(dev, "couldn't get access to PAUSE register\n");
+ goto remove_clocks;
+ }
+
+ /* There is two modes in which the driver works: polling or IRQ */
+ irq[0] = platform_get_irq_byname(pdev, "drex_0");
+ irq[1] = platform_get_irq_byname(pdev, "drex_1");
+ if (irq[0] > 0 && irq[1] > 0) {
+ ret = devm_request_threaded_irq(dev, irq[0], NULL,
+ dmc_irq_thread, IRQF_ONESHOT,
+ dev_name(dev), dmc);
+ if (ret) {
+ dev_err(dev, "couldn't grab IRQ\n");
+ goto remove_clocks;
+ }
+
+ ret = devm_request_threaded_irq(dev, irq[1], NULL,
+ dmc_irq_thread, IRQF_ONESHOT,
+ dev_name(dev), dmc);
+ if (ret) {
+ dev_err(dev, "couldn't grab IRQ\n");
+ goto remove_clocks;
+ }
+
+ /*
+ * Setup default thresholds for the devfreq governor.
+ * The values are chosen based on experiments.
+ */
+ dmc->gov_data.upthreshold = 55;
+ dmc->gov_data.downdifferential = 5;
+
+ exynos5_dmc_enable_perf_events(dmc);
+
+ dmc->in_irq_mode = 1;
+ } else {
+ ret = exynos5_performance_counters_init(dmc);
+ if (ret) {
+ dev_warn(dev, "couldn't probe performance counters\n");
+ goto remove_clocks;
+ }
+
+ /*
+ * Setup default thresholds for the devfreq governor.
+ * The values are chosen based on experiments.
+ */
+ dmc->gov_data.upthreshold = 30;
+ dmc->gov_data.downdifferential = 5;
+
+ exynos5_dmc_df_profile.polling_ms = 500;
+ }
+
+
+ dmc->df = devm_devfreq_add_device(dev, &exynos5_dmc_df_profile,
+ DEVFREQ_GOV_SIMPLE_ONDEMAND,
+ &dmc->gov_data);
+
+ if (IS_ERR(dmc->df)) {
+ ret = PTR_ERR(dmc->df);
+ goto err_devfreq_add;
+ }
+
+ if (dmc->in_irq_mode)
+ exynos5_dmc_start_perf_events(dmc, PERF_COUNTER_START_VALUE);
+
+ dev_info(dev, "DMC initialized\n");
+
+ return 0;
+
+err_devfreq_add:
+ if (dmc->in_irq_mode)
+ exynos5_dmc_disable_perf_events(dmc);
+ else
+ exynos5_counters_disable_edev(dmc);
+remove_clocks:
+ clk_disable_unprepare(dmc->mout_bpll);
+ clk_disable_unprepare(dmc->fout_bpll);
+
+ return ret;
+}
+
+/**
+ * exynos5_dmc_remove() - Remove function for the platform device
+ * @pdev: platform device which is going to be removed
+ *
+ * The function relies on 'devm' framework function which automatically
+ * clean the device's resources. It just calls explicitly disable function for
+ * the performance counters.
+ */
+static int exynos5_dmc_remove(struct platform_device *pdev)
+{
+ struct exynos5_dmc *dmc = dev_get_drvdata(&pdev->dev);
+
+ if (dmc->in_irq_mode)
+ exynos5_dmc_disable_perf_events(dmc);
+ else
+ exynos5_counters_disable_edev(dmc);
+
+ clk_disable_unprepare(dmc->mout_bpll);
+ clk_disable_unprepare(dmc->fout_bpll);
+
+ dev_pm_opp_remove_table(dmc->dev);
+
+ return 0;
+}
+
+static const struct of_device_id exynos5_dmc_of_match[] = {
+ { .compatible = "samsung,exynos5422-dmc", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, exynos5_dmc_of_match);
+
+static struct platform_driver exynos5_dmc_platdrv = {
+ .probe = exynos5_dmc_probe,
+ .remove = exynos5_dmc_remove,
+ .driver = {
+ .name = "exynos5-dmc",
+ .of_match_table = exynos5_dmc_of_match,
+ },
+};
+module_platform_driver(exynos5_dmc_platdrv);
+MODULE_DESCRIPTION("Driver for Exynos5422 Dynamic Memory Controller dynamic frequency and voltage change");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Lukasz Luba");
diff --git a/drivers/memory/tegra/Kconfig b/drivers/memory/tegra/Kconfig
index 4680124ddcab..fbfbaada61a2 100644
--- a/drivers/memory/tegra/Kconfig
+++ b/drivers/memory/tegra/Kconfig
@@ -17,6 +17,16 @@ config TEGRA20_EMC
This driver is required to change memory timings / clock rate for
external memory.
+config TEGRA30_EMC
+ bool "NVIDIA Tegra30 External Memory Controller driver"
+ default y
+ depends on TEGRA_MC && ARCH_TEGRA_3x_SOC
+ help
+ This driver is for the External Memory Controller (EMC) found on
+ Tegra30 chips. The EMC controls the external DRAM on the board.
+ This driver is required to change memory timings / clock rate for
+ external memory.
+
config TEGRA124_EMC
bool "NVIDIA Tegra124 External Memory Controller driver"
default y
diff --git a/drivers/memory/tegra/Makefile b/drivers/memory/tegra/Makefile
index 3971a6b7c487..3d23c4261104 100644
--- a/drivers/memory/tegra/Makefile
+++ b/drivers/memory/tegra/Makefile
@@ -11,5 +11,6 @@ tegra-mc-$(CONFIG_ARCH_TEGRA_210_SOC) += tegra210.o
obj-$(CONFIG_TEGRA_MC) += tegra-mc.o
obj-$(CONFIG_TEGRA20_EMC) += tegra20-emc.o
+obj-$(CONFIG_TEGRA30_EMC) += tegra30-emc.o
obj-$(CONFIG_TEGRA124_EMC) += tegra124-emc.o
obj-$(CONFIG_ARCH_TEGRA_186_SOC) += tegra186.o
diff --git a/drivers/memory/tegra/mc.c b/drivers/memory/tegra/mc.c
index 3d8d322511c5..ec8403557ed4 100644
--- a/drivers/memory/tegra/mc.c
+++ b/drivers/memory/tegra/mc.c
@@ -5,6 +5,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
+#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -18,39 +19,6 @@
#include "mc.h"
-#define MC_INTSTATUS 0x000
-
-#define MC_INTMASK 0x004
-
-#define MC_ERR_STATUS 0x08
-#define MC_ERR_STATUS_TYPE_SHIFT 28
-#define MC_ERR_STATUS_TYPE_INVALID_SMMU_PAGE (6 << MC_ERR_STATUS_TYPE_SHIFT)
-#define MC_ERR_STATUS_TYPE_MASK (0x7 << MC_ERR_STATUS_TYPE_SHIFT)
-#define MC_ERR_STATUS_READABLE (1 << 27)
-#define MC_ERR_STATUS_WRITABLE (1 << 26)
-#define MC_ERR_STATUS_NONSECURE (1 << 25)
-#define MC_ERR_STATUS_ADR_HI_SHIFT 20
-#define MC_ERR_STATUS_ADR_HI_MASK 0x3
-#define MC_ERR_STATUS_SECURITY (1 << 17)
-#define MC_ERR_STATUS_RW (1 << 16)
-
-#define MC_ERR_ADR 0x0c
-
-#define MC_GART_ERROR_REQ 0x30
-#define MC_DECERR_EMEM_OTHERS_STATUS 0x58
-#define MC_SECURITY_VIOLATION_STATUS 0x74
-
-#define MC_EMEM_ARB_CFG 0x90
-#define MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE(x) (((x) & 0x1ff) << 0)
-#define MC_EMEM_ARB_CFG_CYCLES_PER_UPDATE_MASK 0x1ff
-#define MC_EMEM_ARB_MISC0 0xd8
-
-#define MC_EMEM_ADR_CFG 0x54
-#define MC_EMEM_ADR_CFG_EMEM_NUMDEV BIT(0)
-
-#define MC_TIMING_CONTROL 0xfc
-#define MC_TIMING_UPDATE BIT(0)
-
static const struct of_device_id tegra_mc_of_match[] = {
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
{ .compatible = "nvidia,tegra20-mc-gart", .data = &tegra20_mc_soc },
@@ -307,7 +275,7 @@ static int tegra_mc_setup_latency_allowance(struct tegra_mc *mc)
return 0;
}
-void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate)
+int tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate)
{
unsigned int i;
struct tegra_mc_timing *timing = NULL;
@@ -322,11 +290,13 @@ void tegra_mc_write_emem_configuration(struct tegra_mc *mc, unsigned long rate)
if (!timing) {
dev_err(mc->dev, "no memory timing registered for rate %lu\n",
rate);
- return;
+ return -EINVAL;
}
for (i = 0; i < mc->soc->num_emem_regs; ++i)
mc_writel(mc, timing->emem_data[i], mc->soc->emem_regs[i]);
+
+ return 0;
}
unsigned int tegra_mc_get_emem_device_count(struct tegra_mc *mc)
@@ -626,6 +596,7 @@ static int tegra_mc_probe(struct platform_device *pdev)
struct resource *res;
struct tegra_mc *mc;
void *isr;
+ u64 mask;
int err;
mc = devm_kzalloc(&pdev->dev, sizeof(*mc), GFP_KERNEL);
@@ -637,6 +608,14 @@ static int tegra_mc_probe(struct platform_device *pdev)
mc->soc = of_device_get_match_data(&pdev->dev);
mc->dev = &pdev->dev;
+ mask = DMA_BIT_MASK(mc->soc->num_address_bits);
+
+ err = dma_coerce_mask_and_coherent(&pdev->dev, mask);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to set DMA mask: %d\n", err);
+ return err;
+ }
+
/* length of MC tick in nanoseconds */
mc->tick = 30;
@@ -658,6 +637,9 @@ static int tegra_mc_probe(struct platform_device *pdev)
} else
#endif
{
+ /* ensure that debug features are disabled */
+ mc_writel(mc, 0x00000000, MC_TIMING_CONTROL_DBG);
+
err = tegra_mc_setup_latency_allowance(mc);
if (err < 0) {
dev_err(&pdev->dev,
diff --git a/drivers/memory/tegra/mc.h b/drivers/memory/tegra/mc.h
index f9353494b708..957c6eb74ff9 100644
--- a/drivers/memory/tegra/mc.h
+++ b/drivers/memory/tegra/mc.h
@@ -6,20 +6,76 @@
#ifndef MEMORY_TEGRA_MC_H
#define MEMORY_TEGRA_MC_H
+#include <linux/bits.h>
#include <linux/io.h>
#include <linux/types.h>
#include <soc/tegra/mc.h>
-#define MC_INT_DECERR_MTS (1 << 16)
-#define MC_INT_SECERR_SEC (1 << 13)
-#define MC_INT_DECERR_VPR (1 << 12)
-#define MC_INT_INVALID_APB_ASID_UPDATE (1 << 11)
-#define MC_INT_INVALID_SMMU_PAGE (1 << 10)
-#define MC_INT_ARBITRATION_EMEM (1 << 9)
-#define MC_INT_SECURITY_VIOLATION (1 << 8)
-#define MC_INT_INVALID_GART_PAGE (1 << 7)
-#define MC_INT_DECERR_EMEM (1 << 6)
+#define MC_INTSTATUS 0x00
+#define MC_INTMASK 0x04
+#define MC_ERR_STATUS 0x08
+#define MC_ERR_ADR 0x0c
+#define MC_GART_ERROR_REQ 0x30
+#define MC_EMEM_ADR_CFG 0x54
+#define MC_DECERR_EMEM_OTHERS_STATUS 0x58
+#define MC_SECURITY_VIOLATION_STATUS 0x74
+#define MC_EMEM_ARB_CFG 0x90
+#define MC_EMEM_ARB_OUTSTANDING_REQ 0x94
+#define MC_EMEM_ARB_TIMING_RCD 0x98
+#define MC_EMEM_ARB_TIMING_RP 0x9c
+#define MC_EMEM_ARB_TIMING_RC 0xa0
+#define MC_EMEM_ARB_TIMING_RAS 0xa4
+#define MC_EMEM_ARB_TIMING_FAW 0xa8
+#define MC_EMEM_ARB_TIMING_RRD 0xac
+#define MC_EMEM_ARB_TIMING_RAP2PRE 0xb0
+#define MC_EMEM_ARB_TIMING_WAP2PRE 0xb4
+#define MC_EMEM_ARB_TIMING_R2R 0xb8
+#define MC_EMEM_ARB_TIMING_W2W 0xbc
+#define MC_EMEM_ARB_TIMING_R2W 0xc0
+#define MC_EMEM_ARB_TIMING_W2R 0xc4
+#define MC_EMEM_ARB_DA_TURNS 0xd0
+#define MC_EMEM_ARB_DA_COVERS 0xd4
+#define MC_EMEM_ARB_MISC0 0xd8
+#define MC_EMEM_ARB_MISC1 0xdc
+#define MC_EMEM_ARB_RING1_THROTTLE 0xe0
+#define MC_EMEM_ARB_OVERRIDE 0xe8
+#define MC_TIMING_CONTROL_DBG 0xf8
+#define MC_TIMING_CONTROL 0xfc
+
+#define MC_INT_DECERR_MTS BIT(16)
+#define MC_INT_SECERR_SEC BIT(13)
+#define MC_INT_DECERR_VPR BIT(12)
+#define MC_INT_INVALID_APB_ASID_UPDATE BIT(11)
+#define MC_INT_INVALID_SMMU_PAGE BIT(10)
+#define MC_INT_ARBITRATION_EMEM BIT(9)
+#define MC_INT_SECURITY_VIOLATION BIT(8)
+#define MC_INT_INVALID_GART_PAGE BIT(7)
+#define MC_INT_DECERR_EMEM BIT(6)
+
+#define MC_ERR_STATUS_TYPE_SHIFT 28
+#define MC_ERR_STATUS_TYPE_INVALID_SMMU_PAGE (0x6 << 28)
+#define MC_ERR_STATUS_TYPE_MASK (0x7 << 28)
+#define MC_ERR_STATUS_READABLE BIT(27)
+#define MC_ERR_STATUS_WRITABLE BIT(26)
+#define MC_ERR_STATUS_NONSECURE BIT(25)
+#define MC_ERR_STATUS_ADR_HI_SHIFT 20
+#define MC_ERR_STATUS_ADR_HI_MASK 0x3
+#define MC_ERR_STATUS_SECURITY BIT(17)
+#define MC_ERR_STATUS_RW BIT(16)
+
+#define MC_EMEM_ADR_CFG_EMEM_NUMDEV BIT(0)
+
+#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_MAX_MASK 0x1ff
+#define MC_EMEM_ARB_OUTSTANDING_REQ_HOLDOFF_OVERRIDE BIT(30)
+#define MC_EMEM_ARB_OUTSTANDING_REQ_LIMIT_ENABLE BIT(31)
+
+#define MC_EMEM_ARB_OVERRIDE_EACK_MASK 0x3
+
+#define MC_TIMING_UPDATE BIT(0)
static inline u32 mc_readl(struct tegra_mc *mc, unsigned long offset)
{
diff --git a/drivers/memory/tegra/tegra114.c b/drivers/memory/tegra/tegra114.c
index ac8351b5beeb..48ef01c3ff90 100644
--- a/drivers/memory/tegra/tegra114.c
+++ b/drivers/memory/tegra/tegra114.c
@@ -909,16 +909,18 @@ static const struct tegra_smmu_swgroup tegra114_swgroups[] = {
{ .name = "tsec", .swgroup = TEGRA_SWGROUP_TSEC, .reg = 0x294 },
};
-static const unsigned int tegra114_group_display[] = {
+static const unsigned int tegra114_group_drm[] = {
TEGRA_SWGROUP_DC,
TEGRA_SWGROUP_DCB,
+ TEGRA_SWGROUP_G2,
+ TEGRA_SWGROUP_NV,
};
static const struct tegra_smmu_group_soc tegra114_groups[] = {
{
- .name = "display",
- .swgroups = tegra114_group_display,
- .num_swgroups = ARRAY_SIZE(tegra114_group_display),
+ .name = "drm",
+ .swgroups = tegra114_group_drm,
+ .num_swgroups = ARRAY_SIZE(tegra114_group_drm),
},
};
diff --git a/drivers/memory/tegra/tegra124.c b/drivers/memory/tegra/tegra124.c
index 5d0ccb2be206..493b5dc3a4b3 100644
--- a/drivers/memory/tegra/tegra124.c
+++ b/drivers/memory/tegra/tegra124.c
@@ -10,26 +10,6 @@
#include "mc.h"
-#define MC_EMEM_ARB_CFG 0x90
-#define MC_EMEM_ARB_OUTSTANDING_REQ 0x94
-#define MC_EMEM_ARB_TIMING_RCD 0x98
-#define MC_EMEM_ARB_TIMING_RP 0x9c
-#define MC_EMEM_ARB_TIMING_RC 0xa0
-#define MC_EMEM_ARB_TIMING_RAS 0xa4
-#define MC_EMEM_ARB_TIMING_FAW 0xa8
-#define MC_EMEM_ARB_TIMING_RRD 0xac
-#define MC_EMEM_ARB_TIMING_RAP2PRE 0xb0
-#define MC_EMEM_ARB_TIMING_WAP2PRE 0xb4
-#define MC_EMEM_ARB_TIMING_R2R 0xb8
-#define MC_EMEM_ARB_TIMING_W2W 0xbc
-#define MC_EMEM_ARB_TIMING_R2W 0xc0
-#define MC_EMEM_ARB_TIMING_W2R 0xc4
-#define MC_EMEM_ARB_DA_TURNS 0xd0
-#define MC_EMEM_ARB_DA_COVERS 0xd4
-#define MC_EMEM_ARB_MISC0 0xd8
-#define MC_EMEM_ARB_MISC1 0xdc
-#define MC_EMEM_ARB_RING1_THROTTLE 0xe0
-
static const struct tegra_mc_client tegra124_mc_clients[] = {
{
.id = 0x00,
@@ -974,16 +954,18 @@ static const struct tegra_smmu_swgroup tegra124_swgroups[] = {
{ .name = "vi", .swgroup = TEGRA_SWGROUP_VI, .reg = 0x280 },
};
-static const unsigned int tegra124_group_display[] = {
+static const unsigned int tegra124_group_drm[] = {
TEGRA_SWGROUP_DC,
TEGRA_SWGROUP_DCB,
+ TEGRA_SWGROUP_GPU,
+ TEGRA_SWGROUP_VIC,
};
static const struct tegra_smmu_group_soc tegra124_groups[] = {
{
- .name = "display",
- .swgroups = tegra124_group_display,
- .num_swgroups = ARRAY_SIZE(tegra124_group_display),
+ .name = "drm",
+ .swgroups = tegra124_group_drm,
+ .num_swgroups = ARRAY_SIZE(tegra124_group_drm),
},
};
diff --git a/drivers/memory/tegra/tegra20-emc.c b/drivers/memory/tegra/tegra20-emc.c
index 9ee5bef49e47..1b23b1c34476 100644
--- a/drivers/memory/tegra/tegra20-emc.c
+++ b/drivers/memory/tegra/tegra20-emc.c
@@ -6,10 +6,11 @@
*/
#include <linux/clk.h>
+#include <linux/clk/tegra.h>
#include <linux/completion.h>
#include <linux/err.h>
#include <linux/interrupt.h>
-#include <linux/iopoll.h>
+#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -21,6 +22,7 @@
#define EMC_INTSTATUS 0x000
#define EMC_INTMASK 0x004
+#define EMC_DBG 0x008
#define EMC_TIMING_CONTROL 0x028
#define EMC_RC 0x02c
#define EMC_RFC 0x030
@@ -79,6 +81,12 @@
#define EMC_REFRESH_OVERFLOW_INT BIT(3)
#define EMC_CLKCHANGE_COMPLETE_INT BIT(4)
+#define EMC_DBG_READ_MUX_ASSEMBLY BIT(0)
+#define EMC_DBG_WRITE_MUX_ACTIVE BIT(1)
+#define EMC_DBG_FORCE_UPDATE BIT(2)
+#define EMC_DBG_READ_DQM_CTRL BIT(9)
+#define EMC_DBG_CFG_PRIORITY BIT(24)
+
static const u16 emc_timing_registers[] = {
EMC_RC,
EMC_RFC,
@@ -137,9 +145,6 @@ struct tegra_emc {
struct device *dev;
struct completion clk_handshake_complete;
struct notifier_block clk_nb;
- struct clk *backup_clk;
- struct clk *emc_mux;
- struct clk *pll_m;
struct clk *clk;
void __iomem *regs;
@@ -219,7 +224,7 @@ static int emc_prepare_timing_change(struct tegra_emc *emc, unsigned long rate)
static int emc_complete_timing_change(struct tegra_emc *emc, bool flush)
{
- long timeout;
+ unsigned long timeout;
dev_dbg(emc->dev, "%s: flush %d\n", __func__, flush);
@@ -231,14 +236,10 @@ static int emc_complete_timing_change(struct tegra_emc *emc, bool flush)
}
timeout = wait_for_completion_timeout(&emc->clk_handshake_complete,
- usecs_to_jiffies(100));
+ msecs_to_jiffies(100));
if (timeout == 0) {
dev_err(emc->dev, "EMC-CAR handshake failed\n");
return -EIO;
- } else if (timeout < 0) {
- dev_err(emc->dev, "failed to wait for EMC-CAR handshake: %ld\n",
- timeout);
- return timeout;
}
return 0;
@@ -363,6 +364,13 @@ static int tegra_emc_load_timings_from_dt(struct tegra_emc *emc,
sort(emc->timings, emc->num_timings, sizeof(*timing), cmp_timings,
NULL);
+ dev_info(emc->dev,
+ "got %u timings for RAM code %u (min %luMHz max %luMHz)\n",
+ emc->num_timings,
+ tegra_read_ram_code(),
+ emc->timings[0].rate / 1000000,
+ emc->timings[emc->num_timings - 1].rate / 1000000);
+
return 0;
}
@@ -398,7 +406,7 @@ tegra_emc_find_node_by_ram_code(struct device *dev)
static int emc_setup_hw(struct tegra_emc *emc)
{
u32 intmask = EMC_REFRESH_OVERFLOW_INT | EMC_CLKCHANGE_COMPLETE_INT;
- u32 emc_cfg;
+ u32 emc_cfg, emc_dbg;
emc_cfg = readl_relaxed(emc->regs + EMC_CFG_2);
@@ -421,42 +429,53 @@ static int emc_setup_hw(struct tegra_emc *emc)
writel_relaxed(intmask, emc->regs + EMC_INTMASK);
writel_relaxed(intmask, emc->regs + EMC_INTSTATUS);
+ /* ensure that unwanted debug features are disabled */
+ emc_dbg = readl_relaxed(emc->regs + EMC_DBG);
+ emc_dbg |= EMC_DBG_CFG_PRIORITY;
+ emc_dbg &= ~EMC_DBG_READ_MUX_ASSEMBLY;
+ emc_dbg &= ~EMC_DBG_WRITE_MUX_ACTIVE;
+ emc_dbg &= ~EMC_DBG_FORCE_UPDATE;
+ writel_relaxed(emc_dbg, emc->regs + EMC_DBG);
+
return 0;
}
-static int emc_init(struct tegra_emc *emc, unsigned long rate)
+static long emc_round_rate(unsigned long rate,
+ unsigned long min_rate,
+ unsigned long max_rate,
+ void *arg)
{
- int err;
+ struct emc_timing *timing = NULL;
+ struct tegra_emc *emc = arg;
+ unsigned int i;
- err = clk_set_parent(emc->emc_mux, emc->backup_clk);
- if (err) {
- dev_err(emc->dev,
- "failed to reparent to backup source: %d\n", err);
- return err;
- }
+ min_rate = min(min_rate, emc->timings[emc->num_timings - 1].rate);
- err = clk_set_rate(emc->pll_m, rate);
- if (err) {
- dev_err(emc->dev,
- "failed to change pll_m rate: %d\n", err);
- return err;
- }
+ for (i = 0; i < emc->num_timings; i++) {
+ if (emc->timings[i].rate < rate && i != emc->num_timings - 1)
+ continue;
- err = clk_set_parent(emc->emc_mux, emc->pll_m);
- if (err) {
- dev_err(emc->dev,
- "failed to reparent to pll_m: %d\n", err);
- return err;
+ if (emc->timings[i].rate > max_rate) {
+ i = max(i, 1u) - 1;
+
+ if (emc->timings[i].rate < min_rate)
+ break;
+ }
+
+ if (emc->timings[i].rate < min_rate)
+ continue;
+
+ timing = &emc->timings[i];
+ break;
}
- err = clk_set_rate(emc->clk, rate);
- if (err) {
- dev_err(emc->dev,
- "failed to change emc rate: %d\n", err);
- return err;
+ if (!timing) {
+ dev_err(emc->dev, "no timing for rate %lu min %lu max %lu\n",
+ rate, min_rate, max_rate);
+ return -EINVAL;
}
- return 0;
+ return timing->rate;
}
static int tegra_emc_probe(struct platform_device *pdev)
@@ -515,57 +534,26 @@ static int tegra_emc_probe(struct platform_device *pdev)
return err;
}
+ tegra20_clk_set_emc_round_callback(emc_round_rate, emc);
+
emc->clk = devm_clk_get(&pdev->dev, "emc");
if (IS_ERR(emc->clk)) {
err = PTR_ERR(emc->clk);
dev_err(&pdev->dev, "failed to get emc clock: %d\n", err);
- return err;
- }
-
- emc->pll_m = clk_get_sys(NULL, "pll_m");
- if (IS_ERR(emc->pll_m)) {
- err = PTR_ERR(emc->pll_m);
- dev_err(&pdev->dev, "failed to get pll_m clock: %d\n", err);
- return err;
- }
-
- emc->backup_clk = clk_get_sys(NULL, "pll_p");
- if (IS_ERR(emc->backup_clk)) {
- err = PTR_ERR(emc->backup_clk);
- dev_err(&pdev->dev, "failed to get pll_p clock: %d\n", err);
- goto put_pll_m;
- }
-
- emc->emc_mux = clk_get_parent(emc->clk);
- if (IS_ERR(emc->emc_mux)) {
- err = PTR_ERR(emc->emc_mux);
- dev_err(&pdev->dev, "failed to get emc_mux clock: %d\n", err);
- goto put_backup;
+ goto unset_cb;
}
err = clk_notifier_register(emc->clk, &emc->clk_nb);
if (err) {
dev_err(&pdev->dev, "failed to register clk notifier: %d\n",
err);
- goto put_backup;
- }
-
- /* set DRAM clock rate to maximum */
- err = emc_init(emc, emc->timings[emc->num_timings - 1].rate);
- if (err) {
- dev_err(&pdev->dev, "failed to initialize EMC clock rate: %d\n",
- err);
- goto unreg_notifier;
+ goto unset_cb;
}
return 0;
-unreg_notifier:
- clk_notifier_unregister(emc->clk, &emc->clk_nb);
-put_backup:
- clk_put(emc->backup_clk);
-put_pll_m:
- clk_put(emc->pll_m);
+unset_cb:
+ tegra20_clk_set_emc_round_callback(NULL, NULL);
return err;
}
diff --git a/drivers/memory/tegra/tegra30-emc.c b/drivers/memory/tegra/tegra30-emc.c
new file mode 100644
index 000000000000..6929980bf907
--- /dev/null
+++ b/drivers/memory/tegra/tegra30-emc.c
@@ -0,0 +1,1232 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tegra30 External Memory Controller driver
+ *
+ * Based on downstream driver from NVIDIA and tegra124-emc.c
+ * Copyright (C) 2011-2014 NVIDIA Corporation
+ *
+ * Author: Dmitry Osipenko <digetx@gmail.com>
+ * Copyright (C) 2019 GRATE-DRIVER project
+ */
+
+#include <linux/clk.h>
+#include <linux/clk/tegra.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/sort.h>
+#include <linux/types.h>
+
+#include <soc/tegra/fuse.h>
+
+#include "mc.h"
+
+#define EMC_INTSTATUS 0x000
+#define EMC_INTMASK 0x004
+#define EMC_DBG 0x008
+#define EMC_CFG 0x00c
+#define EMC_REFCTRL 0x020
+#define EMC_TIMING_CONTROL 0x028
+#define EMC_RC 0x02c
+#define EMC_RFC 0x030
+#define EMC_RAS 0x034
+#define EMC_RP 0x038
+#define EMC_R2W 0x03c
+#define EMC_W2R 0x040
+#define EMC_R2P 0x044
+#define EMC_W2P 0x048
+#define EMC_RD_RCD 0x04c
+#define EMC_WR_RCD 0x050
+#define EMC_RRD 0x054
+#define EMC_REXT 0x058
+#define EMC_WDV 0x05c
+#define EMC_QUSE 0x060
+#define EMC_QRST 0x064
+#define EMC_QSAFE 0x068
+#define EMC_RDV 0x06c
+#define EMC_REFRESH 0x070
+#define EMC_BURST_REFRESH_NUM 0x074
+#define EMC_PDEX2WR 0x078
+#define EMC_PDEX2RD 0x07c
+#define EMC_PCHG2PDEN 0x080
+#define EMC_ACT2PDEN 0x084
+#define EMC_AR2PDEN 0x088
+#define EMC_RW2PDEN 0x08c
+#define EMC_TXSR 0x090
+#define EMC_TCKE 0x094
+#define EMC_TFAW 0x098
+#define EMC_TRPAB 0x09c
+#define EMC_TCLKSTABLE 0x0a0
+#define EMC_TCLKSTOP 0x0a4
+#define EMC_TREFBW 0x0a8
+#define EMC_QUSE_EXTRA 0x0ac
+#define EMC_ODT_WRITE 0x0b0
+#define EMC_ODT_READ 0x0b4
+#define EMC_WEXT 0x0b8
+#define EMC_CTT 0x0bc
+#define EMC_MRS_WAIT_CNT 0x0c8
+#define EMC_MRS 0x0cc
+#define EMC_EMRS 0x0d0
+#define EMC_SELF_REF 0x0e0
+#define EMC_MRW 0x0e8
+#define EMC_XM2DQSPADCTRL3 0x0f8
+#define EMC_FBIO_SPARE 0x100
+#define EMC_FBIO_CFG5 0x104
+#define EMC_FBIO_CFG6 0x114
+#define EMC_CFG_RSV 0x120
+#define EMC_AUTO_CAL_CONFIG 0x2a4
+#define EMC_AUTO_CAL_INTERVAL 0x2a8
+#define EMC_AUTO_CAL_STATUS 0x2ac
+#define EMC_STATUS 0x2b4
+#define EMC_CFG_2 0x2b8
+#define EMC_CFG_DIG_DLL 0x2bc
+#define EMC_CFG_DIG_DLL_PERIOD 0x2c0
+#define EMC_CTT_DURATION 0x2d8
+#define EMC_CTT_TERM_CTRL 0x2dc
+#define EMC_ZCAL_INTERVAL 0x2e0
+#define EMC_ZCAL_WAIT_CNT 0x2e4
+#define EMC_ZQ_CAL 0x2ec
+#define EMC_XM2CMDPADCTRL 0x2f0
+#define EMC_XM2DQSPADCTRL2 0x2fc
+#define EMC_XM2DQPADCTRL2 0x304
+#define EMC_XM2CLKPADCTRL 0x308
+#define EMC_XM2COMPPADCTRL 0x30c
+#define EMC_XM2VTTGENPADCTRL 0x310
+#define EMC_XM2VTTGENPADCTRL2 0x314
+#define EMC_XM2QUSEPADCTRL 0x318
+#define EMC_DLL_XFORM_DQS0 0x328
+#define EMC_DLL_XFORM_DQS1 0x32c
+#define EMC_DLL_XFORM_DQS2 0x330
+#define EMC_DLL_XFORM_DQS3 0x334
+#define EMC_DLL_XFORM_DQS4 0x338
+#define EMC_DLL_XFORM_DQS5 0x33c
+#define EMC_DLL_XFORM_DQS6 0x340
+#define EMC_DLL_XFORM_DQS7 0x344
+#define EMC_DLL_XFORM_QUSE0 0x348
+#define EMC_DLL_XFORM_QUSE1 0x34c
+#define EMC_DLL_XFORM_QUSE2 0x350
+#define EMC_DLL_XFORM_QUSE3 0x354
+#define EMC_DLL_XFORM_QUSE4 0x358
+#define EMC_DLL_XFORM_QUSE5 0x35c
+#define EMC_DLL_XFORM_QUSE6 0x360
+#define EMC_DLL_XFORM_QUSE7 0x364
+#define EMC_DLL_XFORM_DQ0 0x368
+#define EMC_DLL_XFORM_DQ1 0x36c
+#define EMC_DLL_XFORM_DQ2 0x370
+#define EMC_DLL_XFORM_DQ3 0x374
+#define EMC_DLI_TRIM_TXDQS0 0x3a8
+#define EMC_DLI_TRIM_TXDQS1 0x3ac
+#define EMC_DLI_TRIM_TXDQS2 0x3b0
+#define EMC_DLI_TRIM_TXDQS3 0x3b4
+#define EMC_DLI_TRIM_TXDQS4 0x3b8
+#define EMC_DLI_TRIM_TXDQS5 0x3bc
+#define EMC_DLI_TRIM_TXDQS6 0x3c0
+#define EMC_DLI_TRIM_TXDQS7 0x3c4
+#define EMC_STALL_THEN_EXE_BEFORE_CLKCHANGE 0x3c8
+#define EMC_STALL_THEN_EXE_AFTER_CLKCHANGE 0x3cc
+#define EMC_UNSTALL_RW_AFTER_CLKCHANGE 0x3d0
+#define EMC_SEL_DPD_CTRL 0x3d8
+#define EMC_PRE_REFRESH_REQ_CNT 0x3dc
+#define EMC_DYN_SELF_REF_CONTROL 0x3e0
+#define EMC_TXSRDLL 0x3e4
+
+#define EMC_STATUS_TIMING_UPDATE_STALLED BIT(23)
+
+#define EMC_MODE_SET_DLL_RESET BIT(8)
+#define EMC_MODE_SET_LONG_CNT BIT(26)
+
+#define EMC_SELF_REF_CMD_ENABLED BIT(0)
+
+#define DRAM_DEV_SEL_ALL (0 << 30)
+#define DRAM_DEV_SEL_0 (2 << 30)
+#define DRAM_DEV_SEL_1 (1 << 30)
+#define DRAM_BROADCAST(num) \
+ ((num) > 1 ? DRAM_DEV_SEL_ALL : DRAM_DEV_SEL_0)
+
+#define EMC_ZQ_CAL_CMD BIT(0)
+#define EMC_ZQ_CAL_LONG BIT(4)
+#define EMC_ZQ_CAL_LONG_CMD_DEV0 \
+ (DRAM_DEV_SEL_0 | EMC_ZQ_CAL_LONG | EMC_ZQ_CAL_CMD)
+#define EMC_ZQ_CAL_LONG_CMD_DEV1 \
+ (DRAM_DEV_SEL_1 | EMC_ZQ_CAL_LONG | EMC_ZQ_CAL_CMD)
+
+#define EMC_DBG_READ_MUX_ASSEMBLY BIT(0)
+#define EMC_DBG_WRITE_MUX_ACTIVE BIT(1)
+#define EMC_DBG_FORCE_UPDATE BIT(2)
+#define EMC_DBG_CFG_PRIORITY BIT(24)
+
+#define EMC_CFG5_QUSE_MODE_SHIFT 13
+#define EMC_CFG5_QUSE_MODE_MASK (7 << EMC_CFG5_QUSE_MODE_SHIFT)
+
+#define EMC_CFG5_QUSE_MODE_INTERNAL_LPBK 2
+#define EMC_CFG5_QUSE_MODE_PULSE_INTERN 3
+
+#define EMC_SEL_DPD_CTRL_QUSE_DPD_ENABLE BIT(9)
+
+#define EMC_XM2COMPPADCTRL_VREF_CAL_ENABLE BIT(10)
+
+#define EMC_XM2QUSEPADCTRL_IVREF_ENABLE BIT(4)
+
+#define EMC_XM2DQSPADCTRL2_VREF_ENABLE BIT(5)
+#define EMC_XM2DQSPADCTRL3_VREF_ENABLE BIT(5)
+
+#define EMC_AUTO_CAL_STATUS_ACTIVE BIT(31)
+
+#define EMC_FBIO_CFG5_DRAM_TYPE_MASK 0x3
+
+#define EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK 0x3ff
+#define EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT 16
+#define EMC_MRS_WAIT_CNT_LONG_WAIT_MASK \
+ (0x3ff << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT)
+
+#define EMC_REFCTRL_DEV_SEL_MASK 0x3
+#define EMC_REFCTRL_ENABLE BIT(31)
+#define EMC_REFCTRL_ENABLE_ALL(num) \
+ (((num) > 1 ? 0 : 2) | EMC_REFCTRL_ENABLE)
+#define EMC_REFCTRL_DISABLE_ALL(num) ((num) > 1 ? 0 : 2)
+
+#define EMC_CFG_PERIODIC_QRST BIT(21)
+#define EMC_CFG_DYN_SREF_ENABLE BIT(28)
+
+#define EMC_CLKCHANGE_REQ_ENABLE BIT(0)
+#define EMC_CLKCHANGE_PD_ENABLE BIT(1)
+#define EMC_CLKCHANGE_SR_ENABLE BIT(2)
+
+#define EMC_TIMING_UPDATE BIT(0)
+
+#define EMC_REFRESH_OVERFLOW_INT BIT(3)
+#define EMC_CLKCHANGE_COMPLETE_INT BIT(4)
+
+enum emc_dram_type {
+ DRAM_TYPE_DDR3,
+ DRAM_TYPE_DDR1,
+ DRAM_TYPE_LPDDR2,
+ DRAM_TYPE_DDR2,
+};
+
+enum emc_dll_change {
+ DLL_CHANGE_NONE,
+ DLL_CHANGE_ON,
+ DLL_CHANGE_OFF
+};
+
+static const u16 emc_timing_registers[] = {
+ [0] = EMC_RC,
+ [1] = EMC_RFC,
+ [2] = EMC_RAS,
+ [3] = EMC_RP,
+ [4] = EMC_R2W,
+ [5] = EMC_W2R,
+ [6] = EMC_R2P,
+ [7] = EMC_W2P,
+ [8] = EMC_RD_RCD,
+ [9] = EMC_WR_RCD,
+ [10] = EMC_RRD,
+ [11] = EMC_REXT,
+ [12] = EMC_WEXT,
+ [13] = EMC_WDV,
+ [14] = EMC_QUSE,
+ [15] = EMC_QRST,
+ [16] = EMC_QSAFE,
+ [17] = EMC_RDV,
+ [18] = EMC_REFRESH,
+ [19] = EMC_BURST_REFRESH_NUM,
+ [20] = EMC_PRE_REFRESH_REQ_CNT,
+ [21] = EMC_PDEX2WR,
+ [22] = EMC_PDEX2RD,
+ [23] = EMC_PCHG2PDEN,
+ [24] = EMC_ACT2PDEN,
+ [25] = EMC_AR2PDEN,
+ [26] = EMC_RW2PDEN,
+ [27] = EMC_TXSR,
+ [28] = EMC_TXSRDLL,
+ [29] = EMC_TCKE,
+ [30] = EMC_TFAW,
+ [31] = EMC_TRPAB,
+ [32] = EMC_TCLKSTABLE,
+ [33] = EMC_TCLKSTOP,
+ [34] = EMC_TREFBW,
+ [35] = EMC_QUSE_EXTRA,
+ [36] = EMC_FBIO_CFG6,
+ [37] = EMC_ODT_WRITE,
+ [38] = EMC_ODT_READ,
+ [39] = EMC_FBIO_CFG5,
+ [40] = EMC_CFG_DIG_DLL,
+ [41] = EMC_CFG_DIG_DLL_PERIOD,
+ [42] = EMC_DLL_XFORM_DQS0,
+ [43] = EMC_DLL_XFORM_DQS1,
+ [44] = EMC_DLL_XFORM_DQS2,
+ [45] = EMC_DLL_XFORM_DQS3,
+ [46] = EMC_DLL_XFORM_DQS4,
+ [47] = EMC_DLL_XFORM_DQS5,
+ [48] = EMC_DLL_XFORM_DQS6,
+ [49] = EMC_DLL_XFORM_DQS7,
+ [50] = EMC_DLL_XFORM_QUSE0,
+ [51] = EMC_DLL_XFORM_QUSE1,
+ [52] = EMC_DLL_XFORM_QUSE2,
+ [53] = EMC_DLL_XFORM_QUSE3,
+ [54] = EMC_DLL_XFORM_QUSE4,
+ [55] = EMC_DLL_XFORM_QUSE5,
+ [56] = EMC_DLL_XFORM_QUSE6,
+ [57] = EMC_DLL_XFORM_QUSE7,
+ [58] = EMC_DLI_TRIM_TXDQS0,
+ [59] = EMC_DLI_TRIM_TXDQS1,
+ [60] = EMC_DLI_TRIM_TXDQS2,
+ [61] = EMC_DLI_TRIM_TXDQS3,
+ [62] = EMC_DLI_TRIM_TXDQS4,
+ [63] = EMC_DLI_TRIM_TXDQS5,
+ [64] = EMC_DLI_TRIM_TXDQS6,
+ [65] = EMC_DLI_TRIM_TXDQS7,
+ [66] = EMC_DLL_XFORM_DQ0,
+ [67] = EMC_DLL_XFORM_DQ1,
+ [68] = EMC_DLL_XFORM_DQ2,
+ [69] = EMC_DLL_XFORM_DQ3,
+ [70] = EMC_XM2CMDPADCTRL,
+ [71] = EMC_XM2DQSPADCTRL2,
+ [72] = EMC_XM2DQPADCTRL2,
+ [73] = EMC_XM2CLKPADCTRL,
+ [74] = EMC_XM2COMPPADCTRL,
+ [75] = EMC_XM2VTTGENPADCTRL,
+ [76] = EMC_XM2VTTGENPADCTRL2,
+ [77] = EMC_XM2QUSEPADCTRL,
+ [78] = EMC_XM2DQSPADCTRL3,
+ [79] = EMC_CTT_TERM_CTRL,
+ [80] = EMC_ZCAL_INTERVAL,
+ [81] = EMC_ZCAL_WAIT_CNT,
+ [82] = EMC_MRS_WAIT_CNT,
+ [83] = EMC_AUTO_CAL_CONFIG,
+ [84] = EMC_CTT,
+ [85] = EMC_CTT_DURATION,
+ [86] = EMC_DYN_SELF_REF_CONTROL,
+ [87] = EMC_FBIO_SPARE,
+ [88] = EMC_CFG_RSV,
+};
+
+struct emc_timing {
+ unsigned long rate;
+
+ u32 data[ARRAY_SIZE(emc_timing_registers)];
+
+ u32 emc_auto_cal_interval;
+ u32 emc_mode_1;
+ u32 emc_mode_2;
+ u32 emc_mode_reset;
+ u32 emc_zcal_cnt_long;
+ bool emc_cfg_periodic_qrst;
+ bool emc_cfg_dyn_self_ref;
+};
+
+struct tegra_emc {
+ struct device *dev;
+ struct tegra_mc *mc;
+ struct completion clk_handshake_complete;
+ struct notifier_block clk_nb;
+ struct clk *clk;
+ void __iomem *regs;
+ unsigned int irq;
+
+ struct emc_timing *timings;
+ unsigned int num_timings;
+
+ u32 mc_override;
+ u32 emc_cfg;
+
+ u32 emc_mode_1;
+ u32 emc_mode_2;
+ u32 emc_mode_reset;
+
+ bool vref_cal_toggle : 1;
+ bool zcal_long : 1;
+ bool dll_on : 1;
+ bool prepared : 1;
+ bool bad_state : 1;
+};
+
+static irqreturn_t tegra_emc_isr(int irq, void *data)
+{
+ struct tegra_emc *emc = data;
+ u32 intmask = EMC_REFRESH_OVERFLOW_INT | EMC_CLKCHANGE_COMPLETE_INT;
+ u32 status;
+
+ status = readl_relaxed(emc->regs + EMC_INTSTATUS) & intmask;
+ if (!status)
+ return IRQ_NONE;
+
+ /* notify about EMC-CAR handshake completion */
+ if (status & EMC_CLKCHANGE_COMPLETE_INT)
+ complete(&emc->clk_handshake_complete);
+
+ /* notify about HW problem */
+ if (status & EMC_REFRESH_OVERFLOW_INT)
+ dev_err_ratelimited(emc->dev,
+ "refresh request overflow timeout\n");
+
+ /* clear interrupts */
+ writel_relaxed(status, emc->regs + EMC_INTSTATUS);
+
+ return IRQ_HANDLED;
+}
+
+static struct emc_timing *emc_find_timing(struct tegra_emc *emc,
+ unsigned long rate)
+{
+ struct emc_timing *timing = NULL;
+ unsigned int i;
+
+ for (i = 0; i < emc->num_timings; i++) {
+ if (emc->timings[i].rate >= rate) {
+ timing = &emc->timings[i];
+ break;
+ }
+ }
+
+ if (!timing) {
+ dev_err(emc->dev, "no timing for rate %lu\n", rate);
+ return NULL;
+ }
+
+ return timing;
+}
+
+static bool emc_dqs_preset(struct tegra_emc *emc, struct emc_timing *timing,
+ bool *schmitt_to_vref)
+{
+ bool preset = false;
+ u32 val;
+
+ if (timing->data[71] & EMC_XM2DQSPADCTRL2_VREF_ENABLE) {
+ val = readl_relaxed(emc->regs + EMC_XM2DQSPADCTRL2);
+
+ if (!(val & EMC_XM2DQSPADCTRL2_VREF_ENABLE)) {
+ val |= EMC_XM2DQSPADCTRL2_VREF_ENABLE;
+ writel_relaxed(val, emc->regs + EMC_XM2DQSPADCTRL2);
+
+ preset = true;
+ }
+ }
+
+ if (timing->data[78] & EMC_XM2DQSPADCTRL3_VREF_ENABLE) {
+ val = readl_relaxed(emc->regs + EMC_XM2DQSPADCTRL3);
+
+ if (!(val & EMC_XM2DQSPADCTRL3_VREF_ENABLE)) {
+ val |= EMC_XM2DQSPADCTRL3_VREF_ENABLE;
+ writel_relaxed(val, emc->regs + EMC_XM2DQSPADCTRL3);
+
+ preset = true;
+ }
+ }
+
+ if (timing->data[77] & EMC_XM2QUSEPADCTRL_IVREF_ENABLE) {
+ val = readl_relaxed(emc->regs + EMC_XM2QUSEPADCTRL);
+
+ if (!(val & EMC_XM2QUSEPADCTRL_IVREF_ENABLE)) {
+ val |= EMC_XM2QUSEPADCTRL_IVREF_ENABLE;
+ writel_relaxed(val, emc->regs + EMC_XM2QUSEPADCTRL);
+
+ *schmitt_to_vref = true;
+ preset = true;
+ }
+ }
+
+ return preset;
+}
+
+static int emc_seq_update_timing(struct tegra_emc *emc)
+{
+ u32 val;
+ int err;
+
+ writel_relaxed(EMC_TIMING_UPDATE, emc->regs + EMC_TIMING_CONTROL);
+
+ err = readl_relaxed_poll_timeout_atomic(emc->regs + EMC_STATUS, val,
+ !(val & EMC_STATUS_TIMING_UPDATE_STALLED),
+ 1, 200);
+ if (err) {
+ dev_err(emc->dev, "failed to update timing: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int emc_prepare_mc_clk_cfg(struct tegra_emc *emc, unsigned long rate)
+{
+ struct tegra_mc *mc = emc->mc;
+ unsigned int misc0_index = 16;
+ unsigned int i;
+ bool same;
+
+ for (i = 0; i < mc->num_timings; i++) {
+ if (mc->timings[i].rate != rate)
+ continue;
+
+ if (mc->timings[i].emem_data[misc0_index] & BIT(27))
+ same = true;
+ else
+ same = false;
+
+ return tegra20_clk_prepare_emc_mc_same_freq(emc->clk, same);
+ }
+
+ return -EINVAL;
+}
+
+static int emc_prepare_timing_change(struct tegra_emc *emc, unsigned long rate)
+{
+ struct emc_timing *timing = emc_find_timing(emc, rate);
+ enum emc_dll_change dll_change;
+ enum emc_dram_type dram_type;
+ bool schmitt_to_vref = false;
+ unsigned int pre_wait = 0;
+ bool qrst_used = false;
+ unsigned int dram_num;
+ unsigned int i;
+ u32 fbio_cfg5;
+ u32 emc_dbg;
+ u32 val;
+ int err;
+
+ if (!timing || emc->bad_state)
+ return -EINVAL;
+
+ dev_dbg(emc->dev, "%s: using timing rate %lu for requested rate %lu\n",
+ __func__, timing->rate, rate);
+
+ emc->bad_state = true;
+
+ err = emc_prepare_mc_clk_cfg(emc, rate);
+ if (err) {
+ dev_err(emc->dev, "mc clock preparation failed: %d\n", err);
+ return err;
+ }
+
+ emc->vref_cal_toggle = false;
+ emc->mc_override = mc_readl(emc->mc, MC_EMEM_ARB_OVERRIDE);
+ emc->emc_cfg = readl_relaxed(emc->regs + EMC_CFG);
+ emc_dbg = readl_relaxed(emc->regs + EMC_DBG);
+
+ if (emc->dll_on == !!(timing->emc_mode_1 & 0x1))
+ dll_change = DLL_CHANGE_NONE;
+ else if (timing->emc_mode_1 & 0x1)
+ dll_change = DLL_CHANGE_ON;
+ else
+ dll_change = DLL_CHANGE_OFF;
+
+ emc->dll_on = !!(timing->emc_mode_1 & 0x1);
+
+ if (timing->data[80] && !readl_relaxed(emc->regs + EMC_ZCAL_INTERVAL))
+ emc->zcal_long = true;
+ else
+ emc->zcal_long = false;
+
+ fbio_cfg5 = readl_relaxed(emc->regs + EMC_FBIO_CFG5);
+ dram_type = fbio_cfg5 & EMC_FBIO_CFG5_DRAM_TYPE_MASK;
+
+ dram_num = tegra_mc_get_emem_device_count(emc->mc);
+
+ /* disable dynamic self-refresh */
+ if (emc->emc_cfg & EMC_CFG_DYN_SREF_ENABLE) {
+ emc->emc_cfg &= ~EMC_CFG_DYN_SREF_ENABLE;
+ writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG);
+
+ pre_wait = 5;
+ }
+
+ /* update MC arbiter settings */
+ val = mc_readl(emc->mc, MC_EMEM_ARB_OUTSTANDING_REQ);
+ if (!(val & MC_EMEM_ARB_OUTSTANDING_REQ_HOLDOFF_OVERRIDE) ||
+ ((val & MC_EMEM_ARB_OUTSTANDING_REQ_MAX_MASK) > 0x50)) {
+
+ val = MC_EMEM_ARB_OUTSTANDING_REQ_LIMIT_ENABLE |
+ MC_EMEM_ARB_OUTSTANDING_REQ_HOLDOFF_OVERRIDE | 0x50;
+ mc_writel(emc->mc, val, MC_EMEM_ARB_OUTSTANDING_REQ);
+ mc_writel(emc->mc, MC_TIMING_UPDATE, MC_TIMING_CONTROL);
+ }
+
+ if (emc->mc_override & MC_EMEM_ARB_OVERRIDE_EACK_MASK)
+ mc_writel(emc->mc,
+ emc->mc_override & ~MC_EMEM_ARB_OVERRIDE_EACK_MASK,
+ MC_EMEM_ARB_OVERRIDE);
+
+ /* check DQ/DQS VREF delay */
+ if (emc_dqs_preset(emc, timing, &schmitt_to_vref)) {
+ if (pre_wait < 3)
+ pre_wait = 3;
+ }
+
+ if (pre_wait) {
+ err = emc_seq_update_timing(emc);
+ if (err)
+ return err;
+
+ udelay(pre_wait);
+ }
+
+ /* disable auto-calibration if VREF mode is switching */
+ if (timing->emc_auto_cal_interval) {
+ val = readl_relaxed(emc->regs + EMC_XM2COMPPADCTRL);
+ val ^= timing->data[74];
+
+ if (val & EMC_XM2COMPPADCTRL_VREF_CAL_ENABLE) {
+ writel_relaxed(0, emc->regs + EMC_AUTO_CAL_INTERVAL);
+
+ err = readl_relaxed_poll_timeout_atomic(
+ emc->regs + EMC_AUTO_CAL_STATUS, val,
+ !(val & EMC_AUTO_CAL_STATUS_ACTIVE), 1, 300);
+ if (err) {
+ dev_err(emc->dev,
+ "failed to disable auto-cal: %d\n",
+ err);
+ return err;
+ }
+
+ emc->vref_cal_toggle = true;
+ }
+ }
+
+ /* program shadow registers */
+ for (i = 0; i < ARRAY_SIZE(timing->data); i++) {
+ /* EMC_XM2CLKPADCTRL should be programmed separately */
+ if (i != 73)
+ writel_relaxed(timing->data[i],
+ emc->regs + emc_timing_registers[i]);
+ }
+
+ err = tegra_mc_write_emem_configuration(emc->mc, timing->rate);
+ if (err)
+ return err;
+
+ /* DDR3: predict MRS long wait count */
+ if (dram_type == DRAM_TYPE_DDR3 && dll_change == DLL_CHANGE_ON) {
+ u32 cnt = 512;
+
+ if (emc->zcal_long)
+ cnt -= dram_num * 256;
+
+ val = timing->data[82] & EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK;
+ if (cnt < val)
+ cnt = val;
+
+ val = timing->data[82] & ~EMC_MRS_WAIT_CNT_LONG_WAIT_MASK;
+ val |= (cnt << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT) &
+ EMC_MRS_WAIT_CNT_LONG_WAIT_MASK;
+
+ writel_relaxed(val, emc->regs + EMC_MRS_WAIT_CNT);
+ }
+
+ /* disable interrupt since read access is prohibited after stalling */
+ disable_irq(emc->irq);
+
+ /* this read also completes the writes */
+ val = readl_relaxed(emc->regs + EMC_SEL_DPD_CTRL);
+
+ if (!(val & EMC_SEL_DPD_CTRL_QUSE_DPD_ENABLE) && schmitt_to_vref) {
+ u32 cur_mode, new_mode;
+
+ cur_mode = fbio_cfg5 & EMC_CFG5_QUSE_MODE_MASK;
+ cur_mode >>= EMC_CFG5_QUSE_MODE_SHIFT;
+
+ new_mode = timing->data[39] & EMC_CFG5_QUSE_MODE_MASK;
+ new_mode >>= EMC_CFG5_QUSE_MODE_SHIFT;
+
+ if ((cur_mode != EMC_CFG5_QUSE_MODE_PULSE_INTERN &&
+ cur_mode != EMC_CFG5_QUSE_MODE_INTERNAL_LPBK) ||
+ (new_mode != EMC_CFG5_QUSE_MODE_PULSE_INTERN &&
+ new_mode != EMC_CFG5_QUSE_MODE_INTERNAL_LPBK))
+ qrst_used = true;
+ }
+
+ /* flow control marker 1 */
+ writel_relaxed(0x1, emc->regs + EMC_STALL_THEN_EXE_BEFORE_CLKCHANGE);
+
+ /* enable periodic reset */
+ if (qrst_used) {
+ writel_relaxed(emc_dbg | EMC_DBG_WRITE_MUX_ACTIVE,
+ emc->regs + EMC_DBG);
+ writel_relaxed(emc->emc_cfg | EMC_CFG_PERIODIC_QRST,
+ emc->regs + EMC_CFG);
+ writel_relaxed(emc_dbg, emc->regs + EMC_DBG);
+ }
+
+ /* disable auto-refresh to save time after clock change */
+ writel_relaxed(EMC_REFCTRL_DISABLE_ALL(dram_num),
+ emc->regs + EMC_REFCTRL);
+
+ /* turn off DLL and enter self-refresh on DDR3 */
+ if (dram_type == DRAM_TYPE_DDR3) {
+ if (dll_change == DLL_CHANGE_OFF)
+ writel_relaxed(timing->emc_mode_1,
+ emc->regs + EMC_EMRS);
+
+ writel_relaxed(DRAM_BROADCAST(dram_num) |
+ EMC_SELF_REF_CMD_ENABLED,
+ emc->regs + EMC_SELF_REF);
+ }
+
+ /* flow control marker 2 */
+ writel_relaxed(0x1, emc->regs + EMC_STALL_THEN_EXE_AFTER_CLKCHANGE);
+
+ /* enable write-active MUX, update unshadowed pad control */
+ writel_relaxed(emc_dbg | EMC_DBG_WRITE_MUX_ACTIVE, emc->regs + EMC_DBG);
+ writel_relaxed(timing->data[73], emc->regs + EMC_XM2CLKPADCTRL);
+
+ /* restore periodic QRST and disable write-active MUX */
+ val = !!(emc->emc_cfg & EMC_CFG_PERIODIC_QRST);
+ if (qrst_used || timing->emc_cfg_periodic_qrst != val) {
+ if (timing->emc_cfg_periodic_qrst)
+ emc->emc_cfg |= EMC_CFG_PERIODIC_QRST;
+ else
+ emc->emc_cfg &= ~EMC_CFG_PERIODIC_QRST;
+
+ writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG);
+ }
+ writel_relaxed(emc_dbg, emc->regs + EMC_DBG);
+
+ /* exit self-refresh on DDR3 */
+ if (dram_type == DRAM_TYPE_DDR3)
+ writel_relaxed(DRAM_BROADCAST(dram_num),
+ emc->regs + EMC_SELF_REF);
+
+ /* set DRAM-mode registers */
+ if (dram_type == DRAM_TYPE_DDR3) {
+ if (timing->emc_mode_1 != emc->emc_mode_1)
+ writel_relaxed(timing->emc_mode_1,
+ emc->regs + EMC_EMRS);
+
+ if (timing->emc_mode_2 != emc->emc_mode_2)
+ writel_relaxed(timing->emc_mode_2,
+ emc->regs + EMC_EMRS);
+
+ if (timing->emc_mode_reset != emc->emc_mode_reset ||
+ dll_change == DLL_CHANGE_ON) {
+ val = timing->emc_mode_reset;
+ if (dll_change == DLL_CHANGE_ON) {
+ val |= EMC_MODE_SET_DLL_RESET;
+ val |= EMC_MODE_SET_LONG_CNT;
+ } else {
+ val &= ~EMC_MODE_SET_DLL_RESET;
+ }
+ writel_relaxed(val, emc->regs + EMC_MRS);
+ }
+ } else {
+ if (timing->emc_mode_2 != emc->emc_mode_2)
+ writel_relaxed(timing->emc_mode_2,
+ emc->regs + EMC_MRW);
+
+ if (timing->emc_mode_1 != emc->emc_mode_1)
+ writel_relaxed(timing->emc_mode_1,
+ emc->regs + EMC_MRW);
+ }
+
+ emc->emc_mode_1 = timing->emc_mode_1;
+ emc->emc_mode_2 = timing->emc_mode_2;
+ emc->emc_mode_reset = timing->emc_mode_reset;
+
+ /* issue ZCAL command if turning ZCAL on */
+ if (emc->zcal_long) {
+ writel_relaxed(EMC_ZQ_CAL_LONG_CMD_DEV0,
+ emc->regs + EMC_ZQ_CAL);
+
+ if (dram_num > 1)
+ writel_relaxed(EMC_ZQ_CAL_LONG_CMD_DEV1,
+ emc->regs + EMC_ZQ_CAL);
+ }
+
+ /* re-enable auto-refresh */
+ writel_relaxed(EMC_REFCTRL_ENABLE_ALL(dram_num),
+ emc->regs + EMC_REFCTRL);
+
+ /* flow control marker 3 */
+ writel_relaxed(0x1, emc->regs + EMC_UNSTALL_RW_AFTER_CLKCHANGE);
+
+ reinit_completion(&emc->clk_handshake_complete);
+
+ /* interrupt can be re-enabled now */
+ enable_irq(emc->irq);
+
+ emc->bad_state = false;
+ emc->prepared = true;
+
+ return 0;
+}
+
+static int emc_complete_timing_change(struct tegra_emc *emc,
+ unsigned long rate)
+{
+ struct emc_timing *timing = emc_find_timing(emc, rate);
+ unsigned long timeout;
+ int ret;
+
+ timeout = wait_for_completion_timeout(&emc->clk_handshake_complete,
+ msecs_to_jiffies(100));
+ if (timeout == 0) {
+ dev_err(emc->dev, "emc-car handshake failed\n");
+ emc->bad_state = true;
+ return -EIO;
+ }
+
+ /* restore auto-calibration */
+ if (emc->vref_cal_toggle)
+ writel_relaxed(timing->emc_auto_cal_interval,
+ emc->regs + EMC_AUTO_CAL_INTERVAL);
+
+ /* restore dynamic self-refresh */
+ if (timing->emc_cfg_dyn_self_ref) {
+ emc->emc_cfg |= EMC_CFG_DYN_SREF_ENABLE;
+ writel_relaxed(emc->emc_cfg, emc->regs + EMC_CFG);
+ }
+
+ /* set number of clocks to wait after each ZQ command */
+ if (emc->zcal_long)
+ writel_relaxed(timing->emc_zcal_cnt_long,
+ emc->regs + EMC_ZCAL_WAIT_CNT);
+
+ udelay(2);
+ /* update restored timing */
+ ret = emc_seq_update_timing(emc);
+ if (ret)
+ emc->bad_state = true;
+
+ /* restore early ACK */
+ mc_writel(emc->mc, emc->mc_override, MC_EMEM_ARB_OVERRIDE);
+
+ emc->prepared = false;
+
+ return ret;
+}
+
+static int emc_unprepare_timing_change(struct tegra_emc *emc,
+ unsigned long rate)
+{
+ if (emc->prepared && !emc->bad_state) {
+ /* shouldn't ever happen in practice */
+ dev_err(emc->dev, "timing configuration can't be reverted\n");
+ emc->bad_state = true;
+ }
+
+ return 0;
+}
+
+static int emc_clk_change_notify(struct notifier_block *nb,
+ unsigned long msg, void *data)
+{
+ struct tegra_emc *emc = container_of(nb, struct tegra_emc, clk_nb);
+ struct clk_notifier_data *cnd = data;
+ int err;
+
+ switch (msg) {
+ case PRE_RATE_CHANGE:
+ err = emc_prepare_timing_change(emc, cnd->new_rate);
+ break;
+
+ case ABORT_RATE_CHANGE:
+ err = emc_unprepare_timing_change(emc, cnd->old_rate);
+ break;
+
+ case POST_RATE_CHANGE:
+ err = emc_complete_timing_change(emc, cnd->new_rate);
+ break;
+
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return notifier_from_errno(err);
+}
+
+static int load_one_timing_from_dt(struct tegra_emc *emc,
+ struct emc_timing *timing,
+ struct device_node *node)
+{
+ u32 value;
+ int err;
+
+ err = of_property_read_u32(node, "clock-frequency", &value);
+ if (err) {
+ dev_err(emc->dev, "timing %pOF: failed to read rate: %d\n",
+ node, err);
+ return err;
+ }
+
+ timing->rate = value;
+
+ err = of_property_read_u32_array(node, "nvidia,emc-configuration",
+ timing->data,
+ ARRAY_SIZE(emc_timing_registers));
+ if (err) {
+ dev_err(emc->dev,
+ "timing %pOF: failed to read emc timing data: %d\n",
+ node, err);
+ return err;
+ }
+
+#define EMC_READ_BOOL(prop, dtprop) \
+ timing->prop = of_property_read_bool(node, dtprop);
+
+#define EMC_READ_U32(prop, dtprop) \
+ err = of_property_read_u32(node, dtprop, &timing->prop); \
+ if (err) { \
+ dev_err(emc->dev, \
+ "timing %pOFn: failed to read " #prop ": %d\n", \
+ node, err); \
+ return err; \
+ }
+
+ EMC_READ_U32(emc_auto_cal_interval, "nvidia,emc-auto-cal-interval")
+ EMC_READ_U32(emc_mode_1, "nvidia,emc-mode-1")
+ EMC_READ_U32(emc_mode_2, "nvidia,emc-mode-2")
+ EMC_READ_U32(emc_mode_reset, "nvidia,emc-mode-reset")
+ EMC_READ_U32(emc_zcal_cnt_long, "nvidia,emc-zcal-cnt-long")
+ EMC_READ_BOOL(emc_cfg_dyn_self_ref, "nvidia,emc-cfg-dyn-self-ref")
+ EMC_READ_BOOL(emc_cfg_periodic_qrst, "nvidia,emc-cfg-periodic-qrst")
+
+#undef EMC_READ_U32
+#undef EMC_READ_BOOL
+
+ dev_dbg(emc->dev, "%s: %pOF: rate %lu\n", __func__, node, timing->rate);
+
+ return 0;
+}
+
+static int cmp_timings(const void *_a, const void *_b)
+{
+ const struct emc_timing *a = _a;
+ const struct emc_timing *b = _b;
+
+ if (a->rate < b->rate)
+ return -1;
+
+ if (a->rate > b->rate)
+ return 1;
+
+ return 0;
+}
+
+static int emc_check_mc_timings(struct tegra_emc *emc)
+{
+ struct tegra_mc *mc = emc->mc;
+ unsigned int i;
+
+ if (emc->num_timings != mc->num_timings) {
+ dev_err(emc->dev, "emc/mc timings number mismatch: %u %u\n",
+ emc->num_timings, mc->num_timings);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < mc->num_timings; i++) {
+ if (emc->timings[i].rate != mc->timings[i].rate) {
+ dev_err(emc->dev,
+ "emc/mc timing rate mismatch: %lu %lu\n",
+ emc->timings[i].rate, mc->timings[i].rate);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int emc_load_timings_from_dt(struct tegra_emc *emc,
+ struct device_node *node)
+{
+ struct device_node *child;
+ struct emc_timing *timing;
+ int child_count;
+ int err;
+
+ child_count = of_get_child_count(node);
+ if (!child_count) {
+ dev_err(emc->dev, "no memory timings in: %pOF\n", node);
+ return -EINVAL;
+ }
+
+ emc->timings = devm_kcalloc(emc->dev, child_count, sizeof(*timing),
+ GFP_KERNEL);
+ if (!emc->timings)
+ return -ENOMEM;
+
+ emc->num_timings = child_count;
+ timing = emc->timings;
+
+ for_each_child_of_node(node, child) {
+ err = load_one_timing_from_dt(emc, timing++, child);
+ if (err) {
+ of_node_put(child);
+ return err;
+ }
+ }
+
+ sort(emc->timings, emc->num_timings, sizeof(*timing), cmp_timings,
+ NULL);
+
+ err = emc_check_mc_timings(emc);
+ if (err)
+ return err;
+
+ dev_info(emc->dev,
+ "got %u timings for RAM code %u (min %luMHz max %luMHz)\n",
+ emc->num_timings,
+ tegra_read_ram_code(),
+ emc->timings[0].rate / 1000000,
+ emc->timings[emc->num_timings - 1].rate / 1000000);
+
+ return 0;
+}
+
+static struct device_node *emc_find_node_by_ram_code(struct device *dev)
+{
+ struct device_node *np;
+ u32 value, ram_code;
+ int err;
+
+ ram_code = tegra_read_ram_code();
+
+ for_each_child_of_node(dev->of_node, np) {
+ err = of_property_read_u32(np, "nvidia,ram-code", &value);
+ if (err || value != ram_code)
+ continue;
+
+ return np;
+ }
+
+ dev_err(dev, "no memory timings for RAM code %u found in device-tree\n",
+ ram_code);
+
+ return NULL;
+}
+
+static int emc_setup_hw(struct tegra_emc *emc)
+{
+ u32 intmask = EMC_REFRESH_OVERFLOW_INT | EMC_CLKCHANGE_COMPLETE_INT;
+ u32 fbio_cfg5, emc_cfg, emc_dbg;
+ enum emc_dram_type dram_type;
+
+ fbio_cfg5 = readl_relaxed(emc->regs + EMC_FBIO_CFG5);
+ dram_type = fbio_cfg5 & EMC_FBIO_CFG5_DRAM_TYPE_MASK;
+
+ emc_cfg = readl_relaxed(emc->regs + EMC_CFG_2);
+
+ /* enable EMC and CAR to handshake on PLL divider/source changes */
+ emc_cfg |= EMC_CLKCHANGE_REQ_ENABLE;
+
+ /* configure clock change mode accordingly to DRAM type */
+ switch (dram_type) {
+ case DRAM_TYPE_LPDDR2:
+ emc_cfg |= EMC_CLKCHANGE_PD_ENABLE;
+ emc_cfg &= ~EMC_CLKCHANGE_SR_ENABLE;
+ break;
+
+ default:
+ emc_cfg &= ~EMC_CLKCHANGE_SR_ENABLE;
+ emc_cfg &= ~EMC_CLKCHANGE_PD_ENABLE;
+ break;
+ }
+
+ writel_relaxed(emc_cfg, emc->regs + EMC_CFG_2);
+
+ /* initialize interrupt */
+ writel_relaxed(intmask, emc->regs + EMC_INTMASK);
+ writel_relaxed(0xffffffff, emc->regs + EMC_INTSTATUS);
+
+ /* ensure that unwanted debug features are disabled */
+ emc_dbg = readl_relaxed(emc->regs + EMC_DBG);
+ emc_dbg |= EMC_DBG_CFG_PRIORITY;
+ emc_dbg &= ~EMC_DBG_READ_MUX_ASSEMBLY;
+ emc_dbg &= ~EMC_DBG_WRITE_MUX_ACTIVE;
+ emc_dbg &= ~EMC_DBG_FORCE_UPDATE;
+ writel_relaxed(emc_dbg, emc->regs + EMC_DBG);
+
+ return 0;
+}
+
+static long emc_round_rate(unsigned long rate,
+ unsigned long min_rate,
+ unsigned long max_rate,
+ void *arg)
+{
+ struct emc_timing *timing = NULL;
+ struct tegra_emc *emc = arg;
+ unsigned int i;
+
+ min_rate = min(min_rate, emc->timings[emc->num_timings - 1].rate);
+
+ for (i = 0; i < emc->num_timings; i++) {
+ if (emc->timings[i].rate < rate && i != emc->num_timings - 1)
+ continue;
+
+ if (emc->timings[i].rate > max_rate) {
+ i = max(i, 1u) - 1;
+
+ if (emc->timings[i].rate < min_rate)
+ break;
+ }
+
+ if (emc->timings[i].rate < min_rate)
+ continue;
+
+ timing = &emc->timings[i];
+ break;
+ }
+
+ if (!timing) {
+ dev_err(emc->dev, "no timing for rate %lu min %lu max %lu\n",
+ rate, min_rate, max_rate);
+ return -EINVAL;
+ }
+
+ return timing->rate;
+}
+
+static int tegra_emc_probe(struct platform_device *pdev)
+{
+ struct platform_device *mc;
+ struct device_node *np;
+ struct tegra_emc *emc;
+ int err;
+
+ if (of_get_child_count(pdev->dev.of_node) == 0) {
+ dev_info(&pdev->dev,
+ "device-tree node doesn't have memory timings\n");
+ return 0;
+ }
+
+ np = of_parse_phandle(pdev->dev.of_node, "nvidia,memory-controller", 0);
+ if (!np) {
+ dev_err(&pdev->dev, "could not get memory controller node\n");
+ return -ENOENT;
+ }
+
+ mc = of_find_device_by_node(np);
+ of_node_put(np);
+ if (!mc)
+ return -ENOENT;
+
+ np = emc_find_node_by_ram_code(&pdev->dev);
+ if (!np)
+ return -EINVAL;
+
+ emc = devm_kzalloc(&pdev->dev, sizeof(*emc), GFP_KERNEL);
+ if (!emc) {
+ of_node_put(np);
+ return -ENOMEM;
+ }
+
+ emc->mc = platform_get_drvdata(mc);
+ if (!emc->mc)
+ return -EPROBE_DEFER;
+
+ init_completion(&emc->clk_handshake_complete);
+ emc->clk_nb.notifier_call = emc_clk_change_notify;
+ emc->dev = &pdev->dev;
+
+ err = emc_load_timings_from_dt(emc, np);
+ of_node_put(np);
+ if (err)
+ return err;
+
+ emc->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(emc->regs))
+ return PTR_ERR(emc->regs);
+
+ err = emc_setup_hw(emc);
+ if (err)
+ return err;
+
+ err = platform_get_irq(pdev, 0);
+ if (err < 0) {
+ dev_err(&pdev->dev, "interrupt not specified: %d\n", err);
+ return err;
+ }
+ emc->irq = err;
+
+ err = devm_request_irq(&pdev->dev, emc->irq, tegra_emc_isr, 0,
+ dev_name(&pdev->dev), emc);
+ if (err) {
+ dev_err(&pdev->dev, "failed to request irq: %d\n", err);
+ return err;
+ }
+
+ tegra20_clk_set_emc_round_callback(emc_round_rate, emc);
+
+ emc->clk = devm_clk_get(&pdev->dev, "emc");
+ if (IS_ERR(emc->clk)) {
+ err = PTR_ERR(emc->clk);
+ dev_err(&pdev->dev, "failed to get emc clock: %d\n", err);
+ goto unset_cb;
+ }
+
+ err = clk_notifier_register(emc->clk, &emc->clk_nb);
+ if (err) {
+ dev_err(&pdev->dev, "failed to register clk notifier: %d\n",
+ err);
+ goto unset_cb;
+ }
+
+ platform_set_drvdata(pdev, emc);
+
+ return 0;
+
+unset_cb:
+ tegra20_clk_set_emc_round_callback(NULL, NULL);
+
+ return err;
+}
+
+static int tegra_emc_suspend(struct device *dev)
+{
+ struct tegra_emc *emc = dev_get_drvdata(dev);
+
+ /*
+ * Suspending in a bad state will hang machine. The "prepared" var
+ * shall be always false here unless it's a kernel bug that caused
+ * suspending in a wrong order.
+ */
+ if (WARN_ON(emc->prepared) || emc->bad_state)
+ return -EINVAL;
+
+ emc->bad_state = true;
+
+ return 0;
+}
+
+static int tegra_emc_resume(struct device *dev)
+{
+ struct tegra_emc *emc = dev_get_drvdata(dev);
+
+ emc_setup_hw(emc);
+ emc->bad_state = false;
+
+ return 0;
+}
+
+static const struct dev_pm_ops tegra_emc_pm_ops = {
+ .suspend = tegra_emc_suspend,
+ .resume = tegra_emc_resume,
+};
+
+static const struct of_device_id tegra_emc_of_match[] = {
+ { .compatible = "nvidia,tegra30-emc", },
+ {},
+};
+
+static struct platform_driver tegra_emc_driver = {
+ .probe = tegra_emc_probe,
+ .driver = {
+ .name = "tegra30-emc",
+ .of_match_table = tegra_emc_of_match,
+ .pm = &tegra_emc_pm_ops,
+ .suppress_bind_attrs = true,
+ },
+};
+
+static int __init tegra_emc_init(void)
+{
+ return platform_driver_register(&tegra_emc_driver);
+}
+subsys_initcall(tegra_emc_init);
diff --git a/drivers/memory/tegra/tegra30.c b/drivers/memory/tegra/tegra30.c
index 14788fc2f9e8..fcdd812eed80 100644
--- a/drivers/memory/tegra/tegra30.c
+++ b/drivers/memory/tegra/tegra30.c
@@ -10,6 +10,27 @@
#include "mc.h"
+static const unsigned long tegra30_mc_emem_regs[] = {
+ MC_EMEM_ARB_CFG,
+ MC_EMEM_ARB_OUTSTANDING_REQ,
+ MC_EMEM_ARB_TIMING_RCD,
+ MC_EMEM_ARB_TIMING_RP,
+ MC_EMEM_ARB_TIMING_RC,
+ MC_EMEM_ARB_TIMING_RAS,
+ MC_EMEM_ARB_TIMING_FAW,
+ MC_EMEM_ARB_TIMING_RRD,
+ MC_EMEM_ARB_TIMING_RAP2PRE,
+ MC_EMEM_ARB_TIMING_WAP2PRE,
+ MC_EMEM_ARB_TIMING_R2R,
+ MC_EMEM_ARB_TIMING_W2W,
+ MC_EMEM_ARB_TIMING_R2W,
+ MC_EMEM_ARB_TIMING_W2R,
+ MC_EMEM_ARB_DA_TURNS,
+ MC_EMEM_ARB_DA_COVERS,
+ MC_EMEM_ARB_MISC0,
+ MC_EMEM_ARB_RING1_THROTTLE,
+};
+
static const struct tegra_mc_client tegra30_mc_clients[] = {
{
.id = 0x00,
@@ -931,16 +952,19 @@ static const struct tegra_smmu_swgroup tegra30_swgroups[] = {
{ .name = "isp", .swgroup = TEGRA_SWGROUP_ISP, .reg = 0x258 },
};
-static const unsigned int tegra30_group_display[] = {
+static const unsigned int tegra30_group_drm[] = {
TEGRA_SWGROUP_DC,
TEGRA_SWGROUP_DCB,
+ TEGRA_SWGROUP_G2,
+ TEGRA_SWGROUP_NV,
+ TEGRA_SWGROUP_NV2,
};
static const struct tegra_smmu_group_soc tegra30_groups[] = {
{
- .name = "display",
- .swgroups = tegra30_group_display,
- .num_swgroups = ARRAY_SIZE(tegra30_group_display),
+ .name = "drm",
+ .swgroups = tegra30_group_drm,
+ .num_swgroups = ARRAY_SIZE(tegra30_group_drm),
},
};
@@ -994,6 +1018,8 @@ const struct tegra_mc_soc tegra30_mc_soc = {
.atom_size = 16,
.client_id_mask = 0x7f,
.smmu = &tegra30_smmu_soc,
+ .emem_regs = tegra30_mc_emem_regs,
+ .num_emem_regs = ARRAY_SIZE(tegra30_mc_emem_regs),
.intmask = MC_INT_INVALID_SMMU_PAGE | MC_INT_SECURITY_VIOLATION |
MC_INT_DECERR_EMEM,
.reset_ops = &tegra_mc_reset_ops_common,
diff --git a/drivers/nvmem/meson-efuse.c b/drivers/nvmem/meson-efuse.c
index 39bd76306033..d6b533497ce1 100644
--- a/drivers/nvmem/meson-efuse.c
+++ b/drivers/nvmem/meson-efuse.c
@@ -17,14 +17,18 @@
static int meson_efuse_read(void *context, unsigned int offset,
void *val, size_t bytes)
{
- return meson_sm_call_read((u8 *)val, bytes, SM_EFUSE_READ, offset,
+ struct meson_sm_firmware *fw = context;
+
+ return meson_sm_call_read(fw, (u8 *)val, bytes, SM_EFUSE_READ, offset,
bytes, 0, 0, 0);
}
static int meson_efuse_write(void *context, unsigned int offset,
void *val, size_t bytes)
{
- return meson_sm_call_write((u8 *)val, bytes, SM_EFUSE_WRITE, offset,
+ struct meson_sm_firmware *fw = context;
+
+ return meson_sm_call_write(fw, (u8 *)val, bytes, SM_EFUSE_WRITE, offset,
bytes, 0, 0, 0);
}
@@ -37,12 +41,25 @@ MODULE_DEVICE_TABLE(of, meson_efuse_match);
static int meson_efuse_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
+ struct meson_sm_firmware *fw;
+ struct device_node *sm_np;
struct nvmem_device *nvmem;
struct nvmem_config *econfig;
struct clk *clk;
unsigned int size;
int ret;
+ sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0);
+ if (!sm_np) {
+ dev_err(&pdev->dev, "no secure-monitor node\n");
+ return -ENODEV;
+ }
+
+ fw = meson_sm_get(sm_np);
+ of_node_put(sm_np);
+ if (!fw)
+ return -EPROBE_DEFER;
+
clk = devm_clk_get(dev, NULL);
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
@@ -65,7 +82,7 @@ static int meson_efuse_probe(struct platform_device *pdev)
return ret;
}
- if (meson_sm_call(SM_EFUSE_USER_MAX, &size, 0, 0, 0, 0, 0) < 0) {
+ if (meson_sm_call(fw, SM_EFUSE_USER_MAX, &size, 0, 0, 0, 0, 0) < 0) {
dev_err(dev, "failed to get max user");
return -EINVAL;
}
@@ -81,6 +98,7 @@ static int meson_efuse_probe(struct platform_device *pdev)
econfig->reg_read = meson_efuse_read;
econfig->reg_write = meson_efuse_write;
econfig->size = size;
+ econfig->priv = fw;
nvmem = devm_nvmem_register(&pdev->dev, econfig);
diff --git a/drivers/phy/marvell/Kconfig b/drivers/phy/marvell/Kconfig
index 4053ba6cd0fb..005e02dd4a91 100644
--- a/drivers/phy/marvell/Kconfig
+++ b/drivers/phy/marvell/Kconfig
@@ -103,3 +103,14 @@ config PHY_PXA_USB
The PHY driver will be used by Marvell udc/ehci/otg driver.
To compile this driver as a module, choose M here.
+
+config PHY_MMP3_USB
+ tristate "Marvell MMP3 USB PHY Driver"
+ depends on MACH_MMP3_DT || COMPILE_TEST
+ select GENERIC_PHY
+ help
+ Enable this to support Marvell MMP3 USB PHY driver for Marvell
+ SoC. This driver will do the PHY initialization and shutdown.
+ The PHY driver will be used by Marvell udc/ehci/otg driver.
+
+ To compile this driver as a module, choose M here.
diff --git a/drivers/phy/marvell/Makefile b/drivers/phy/marvell/Makefile
index 434eb9ca6cc3..5a106b1549f4 100644
--- a/drivers/phy/marvell/Makefile
+++ b/drivers/phy/marvell/Makefile
@@ -2,6 +2,7 @@
obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o
obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o
obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o
+obj-$(CONFIG_PHY_MMP3_USB) += phy-mmp3-usb.o
obj-$(CONFIG_PHY_MVEBU_A3700_COMPHY) += phy-mvebu-a3700-comphy.o
obj-$(CONFIG_PHY_MVEBU_A3700_UTMI) += phy-mvebu-a3700-utmi.o
obj-$(CONFIG_PHY_MVEBU_A38X_COMPHY) += phy-armada38x-comphy.o
diff --git a/drivers/phy/marvell/phy-mmp3-usb.c b/drivers/phy/marvell/phy-mmp3-usb.c
new file mode 100644
index 000000000000..499869595a58
--- /dev/null
+++ b/drivers/phy/marvell/phy-mmp3-usb.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2011 Marvell International Ltd. All rights reserved.
+ * Copyright (C) 2018,2019 Lubomir Rintel <lkundrak@v3.sk>
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/soc/mmp/cputype.h>
+
+#define USB2_PLL_REG0 0x4
+#define USB2_PLL_REG1 0x8
+#define USB2_TX_REG0 0x10
+#define USB2_TX_REG1 0x14
+#define USB2_TX_REG2 0x18
+#define USB2_RX_REG0 0x20
+#define USB2_RX_REG1 0x24
+#define USB2_RX_REG2 0x28
+#define USB2_ANA_REG0 0x30
+#define USB2_ANA_REG1 0x34
+#define USB2_ANA_REG2 0x38
+#define USB2_DIG_REG0 0x3C
+#define USB2_DIG_REG1 0x40
+#define USB2_DIG_REG2 0x44
+#define USB2_DIG_REG3 0x48
+#define USB2_TEST_REG0 0x4C
+#define USB2_TEST_REG1 0x50
+#define USB2_TEST_REG2 0x54
+#define USB2_CHARGER_REG0 0x58
+#define USB2_OTG_REG0 0x5C
+#define USB2_PHY_MON0 0x60
+#define USB2_RESETVE_REG0 0x64
+#define USB2_ICID_REG0 0x78
+#define USB2_ICID_REG1 0x7C
+
+/* USB2_PLL_REG0 */
+
+/* This is for Ax stepping */
+#define USB2_PLL_FBDIV_SHIFT_MMP3 0
+#define USB2_PLL_FBDIV_MASK_MMP3 (0xFF << 0)
+
+#define USB2_PLL_REFDIV_SHIFT_MMP3 8
+#define USB2_PLL_REFDIV_MASK_MMP3 (0xF << 8)
+
+#define USB2_PLL_VDD12_SHIFT_MMP3 12
+#define USB2_PLL_VDD18_SHIFT_MMP3 14
+
+/* This is for B0 stepping */
+#define USB2_PLL_FBDIV_SHIFT_MMP3_B0 0
+#define USB2_PLL_REFDIV_SHIFT_MMP3_B0 9
+#define USB2_PLL_VDD18_SHIFT_MMP3_B0 14
+#define USB2_PLL_FBDIV_MASK_MMP3_B0 0x01FF
+#define USB2_PLL_REFDIV_MASK_MMP3_B0 0x3E00
+
+#define USB2_PLL_CAL12_SHIFT_MMP3 0
+#define USB2_PLL_CALI12_MASK_MMP3 (0x3 << 0)
+
+#define USB2_PLL_VCOCAL_START_SHIFT_MMP3 2
+
+#define USB2_PLL_KVCO_SHIFT_MMP3 4
+#define USB2_PLL_KVCO_MASK_MMP3 (0x7<<4)
+
+#define USB2_PLL_ICP_SHIFT_MMP3 8
+#define USB2_PLL_ICP_MASK_MMP3 (0x7<<8)
+
+#define USB2_PLL_LOCK_BYPASS_SHIFT_MMP3 12
+
+#define USB2_PLL_PU_PLL_SHIFT_MMP3 13
+#define USB2_PLL_PU_PLL_MASK (0x1 << 13)
+
+#define USB2_PLL_READY_MASK_MMP3 (0x1 << 15)
+
+/* USB2_TX_REG0 */
+#define USB2_TX_IMPCAL_VTH_SHIFT_MMP3 8
+#define USB2_TX_IMPCAL_VTH_MASK_MMP3 (0x7 << 8)
+
+#define USB2_TX_RCAL_START_SHIFT_MMP3 13
+
+/* USB2_TX_REG1 */
+#define USB2_TX_CK60_PHSEL_SHIFT_MMP3 0
+#define USB2_TX_CK60_PHSEL_MASK_MMP3 (0xf << 0)
+
+#define USB2_TX_AMP_SHIFT_MMP3 4
+#define USB2_TX_AMP_MASK_MMP3 (0x7 << 4)
+
+#define USB2_TX_VDD12_SHIFT_MMP3 8
+#define USB2_TX_VDD12_MASK_MMP3 (0x3 << 8)
+
+/* USB2_TX_REG2 */
+#define USB2_TX_DRV_SLEWRATE_SHIFT 10
+
+/* USB2_RX_REG0 */
+#define USB2_RX_SQ_THRESH_SHIFT_MMP3 4
+#define USB2_RX_SQ_THRESH_MASK_MMP3 (0xf << 4)
+
+#define USB2_RX_SQ_LENGTH_SHIFT_MMP3 10
+#define USB2_RX_SQ_LENGTH_MASK_MMP3 (0x3 << 10)
+
+/* USB2_ANA_REG1*/
+#define USB2_ANA_PU_ANA_SHIFT_MMP3 14
+
+/* USB2_OTG_REG0 */
+#define USB2_OTG_PU_OTG_SHIFT_MMP3 3
+
+struct mmp3_usb_phy {
+ struct phy *phy;
+ void __iomem *base;
+};
+
+static unsigned int u2o_get(void __iomem *base, unsigned int offset)
+{
+ return readl_relaxed(base + offset);
+}
+
+static void u2o_set(void __iomem *base, unsigned int offset,
+ unsigned int value)
+{
+ u32 reg;
+
+ reg = readl_relaxed(base + offset);
+ reg |= value;
+ writel_relaxed(reg, base + offset);
+ readl_relaxed(base + offset);
+}
+
+static void u2o_clear(void __iomem *base, unsigned int offset,
+ unsigned int value)
+{
+ u32 reg;
+
+ reg = readl_relaxed(base + offset);
+ reg &= ~value;
+ writel_relaxed(reg, base + offset);
+ readl_relaxed(base + offset);
+}
+
+static int mmp3_usb_phy_init(struct phy *phy)
+{
+ struct mmp3_usb_phy *mmp3_usb_phy = phy_get_drvdata(phy);
+ void __iomem *base = mmp3_usb_phy->base;
+
+ if (cpu_is_mmp3_a0()) {
+ u2o_clear(base, USB2_PLL_REG0, (USB2_PLL_FBDIV_MASK_MMP3
+ | USB2_PLL_REFDIV_MASK_MMP3));
+ u2o_set(base, USB2_PLL_REG0,
+ 0xd << USB2_PLL_REFDIV_SHIFT_MMP3
+ | 0xf0 << USB2_PLL_FBDIV_SHIFT_MMP3);
+ } else if (cpu_is_mmp3_b0()) {
+ u2o_clear(base, USB2_PLL_REG0, USB2_PLL_REFDIV_MASK_MMP3_B0
+ | USB2_PLL_FBDIV_MASK_MMP3_B0);
+ u2o_set(base, USB2_PLL_REG0,
+ 0xd << USB2_PLL_REFDIV_SHIFT_MMP3_B0
+ | 0xf0 << USB2_PLL_FBDIV_SHIFT_MMP3_B0);
+ } else {
+ dev_err(&phy->dev, "unsupported silicon revision\n");
+ return -ENODEV;
+ }
+
+ u2o_clear(base, USB2_PLL_REG1, USB2_PLL_PU_PLL_MASK
+ | USB2_PLL_ICP_MASK_MMP3
+ | USB2_PLL_KVCO_MASK_MMP3
+ | USB2_PLL_CALI12_MASK_MMP3);
+ u2o_set(base, USB2_PLL_REG1, 1 << USB2_PLL_PU_PLL_SHIFT_MMP3
+ | 1 << USB2_PLL_LOCK_BYPASS_SHIFT_MMP3
+ | 3 << USB2_PLL_ICP_SHIFT_MMP3
+ | 3 << USB2_PLL_KVCO_SHIFT_MMP3
+ | 3 << USB2_PLL_CAL12_SHIFT_MMP3);
+
+ u2o_clear(base, USB2_TX_REG0, USB2_TX_IMPCAL_VTH_MASK_MMP3);
+ u2o_set(base, USB2_TX_REG0, 2 << USB2_TX_IMPCAL_VTH_SHIFT_MMP3);
+
+ u2o_clear(base, USB2_TX_REG1, USB2_TX_VDD12_MASK_MMP3
+ | USB2_TX_AMP_MASK_MMP3
+ | USB2_TX_CK60_PHSEL_MASK_MMP3);
+ u2o_set(base, USB2_TX_REG1, 3 << USB2_TX_VDD12_SHIFT_MMP3
+ | 4 << USB2_TX_AMP_SHIFT_MMP3
+ | 4 << USB2_TX_CK60_PHSEL_SHIFT_MMP3);
+
+ u2o_clear(base, USB2_TX_REG2, 3 << USB2_TX_DRV_SLEWRATE_SHIFT);
+ u2o_set(base, USB2_TX_REG2, 2 << USB2_TX_DRV_SLEWRATE_SHIFT);
+
+ u2o_clear(base, USB2_RX_REG0, USB2_RX_SQ_THRESH_MASK_MMP3);
+ u2o_set(base, USB2_RX_REG0, 0xa << USB2_RX_SQ_THRESH_SHIFT_MMP3);
+
+ u2o_set(base, USB2_ANA_REG1, 0x1 << USB2_ANA_PU_ANA_SHIFT_MMP3);
+
+ u2o_set(base, USB2_OTG_REG0, 0x1 << USB2_OTG_PU_OTG_SHIFT_MMP3);
+
+ return 0;
+}
+
+static int mmp3_usb_phy_calibrate(struct phy *phy)
+{
+ struct mmp3_usb_phy *mmp3_usb_phy = phy_get_drvdata(phy);
+ void __iomem *base = mmp3_usb_phy->base;
+ int loops;
+
+ /*
+ * PLL VCO and TX Impedance Calibration Timing:
+ *
+ * _____________________________________
+ * PU __________|
+ * _____________________________
+ * VCOCAL START _________|
+ * ___
+ * REG_RCAL_START ________________| |________|_______
+ * | 200us | 400us | 40| 400us | USB PHY READY
+ */
+
+ udelay(200);
+ u2o_set(base, USB2_PLL_REG1, 1 << USB2_PLL_VCOCAL_START_SHIFT_MMP3);
+ udelay(400);
+ u2o_set(base, USB2_TX_REG0, 1 << USB2_TX_RCAL_START_SHIFT_MMP3);
+ udelay(40);
+ u2o_clear(base, USB2_TX_REG0, 1 << USB2_TX_RCAL_START_SHIFT_MMP3);
+ udelay(400);
+
+ loops = 0;
+ while ((u2o_get(base, USB2_PLL_REG1) & USB2_PLL_READY_MASK_MMP3) == 0) {
+ mdelay(1);
+ loops++;
+ if (loops > 100) {
+ dev_err(&phy->dev, "PLL_READY not set after 100mS.\n");
+ return -ETIMEDOUT;
+ }
+ }
+
+ return 0;
+}
+
+static const struct phy_ops mmp3_usb_phy_ops = {
+ .init = mmp3_usb_phy_init,
+ .calibrate = mmp3_usb_phy_calibrate,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id mmp3_usb_phy_of_match[] = {
+ { .compatible = "marvell,mmp3-usb-phy", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, mmp3_usb_phy_of_match);
+
+static int mmp3_usb_phy_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *resource;
+ struct mmp3_usb_phy *mmp3_usb_phy;
+ struct phy_provider *provider;
+
+ mmp3_usb_phy = devm_kzalloc(dev, sizeof(*mmp3_usb_phy), GFP_KERNEL);
+ if (!mmp3_usb_phy)
+ return -ENOMEM;
+
+ resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mmp3_usb_phy->base = devm_ioremap_resource(dev, resource);
+ if (IS_ERR(mmp3_usb_phy->base)) {
+ dev_err(dev, "failed to remap PHY regs\n");
+ return PTR_ERR(mmp3_usb_phy->base);
+ }
+
+ mmp3_usb_phy->phy = devm_phy_create(dev, NULL, &mmp3_usb_phy_ops);
+ if (IS_ERR(mmp3_usb_phy->phy)) {
+ dev_err(dev, "failed to create PHY\n");
+ return PTR_ERR(mmp3_usb_phy->phy);
+ }
+
+ phy_set_drvdata(mmp3_usb_phy->phy, mmp3_usb_phy);
+ provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(provider)) {
+ dev_err(dev, "failed to register PHY provider\n");
+ return PTR_ERR(provider);
+ }
+
+ return 0;
+}
+
+static struct platform_driver mmp3_usb_phy_driver = {
+ .probe = mmp3_usb_phy_probe,
+ .driver = {
+ .name = "mmp3-usb-phy",
+ .of_match_table = mmp3_usb_phy_of_match,
+ },
+};
+module_platform_driver(mmp3_usb_phy_driver);
+
+MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");
+MODULE_DESCRIPTION("Marvell MMP3 USB PHY Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index 7b07281aa0ae..3ad7817ce1f0 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -129,7 +129,7 @@ config RESET_SCMI
config RESET_SIMPLE
bool "Simple Reset Controller Driver" if COMPILE_TEST
- default ARCH_STM32 || ARCH_STRATIX10 || ARCH_SUNXI || ARCH_ZX || ARCH_ASPEED || ARCH_BITMAIN || ARC
+ default ARCH_AGILEX || ARCH_ASPEED || ARCH_BITMAIN || ARCH_REALTEK || ARCH_STM32 || ARCH_STRATIX10 || ARCH_SUNXI || ARCH_ZX || ARC
help
This enables a simple reset controller driver for reset lines that
that can be asserted and deasserted by toggling bits in a contiguous,
@@ -138,10 +138,11 @@ config RESET_SIMPLE
Currently this driver supports:
- Altera SoCFPGAs
- ASPEED BMC SoCs
+ - Bitmain BM1880 SoC
+ - Realtek SoCs
- RCC reset controller in STM32 MCUs
- Allwinner SoCs
- ZTE's zx2967 family
- - Bitmain BM1880 SoC
config RESET_STM32MP157
bool "STM32MP157 Reset Driver" if COMPILE_TEST
diff --git a/drivers/reset/core.c b/drivers/reset/core.c
index 3c9a64c1b7a8..ca1d49146f61 100644
--- a/drivers/reset/core.c
+++ b/drivers/reset/core.c
@@ -77,8 +77,10 @@ static const char *rcdev_name(struct reset_controller_dev *rcdev)
* @rcdev: a pointer to the reset controller device
* @reset_spec: reset line specifier as found in the device tree
*
- * This simple translation function should be used for reset controllers
- * with 1:1 mapping, where reset lines can be indexed by number without gaps.
+ * This static translation function is used by default if of_xlate in
+ * :c:type:`reset_controller_dev` is not set. It is useful for all reset
+ * controllers with 1:1 mapping, where reset lines can be indexed by number
+ * without gaps.
*/
static int of_reset_simple_xlate(struct reset_controller_dev *rcdev,
const struct of_phandle_args *reset_spec)
@@ -333,7 +335,6 @@ EXPORT_SYMBOL_GPL(reset_control_reset);
* internal state to be reset, but must be prepared for this to happen.
* Consumers must not use reset_control_reset on shared reset lines when
* reset_control_(de)assert has been used.
- * return 0.
*
* If rstc is NULL it is an optional reset and the function will just
* return 0.
@@ -392,7 +393,6 @@ EXPORT_SYMBOL_GPL(reset_control_assert);
* After calling this function, the reset is guaranteed to be deasserted.
* Consumers must not use reset_control_reset on shared reset lines when
* reset_control_(de)assert has been used.
- * return 0.
*
* If rstc is NULL it is an optional reset and the function will just
* return 0.
diff --git a/drivers/reset/hisilicon/reset-hi3660.c b/drivers/reset/hisilicon/reset-hi3660.c
index f690b1878071..a7d4445924e5 100644
--- a/drivers/reset/hisilicon/reset-hi3660.c
+++ b/drivers/reset/hisilicon/reset-hi3660.c
@@ -56,7 +56,7 @@ static int hi3660_reset_dev(struct reset_controller_dev *rcdev,
return hi3660_reset_deassert(rcdev, idx);
}
-static struct reset_control_ops hi3660_reset_ops = {
+static const struct reset_control_ops hi3660_reset_ops = {
.reset = hi3660_reset_dev,
.assert = hi3660_reset_assert,
.deassert = hi3660_reset_deassert,
diff --git a/drivers/reset/reset-meson-audio-arb.c b/drivers/reset/reset-meson-audio-arb.c
index c53a2185a039..1dc06e08a8da 100644
--- a/drivers/reset/reset-meson-audio-arb.c
+++ b/drivers/reset/reset-meson-audio-arb.c
@@ -19,6 +19,11 @@ struct meson_audio_arb_data {
spinlock_t lock;
};
+struct meson_audio_arb_match_data {
+ const unsigned int *reset_bits;
+ unsigned int reset_num;
+};
+
#define ARB_GENERAL_BIT 31
static const unsigned int axg_audio_arb_reset_bits[] = {
@@ -30,6 +35,27 @@ static const unsigned int axg_audio_arb_reset_bits[] = {
[AXG_ARB_FRDDR_C] = 6,
};
+static const struct meson_audio_arb_match_data axg_audio_arb_match = {
+ .reset_bits = axg_audio_arb_reset_bits,
+ .reset_num = ARRAY_SIZE(axg_audio_arb_reset_bits),
+};
+
+static const unsigned int sm1_audio_arb_reset_bits[] = {
+ [AXG_ARB_TODDR_A] = 0,
+ [AXG_ARB_TODDR_B] = 1,
+ [AXG_ARB_TODDR_C] = 2,
+ [AXG_ARB_FRDDR_A] = 4,
+ [AXG_ARB_FRDDR_B] = 5,
+ [AXG_ARB_FRDDR_C] = 6,
+ [AXG_ARB_TODDR_D] = 3,
+ [AXG_ARB_FRDDR_D] = 7,
+};
+
+static const struct meson_audio_arb_match_data sm1_audio_arb_match = {
+ .reset_bits = sm1_audio_arb_reset_bits,
+ .reset_num = ARRAY_SIZE(sm1_audio_arb_reset_bits),
+};
+
static int meson_audio_arb_update(struct reset_controller_dev *rcdev,
unsigned long id, bool assert)
{
@@ -82,7 +108,13 @@ static const struct reset_control_ops meson_audio_arb_rstc_ops = {
};
static const struct of_device_id meson_audio_arb_of_match[] = {
- { .compatible = "amlogic,meson-axg-audio-arb", },
+ {
+ .compatible = "amlogic,meson-axg-audio-arb",
+ .data = &axg_audio_arb_match,
+ }, {
+ .compatible = "amlogic,meson-sm1-audio-arb",
+ .data = &sm1_audio_arb_match,
+ },
{}
};
MODULE_DEVICE_TABLE(of, meson_audio_arb_of_match);
@@ -104,10 +136,15 @@ static int meson_audio_arb_remove(struct platform_device *pdev)
static int meson_audio_arb_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
+ const struct meson_audio_arb_match_data *data;
struct meson_audio_arb_data *arb;
struct resource *res;
int ret;
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return -EINVAL;
+
arb = devm_kzalloc(dev, sizeof(*arb), GFP_KERNEL);
if (!arb)
return -ENOMEM;
@@ -126,8 +163,8 @@ static int meson_audio_arb_probe(struct platform_device *pdev)
return PTR_ERR(arb->regs);
spin_lock_init(&arb->lock);
- arb->reset_bits = axg_audio_arb_reset_bits;
- arb->rstc.nr_resets = ARRAY_SIZE(axg_audio_arb_reset_bits);
+ arb->reset_bits = data->reset_bits;
+ arb->rstc.nr_resets = data->reset_num;
arb->rstc.ops = &meson_audio_arb_rstc_ops;
arb->rstc.of_node = dev->of_node;
arb->rstc.owner = THIS_MODULE;
diff --git a/drivers/reset/reset-meson.c b/drivers/reset/reset-meson.c
index 7d05d766e1ea..94d7ba88d7d2 100644
--- a/drivers/reset/reset-meson.c
+++ b/drivers/reset/reset-meson.c
@@ -15,12 +15,16 @@
#include <linux/types.h>
#include <linux/of_device.h>
-#define REG_COUNT 8
#define BITS_PER_REG 32
-#define LEVEL_OFFSET 0x7c
+
+struct meson_reset_param {
+ int reg_count;
+ int level_offset;
+};
struct meson_reset {
void __iomem *reg_base;
+ const struct meson_reset_param *param;
struct reset_controller_dev rcdev;
spinlock_t lock;
};
@@ -46,10 +50,12 @@ static int meson_reset_level(struct reset_controller_dev *rcdev,
container_of(rcdev, struct meson_reset, rcdev);
unsigned int bank = id / BITS_PER_REG;
unsigned int offset = id % BITS_PER_REG;
- void __iomem *reg_addr = data->reg_base + LEVEL_OFFSET + (bank << 2);
+ void __iomem *reg_addr;
unsigned long flags;
u32 reg;
+ reg_addr = data->reg_base + data->param->level_offset + (bank << 2);
+
spin_lock_irqsave(&data->lock, flags);
reg = readl(reg_addr);
@@ -81,10 +87,21 @@ static const struct reset_control_ops meson_reset_ops = {
.deassert = meson_reset_deassert,
};
+static const struct meson_reset_param meson8b_param = {
+ .reg_count = 8,
+ .level_offset = 0x7c,
+};
+
+static const struct meson_reset_param meson_a1_param = {
+ .reg_count = 3,
+ .level_offset = 0x40,
+};
+
static const struct of_device_id meson_reset_dt_ids[] = {
- { .compatible = "amlogic,meson8b-reset" },
- { .compatible = "amlogic,meson-gxbb-reset" },
- { .compatible = "amlogic,meson-axg-reset" },
+ { .compatible = "amlogic,meson8b-reset", .data = &meson8b_param},
+ { .compatible = "amlogic,meson-gxbb-reset", .data = &meson8b_param},
+ { .compatible = "amlogic,meson-axg-reset", .data = &meson8b_param},
+ { .compatible = "amlogic,meson-a1-reset", .data = &meson_a1_param},
{ /* sentinel */ },
};
@@ -102,12 +119,16 @@ static int meson_reset_probe(struct platform_device *pdev)
if (IS_ERR(data->reg_base))
return PTR_ERR(data->reg_base);
+ data->param = of_device_get_match_data(&pdev->dev);
+ if (!data->param)
+ return -ENODEV;
+
platform_set_drvdata(pdev, data);
spin_lock_init(&data->lock);
data->rcdev.owner = THIS_MODULE;
- data->rcdev.nr_resets = REG_COUNT * BITS_PER_REG;
+ data->rcdev.nr_resets = data->param->reg_count * BITS_PER_REG;
data->rcdev.ops = &meson_reset_ops;
data->rcdev.of_node = pdev->dev.of_node;
diff --git a/drivers/reset/reset-uniphier-glue.c b/drivers/reset/reset-uniphier-glue.c
index a45923f4df6d..2b188b3bb69a 100644
--- a/drivers/reset/reset-uniphier-glue.c
+++ b/drivers/reset/reset-uniphier-glue.c
@@ -141,6 +141,10 @@ static const struct of_device_id uniphier_glue_reset_match[] = {
.data = &uniphier_pro4_data,
},
{
+ .compatible = "socionext,uniphier-pro5-usb3-reset",
+ .data = &uniphier_pro4_data,
+ },
+ {
.compatible = "socionext,uniphier-pxs2-usb3-reset",
.data = &uniphier_pxs2_data,
},
diff --git a/drivers/reset/reset-zynqmp.c b/drivers/reset/reset-zynqmp.c
index 99e75d92dada..0144075b11a6 100644
--- a/drivers/reset/reset-zynqmp.c
+++ b/drivers/reset/reset-zynqmp.c
@@ -64,7 +64,7 @@ static int zynqmp_reset_reset(struct reset_controller_dev *rcdev,
PM_RESET_ACTION_PULSE);
}
-static struct reset_control_ops zynqmp_reset_ops = {
+static const struct reset_control_ops zynqmp_reset_ops = {
.reset = zynqmp_reset_reset,
.assert = zynqmp_reset_assert,
.deassert = zynqmp_reset_deassert,
diff --git a/drivers/soc/amlogic/meson-gx-socinfo.c b/drivers/soc/amlogic/meson-gx-socinfo.c
index 6d0d04f163cb..01fc0d20a70d 100644
--- a/drivers/soc/amlogic/meson-gx-socinfo.c
+++ b/drivers/soc/amlogic/meson-gx-socinfo.c
@@ -40,6 +40,7 @@ static const struct meson_gx_soc_id {
{ "G12A", 0x28 },
{ "G12B", 0x29 },
{ "SM1", 0x2b },
+ { "A1", 0x2c },
};
static const struct meson_gx_package_id {
@@ -68,6 +69,8 @@ static const struct meson_gx_package_id {
{ "S922X", 0x29, 0x40, 0xf0 },
{ "A311D", 0x29, 0x10, 0xf0 },
{ "S905X3", 0x2b, 0x5, 0xf },
+ { "S905D3", 0x2b, 0xb0, 0xf0 },
+ { "A113L", 0x2c, 0x0, 0xf8 },
};
static inline unsigned int socinfo_to_major(u32 socinfo)
diff --git a/drivers/soc/atmel/Kconfig b/drivers/soc/atmel/Kconfig
index 05528139b023..50caf6db9c0e 100644
--- a/drivers/soc/atmel/Kconfig
+++ b/drivers/soc/atmel/Kconfig
@@ -5,3 +5,14 @@ config AT91_SOC_ID
default ARCH_AT91
help
Include support for the SoC bus on the Atmel ARM SoCs.
+
+config AT91_SOC_SFR
+ tristate "Special Function Registers support"
+ depends on ARCH_AT91 || COMPILE_TEST
+ help
+ This is a driver for the Special Function Registers available on
+ Atmel SAMA5Dx SoCs, providing access to specific aspects of the
+ integrated memory, bridge implementations, processor etc.
+
+ This driver can also be built as a module. If so, the module
+ will be called sfr.
diff --git a/drivers/soc/atmel/Makefile b/drivers/soc/atmel/Makefile
index 7ca355d10553..d849a897cd77 100644
--- a/drivers/soc/atmel/Makefile
+++ b/drivers/soc/atmel/Makefile
@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_AT91_SOC_ID) += soc.o
+obj-$(CONFIG_AT91_SOC_SFR) += sfr.o
diff --git a/drivers/soc/atmel/sfr.c b/drivers/soc/atmel/sfr.c
new file mode 100644
index 000000000000..0525eef49d1a
--- /dev/null
+++ b/drivers/soc/atmel/sfr.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sfr.c - driver for special function registers
+ *
+ * Copyright (C) 2019 Bootlin.
+ *
+ */
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/random.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define SFR_SN0 0x4c
+#define SFR_SN_SIZE 8
+
+struct atmel_sfr_priv {
+ struct regmap *regmap;
+};
+
+static int atmel_sfr_read(void *context, unsigned int offset,
+ void *buf, size_t bytes)
+{
+ struct atmel_sfr_priv *priv = context;
+
+ return regmap_bulk_read(priv->regmap, SFR_SN0 + offset,
+ buf, bytes / 4);
+}
+
+static struct nvmem_config atmel_sfr_nvmem_config = {
+ .name = "atmel-sfr",
+ .read_only = true,
+ .word_size = 4,
+ .stride = 4,
+ .size = SFR_SN_SIZE,
+ .reg_read = atmel_sfr_read,
+};
+
+static int atmel_sfr_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct nvmem_device *nvmem;
+ struct atmel_sfr_priv *priv;
+ u8 sn[SFR_SN_SIZE];
+ int ret;
+
+ priv = devm_kmalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = syscon_node_to_regmap(np);
+ if (IS_ERR(priv->regmap)) {
+ dev_err(dev, "cannot get parent's regmap\n");
+ return PTR_ERR(priv->regmap);
+ }
+
+ atmel_sfr_nvmem_config.dev = dev;
+ atmel_sfr_nvmem_config.priv = priv;
+
+ nvmem = devm_nvmem_register(dev, &atmel_sfr_nvmem_config);
+ if (IS_ERR(nvmem)) {
+ dev_err(dev, "error registering nvmem config\n");
+ return PTR_ERR(nvmem);
+ }
+
+ ret = atmel_sfr_read(priv, 0, sn, SFR_SN_SIZE);
+ if (ret == 0)
+ add_device_randomness(sn, SFR_SN_SIZE);
+
+ return ret;
+}
+
+static const struct of_device_id atmel_sfr_dt_ids[] = {
+ {
+ .compatible = "atmel,sama5d2-sfr",
+ }, {
+ .compatible = "atmel,sama5d4-sfr",
+ }, {
+ /* sentinel */
+ },
+};
+MODULE_DEVICE_TABLE(of, atmel_sfr_dt_ids);
+
+static struct platform_driver atmel_sfr_driver = {
+ .probe = atmel_sfr_probe,
+ .driver = {
+ .name = "atmel-sfr",
+ .of_match_table = atmel_sfr_dt_ids,
+ },
+};
+module_platform_driver(atmel_sfr_driver);
+
+MODULE_AUTHOR("Kamel Bouhara <kamel.bouhara@bootlin.com>");
+MODULE_DESCRIPTION("Atmel SFR SN driver for SAMA5D2/4 SoC family");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soc/fsl/Kconfig b/drivers/soc/fsl/Kconfig
index f9ad8ad54a7d..4df32bc4c7a6 100644
--- a/drivers/soc/fsl/Kconfig
+++ b/drivers/soc/fsl/Kconfig
@@ -40,4 +40,14 @@ config DPAA2_CONSOLE
/dev/dpaa2_mc_console and /dev/dpaa2_aiop_console,
which can be used to dump the Management Complex and AIOP
firmware logs.
+
+config FSL_RCPM
+ bool "Freescale RCPM support"
+ depends on PM_SLEEP && (ARM || ARM64)
+ help
+ The NXP QorIQ Processors based on ARM Core have RCPM module
+ (Run Control and Power Management), which performs all device-level
+ tasks associated with power management, such as wakeup source control.
+ Note that currently this driver will not support PowerPC based
+ QorIQ processor.
endmenu
diff --git a/drivers/soc/fsl/Makefile b/drivers/soc/fsl/Makefile
index 71dee8d0d1f0..906f1cd8af01 100644
--- a/drivers/soc/fsl/Makefile
+++ b/drivers/soc/fsl/Makefile
@@ -6,6 +6,7 @@
obj-$(CONFIG_FSL_DPAA) += qbman/
obj-$(CONFIG_QUICC_ENGINE) += qe/
obj-$(CONFIG_CPM) += qe/
+obj-$(CONFIG_FSL_RCPM) += rcpm.o
obj-$(CONFIG_FSL_GUTS) += guts.o
obj-$(CONFIG_FSL_MC_DPIO) += dpio/
obj-$(CONFIG_DPAA2_CONSOLE) += dpaa2-console.o
diff --git a/drivers/soc/fsl/rcpm.c b/drivers/soc/fsl/rcpm.c
new file mode 100644
index 000000000000..a093dbe6d2cb
--- /dev/null
+++ b/drivers/soc/fsl/rcpm.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// rcpm.c - Freescale QorIQ RCPM driver
+//
+// Copyright 2019 NXP
+//
+// Author: Ran Wang <ran.wang_1@nxp.com>
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/kernel.h>
+
+#define RCPM_WAKEUP_CELL_MAX_SIZE 7
+
+struct rcpm {
+ unsigned int wakeup_cells;
+ void __iomem *ippdexpcr_base;
+ bool little_endian;
+};
+
+/**
+ * rcpm_pm_prepare - performs device-level tasks associated with power
+ * management, such as programming related to the wakeup source control.
+ * @dev: Device to handle.
+ *
+ */
+static int rcpm_pm_prepare(struct device *dev)
+{
+ int i, ret, idx;
+ void __iomem *base;
+ struct wakeup_source *ws;
+ struct rcpm *rcpm;
+ struct device_node *np = dev->of_node;
+ u32 value[RCPM_WAKEUP_CELL_MAX_SIZE + 1];
+ u32 setting[RCPM_WAKEUP_CELL_MAX_SIZE] = {0};
+
+ rcpm = dev_get_drvdata(dev);
+ if (!rcpm)
+ return -EINVAL;
+
+ base = rcpm->ippdexpcr_base;
+ idx = wakeup_sources_read_lock();
+
+ /* Begin with first registered wakeup source */
+ for_each_wakeup_source(ws) {
+
+ /* skip object which is not attached to device */
+ if (!ws->dev || !ws->dev->parent)
+ continue;
+
+ ret = device_property_read_u32_array(ws->dev->parent,
+ "fsl,rcpm-wakeup", value,
+ rcpm->wakeup_cells + 1);
+
+ /* Wakeup source should refer to current rcpm device */
+ if (ret || (np->phandle != value[0]))
+ continue;
+
+ /* Property "#fsl,rcpm-wakeup-cells" of rcpm node defines the
+ * number of IPPDEXPCR register cells, and "fsl,rcpm-wakeup"
+ * of wakeup source IP contains an integer array: <phandle to
+ * RCPM node, IPPDEXPCR0 setting, IPPDEXPCR1 setting,
+ * IPPDEXPCR2 setting, etc>.
+ *
+ * So we will go thought them to collect setting data.
+ */
+ for (i = 0; i < rcpm->wakeup_cells; i++)
+ setting[i] |= value[i + 1];
+ }
+
+ wakeup_sources_read_unlock(idx);
+
+ /* Program all IPPDEXPCRn once */
+ for (i = 0; i < rcpm->wakeup_cells; i++) {
+ u32 tmp = setting[i];
+ void __iomem *address = base + i * 4;
+
+ if (!tmp)
+ continue;
+
+ /* We can only OR related bits */
+ if (rcpm->little_endian) {
+ tmp |= ioread32(address);
+ iowrite32(tmp, address);
+ } else {
+ tmp |= ioread32be(address);
+ iowrite32be(tmp, address);
+ }
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops rcpm_pm_ops = {
+ .prepare = rcpm_pm_prepare,
+};
+
+static int rcpm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *r;
+ struct rcpm *rcpm;
+ int ret;
+
+ rcpm = devm_kzalloc(dev, sizeof(*rcpm), GFP_KERNEL);
+ if (!rcpm)
+ return -ENOMEM;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r)
+ return -ENODEV;
+
+ rcpm->ippdexpcr_base = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(rcpm->ippdexpcr_base)) {
+ ret = PTR_ERR(rcpm->ippdexpcr_base);
+ return ret;
+ }
+
+ rcpm->little_endian = device_property_read_bool(
+ &pdev->dev, "little-endian");
+
+ ret = device_property_read_u32(&pdev->dev,
+ "#fsl,rcpm-wakeup-cells", &rcpm->wakeup_cells);
+ if (ret)
+ return ret;
+
+ dev_set_drvdata(&pdev->dev, rcpm);
+
+ return 0;
+}
+
+static const struct of_device_id rcpm_of_match[] = {
+ { .compatible = "fsl,qoriq-rcpm-2.1+", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rcpm_of_match);
+
+static struct platform_driver rcpm_driver = {
+ .driver = {
+ .name = "rcpm",
+ .of_match_table = rcpm_of_match,
+ .pm = &rcpm_pm_ops,
+ },
+ .probe = rcpm_probe,
+};
+
+module_platform_driver(rcpm_driver);
diff --git a/drivers/soc/imx/soc-imx-scu.c b/drivers/soc/imx/soc-imx-scu.c
index c68882eb80f7..fb70b8a3f7c5 100644
--- a/drivers/soc/imx/soc-imx-scu.c
+++ b/drivers/soc/imx/soc-imx-scu.c
@@ -33,12 +33,10 @@ struct imx_sc_msg_misc_get_soc_uid {
u32 uid_high;
} __packed;
-static ssize_t soc_uid_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static int imx_scu_soc_uid(u64 *soc_uid)
{
struct imx_sc_msg_misc_get_soc_uid msg;
struct imx_sc_rpc_msg *hdr = &msg.hdr;
- u64 soc_uid;
int ret;
hdr->ver = IMX_SC_RPC_VERSION;
@@ -52,15 +50,13 @@ static ssize_t soc_uid_show(struct device *dev,
return ret;
}
- soc_uid = msg.uid_high;
- soc_uid <<= 32;
- soc_uid |= msg.uid_low;
+ *soc_uid = msg.uid_high;
+ *soc_uid <<= 32;
+ *soc_uid |= msg.uid_low;
- return sprintf(buf, "%016llX\n", soc_uid);
+ return 0;
}
-static DEVICE_ATTR_RO(soc_uid);
-
static int imx_scu_soc_id(void)
{
struct imx_sc_msg_misc_get_soc_id msg;
@@ -89,6 +85,7 @@ static int imx_scu_soc_probe(struct platform_device *pdev)
struct soc_device_attribute *soc_dev_attr;
struct soc_device *soc_dev;
int id, ret;
+ u64 uid = 0;
u32 val;
ret = imx_scu_get_handle(&soc_ipc_handle);
@@ -112,6 +109,10 @@ static int imx_scu_soc_probe(struct platform_device *pdev)
if (id < 0)
return -EINVAL;
+ ret = imx_scu_soc_uid(&uid);
+ if (ret < 0)
+ return -EINVAL;
+
/* format soc_id value passed from SCU firmware */
val = id & 0x1f;
soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "0x%x", val);
@@ -130,19 +131,22 @@ static int imx_scu_soc_probe(struct platform_device *pdev)
goto free_soc_id;
}
+ soc_dev_attr->serial_number = kasprintf(GFP_KERNEL, "%016llX", uid);
+ if (!soc_dev_attr->serial_number) {
+ ret = -ENOMEM;
+ goto free_revision;
+ }
+
soc_dev = soc_device_register(soc_dev_attr);
if (IS_ERR(soc_dev)) {
ret = PTR_ERR(soc_dev);
- goto free_revision;
+ goto free_serial_number;
}
- ret = device_create_file(soc_device_to_device(soc_dev),
- &dev_attr_soc_uid);
- if (ret)
- goto free_revision;
-
return 0;
+free_serial_number:
+ kfree(soc_dev_attr->serial_number);
free_revision:
kfree(soc_dev_attr->revision);
free_soc_id:
diff --git a/drivers/soc/imx/soc-imx8.c b/drivers/soc/imx/soc-imx8.c
index b9831576dd25..d84ed736cdb0 100644
--- a/drivers/soc/imx/soc-imx8.c
+++ b/drivers/soc/imx/soc-imx8.c
@@ -9,6 +9,7 @@
#include <linux/slab.h>
#include <linux/sys_soc.h>
#include <linux/platform_device.h>
+#include <linux/arm-smccc.h>
#include <linux/of.h>
#define REV_B1 0x21
@@ -16,6 +17,8 @@
#define IMX8MQ_SW_INFO_B1 0x40
#define IMX8MQ_SW_MAGIC_B1 0xff0055aa
+#define IMX_SIP_GET_SOC_INFO 0xc2000006
+
#define OCOTP_UID_LOW 0x410
#define OCOTP_UID_HIGH 0x420
@@ -29,13 +32,21 @@ struct imx8_soc_data {
static u64 soc_uid;
-static ssize_t soc_uid_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+#ifdef CONFIG_HAVE_ARM_SMCCC
+static u32 imx8mq_soc_revision_from_atf(void)
{
- return sprintf(buf, "%016llX\n", soc_uid);
-}
+ struct arm_smccc_res res;
-static DEVICE_ATTR_RO(soc_uid);
+ arm_smccc_smc(IMX_SIP_GET_SOC_INFO, 0, 0, 0, 0, 0, 0, 0, &res);
+
+ if (res.a0 == SMCCC_RET_NOT_SUPPORTED)
+ return 0;
+ else
+ return res.a0 & 0xff;
+}
+#else
+static inline u32 imx8mq_soc_revision_from_atf(void) { return 0; };
+#endif
static u32 __init imx8mq_soc_revision(void)
{
@@ -51,9 +62,16 @@ static u32 __init imx8mq_soc_revision(void)
ocotp_base = of_iomap(np, 0);
WARN_ON(!ocotp_base);
- magic = readl_relaxed(ocotp_base + IMX8MQ_SW_INFO_B1);
- if (magic == IMX8MQ_SW_MAGIC_B1)
- rev = REV_B1;
+ /*
+ * SOC revision on older imx8mq is not available in fuses so query
+ * the value from ATF instead.
+ */
+ rev = imx8mq_soc_revision_from_atf();
+ if (!rev) {
+ magic = readl_relaxed(ocotp_base + IMX8MQ_SW_INFO_B1);
+ if (magic == IMX8MQ_SW_MAGIC_B1)
+ rev = REV_B1;
+ }
soc_uid = readl_relaxed(ocotp_base + OCOTP_UID_HIGH);
soc_uid <<= 32;
@@ -174,22 +192,25 @@ static int __init imx8_soc_init(void)
goto free_soc;
}
+ soc_dev_attr->serial_number = kasprintf(GFP_KERNEL, "%016llX", soc_uid);
+ if (!soc_dev_attr->serial_number) {
+ ret = -ENOMEM;
+ goto free_rev;
+ }
+
soc_dev = soc_device_register(soc_dev_attr);
if (IS_ERR(soc_dev)) {
ret = PTR_ERR(soc_dev);
- goto free_rev;
+ goto free_serial_number;
}
- ret = device_create_file(soc_device_to_device(soc_dev),
- &dev_attr_soc_uid);
- if (ret)
- goto free_rev;
-
if (IS_ENABLED(CONFIG_ARM_IMX_CPUFREQ_DT))
platform_device_register_simple("imx-cpufreq-dt", -1, NULL, 0);
return 0;
+free_serial_number:
+ kfree(soc_dev_attr->serial_number);
free_rev:
if (strcmp(soc_dev_attr->revision, "unknown"))
kfree(soc_dev_attr->revision);
diff --git a/drivers/soc/mediatek/mtk-scpsys.c b/drivers/soc/mediatek/mtk-scpsys.c
index 503222d0d0da..f669d3754627 100644
--- a/drivers/soc/mediatek/mtk-scpsys.c
+++ b/drivers/soc/mediatek/mtk-scpsys.c
@@ -21,7 +21,7 @@
#include <dt-bindings/power/mt8173-power.h>
#define MTK_POLL_DELAY_US 10
-#define MTK_POLL_TIMEOUT (jiffies_to_usecs(HZ))
+#define MTK_POLL_TIMEOUT USEC_PER_SEC
#define MTK_SCPD_ACTIVE_WAKEUP BIT(0)
#define MTK_SCPD_FWAIT_SRAM BIT(1)
@@ -108,6 +108,17 @@ static const char * const clk_names[] = {
#define MAX_CLKS 3
+/**
+ * struct scp_domain_data - scp domain data for power on/off flow
+ * @name: The domain name.
+ * @sta_mask: The mask for power on/off status bit.
+ * @ctl_offs: The offset for main power control register.
+ * @sram_pdn_bits: The mask for sram power control bits.
+ * @sram_pdn_ack_bits: The mask for sram power control acked bits.
+ * @bus_prot_mask: The mask for single step bus protection.
+ * @clk_id: The basic clocks required by this power domain.
+ * @caps: The flag for active wake-up action.
+ */
struct scp_domain_data {
const char *name;
u32 sta_mask;
@@ -180,32 +191,132 @@ static int scpsys_domain_is_on(struct scp_domain *scpd)
return -EINVAL;
}
+static int scpsys_regulator_enable(struct scp_domain *scpd)
+{
+ if (!scpd->supply)
+ return 0;
+
+ return regulator_enable(scpd->supply);
+}
+
+static int scpsys_regulator_disable(struct scp_domain *scpd)
+{
+ if (!scpd->supply)
+ return 0;
+
+ return regulator_disable(scpd->supply);
+}
+
+static void scpsys_clk_disable(struct clk *clk[], int max_num)
+{
+ int i;
+
+ for (i = max_num - 1; i >= 0; i--)
+ clk_disable_unprepare(clk[i]);
+}
+
+static int scpsys_clk_enable(struct clk *clk[], int max_num)
+{
+ int i, ret = 0;
+
+ for (i = 0; i < max_num && clk[i]; i++) {
+ ret = clk_prepare_enable(clk[i]);
+ if (ret) {
+ scpsys_clk_disable(clk, i);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int scpsys_sram_enable(struct scp_domain *scpd, void __iomem *ctl_addr)
+{
+ u32 val;
+ u32 pdn_ack = scpd->data->sram_pdn_ack_bits;
+ int tmp;
+
+ val = readl(ctl_addr);
+ val &= ~scpd->data->sram_pdn_bits;
+ writel(val, ctl_addr);
+
+ /* Either wait until SRAM_PDN_ACK all 0 or have a force wait */
+ if (MTK_SCPD_CAPS(scpd, MTK_SCPD_FWAIT_SRAM)) {
+ /*
+ * Currently, MTK_SCPD_FWAIT_SRAM is necessary only for
+ * MT7622_POWER_DOMAIN_WB and thus just a trivial setup
+ * is applied here.
+ */
+ usleep_range(12000, 12100);
+ } else {
+ /* Either wait until SRAM_PDN_ACK all 1 or 0 */
+ int ret = readl_poll_timeout(ctl_addr, tmp,
+ (tmp & pdn_ack) == 0,
+ MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int scpsys_sram_disable(struct scp_domain *scpd, void __iomem *ctl_addr)
+{
+ u32 val;
+ u32 pdn_ack = scpd->data->sram_pdn_ack_bits;
+ int tmp;
+
+ val = readl(ctl_addr);
+ val |= scpd->data->sram_pdn_bits;
+ writel(val, ctl_addr);
+
+ /* Either wait until SRAM_PDN_ACK all 1 or 0 */
+ return readl_poll_timeout(ctl_addr, tmp,
+ (tmp & pdn_ack) == pdn_ack,
+ MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT);
+}
+
+static int scpsys_bus_protect_enable(struct scp_domain *scpd)
+{
+ struct scp *scp = scpd->scp;
+
+ if (!scpd->data->bus_prot_mask)
+ return 0;
+
+ return mtk_infracfg_set_bus_protection(scp->infracfg,
+ scpd->data->bus_prot_mask,
+ scp->bus_prot_reg_update);
+}
+
+static int scpsys_bus_protect_disable(struct scp_domain *scpd)
+{
+ struct scp *scp = scpd->scp;
+
+ if (!scpd->data->bus_prot_mask)
+ return 0;
+
+ return mtk_infracfg_clear_bus_protection(scp->infracfg,
+ scpd->data->bus_prot_mask,
+ scp->bus_prot_reg_update);
+}
+
static int scpsys_power_on(struct generic_pm_domain *genpd)
{
struct scp_domain *scpd = container_of(genpd, struct scp_domain, genpd);
struct scp *scp = scpd->scp;
void __iomem *ctl_addr = scp->base + scpd->data->ctl_offs;
- u32 pdn_ack = scpd->data->sram_pdn_ack_bits;
u32 val;
int ret, tmp;
- int i;
- if (scpd->supply) {
- ret = regulator_enable(scpd->supply);
- if (ret)
- return ret;
- }
-
- for (i = 0; i < MAX_CLKS && scpd->clk[i]; i++) {
- ret = clk_prepare_enable(scpd->clk[i]);
- if (ret) {
- for (--i; i >= 0; i--)
- clk_disable_unprepare(scpd->clk[i]);
+ ret = scpsys_regulator_enable(scpd);
+ if (ret < 0)
+ return ret;
- goto err_clk;
- }
- }
+ ret = scpsys_clk_enable(scpd->clk, MAX_CLKS);
+ if (ret)
+ goto err_clk;
+ /* subsys power on */
val = readl(ctl_addr);
val |= PWR_ON_BIT;
writel(val, ctl_addr);
@@ -227,43 +338,20 @@ static int scpsys_power_on(struct generic_pm_domain *genpd)
val |= PWR_RST_B_BIT;
writel(val, ctl_addr);
- val &= ~scpd->data->sram_pdn_bits;
- writel(val, ctl_addr);
-
- /* Either wait until SRAM_PDN_ACK all 0 or have a force wait */
- if (MTK_SCPD_CAPS(scpd, MTK_SCPD_FWAIT_SRAM)) {
- /*
- * Currently, MTK_SCPD_FWAIT_SRAM is necessary only for
- * MT7622_POWER_DOMAIN_WB and thus just a trivial setup is
- * applied here.
- */
- usleep_range(12000, 12100);
-
- } else {
- ret = readl_poll_timeout(ctl_addr, tmp, (tmp & pdn_ack) == 0,
- MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT);
- if (ret < 0)
- goto err_pwr_ack;
- }
+ ret = scpsys_sram_enable(scpd, ctl_addr);
+ if (ret < 0)
+ goto err_pwr_ack;
- if (scpd->data->bus_prot_mask) {
- ret = mtk_infracfg_clear_bus_protection(scp->infracfg,
- scpd->data->bus_prot_mask,
- scp->bus_prot_reg_update);
- if (ret)
- goto err_pwr_ack;
- }
+ ret = scpsys_bus_protect_disable(scpd);
+ if (ret < 0)
+ goto err_pwr_ack;
return 0;
err_pwr_ack:
- for (i = MAX_CLKS - 1; i >= 0; i--) {
- if (scpd->clk[i])
- clk_disable_unprepare(scpd->clk[i]);
- }
+ scpsys_clk_disable(scpd->clk, MAX_CLKS);
err_clk:
- if (scpd->supply)
- regulator_disable(scpd->supply);
+ scpsys_regulator_disable(scpd);
dev_err(scp->dev, "Failed to power on domain %s\n", genpd->name);
@@ -275,29 +363,19 @@ static int scpsys_power_off(struct generic_pm_domain *genpd)
struct scp_domain *scpd = container_of(genpd, struct scp_domain, genpd);
struct scp *scp = scpd->scp;
void __iomem *ctl_addr = scp->base + scpd->data->ctl_offs;
- u32 pdn_ack = scpd->data->sram_pdn_ack_bits;
u32 val;
int ret, tmp;
- int i;
-
- if (scpd->data->bus_prot_mask) {
- ret = mtk_infracfg_set_bus_protection(scp->infracfg,
- scpd->data->bus_prot_mask,
- scp->bus_prot_reg_update);
- if (ret)
- goto out;
- }
- val = readl(ctl_addr);
- val |= scpd->data->sram_pdn_bits;
- writel(val, ctl_addr);
+ ret = scpsys_bus_protect_enable(scpd);
+ if (ret < 0)
+ goto out;
- /* wait until SRAM_PDN_ACK all 1 */
- ret = readl_poll_timeout(ctl_addr, tmp, (tmp & pdn_ack) == pdn_ack,
- MTK_POLL_DELAY_US, MTK_POLL_TIMEOUT);
+ ret = scpsys_sram_disable(scpd, ctl_addr);
if (ret < 0)
goto out;
+ /* subsys power off */
+ val = readl(ctl_addr);
val |= PWR_ISO_BIT;
writel(val, ctl_addr);
@@ -319,11 +397,11 @@ static int scpsys_power_off(struct generic_pm_domain *genpd)
if (ret < 0)
goto out;
- for (i = 0; i < MAX_CLKS && scpd->clk[i]; i++)
- clk_disable_unprepare(scpd->clk[i]);
+ scpsys_clk_disable(scpd->clk, MAX_CLKS);
- if (scpd->supply)
- regulator_disable(scpd->supply);
+ ret = scpsys_regulator_disable(scpd);
+ if (ret < 0)
+ goto out;
return 0;
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 661e47acc354..c6df8b43fa6d 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -58,17 +58,9 @@ config QCOM_LLCC
depends on ARCH_QCOM || COMPILE_TEST
help
Qualcomm Technologies, Inc. platform specific
- Last Level Cache Controller(LLCC) driver. This provides interfaces
- to clients that use the LLCC. Say yes here to enable LLCC slice
- driver.
-
-config QCOM_SDM845_LLCC
- tristate "Qualcomm Technologies, Inc. SDM845 LLCC driver"
- depends on QCOM_LLCC
- help
- Say yes here to enable the LLCC driver for SDM845. This provides
- data required to configure LLCC so that clients can start using the
- LLCC slices.
+ Last Level Cache Controller(LLCC) driver for platforms such as,
+ SDM845. This provides interfaces to clients that use the LLCC.
+ Say yes here to enable LLCC slice driver.
config QCOM_MDT_LOADER
tristate
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 162788701a77..2559fe948ce0 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -21,7 +21,6 @@ obj-$(CONFIG_QCOM_SMSM) += smsm.o
obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o
obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
obj-$(CONFIG_QCOM_APR) += apr.o
-obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o
-obj-$(CONFIG_QCOM_SDM845_LLCC) += llcc-sdm845.o
+obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o
obj-$(CONFIG_QCOM_RPMHPD) += rpmhpd.o
obj-$(CONFIG_QCOM_RPMPD) += rpmpd.o
diff --git a/drivers/soc/qcom/llcc-slice.c b/drivers/soc/qcom/llcc-qcom.c
index 9090ea12eaf3..429b5a60a1ba 100644
--- a/drivers/soc/qcom/llcc-slice.c
+++ b/drivers/soc/qcom/llcc-qcom.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017-2019, The Linux Foundation. All rights reserved.
*
*/
@@ -11,6 +11,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/sizes.h>
@@ -46,15 +47,90 @@
#define BANK_OFFSET_STRIDE 0x80000
-static struct llcc_drv_data *drv_data = (void *) -EPROBE_DEFER;
+/**
+ * llcc_slice_config - Data associated with the llcc slice
+ * @usecase_id: Unique id for the client's use case
+ * @slice_id: llcc slice id for each client
+ * @max_cap: The maximum capacity of the cache slice provided in KB
+ * @priority: Priority of the client used to select victim line for replacement
+ * @fixed_size: Boolean indicating if the slice has a fixed capacity
+ * @bonus_ways: Bonus ways are additional ways to be used for any slice,
+ * if client ends up using more than reserved cache ways. Bonus
+ * ways are allocated only if they are not reserved for some
+ * other client.
+ * @res_ways: Reserved ways for the cache slice, the reserved ways cannot
+ * be used by any other client than the one its assigned to.
+ * @cache_mode: Each slice operates as a cache, this controls the mode of the
+ * slice: normal or TCM(Tightly Coupled Memory)
+ * @probe_target_ways: Determines what ways to probe for access hit. When
+ * configured to 1 only bonus and reserved ways are probed.
+ * When configured to 0 all ways in llcc are probed.
+ * @dis_cap_alloc: Disable capacity based allocation for a client
+ * @retain_on_pc: If this bit is set and client has maintained active vote
+ * then the ways assigned to this client are not flushed on power
+ * collapse.
+ * @activate_on_init: Activate the slice immediately after it is programmed
+ */
+struct llcc_slice_config {
+ u32 usecase_id;
+ u32 slice_id;
+ u32 max_cap;
+ u32 priority;
+ bool fixed_size;
+ u32 bonus_ways;
+ u32 res_ways;
+ u32 cache_mode;
+ u32 probe_target_ways;
+ bool dis_cap_alloc;
+ bool retain_on_pc;
+ bool activate_on_init;
+};
+
+struct qcom_llcc_config {
+ const struct llcc_slice_config *sct_data;
+ int size;
+};
+
+static const struct llcc_slice_config sc7180_data[] = {
+ { LLCC_CPUSS, 1, 256, 1, 0, 0xf, 0x0, 0, 0, 0, 1, 1 },
+ { LLCC_MDM, 8, 128, 1, 0, 0xf, 0x0, 0, 0, 0, 1, 0 },
+ { LLCC_GPUHTW, 11, 128, 1, 0, 0xf, 0x0, 0, 0, 0, 1, 0 },
+ { LLCC_GPU, 12, 128, 1, 0, 0xf, 0x0, 0, 0, 0, 1, 0 },
+};
+
+static const struct llcc_slice_config sdm845_data[] = {
+ { LLCC_CPUSS, 1, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 1 },
+ { LLCC_VIDSC0, 2, 512, 2, 1, 0x0, 0x0f0, 0, 0, 1, 1, 0 },
+ { LLCC_VIDSC1, 3, 512, 2, 1, 0x0, 0x0f0, 0, 0, 1, 1, 0 },
+ { LLCC_ROTATOR, 4, 563, 2, 1, 0x0, 0x00e, 2, 0, 1, 1, 0 },
+ { LLCC_VOICE, 5, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0 },
+ { LLCC_AUDIO, 6, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0 },
+ { LLCC_MDMHPGRW, 7, 1024, 2, 0, 0xfc, 0xf00, 0, 0, 1, 1, 0 },
+ { LLCC_MDM, 8, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0 },
+ { LLCC_CMPT, 10, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0 },
+ { LLCC_GPUHTW, 11, 512, 1, 1, 0xc, 0x0, 0, 0, 1, 1, 0 },
+ { LLCC_GPU, 12, 2304, 1, 0, 0xff0, 0x2, 0, 0, 1, 1, 0 },
+ { LLCC_MMUHWT, 13, 256, 2, 0, 0x0, 0x1, 0, 0, 1, 0, 1 },
+ { LLCC_CMPTDMA, 15, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0 },
+ { LLCC_DISP, 16, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0 },
+ { LLCC_VIDFW, 17, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0 },
+ { LLCC_MDMHPFX, 20, 1024, 2, 1, 0x0, 0xf00, 0, 0, 1, 1, 0 },
+ { LLCC_MDMPNG, 21, 1024, 0, 1, 0x1e, 0x0, 0, 0, 1, 1, 0 },
+ { LLCC_AUDHW, 22, 1024, 1, 1, 0xffc, 0x2, 0, 0, 1, 1, 0 },
+};
+
+static const struct qcom_llcc_config sc7180_cfg = {
+ .sct_data = sc7180_data,
+ .size = ARRAY_SIZE(sc7180_data),
+};
-static const struct regmap_config llcc_regmap_config = {
- .reg_bits = 32,
- .reg_stride = 4,
- .val_bits = 32,
- .fast_io = true,
+static const struct qcom_llcc_config sdm845_cfg = {
+ .sct_data = sdm845_data,
+ .size = ARRAY_SIZE(sdm845_data),
};
+static struct llcc_drv_data *drv_data = (void *) -EPROBE_DEFER;
+
/**
* llcc_slice_getd - get llcc slice descriptor
* @uid: usecase_id for the client
@@ -301,19 +377,24 @@ static int qcom_llcc_cfg_program(struct platform_device *pdev)
return ret;
}
-int qcom_llcc_remove(struct platform_device *pdev)
+static int qcom_llcc_remove(struct platform_device *pdev)
{
/* Set the global pointer to a error code to avoid referencing it */
drv_data = ERR_PTR(-ENODEV);
return 0;
}
-EXPORT_SYMBOL_GPL(qcom_llcc_remove);
static struct regmap *qcom_llcc_init_mmio(struct platform_device *pdev,
const char *name)
{
struct resource *res;
void __iomem *base;
+ struct regmap_config llcc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .fast_io = true,
+ };
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
if (!res)
@@ -323,16 +404,19 @@ static struct regmap *qcom_llcc_init_mmio(struct platform_device *pdev,
if (IS_ERR(base))
return ERR_CAST(base);
+ llcc_regmap_config.name = name;
return devm_regmap_init_mmio(&pdev->dev, base, &llcc_regmap_config);
}
-int qcom_llcc_probe(struct platform_device *pdev,
- const struct llcc_slice_config *llcc_cfg, u32 sz)
+static int qcom_llcc_probe(struct platform_device *pdev)
{
u32 num_banks;
struct device *dev = &pdev->dev;
int ret, i;
struct platform_device *llcc_edac;
+ const struct qcom_llcc_config *cfg;
+ const struct llcc_slice_config *llcc_cfg;
+ u32 sz;
drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL);
if (!drv_data) {
@@ -362,6 +446,10 @@ int qcom_llcc_probe(struct platform_device *pdev,
num_banks >>= LLCC_LB_CNT_SHIFT;
drv_data->num_banks = num_banks;
+ cfg = of_device_get_match_data(&pdev->dev);
+ llcc_cfg = cfg->sct_data;
+ sz = cfg->size;
+
for (i = 0; i < sz; i++)
if (llcc_cfg[i].slice_id > drv_data->max_slices)
drv_data->max_slices = llcc_cfg[i].slice_id;
@@ -407,6 +495,22 @@ err:
drv_data = ERR_PTR(-ENODEV);
return ret;
}
-EXPORT_SYMBOL_GPL(qcom_llcc_probe);
-MODULE_LICENSE("GPL v2");
+
+static const struct of_device_id qcom_llcc_of_match[] = {
+ { .compatible = "qcom,sc7180-llcc", .data = &sc7180_cfg },
+ { .compatible = "qcom,sdm845-llcc", .data = &sdm845_cfg },
+ { }
+};
+
+static struct platform_driver qcom_llcc_driver = {
+ .driver = {
+ .name = "qcom-llcc",
+ .of_match_table = qcom_llcc_of_match,
+ },
+ .probe = qcom_llcc_probe,
+ .remove = qcom_llcc_remove,
+};
+module_platform_driver(qcom_llcc_driver);
+
MODULE_DESCRIPTION("Qualcomm Last Level Cache Controller");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soc/qcom/llcc-sdm845.c b/drivers/soc/qcom/llcc-sdm845.c
deleted file mode 100644
index 86600d97c36d..000000000000
--- a/drivers/soc/qcom/llcc-sdm845.c
+++ /dev/null
@@ -1,100 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
- *
- */
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/of_device.h>
-#include <linux/soc/qcom/llcc-qcom.h>
-
-/*
- * SCT(System Cache Table) entry contains of the following members:
- * usecase_id: Unique id for the client's use case
- * slice_id: llcc slice id for each client
- * max_cap: The maximum capacity of the cache slice provided in KB
- * priority: Priority of the client used to select victim line for replacement
- * fixed_size: Boolean indicating if the slice has a fixed capacity
- * bonus_ways: Bonus ways are additional ways to be used for any slice,
- * if client ends up using more than reserved cache ways. Bonus
- * ways are allocated only if they are not reserved for some
- * other client.
- * res_ways: Reserved ways for the cache slice, the reserved ways cannot
- * be used by any other client than the one its assigned to.
- * cache_mode: Each slice operates as a cache, this controls the mode of the
- * slice: normal or TCM(Tightly Coupled Memory)
- * probe_target_ways: Determines what ways to probe for access hit. When
- * configured to 1 only bonus and reserved ways are probed.
- * When configured to 0 all ways in llcc are probed.
- * dis_cap_alloc: Disable capacity based allocation for a client
- * retain_on_pc: If this bit is set and client has maintained active vote
- * then the ways assigned to this client are not flushed on power
- * collapse.
- * activate_on_init: Activate the slice immediately after the SCT is programmed
- */
-#define SCT_ENTRY(uid, sid, mc, p, fs, bway, rway, cmod, ptw, dca, rp, a) \
- { \
- .usecase_id = uid, \
- .slice_id = sid, \
- .max_cap = mc, \
- .priority = p, \
- .fixed_size = fs, \
- .bonus_ways = bway, \
- .res_ways = rway, \
- .cache_mode = cmod, \
- .probe_target_ways = ptw, \
- .dis_cap_alloc = dca, \
- .retain_on_pc = rp, \
- .activate_on_init = a, \
- }
-
-static struct llcc_slice_config sdm845_data[] = {
- SCT_ENTRY(LLCC_CPUSS, 1, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 1),
- SCT_ENTRY(LLCC_VIDSC0, 2, 512, 2, 1, 0x0, 0x0f0, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_VIDSC1, 3, 512, 2, 1, 0x0, 0x0f0, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_ROTATOR, 4, 563, 2, 1, 0x0, 0x00e, 2, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_VOICE, 5, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_AUDIO, 6, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_MDMHPGRW, 7, 1024, 2, 0, 0xfc, 0xf00, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_MDM, 8, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_CMPT, 10, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_GPUHTW, 11, 512, 1, 1, 0xc, 0x0, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_GPU, 12, 2304, 1, 0, 0xff0, 0x2, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_MMUHWT, 13, 256, 2, 0, 0x0, 0x1, 0, 0, 1, 0, 1),
- SCT_ENTRY(LLCC_CMPTDMA, 15, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_DISP, 16, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_VIDFW, 17, 2816, 1, 0, 0xffc, 0x2, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_MDMHPFX, 20, 1024, 2, 1, 0x0, 0xf00, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_MDMPNG, 21, 1024, 0, 1, 0x1e, 0x0, 0, 0, 1, 1, 0),
- SCT_ENTRY(LLCC_AUDHW, 22, 1024, 1, 1, 0xffc, 0x2, 0, 0, 1, 1, 0),
-};
-
-static int sdm845_qcom_llcc_remove(struct platform_device *pdev)
-{
- return qcom_llcc_remove(pdev);
-}
-
-static int sdm845_qcom_llcc_probe(struct platform_device *pdev)
-{
- return qcom_llcc_probe(pdev, sdm845_data, ARRAY_SIZE(sdm845_data));
-}
-
-static const struct of_device_id sdm845_qcom_llcc_of_match[] = {
- { .compatible = "qcom,sdm845-llcc", },
- { }
-};
-
-static struct platform_driver sdm845_qcom_llcc_driver = {
- .driver = {
- .name = "sdm845-llcc",
- .of_match_table = sdm845_qcom_llcc_of_match,
- },
- .probe = sdm845_qcom_llcc_probe,
- .remove = sdm845_qcom_llcc_remove,
-};
-module_platform_driver(sdm845_qcom_llcc_driver);
-
-MODULE_DESCRIPTION("QCOM sdm845 LLCC driver");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/soc/qcom/rpmpd.c b/drivers/soc/qcom/rpmpd.c
index 3c1a55cf25d6..2b1834c5609a 100644
--- a/drivers/soc/qcom/rpmpd.c
+++ b/drivers/soc/qcom/rpmpd.c
@@ -115,6 +115,28 @@ struct rpmpd_desc {
static DEFINE_MUTEX(rpmpd_lock);
+/* msm8976 RPM Power Domains */
+DEFINE_RPMPD_PAIR(msm8976, vddcx, vddcx_ao, SMPA, LEVEL, 2);
+DEFINE_RPMPD_PAIR(msm8976, vddmx, vddmx_ao, SMPA, LEVEL, 6);
+
+DEFINE_RPMPD_VFL(msm8976, vddcx_vfl, RWSC, 2);
+DEFINE_RPMPD_VFL(msm8976, vddmx_vfl, RWSM, 6);
+
+static struct rpmpd *msm8976_rpmpds[] = {
+ [MSM8976_VDDCX] = &msm8976_vddcx,
+ [MSM8976_VDDCX_AO] = &msm8976_vddcx_ao,
+ [MSM8976_VDDCX_VFL] = &msm8976_vddcx_vfl,
+ [MSM8976_VDDMX] = &msm8976_vddmx,
+ [MSM8976_VDDMX_AO] = &msm8976_vddmx_ao,
+ [MSM8976_VDDMX_VFL] = &msm8976_vddmx_vfl,
+};
+
+static const struct rpmpd_desc msm8976_desc = {
+ .rpmpds = msm8976_rpmpds,
+ .num_pds = ARRAY_SIZE(msm8976_rpmpds),
+ .max_state = RPM_SMD_LEVEL_TURBO_HIGH,
+};
+
/* msm8996 RPM Power domains */
DEFINE_RPMPD_PAIR(msm8996, vddcx, vddcx_ao, SMPA, CORNER, 1);
DEFINE_RPMPD_PAIR(msm8996, vddmx, vddmx_ao, SMPA, CORNER, 2);
@@ -198,6 +220,7 @@ static const struct rpmpd_desc qcs404_desc = {
};
static const struct of_device_id rpmpd_match_table[] = {
+ { .compatible = "qcom,msm8976-rpmpd", .data = &msm8976_desc },
{ .compatible = "qcom,msm8996-rpmpd", .data = &msm8996_desc },
{ .compatible = "qcom,msm8998-rpmpd", .data = &msm8998_desc },
{ .compatible = "qcom,qcs404-rpmpd", .data = &qcs404_desc },
diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c
index fa9dd12b5e39..005dd30c58fa 100644
--- a/drivers/soc/qcom/smd-rpm.c
+++ b/drivers/soc/qcom/smd-rpm.c
@@ -19,12 +19,14 @@
/**
* struct qcom_smd_rpm - state of the rpm device driver
* @rpm_channel: reference to the smd channel
+ * @icc: interconnect proxy device
* @ack: completion for acks
* @lock: mutual exclusion around the send/complete pair
* @ack_status: result of the rpm request
*/
struct qcom_smd_rpm {
struct rpmsg_endpoint *rpm_channel;
+ struct platform_device *icc;
struct device *dev;
struct completion ack;
@@ -193,6 +195,7 @@ static int qcom_smd_rpm_callback(struct rpmsg_device *rpdev,
static int qcom_smd_rpm_probe(struct rpmsg_device *rpdev)
{
struct qcom_smd_rpm *rpm;
+ int ret;
rpm = devm_kzalloc(&rpdev->dev, sizeof(*rpm), GFP_KERNEL);
if (!rpm)
@@ -205,11 +208,23 @@ static int qcom_smd_rpm_probe(struct rpmsg_device *rpdev)
rpm->rpm_channel = rpdev->ept;
dev_set_drvdata(&rpdev->dev, rpm);
- return of_platform_populate(rpdev->dev.of_node, NULL, NULL, &rpdev->dev);
+ rpm->icc = platform_device_register_data(&rpdev->dev, "icc_smd_rpm", -1,
+ NULL, 0);
+ if (IS_ERR(rpm->icc))
+ return PTR_ERR(rpm->icc);
+
+ ret = of_platform_populate(rpdev->dev.of_node, NULL, NULL, &rpdev->dev);
+ if (ret)
+ platform_device_unregister(rpm->icc);
+
+ return ret;
}
static void qcom_smd_rpm_remove(struct rpmsg_device *rpdev)
{
+ struct qcom_smd_rpm *rpm = dev_get_drvdata(&rpdev->dev);
+
+ platform_device_unregister(rpm->icc);
of_platform_depopulate(&rpdev->dev);
}
@@ -217,6 +232,7 @@ static const struct of_device_id qcom_smd_rpm_of_match[] = {
{ .compatible = "qcom,rpm-apq8084" },
{ .compatible = "qcom,rpm-msm8916" },
{ .compatible = "qcom,rpm-msm8974" },
+ { .compatible = "qcom,rpm-msm8976" },
{ .compatible = "qcom,rpm-msm8996" },
{ .compatible = "qcom,rpm-msm8998" },
{ .compatible = "qcom,rpm-sdm660" },
diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c
index a39ea5061dc5..7864b75ce569 100644
--- a/drivers/soc/qcom/socinfo.c
+++ b/drivers/soc/qcom/socinfo.c
@@ -198,6 +198,8 @@ static const struct soc_id soc_id[] = {
{ 310, "MSM8996AU" },
{ 311, "APQ8096AU" },
{ 312, "APQ8096SG" },
+ { 321, "SDM845" },
+ { 341, "SDA845" },
};
static const char *socinfo_machine(struct device *dev, unsigned int id)
diff --git a/drivers/soc/renesas/Kconfig b/drivers/soc/renesas/Kconfig
index 3c5e017bacba..f93492b72c04 100644
--- a/drivers/soc/renesas/Kconfig
+++ b/drivers/soc/renesas/Kconfig
@@ -178,6 +178,13 @@ config ARCH_R8A774A1
help
This enables support for the Renesas RZ/G2M SoC.
+config ARCH_R8A774B1
+ bool "Renesas RZ/G2N SoC Platform"
+ select ARCH_RCAR_GEN3
+ select SYSC_R8A774B1
+ help
+ This enables support for the Renesas RZ/G2N SoC.
+
config ARCH_R8A774C0
bool "Renesas RZ/G2E SoC Platform"
select ARCH_RCAR_GEN3
@@ -192,13 +199,24 @@ config ARCH_R8A7795
help
This enables support for the Renesas R-Car H3 SoC.
+config ARCH_R8A77960
+ bool
+ select ARCH_RCAR_GEN3
+ select SYSC_R8A77960
+
config ARCH_R8A7796
bool "Renesas R-Car M3-W SoC Platform"
- select ARCH_RCAR_GEN3
- select SYSC_R8A7796
+ select ARCH_R8A77960
help
This enables support for the Renesas R-Car M3-W SoC.
+config ARCH_R8A77961
+ bool "Renesas R-Car M3-W+ SoC Platform"
+ select ARCH_RCAR_GEN3
+ select SYSC_R8A77961
+ help
+ This enables support for the Renesas R-Car M3-W+ SoC.
+
config ARCH_R8A77965
bool "Renesas R-Car M3-N SoC Platform"
select ARCH_RCAR_GEN3
@@ -253,6 +271,10 @@ config SYSC_R8A774A1
bool "RZ/G2M System Controller support" if COMPILE_TEST
select SYSC_RCAR
+config SYSC_R8A774B1
+ bool "RZ/G2N System Controller support" if COMPILE_TEST
+ select SYSC_RCAR
+
config SYSC_R8A774C0
bool "RZ/G2E System Controller support" if COMPILE_TEST
select SYSC_RCAR
@@ -281,10 +303,14 @@ config SYSC_R8A7795
bool "R-Car H3 System Controller support" if COMPILE_TEST
select SYSC_RCAR
-config SYSC_R8A7796
+config SYSC_R8A77960
bool "R-Car M3-W System Controller support" if COMPILE_TEST
select SYSC_RCAR
+config SYSC_R8A77961
+ bool "R-Car M3-W+ System Controller support" if COMPILE_TEST
+ select SYSC_RCAR
+
config SYSC_R8A77965
bool "R-Car M3-N System Controller support" if COMPILE_TEST
select SYSC_RCAR
diff --git a/drivers/soc/renesas/Makefile b/drivers/soc/renesas/Makefile
index 00764d5a60b3..e595c3c3bd10 100644
--- a/drivers/soc/renesas/Makefile
+++ b/drivers/soc/renesas/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_SYSC_R8A7743) += r8a7743-sysc.o
obj-$(CONFIG_SYSC_R8A7745) += r8a7745-sysc.o
obj-$(CONFIG_SYSC_R8A77470) += r8a77470-sysc.o
obj-$(CONFIG_SYSC_R8A774A1) += r8a774a1-sysc.o
+obj-$(CONFIG_SYSC_R8A774B1) += r8a774b1-sysc.o
obj-$(CONFIG_SYSC_R8A774C0) += r8a774c0-sysc.o
obj-$(CONFIG_SYSC_R8A7779) += r8a7779-sysc.o
obj-$(CONFIG_SYSC_R8A7790) += r8a7790-sysc.o
@@ -14,7 +15,8 @@ obj-$(CONFIG_SYSC_R8A7791) += r8a7791-sysc.o
obj-$(CONFIG_SYSC_R8A7792) += r8a7792-sysc.o
obj-$(CONFIG_SYSC_R8A7794) += r8a7794-sysc.o
obj-$(CONFIG_SYSC_R8A7795) += r8a7795-sysc.o
-obj-$(CONFIG_SYSC_R8A7796) += r8a7796-sysc.o
+obj-$(CONFIG_SYSC_R8A77960) += r8a7796-sysc.o
+obj-$(CONFIG_SYSC_R8A77961) += r8a7796-sysc.o
obj-$(CONFIG_SYSC_R8A77965) += r8a77965-sysc.o
obj-$(CONFIG_SYSC_R8A77970) += r8a77970-sysc.o
obj-$(CONFIG_SYSC_R8A77980) += r8a77980-sysc.o
diff --git a/drivers/soc/renesas/r8a7743-sysc.c b/drivers/soc/renesas/r8a7743-sysc.c
index edf6436e879f..4e2c0ab951b3 100644
--- a/drivers/soc/renesas/r8a7743-sysc.c
+++ b/drivers/soc/renesas/r8a7743-sysc.c
@@ -5,7 +5,6 @@
* Copyright (C) 2016 Cogent Embedded Inc.
*/
-#include <linux/bug.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a7743-sysc.h>
diff --git a/drivers/soc/renesas/r8a7745-sysc.c b/drivers/soc/renesas/r8a7745-sysc.c
index 65dc6b09cc85..865821a2f0c6 100644
--- a/drivers/soc/renesas/r8a7745-sysc.c
+++ b/drivers/soc/renesas/r8a7745-sysc.c
@@ -5,7 +5,6 @@
* Copyright (C) 2016 Cogent Embedded Inc.
*/
-#include <linux/bug.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a7745-sysc.h>
diff --git a/drivers/soc/renesas/r8a77470-sysc.c b/drivers/soc/renesas/r8a77470-sysc.c
index cfa015e208ef..1eeb8018df50 100644
--- a/drivers/soc/renesas/r8a77470-sysc.c
+++ b/drivers/soc/renesas/r8a77470-sysc.c
@@ -5,7 +5,6 @@
* Copyright (C) 2018 Renesas Electronics Corp.
*/
-#include <linux/bug.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a77470-sysc.h>
diff --git a/drivers/soc/renesas/r8a774a1-sysc.c b/drivers/soc/renesas/r8a774a1-sysc.c
index 9db51ff6f5ed..38ac2c689ff0 100644
--- a/drivers/soc/renesas/r8a774a1-sysc.c
+++ b/drivers/soc/renesas/r8a774a1-sysc.c
@@ -7,7 +7,6 @@
* Copyright (C) 2016 Glider bvba
*/
-#include <linux/bug.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a774a1-sysc.h>
diff --git a/drivers/soc/renesas/r8a774b1-sysc.c b/drivers/soc/renesas/r8a774b1-sysc.c
new file mode 100644
index 000000000000..5f97ff26f3f8
--- /dev/null
+++ b/drivers/soc/renesas/r8a774b1-sysc.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G2N System Controller
+ * Copyright (C) 2019 Renesas Electronics Corp.
+ *
+ * Based on Renesas R-Car M3-W System Controller
+ * Copyright (C) 2016 Glider bvba
+ */
+
+#include <linux/bits.h>
+#include <linux/kernel.h>
+
+#include <dt-bindings/power/r8a774b1-sysc.h>
+
+#include "rcar-sysc.h"
+
+static const struct rcar_sysc_area r8a774b1_areas[] __initconst = {
+ { "always-on", 0, 0, R8A774B1_PD_ALWAYS_ON, -1, PD_ALWAYS_ON },
+ { "ca57-scu", 0x1c0, 0, R8A774B1_PD_CA57_SCU, R8A774B1_PD_ALWAYS_ON,
+ PD_SCU },
+ { "ca57-cpu0", 0x80, 0, R8A774B1_PD_CA57_CPU0, R8A774B1_PD_CA57_SCU,
+ PD_CPU_NOCR },
+ { "ca57-cpu1", 0x80, 1, R8A774B1_PD_CA57_CPU1, R8A774B1_PD_CA57_SCU,
+ PD_CPU_NOCR },
+ { "a3vc", 0x380, 0, R8A774B1_PD_A3VC, R8A774B1_PD_ALWAYS_ON },
+ { "a3vp", 0x340, 0, R8A774B1_PD_A3VP, R8A774B1_PD_ALWAYS_ON },
+ { "a2vc1", 0x3c0, 1, R8A774B1_PD_A2VC1, R8A774B1_PD_A3VC },
+ { "3dg-a", 0x100, 0, R8A774B1_PD_3DG_A, R8A774B1_PD_ALWAYS_ON },
+ { "3dg-b", 0x100, 1, R8A774B1_PD_3DG_B, R8A774B1_PD_3DG_A },
+};
+
+const struct rcar_sysc_info r8a774b1_sysc_info __initconst = {
+ .areas = r8a774b1_areas,
+ .num_areas = ARRAY_SIZE(r8a774b1_areas),
+ .extmask_offs = 0x2f8,
+ .extmask_val = BIT(0),
+};
diff --git a/drivers/soc/renesas/r8a774c0-sysc.c b/drivers/soc/renesas/r8a774c0-sysc.c
index 11050e17ea81..c1c216f7d073 100644
--- a/drivers/soc/renesas/r8a774c0-sysc.c
+++ b/drivers/soc/renesas/r8a774c0-sysc.c
@@ -6,7 +6,7 @@
* Based on Renesas R-Car E3 System Controller
*/
-#include <linux/bug.h>
+#include <linux/bits.h>
#include <linux/kernel.h>
#include <linux/sys_soc.h>
@@ -50,4 +50,6 @@ const struct rcar_sysc_info r8a774c0_sysc_info __initconst = {
.init = r8a774c0_sysc_init,
.areas = r8a774c0_areas,
.num_areas = ARRAY_SIZE(r8a774c0_areas),
+ .extmask_offs = 0x2f8,
+ .extmask_val = BIT(0),
};
diff --git a/drivers/soc/renesas/r8a7779-sysc.c b/drivers/soc/renesas/r8a7779-sysc.c
index 517aa40fa6e6..e24a7151d55f 100644
--- a/drivers/soc/renesas/r8a7779-sysc.c
+++ b/drivers/soc/renesas/r8a7779-sysc.c
@@ -5,7 +5,6 @@
* Copyright (C) 2016 Glider bvba
*/
-#include <linux/bug.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a7779-sysc.h>
diff --git a/drivers/soc/renesas/r8a7790-sysc.c b/drivers/soc/renesas/r8a7790-sysc.c
index 9b5a6bb62152..b9afe7f6245b 100644
--- a/drivers/soc/renesas/r8a7790-sysc.c
+++ b/drivers/soc/renesas/r8a7790-sysc.c
@@ -5,7 +5,6 @@
* Copyright (C) 2016 Glider bvba
*/
-#include <linux/bug.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a7790-sysc.h>
diff --git a/drivers/soc/renesas/r8a7791-sysc.c b/drivers/soc/renesas/r8a7791-sysc.c
index acf545cdebfb..f00fa24522a3 100644
--- a/drivers/soc/renesas/r8a7791-sysc.c
+++ b/drivers/soc/renesas/r8a7791-sysc.c
@@ -5,7 +5,6 @@
* Copyright (C) 2016 Glider bvba
*/
-#include <linux/bug.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a7791-sysc.h>
diff --git a/drivers/soc/renesas/r8a7792-sysc.c b/drivers/soc/renesas/r8a7792-sysc.c
index 05b78525cc43..60aae242c43f 100644
--- a/drivers/soc/renesas/r8a7792-sysc.c
+++ b/drivers/soc/renesas/r8a7792-sysc.c
@@ -5,7 +5,6 @@
* Copyright (C) 2016 Cogent Embedded Inc.
*/
-#include <linux/bug.h>
#include <linux/init.h>
#include <linux/kernel.h>
diff --git a/drivers/soc/renesas/r8a7794-sysc.c b/drivers/soc/renesas/r8a7794-sysc.c
index 0d42637fa662..72ef4e85458f 100644
--- a/drivers/soc/renesas/r8a7794-sysc.c
+++ b/drivers/soc/renesas/r8a7794-sysc.c
@@ -5,7 +5,6 @@
* Copyright (C) 2016 Glider bvba
*/
-#include <linux/bug.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a7794-sysc.h>
diff --git a/drivers/soc/renesas/r8a7795-sysc.c b/drivers/soc/renesas/r8a7795-sysc.c
index cda27a67de98..91074411b8cf 100644
--- a/drivers/soc/renesas/r8a7795-sysc.c
+++ b/drivers/soc/renesas/r8a7795-sysc.c
@@ -5,7 +5,7 @@
* Copyright (C) 2016-2017 Glider bvba
*/
-#include <linux/bug.h>
+#include <linux/bits.h>
#include <linux/kernel.h>
#include <linux/sys_soc.h>
@@ -51,25 +51,46 @@ static struct rcar_sysc_area r8a7795_areas[] __initdata = {
/*
- * Fixups for R-Car H3 revisions after ES1.x
+ * Fixups for R-Car H3 revisions
*/
-static const struct soc_device_attribute r8a7795es1[] __initconst = {
- { .soc_id = "r8a7795", .revision = "ES1.*" },
+#define HAS_A2VC0 BIT(0) /* Power domain A2VC0 is present */
+#define NO_EXTMASK BIT(1) /* Missing SYSCEXTMASK register */
+
+static const struct soc_device_attribute r8a7795_quirks_match[] __initconst = {
+ {
+ .soc_id = "r8a7795", .revision = "ES1.*",
+ .data = (void *)(HAS_A2VC0 | NO_EXTMASK),
+ }, {
+ .soc_id = "r8a7795", .revision = "ES2.*",
+ .data = (void *)(NO_EXTMASK),
+ },
{ /* sentinel */ }
};
static int __init r8a7795_sysc_init(void)
{
- if (!soc_device_match(r8a7795es1))
+ const struct soc_device_attribute *attr;
+ u32 quirks = 0;
+
+ attr = soc_device_match(r8a7795_quirks_match);
+ if (attr)
+ quirks = (uintptr_t)attr->data;
+
+ if (!(quirks & HAS_A2VC0))
rcar_sysc_nullify(r8a7795_areas, ARRAY_SIZE(r8a7795_areas),
R8A7795_PD_A2VC0);
+ if (quirks & NO_EXTMASK)
+ r8a7795_sysc_info.extmask_val = 0;
+
return 0;
}
-const struct rcar_sysc_info r8a7795_sysc_info __initconst = {
+struct rcar_sysc_info r8a7795_sysc_info __initdata = {
.init = r8a7795_sysc_init,
.areas = r8a7795_areas,
.num_areas = ARRAY_SIZE(r8a7795_areas),
+ .extmask_offs = 0x2f8,
+ .extmask_val = BIT(0),
};
diff --git a/drivers/soc/renesas/r8a7796-sysc.c b/drivers/soc/renesas/r8a7796-sysc.c
index 1b06f868b6e8..471bd5b3b6ad 100644
--- a/drivers/soc/renesas/r8a7796-sysc.c
+++ b/drivers/soc/renesas/r8a7796-sysc.c
@@ -1,18 +1,19 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Renesas R-Car M3-W System Controller
+ * Renesas R-Car M3-W/W+ System Controller
*
* Copyright (C) 2016 Glider bvba
+ * Copyright (C) 2018-2019 Renesas Electronics Corporation
*/
-#include <linux/bug.h>
+#include <linux/bits.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a7796-sysc.h>
#include "rcar-sysc.h"
-static const struct rcar_sysc_area r8a7796_areas[] __initconst = {
+static struct rcar_sysc_area r8a7796_areas[] __initdata = {
{ "always-on", 0, 0, R8A7796_PD_ALWAYS_ON, -1, PD_ALWAYS_ON },
{ "ca57-scu", 0x1c0, 0, R8A7796_PD_CA57_SCU, R8A7796_PD_ALWAYS_ON,
PD_SCU },
@@ -39,7 +40,28 @@ static const struct rcar_sysc_area r8a7796_areas[] __initconst = {
{ "a3ir", 0x180, 0, R8A7796_PD_A3IR, R8A7796_PD_ALWAYS_ON },
};
-const struct rcar_sysc_info r8a7796_sysc_info __initconst = {
+
+#ifdef CONFIG_SYSC_R8A77960
+const struct rcar_sysc_info r8a77960_sysc_info __initconst = {
+ .areas = r8a7796_areas,
+ .num_areas = ARRAY_SIZE(r8a7796_areas),
+};
+#endif /* CONFIG_SYSC_R8A77960 */
+
+#ifdef CONFIG_SYSC_R8A77961
+static int __init r8a77961_sysc_init(void)
+{
+ rcar_sysc_nullify(r8a7796_areas, ARRAY_SIZE(r8a7796_areas),
+ R8A7796_PD_A2VC0);
+
+ return 0;
+}
+
+const struct rcar_sysc_info r8a77961_sysc_info __initconst = {
+ .init = r8a77961_sysc_init,
.areas = r8a7796_areas,
.num_areas = ARRAY_SIZE(r8a7796_areas),
+ .extmask_offs = 0x2f8,
+ .extmask_val = BIT(0),
};
+#endif /* CONFIG_SYSC_R8A77961 */
diff --git a/drivers/soc/renesas/r8a77965-sysc.c b/drivers/soc/renesas/r8a77965-sysc.c
index e0533beb50fd..ff0b0d116992 100644
--- a/drivers/soc/renesas/r8a77965-sysc.c
+++ b/drivers/soc/renesas/r8a77965-sysc.c
@@ -7,7 +7,7 @@
* Copyright (C) 2016 Glider bvba
*/
-#include <linux/bug.h>
+#include <linux/bits.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a77965-sysc.h>
@@ -33,4 +33,6 @@ static const struct rcar_sysc_area r8a77965_areas[] __initconst = {
const struct rcar_sysc_info r8a77965_sysc_info __initconst = {
.areas = r8a77965_areas,
.num_areas = ARRAY_SIZE(r8a77965_areas),
+ .extmask_offs = 0x2f8,
+ .extmask_val = BIT(0),
};
diff --git a/drivers/soc/renesas/r8a77970-sysc.c b/drivers/soc/renesas/r8a77970-sysc.c
index 280c48b80f24..706258250600 100644
--- a/drivers/soc/renesas/r8a77970-sysc.c
+++ b/drivers/soc/renesas/r8a77970-sysc.c
@@ -5,7 +5,7 @@
* Copyright (C) 2017 Cogent Embedded Inc.
*/
-#include <linux/bug.h>
+#include <linux/bits.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a77970-sysc.h>
@@ -32,4 +32,6 @@ static const struct rcar_sysc_area r8a77970_areas[] __initconst = {
const struct rcar_sysc_info r8a77970_sysc_info __initconst = {
.areas = r8a77970_areas,
.num_areas = ARRAY_SIZE(r8a77970_areas),
+ .extmask_offs = 0x1b0,
+ .extmask_val = BIT(0),
};
diff --git a/drivers/soc/renesas/r8a77980-sysc.c b/drivers/soc/renesas/r8a77980-sysc.c
index a8dbe55e8ba8..39ca84a67daa 100644
--- a/drivers/soc/renesas/r8a77980-sysc.c
+++ b/drivers/soc/renesas/r8a77980-sysc.c
@@ -6,7 +6,7 @@
* Copyright (C) 2018 Cogent Embedded, Inc.
*/
-#include <linux/bug.h>
+#include <linux/bits.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a77980-sysc.h>
@@ -49,4 +49,6 @@ static const struct rcar_sysc_area r8a77980_areas[] __initconst = {
const struct rcar_sysc_info r8a77980_sysc_info __initconst = {
.areas = r8a77980_areas,
.num_areas = ARRAY_SIZE(r8a77980_areas),
+ .extmask_offs = 0x138,
+ .extmask_val = BIT(0),
};
diff --git a/drivers/soc/renesas/r8a77990-sysc.c b/drivers/soc/renesas/r8a77990-sysc.c
index 664b244eb1dd..9f92737dc352 100644
--- a/drivers/soc/renesas/r8a77990-sysc.c
+++ b/drivers/soc/renesas/r8a77990-sysc.c
@@ -5,7 +5,7 @@
* Copyright (C) 2018 Renesas Electronics Corp.
*/
-#include <linux/bug.h>
+#include <linux/bits.h>
#include <linux/kernel.h>
#include <linux/sys_soc.h>
@@ -50,4 +50,6 @@ const struct rcar_sysc_info r8a77990_sysc_info __initconst = {
.init = r8a77990_sysc_init,
.areas = r8a77990_areas,
.num_areas = ARRAY_SIZE(r8a77990_areas),
+ .extmask_offs = 0x2f8,
+ .extmask_val = BIT(0),
};
diff --git a/drivers/soc/renesas/r8a77995-sysc.c b/drivers/soc/renesas/r8a77995-sysc.c
index 6243aaaf60fb..efcc67e3d76d 100644
--- a/drivers/soc/renesas/r8a77995-sysc.c
+++ b/drivers/soc/renesas/r8a77995-sysc.c
@@ -5,7 +5,6 @@
* Copyright (C) 2017 Glider bvba
*/
-#include <linux/bug.h>
#include <linux/kernel.h>
#include <dt-bindings/power/r8a77995-sysc.h>
diff --git a/drivers/soc/renesas/rcar-rst.c b/drivers/soc/renesas/rcar-rst.c
index d183c381e8db..14d05a070dd3 100644
--- a/drivers/soc/renesas/rcar-rst.c
+++ b/drivers/soc/renesas/rcar-rst.c
@@ -45,6 +45,7 @@ static const struct of_device_id rcar_rst_matches[] __initconst = {
{ .compatible = "renesas,r8a77470-rst", .data = &rcar_rst_gen2 },
/* RZ/G2 is handled like R-Car Gen3 */
{ .compatible = "renesas,r8a774a1-rst", .data = &rcar_rst_gen3 },
+ { .compatible = "renesas,r8a774b1-rst", .data = &rcar_rst_gen3 },
{ .compatible = "renesas,r8a774c0-rst", .data = &rcar_rst_gen3 },
/* R-Car Gen1 */
{ .compatible = "renesas,r8a7778-reset-wdt", .data = &rcar_rst_gen1 },
@@ -58,6 +59,7 @@ static const struct of_device_id rcar_rst_matches[] __initconst = {
/* R-Car Gen3 */
{ .compatible = "renesas,r8a7795-rst", .data = &rcar_rst_gen3 },
{ .compatible = "renesas,r8a7796-rst", .data = &rcar_rst_gen3 },
+ { .compatible = "renesas,r8a77961-rst", .data = &rcar_rst_gen3 },
{ .compatible = "renesas,r8a77965-rst", .data = &rcar_rst_gen3 },
{ .compatible = "renesas,r8a77970-rst", .data = &rcar_rst_gen3 },
{ .compatible = "renesas,r8a77980-rst", .data = &rcar_rst_gen3 },
diff --git a/drivers/soc/renesas/rcar-sysc.c b/drivers/soc/renesas/rcar-sysc.c
index 59b5e6b10272..f0b291e02b8a 100644
--- a/drivers/soc/renesas/rcar-sysc.c
+++ b/drivers/soc/renesas/rcar-sysc.c
@@ -63,6 +63,7 @@ struct rcar_sysc_ch {
static void __iomem *rcar_sysc_base;
static DEFINE_SPINLOCK(rcar_sysc_lock); /* SMP CPUs + I/O devices */
+static u32 rcar_sysc_extmask_offs, rcar_sysc_extmask_val;
static int rcar_sysc_pwr_on_off(const struct rcar_sysc_ch *sysc_ch, bool on)
{
@@ -106,6 +107,14 @@ static int rcar_sysc_power(const struct rcar_sysc_ch *sysc_ch, bool on)
spin_lock_irqsave(&rcar_sysc_lock, flags);
/*
+ * Mask external power requests for CPU or 3DG domains
+ */
+ if (rcar_sysc_extmask_val) {
+ iowrite32(rcar_sysc_extmask_val,
+ rcar_sysc_base + rcar_sysc_extmask_offs);
+ }
+
+ /*
* The interrupt source needs to be enabled, but masked, to prevent the
* CPU from receiving it.
*/
@@ -148,6 +157,9 @@ static int rcar_sysc_power(const struct rcar_sysc_ch *sysc_ch, bool on)
iowrite32(isr_mask, rcar_sysc_base + SYSCISCR);
out:
+ if (rcar_sysc_extmask_val)
+ iowrite32(0, rcar_sysc_base + rcar_sysc_extmask_offs);
+
spin_unlock_irqrestore(&rcar_sysc_lock, flags);
pr_debug("sysc power %s domain %d: %08x -> %d\n", on ? "on" : "off",
@@ -275,6 +287,9 @@ static const struct of_device_id rcar_sysc_matches[] __initconst = {
#ifdef CONFIG_SYSC_R8A774A1
{ .compatible = "renesas,r8a774a1-sysc", .data = &r8a774a1_sysc_info },
#endif
+#ifdef CONFIG_SYSC_R8A774B1
+ { .compatible = "renesas,r8a774b1-sysc", .data = &r8a774b1_sysc_info },
+#endif
#ifdef CONFIG_SYSC_R8A774C0
{ .compatible = "renesas,r8a774c0-sysc", .data = &r8a774c0_sysc_info },
#endif
@@ -298,8 +313,11 @@ static const struct of_device_id rcar_sysc_matches[] __initconst = {
#ifdef CONFIG_SYSC_R8A7795
{ .compatible = "renesas,r8a7795-sysc", .data = &r8a7795_sysc_info },
#endif
-#ifdef CONFIG_SYSC_R8A7796
- { .compatible = "renesas,r8a7796-sysc", .data = &r8a7796_sysc_info },
+#ifdef CONFIG_SYSC_R8A77960
+ { .compatible = "renesas,r8a7796-sysc", .data = &r8a77960_sysc_info },
+#endif
+#ifdef CONFIG_SYSC_R8A77961
+ { .compatible = "renesas,r8a77961-sysc", .data = &r8a77961_sysc_info },
#endif
#ifdef CONFIG_SYSC_R8A77965
{ .compatible = "renesas,r8a77965-sysc", .data = &r8a77965_sysc_info },
@@ -360,6 +378,10 @@ static int __init rcar_sysc_pd_init(void)
rcar_sysc_base = base;
+ /* Optional External Request Mask Register */
+ rcar_sysc_extmask_offs = info->extmask_offs;
+ rcar_sysc_extmask_val = info->extmask_val;
+
domains = kzalloc(sizeof(*domains), GFP_KERNEL);
if (!domains) {
error = -ENOMEM;
diff --git a/drivers/soc/renesas/rcar-sysc.h b/drivers/soc/renesas/rcar-sysc.h
index 485520a5b295..8d074489fba9 100644
--- a/drivers/soc/renesas/rcar-sysc.h
+++ b/drivers/soc/renesas/rcar-sysc.h
@@ -44,20 +44,25 @@ struct rcar_sysc_info {
int (*init)(void); /* Optional */
const struct rcar_sysc_area *areas;
unsigned int num_areas;
+ /* Optional External Request Mask Register */
+ u32 extmask_offs; /* SYSCEXTMASK register offset */
+ u32 extmask_val; /* SYSCEXTMASK register mask value */
};
extern const struct rcar_sysc_info r8a7743_sysc_info;
extern const struct rcar_sysc_info r8a7745_sysc_info;
extern const struct rcar_sysc_info r8a77470_sysc_info;
extern const struct rcar_sysc_info r8a774a1_sysc_info;
+extern const struct rcar_sysc_info r8a774b1_sysc_info;
extern const struct rcar_sysc_info r8a774c0_sysc_info;
extern const struct rcar_sysc_info r8a7779_sysc_info;
extern const struct rcar_sysc_info r8a7790_sysc_info;
extern const struct rcar_sysc_info r8a7791_sysc_info;
extern const struct rcar_sysc_info r8a7792_sysc_info;
extern const struct rcar_sysc_info r8a7794_sysc_info;
-extern const struct rcar_sysc_info r8a7795_sysc_info;
-extern const struct rcar_sysc_info r8a7796_sysc_info;
+extern struct rcar_sysc_info r8a7795_sysc_info;
+extern const struct rcar_sysc_info r8a77960_sysc_info;
+extern const struct rcar_sysc_info r8a77961_sysc_info;
extern const struct rcar_sysc_info r8a77965_sysc_info;
extern const struct rcar_sysc_info r8a77970_sysc_info;
extern const struct rcar_sysc_info r8a77980_sysc_info;
diff --git a/drivers/soc/renesas/renesas-soc.c b/drivers/soc/renesas/renesas-soc.c
index 3299cf5365f3..850f5733dc88 100644
--- a/drivers/soc/renesas/renesas-soc.c
+++ b/drivers/soc/renesas/renesas-soc.c
@@ -116,6 +116,11 @@ static const struct renesas_soc soc_rz_g2m __initconst __maybe_unused = {
.id = 0x52,
};
+static const struct renesas_soc soc_rz_g2n __initconst __maybe_unused = {
+ .family = &fam_rzg2,
+ .id = 0x55,
+};
+
static const struct renesas_soc soc_rz_g2e __initconst __maybe_unused = {
.family = &fam_rzg2,
.id = 0x57,
@@ -227,6 +232,9 @@ static const struct of_device_id renesas_socs[] __initconst = {
#ifdef CONFIG_ARCH_R8A774A1
{ .compatible = "renesas,r8a774a1", .data = &soc_rz_g2m },
#endif
+#ifdef CONFIG_ARCH_R8A774B1
+ { .compatible = "renesas,r8a774b1", .data = &soc_rz_g2n },
+#endif
#ifdef CONFIG_ARCH_R8A774C0
{ .compatible = "renesas,r8a774c0", .data = &soc_rz_g2e },
#endif
@@ -254,9 +262,12 @@ static const struct of_device_id renesas_socs[] __initconst = {
#ifdef CONFIG_ARCH_R8A7795
{ .compatible = "renesas,r8a7795", .data = &soc_rcar_h3 },
#endif
-#ifdef CONFIG_ARCH_R8A7796
+#ifdef CONFIG_ARCH_R8A77960
{ .compatible = "renesas,r8a7796", .data = &soc_rcar_m3_w },
#endif
+#ifdef CONFIG_ARCH_R8A77961
+ { .compatible = "renesas,r8a77961", .data = &soc_rcar_m3_w },
+#endif
#ifdef CONFIG_ARCH_R8A77965
{ .compatible = "renesas,r8a77965", .data = &soc_rcar_m3_n },
#endif
@@ -326,7 +337,7 @@ static int __init renesas_soc_init(void)
if (np) {
chipid = of_iomap(np, 0);
of_node_put(np);
- } else if (soc->id) {
+ } else if (soc->id && family->reg) {
chipid = ioremap(family->reg, 4);
}
if (chipid) {
diff --git a/drivers/soc/samsung/Kconfig b/drivers/soc/samsung/Kconfig
index 33ad0de2de3c..27fc59bbb520 100644
--- a/drivers/soc/samsung/Kconfig
+++ b/drivers/soc/samsung/Kconfig
@@ -7,6 +7,16 @@ menuconfig SOC_SAMSUNG
if SOC_SAMSUNG
+config EXYNOS_ASV
+ bool "Exynos Adaptive Supply Voltage support" if COMPILE_TEST
+ depends on (ARCH_EXYNOS && EXYNOS_CHIPID) || COMPILE_TEST
+ select EXYNOS_ASV_ARM if ARM && ARCH_EXYNOS
+
+# There is no need to enable these drivers for ARMv8
+config EXYNOS_ASV_ARM
+ bool "Exynos ASV ARMv7-specific driver extensions" if COMPILE_TEST
+ depends on EXYNOS_ASV
+
config EXYNOS_CHIPID
bool "Exynos Chipid controller driver" if COMPILE_TEST
depends on ARCH_EXYNOS || COMPILE_TEST
diff --git a/drivers/soc/samsung/Makefile b/drivers/soc/samsung/Makefile
index 3b6a8797416c..edd1d6ea064d 100644
--- a/drivers/soc/samsung/Makefile
+++ b/drivers/soc/samsung/Makefile
@@ -1,5 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_EXYNOS_ASV) += exynos-asv.o
+obj-$(CONFIG_EXYNOS_ASV_ARM) += exynos5422-asv.o
+
obj-$(CONFIG_EXYNOS_CHIPID) += exynos-chipid.o
obj-$(CONFIG_EXYNOS_PMU) += exynos-pmu.o
diff --git a/drivers/soc/samsung/exynos-asv.c b/drivers/soc/samsung/exynos-asv.c
new file mode 100644
index 000000000000..30bb7b7cc769
--- /dev/null
+++ b/drivers/soc/samsung/exynos-asv.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ *
+ * Samsung Exynos SoC Adaptive Supply Voltage support
+ */
+
+#include <linux/cpu.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/regmap.h>
+#include <linux/soc/samsung/exynos-chipid.h>
+
+#include "exynos-asv.h"
+#include "exynos5422-asv.h"
+
+#define MHZ 1000000U
+
+static int exynos_asv_update_cpu_opps(struct exynos_asv *asv,
+ struct device *cpu)
+{
+ struct exynos_asv_subsys *subsys = NULL;
+ struct dev_pm_opp *opp;
+ unsigned int opp_freq;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(asv->subsys); i++) {
+ if (of_device_is_compatible(cpu->of_node,
+ asv->subsys[i].cpu_dt_compat)) {
+ subsys = &asv->subsys[i];
+ break;
+ }
+ }
+ if (!subsys)
+ return -EINVAL;
+
+ for (i = 0; i < subsys->table.num_rows; i++) {
+ unsigned int new_volt, volt;
+ int ret;
+
+ opp_freq = exynos_asv_opp_get_frequency(subsys, i);
+
+ opp = dev_pm_opp_find_freq_exact(cpu, opp_freq * MHZ, true);
+ if (IS_ERR(opp)) {
+ dev_info(asv->dev, "cpu%d opp%d, freq: %u missing\n",
+ cpu->id, i, opp_freq);
+
+ continue;
+ }
+
+ volt = dev_pm_opp_get_voltage(opp);
+ new_volt = asv->opp_get_voltage(subsys, i, volt);
+ dev_pm_opp_put(opp);
+
+ if (new_volt == volt)
+ continue;
+
+ ret = dev_pm_opp_adjust_voltage(cpu, opp_freq * MHZ,
+ new_volt, new_volt, new_volt);
+ if (ret < 0)
+ dev_err(asv->dev,
+ "Failed to adjust OPP %u Hz/%u uV for cpu%d\n",
+ opp_freq, new_volt, cpu->id);
+ else
+ dev_dbg(asv->dev,
+ "Adjusted OPP %u Hz/%u -> %u uV, cpu%d\n",
+ opp_freq, volt, new_volt, cpu->id);
+ }
+
+ return 0;
+}
+
+static int exynos_asv_update_opps(struct exynos_asv *asv)
+{
+ struct opp_table *last_opp_table = NULL;
+ struct device *cpu;
+ int ret, cpuid;
+
+ for_each_possible_cpu(cpuid) {
+ struct opp_table *opp_table;
+
+ cpu = get_cpu_device(cpuid);
+ if (!cpu)
+ continue;
+
+ opp_table = dev_pm_opp_get_opp_table(cpu);
+ if (IS_ERR_OR_NULL(opp_table))
+ continue;
+
+ if (!last_opp_table || opp_table != last_opp_table) {
+ last_opp_table = opp_table;
+
+ ret = exynos_asv_update_cpu_opps(asv, cpu);
+ if (ret < 0)
+ dev_err(asv->dev, "Couldn't udate OPPs for cpu%d\n",
+ cpuid);
+ }
+
+ dev_pm_opp_put_opp_table(opp_table);
+ }
+
+ return 0;
+}
+
+static int exynos_asv_probe(struct platform_device *pdev)
+{
+ int (*probe_func)(struct exynos_asv *asv);
+ struct exynos_asv *asv;
+ struct device *cpu_dev;
+ u32 product_id = 0;
+ int ret, i;
+
+ cpu_dev = get_cpu_device(0);
+ ret = dev_pm_opp_get_opp_count(cpu_dev);
+ if (ret < 0)
+ return -EPROBE_DEFER;
+
+ asv = devm_kzalloc(&pdev->dev, sizeof(*asv), GFP_KERNEL);
+ if (!asv)
+ return -ENOMEM;
+
+ asv->chipid_regmap = device_node_to_regmap(pdev->dev.of_node);
+ if (IS_ERR(asv->chipid_regmap)) {
+ dev_err(&pdev->dev, "Could not find syscon regmap\n");
+ return PTR_ERR(asv->chipid_regmap);
+ }
+
+ regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_PRO_ID, &product_id);
+
+ switch (product_id & EXYNOS_MASK) {
+ case 0xE5422000:
+ probe_func = exynos5422_asv_init;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ ret = of_property_read_u32(pdev->dev.of_node, "samsung,asv-bin",
+ &asv->of_bin);
+ if (ret < 0)
+ asv->of_bin = -EINVAL;
+
+ asv->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, asv);
+
+ for (i = 0; i < ARRAY_SIZE(asv->subsys); i++)
+ asv->subsys[i].asv = asv;
+
+ ret = probe_func(asv);
+ if (ret < 0)
+ return ret;
+
+ return exynos_asv_update_opps(asv);
+}
+
+static const struct of_device_id exynos_asv_of_device_ids[] = {
+ { .compatible = "samsung,exynos4210-chipid" },
+ {}
+};
+
+static struct platform_driver exynos_asv_driver = {
+ .driver = {
+ .name = "exynos-asv",
+ .of_match_table = exynos_asv_of_device_ids,
+ },
+ .probe = exynos_asv_probe,
+};
+module_platform_driver(exynos_asv_driver);
diff --git a/drivers/soc/samsung/exynos-asv.h b/drivers/soc/samsung/exynos-asv.h
new file mode 100644
index 000000000000..3fd1f2acd999
--- /dev/null
+++ b/drivers/soc/samsung/exynos-asv.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ *
+ * Samsung Exynos SoC Adaptive Supply Voltage support
+ */
+#ifndef __LINUX_SOC_EXYNOS_ASV_H
+#define __LINUX_SOC_EXYNOS_ASV_H
+
+struct regmap;
+
+/* HPM, IDS values to select target group */
+struct asv_limit_entry {
+ unsigned int hpm;
+ unsigned int ids;
+};
+
+struct exynos_asv_table {
+ unsigned int num_rows;
+ unsigned int num_cols;
+ u32 *buf;
+};
+
+struct exynos_asv_subsys {
+ struct exynos_asv *asv;
+ const char *cpu_dt_compat;
+ int id;
+ struct exynos_asv_table table;
+
+ unsigned int base_volt;
+ unsigned int offset_volt_h;
+ unsigned int offset_volt_l;
+};
+
+struct exynos_asv {
+ struct device *dev;
+ struct regmap *chipid_regmap;
+ struct exynos_asv_subsys subsys[2];
+
+ int (*opp_get_voltage)(const struct exynos_asv_subsys *subs,
+ int level, unsigned int voltage);
+ unsigned int group;
+ unsigned int table;
+
+ /* True if SG fields from PKG_ID register should be used */
+ bool use_sg;
+ /* ASV bin read from DT */
+ int of_bin;
+};
+
+static inline u32 __asv_get_table_entry(const struct exynos_asv_table *table,
+ unsigned int row, unsigned int col)
+{
+ return table->buf[row * (table->num_cols) + col];
+}
+
+static inline u32 exynos_asv_opp_get_voltage(const struct exynos_asv_subsys *subsys,
+ unsigned int level, unsigned int group)
+{
+ return __asv_get_table_entry(&subsys->table, level, group + 1);
+}
+
+static inline u32 exynos_asv_opp_get_frequency(const struct exynos_asv_subsys *subsys,
+ unsigned int level)
+{
+ return __asv_get_table_entry(&subsys->table, level, 0);
+}
+
+#endif /* __LINUX_SOC_EXYNOS_ASV_H */
diff --git a/drivers/soc/samsung/exynos-chipid.c b/drivers/soc/samsung/exynos-chipid.c
index c55a47cfe617..b89c26a71c6e 100644
--- a/drivers/soc/samsung/exynos-chipid.c
+++ b/drivers/soc/samsung/exynos-chipid.c
@@ -45,17 +45,25 @@ static const char * __init product_id_to_soc_id(unsigned int product_id)
return NULL;
}
-int __init exynos_chipid_early_init(void)
+static int __init exynos_chipid_early_init(void)
{
struct soc_device_attribute *soc_dev_attr;
struct soc_device *soc_dev;
struct device_node *root;
+ struct device_node *syscon;
struct regmap *regmap;
u32 product_id;
u32 revision;
int ret;
- regmap = syscon_regmap_lookup_by_compatible("samsung,exynos4210-chipid");
+ syscon = of_find_compatible_node(NULL, NULL,
+ "samsung,exynos4210-chipid");
+ if (!syscon)
+ return ENODEV;
+
+ regmap = device_node_to_regmap(syscon);
+ of_node_put(syscon);
+
if (IS_ERR(regmap))
return PTR_ERR(regmap);
diff --git a/drivers/soc/samsung/exynos5422-asv.c b/drivers/soc/samsung/exynos5422-asv.c
new file mode 100644
index 000000000000..01bb3050d678
--- /dev/null
+++ b/drivers/soc/samsung/exynos5422-asv.c
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Samsung Exynos 5422 SoC Adaptive Supply Voltage support
+ */
+
+#include <linux/bitrev.h>
+#include <linux/errno.h>
+#include <linux/regmap.h>
+#include <linux/soc/samsung/exynos-chipid.h>
+#include <linux/slab.h>
+
+#include "exynos-asv.h"
+#include "exynos5422-asv.h"
+
+#define ASV_GROUPS_NUM 14
+#define ASV_ARM_DVFS_NUM 20
+#define ASV_ARM_BIN2_DVFS_NUM 17
+#define ASV_KFC_DVFS_NUM 14
+#define ASV_KFC_BIN2_DVFS_NUM 12
+
+/*
+ * This array is a set of 4 ASV data tables, first column of each ASV table
+ * contains frequency value in MHz and subsequent columns contain the CPU
+ * cluster's supply voltage values in uV.
+ * In order to create a set of OPPs for specific SoC revision one of the voltage
+ * columns (1...14) from one of the tables (0...3) is selected during
+ * initialization. There are separate ASV tables for the big (ARM) and little
+ * (KFC) CPU cluster. Only OPPs which are already defined in devicetree
+ * will be updated.
+ */
+
+static const u32 asv_arm_table[][ASV_ARM_DVFS_NUM][ASV_GROUPS_NUM + 1] = {
+{
+ /* ARM 0, 1 */
+ { 2100, 1362500, 1362500, 1350000, 1337500, 1325000, 1312500, 1300000,
+ 1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000 },
+ { 2000, 1312500, 1312500, 1300000, 1287500, 1275000, 1262500, 1250000,
+ 1237500, 1225000, 1237500, 1225000, 1212500, 1200000, 1187500 },
+ { 1900, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500, 1175000,
+ 1162500, 1150000, 1162500, 1150000, 1137500, 1125000, 1112500 },
+ { 1800, 1200000, 1187500, 1175000, 1162500, 1150000, 1137500, 1125000,
+ 1112500, 1100000, 1112500, 1100000, 1087500, 1075000, 1062500 },
+ { 1700, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000, 1087500,
+ 1075000, 1062500, 1075000, 1062500, 1050000, 1037500, 1025000 },
+ { 1600, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500, 1050000,
+ 1037500, 1025000, 1037500, 1025000, 1012500, 1000000, 987500 },
+ { 1500, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500,
+ 1000000, 987500, 1000000, 987500, 975000, 962500, 950000 },
+ { 1400, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000, 987500,
+ 975000, 962500, 975000, 962500, 950000, 937500, 925000 },
+ { 1300, 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000,
+ 962500, 950000, 962500, 950000, 937500, 925000, 912500 },
+ { 1200, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
+ 937500, 925000, 937500, 925000, 912500, 900000, 900000 },
+ { 1100, 1000000, 987500, 975000, 962500, 950000, 937500, 925000,
+ 912500, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 1000, 975000, 962500, 950000, 937500, 925000, 912500, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 900, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 800, 925000, 912500, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 700, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 600, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 500, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+}, {
+ /* ARM 2 */
+ { 2100, 1362500, 1362500, 1350000, 1337500, 1325000, 1312500, 1300000,
+ 1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000 },
+ { 2000, 1312500, 1312500, 1312500, 1300000, 1275000, 1262500, 1250000,
+ 1237500, 1225000, 1237500, 1225000, 1212500, 1200000, 1187500 },
+ { 1900, 1262500, 1250000, 1250000, 1237500, 1212500, 1200000, 1187500,
+ 1175000, 1162500, 1175000, 1162500, 1150000, 1137500, 1125000 },
+ { 1800, 1212500, 1200000, 1187500, 1175000, 1162500, 1150000, 1137500,
+ 1125000, 1112500, 1125000, 1112500, 1100000, 1087500, 1075000 },
+ { 1700, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000,
+ 1087500, 1075000, 1087500, 1075000, 1062500, 1050000, 1037500 },
+ { 1600, 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500,
+ 1050000, 1037500, 1050000, 1037500, 1025000, 1012500, 1000000 },
+ { 1500, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
+ 1012500, 1000000, 1012500, 1000000, 987500, 975000, 962500 },
+ { 1400, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000,
+ 987500, 975000, 987500, 975000, 962500, 950000, 937500 },
+ { 1300, 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000,
+ 962500, 950000, 962500, 950000, 937500, 925000, 912500 },
+ { 1200, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
+ 937500, 925000, 937500, 925000, 912500, 900000, 900000 },
+ { 1100, 1000000, 987500, 975000, 962500, 950000, 937500, 925000,
+ 912500, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 1000, 975000, 962500, 950000, 937500, 925000, 912500, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 900, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 800, 925000, 912500, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 700, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 600, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 500, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+}, {
+ /* ARM 3 */
+ { 2100, 1362500, 1362500, 1350000, 1337500, 1325000, 1312500, 1300000,
+ 1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000 },
+ { 2000, 1312500, 1312500, 1300000, 1287500, 1275000, 1262500, 1250000,
+ 1237500, 1225000, 1237500, 1225000, 1212500, 1200000, 1187500 },
+ { 1900, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500,
+ 1175000, 1162500, 1175000, 1162500, 1150000, 1137500, 1125000 },
+ { 1800, 1212500, 1200000, 1187500, 1175000, 1162500, 1150000, 1137500,
+ 1125000, 1112500, 1125000, 1112500, 1100000, 1087500, 1075000 },
+ { 1700, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000,
+ 1087500, 1075000, 1087500, 1075000, 1062500, 1050000, 1037500 },
+ { 1600, 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500,
+ 1050000, 1037500, 1050000, 1037500, 1025000, 1012500, 1000000 },
+ { 1500, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
+ 1012500, 1000000, 1012500, 1000000, 987500, 975000, 962500 },
+ { 1400, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000,
+ 987500, 975000, 987500, 975000, 962500, 950000, 937500 },
+ { 1300, 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000,
+ 962500, 950000, 962500, 950000, 937500, 925000, 912500 },
+ { 1200, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
+ 937500, 925000, 937500, 925000, 912500, 900000, 900000 },
+ { 1100, 1000000, 987500, 975000, 962500, 950000, 937500, 925000,
+ 912500, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 1000, 975000, 962500, 950000, 937500, 925000, 912500, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 900, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 800, 925000, 912500, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 700, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 600, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 500, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+}, {
+ /* ARM bin 2 */
+ { 1800, 1237500, 1225000, 1212500, 1200000, 1187500, 1175000, 1162500,
+ 1150000, 1137500, 1150000, 1137500, 1125000, 1112500, 1100000 },
+ { 1700, 1200000, 1187500, 1175000, 1162500, 1150000, 1137500, 1125000,
+ 1112500, 1100000, 1112500, 1100000, 1087500, 1075000, 1062500 },
+ { 1600, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000, 1087500,
+ 1075000, 1062500, 1075000, 1062500, 1050000, 1037500, 1025000 },
+ { 1500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500, 1050000,
+ 1037500, 1025000, 1037500, 1025000, 1012500, 1000000, 987500 },
+ { 1400, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
+ 1012500, 1000000, 1012500, 1000000, 987500, 975000, 962500 },
+ { 1300, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500,
+ 1000000, 987500, 1000000, 987500, 975000, 962500, 950000 },
+ { 1200, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000, 987500,
+ 975000, 962500, 975000, 962500, 950000, 937500, 925000 },
+ { 1100, 1037500, 1025000, 1012500, 1000000, 987500, 975000, 962500,
+ 950000, 937500, 950000, 937500, 925000, 912500, 900000 },
+ { 1000, 1012500, 1000000, 987500, 975000, 962500, 950000, 937500,
+ 925000, 912500, 925000, 912500, 900000, 900000, 900000 },
+ { 900, 987500, 975000, 962500, 950000, 937500, 925000, 912500,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 800, 962500, 950000, 937500, 925000, 912500, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 700, 937500, 925000, 912500, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 600, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 500, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+}
+};
+
+static const u32 asv_kfc_table[][ASV_KFC_DVFS_NUM][ASV_GROUPS_NUM + 1] = {
+{
+ /* KFC 0, 1 */
+ { 1500000, 1300000, 1300000, 1300000, 1287500, 1287500, 1287500, 1275000,
+ 1262500, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500 },
+ { 1400000, 1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000,
+ 1187500, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500 },
+ { 1300000, 1225000, 1212500, 1200000, 1187500, 1175000, 1162500, 1150000,
+ 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500 },
+ { 1200000, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000,
+ 1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500 },
+ { 1100000, 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500,
+ 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000 },
+ { 1000000, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
+ 1012500, 1000000, 987500, 975000, 962500, 950000, 937500 },
+ { 900000, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000, 987500,
+ 975000, 962500, 950000, 937500, 925000, 912500, 900000 },
+ { 800000, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
+ 937500, 925000, 912500, 900000, 900000, 900000, 900000 },
+ { 700000, 987500, 975000, 962500, 950000, 937500, 925000, 912500,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 600000, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 500000, 912500, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 400000, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 300000, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 200000, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+}, {
+ /* KFC 2 */
+ { 1500, 1300000, 1300000, 1300000, 1287500, 1287500, 1287500, 1275000,
+ 1262500, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500 },
+ { 1400, 1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000,
+ 1187500, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500 },
+ { 1300, 1225000, 1212500, 1200000, 1187500, 1175000, 1162500, 1150000,
+ 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500 },
+ { 1200, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000,
+ 1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500 },
+ { 1100, 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500,
+ 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000 },
+ { 1000, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
+ 1012500, 1000000, 987500, 975000, 962500, 950000, 937500 },
+ { 900, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000, 987500,
+ 975000, 962500, 950000, 937500, 925000, 912500, 900000 },
+ { 800, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
+ 937500, 925000, 912500, 900000, 900000, 900000, 900000 },
+ { 700, 987500, 975000, 962500, 950000, 937500, 925000, 912500,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 600, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 500, 912500, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+}, {
+ /* KFC 3 */
+ { 1500, 1300000, 1300000, 1300000, 1287500, 1287500, 1287500, 1275000,
+ 1262500, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500 },
+ { 1400, 1275000, 1262500, 1250000, 1237500, 1225000, 1212500, 1200000,
+ 1187500, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500 },
+ { 1300, 1225000, 1212500, 1200000, 1187500, 1175000, 1162500, 1150000,
+ 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500 },
+ { 1200, 1175000, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000,
+ 1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500 },
+ { 1100, 1137500, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500,
+ 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000 },
+ { 1000, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000,
+ 1012500, 1000000, 987500, 975000, 962500, 950000, 937500 },
+ { 900, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000, 987500,
+ 975000, 962500, 950000, 937500, 925000, 912500, 900000 },
+ { 800, 1025000, 1012500, 1000000, 987500, 975000, 962500, 950000,
+ 937500, 925000, 912500, 900000, 900000, 900000, 900000 },
+ { 700, 987500, 975000, 962500, 950000, 937500, 925000, 912500,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 600, 950000, 937500, 925000, 912500, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 500, 912500, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 400, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+}, {
+ /* KFC bin 2 */
+ { 1300, 1250000, 1237500, 1225000, 1212500, 1200000, 1187500, 1175000,
+ 1162500, 1150000, 1137500, 1125000, 1112500, 1100000, 1087500 },
+ { 1200, 1200000, 1187500, 1175000, 1162500, 1150000, 1137500, 1125000,
+ 1112500, 1100000, 1087500, 1075000, 1062500, 1050000, 1037500 },
+ { 1100, 1162500, 1150000, 1137500, 1125000, 1112500, 1100000, 1087500,
+ 1075000, 1062500, 1050000, 1037500, 1025000, 1012500, 1000000 },
+ { 1000, 1125000, 1112500, 1100000, 1087500, 1075000, 1062500, 1050000,
+ 1037500, 1025000, 1012500, 1000000, 987500, 975000, 962500 },
+ { 900, 1087500, 1075000, 1062500, 1050000, 1037500, 1025000, 1012500,
+ 1000000, 987500, 975000, 962500, 950000, 937500, 925000 },
+ { 800, 1050000, 1037500, 1025000, 1012500, 1000000, 987500, 975000,
+ 962500, 950000, 937500, 925000, 912500, 900000, 900000 },
+ { 700, 1012500, 1000000, 987500, 975000, 962500, 950000, 937500,
+ 925000, 912500, 900000, 900000, 900000, 900000, 900000 },
+ { 600, 975000, 962500, 950000, 937500, 925000, 912500, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 500, 937500, 925000, 912500, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 400, 925000, 912500, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 300, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+ { 200, 900000, 900000, 900000, 900000, 900000, 900000, 900000,
+ 900000, 900000, 900000, 900000, 900000, 900000, 900000 },
+}
+};
+
+static const struct asv_limit_entry __asv_limits[ASV_GROUPS_NUM] = {
+ { 13, 55 },
+ { 21, 65 },
+ { 25, 69 },
+ { 30, 72 },
+ { 36, 74 },
+ { 43, 76 },
+ { 51, 78 },
+ { 65, 80 },
+ { 81, 82 },
+ { 98, 84 },
+ { 119, 87 },
+ { 135, 89 },
+ { 150, 92 },
+ { 999, 999 },
+};
+
+static int exynos5422_asv_get_group(struct exynos_asv *asv)
+{
+ unsigned int pkgid_reg, auxi_reg;
+ int hpm, ids, i;
+
+ regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_PKG_ID, &pkgid_reg);
+ regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_AUX_INFO, &auxi_reg);
+
+ if (asv->use_sg) {
+ u32 sga = (pkgid_reg >> EXYNOS5422_SG_A_OFFSET) &
+ EXYNOS5422_SG_A_MASK;
+
+ u32 sgb = (pkgid_reg >> EXYNOS5422_SG_B_OFFSET) &
+ EXYNOS5422_SG_B_MASK;
+
+ if ((pkgid_reg >> EXYNOS5422_SG_BSIGN_OFFSET) &
+ EXYNOS5422_SG_BSIGN_MASK)
+ return sga + sgb;
+ else
+ return sga - sgb;
+ }
+
+ hpm = (auxi_reg >> EXYNOS5422_TMCB_OFFSET) & EXYNOS5422_TMCB_MASK;
+ ids = (pkgid_reg >> EXYNOS5422_IDS_OFFSET) & EXYNOS5422_IDS_MASK;
+
+ for (i = 0; i < ASV_GROUPS_NUM; i++) {
+ if (ids <= __asv_limits[i].ids)
+ break;
+ if (hpm <= __asv_limits[i].hpm)
+ break;
+ }
+ if (i < ASV_GROUPS_NUM)
+ return i;
+
+ return 0;
+}
+
+static int __asv_offset_voltage(unsigned int index)
+{
+ switch (index) {
+ case 1:
+ return 12500;
+ case 2:
+ return 50000;
+ case 3:
+ return 25000;
+ default:
+ return 0;
+ };
+}
+
+static void exynos5422_asv_offset_voltage_setup(struct exynos_asv *asv)
+{
+ struct exynos_asv_subsys *subsys;
+ unsigned int reg, value;
+
+ regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_AUX_INFO, &reg);
+
+ /* ARM offset voltage setup */
+ subsys = &asv->subsys[EXYNOS_ASV_SUBSYS_ID_ARM];
+
+ subsys->base_volt = 1000000;
+
+ value = (reg >> EXYNOS5422_ARM_UP_OFFSET) & EXYNOS5422_ARM_UP_MASK;
+ subsys->offset_volt_h = __asv_offset_voltage(value);
+
+ value = (reg >> EXYNOS5422_ARM_DN_OFFSET) & EXYNOS5422_ARM_DN_MASK;
+ subsys->offset_volt_l = __asv_offset_voltage(value);
+
+ /* KFC offset voltage setup */
+ subsys = &asv->subsys[EXYNOS_ASV_SUBSYS_ID_KFC];
+
+ subsys->base_volt = 1000000;
+
+ value = (reg >> EXYNOS5422_KFC_UP_OFFSET) & EXYNOS5422_KFC_UP_MASK;
+ subsys->offset_volt_h = __asv_offset_voltage(value);
+
+ value = (reg >> EXYNOS5422_KFC_DN_OFFSET) & EXYNOS5422_KFC_DN_MASK;
+ subsys->offset_volt_l = __asv_offset_voltage(value);
+}
+
+static int exynos5422_asv_opp_get_voltage(const struct exynos_asv_subsys *subsys,
+ int level, unsigned int volt)
+{
+ unsigned int asv_volt;
+
+ if (level >= subsys->table.num_rows)
+ return volt;
+
+ asv_volt = exynos_asv_opp_get_voltage(subsys, level,
+ subsys->asv->group);
+
+ if (volt > subsys->base_volt)
+ asv_volt += subsys->offset_volt_h;
+ else
+ asv_volt += subsys->offset_volt_l;
+
+ return asv_volt;
+}
+
+static unsigned int exynos5422_asv_parse_table(unsigned int pkg_id)
+{
+ return (pkg_id >> EXYNOS5422_TABLE_OFFSET) & EXYNOS5422_TABLE_MASK;
+}
+
+static bool exynos5422_asv_parse_bin2(unsigned int pkg_id)
+{
+ return (pkg_id >> EXYNOS5422_BIN2_OFFSET) & EXYNOS5422_BIN2_MASK;
+}
+
+static bool exynos5422_asv_parse_sg(unsigned int pkg_id)
+{
+ return (pkg_id >> EXYNOS5422_USESG_OFFSET) & EXYNOS5422_USESG_MASK;
+}
+
+int exynos5422_asv_init(struct exynos_asv *asv)
+{
+ struct exynos_asv_subsys *subsys;
+ unsigned int table_index;
+ unsigned int pkg_id;
+ bool bin2;
+
+ regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_PKG_ID, &pkg_id);
+
+ if (asv->of_bin == 2) {
+ bin2 = true;
+ asv->use_sg = false;
+ } else {
+ asv->use_sg = exynos5422_asv_parse_sg(pkg_id);
+ bin2 = exynos5422_asv_parse_bin2(pkg_id);
+ }
+
+ asv->group = exynos5422_asv_get_group(asv);
+ asv->table = exynos5422_asv_parse_table(pkg_id);
+
+ exynos5422_asv_offset_voltage_setup(asv);
+
+ if (bin2) {
+ table_index = 3;
+ } else {
+ if (asv->table == 2 || asv->table == 3)
+ table_index = asv->table - 1;
+ else
+ table_index = 0;
+ }
+
+ subsys = &asv->subsys[EXYNOS_ASV_SUBSYS_ID_ARM];
+ subsys->cpu_dt_compat = "arm,cortex-a15";
+ if (bin2)
+ subsys->table.num_rows = ASV_ARM_BIN2_DVFS_NUM;
+ else
+ subsys->table.num_rows = ASV_ARM_DVFS_NUM;
+ subsys->table.num_cols = ASV_GROUPS_NUM + 1;
+ subsys->table.buf = (u32 *)asv_arm_table[table_index];
+
+ subsys = &asv->subsys[EXYNOS_ASV_SUBSYS_ID_KFC];
+ subsys->cpu_dt_compat = "arm,cortex-a7";
+ if (bin2)
+ subsys->table.num_rows = ASV_KFC_BIN2_DVFS_NUM;
+ else
+ subsys->table.num_rows = ASV_KFC_DVFS_NUM;
+ subsys->table.num_cols = ASV_GROUPS_NUM + 1;
+ subsys->table.buf = (u32 *)asv_kfc_table[table_index];
+
+ asv->opp_get_voltage = exynos5422_asv_opp_get_voltage;
+
+ return 0;
+}
diff --git a/drivers/soc/samsung/exynos5422-asv.h b/drivers/soc/samsung/exynos5422-asv.h
new file mode 100644
index 000000000000..95a5fb1a7508
--- /dev/null
+++ b/drivers/soc/samsung/exynos5422-asv.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Samsung Exynos 5422 SoC Adaptive Supply Voltage support
+ */
+
+#ifndef __LINUX_SOC_EXYNOS5422_ASV_H
+#define __LINUX_SOC_EXYNOS5422_ASV_H
+
+#include <linux/errno.h>
+
+enum {
+ EXYNOS_ASV_SUBSYS_ID_ARM,
+ EXYNOS_ASV_SUBSYS_ID_KFC,
+ EXYNOS_ASV_SUBSYS_ID_MAX
+};
+
+struct exynos_asv;
+
+#ifdef CONFIG_EXYNOS_ASV_ARM
+int exynos5422_asv_init(struct exynos_asv *asv);
+#else
+static inline int exynos5422_asv_init(struct exynos_asv *asv)
+{
+ return -ENOTSUPP;
+}
+#endif
+
+#endif /* __LINUX_SOC_EXYNOS5422_ASV_H */
diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig
index c8ef05d6b8c7..84bd615c4a92 100644
--- a/drivers/soc/tegra/Kconfig
+++ b/drivers/soc/tegra/Kconfig
@@ -15,6 +15,7 @@ config ARCH_TEGRA_2x_SOC
select PL310_ERRATA_769419 if CACHE_L2X0
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
+ select SOC_TEGRA20_VOLTAGE_COUPLER
select TEGRA_TIMER
help
Support for NVIDIA Tegra AP20 and T20 processors, based on the
@@ -28,6 +29,7 @@ config ARCH_TEGRA_3x_SOC
select PL310_ERRATA_769419 if CACHE_L2X0
select SOC_TEGRA_FLOWCTRL
select SOC_TEGRA_PMC
+ select SOC_TEGRA30_VOLTAGE_COUPLER
select TEGRA_TIMER
help
Support for NVIDIA Tegra T30 processor family, based on the
@@ -135,3 +137,11 @@ config SOC_TEGRA_POWERGATE_BPMP
def_bool y
depends on PM_GENERIC_DOMAINS
depends on TEGRA_BPMP
+
+config SOC_TEGRA20_VOLTAGE_COUPLER
+ bool "Voltage scaling support for Tegra20 SoCs"
+ depends on ARCH_TEGRA_2x_SOC || COMPILE_TEST
+
+config SOC_TEGRA30_VOLTAGE_COUPLER
+ bool "Voltage scaling support for Tegra30 SoCs"
+ depends on ARCH_TEGRA_3x_SOC || COMPILE_TEST
diff --git a/drivers/soc/tegra/Makefile b/drivers/soc/tegra/Makefile
index 902759fe5f4d..9c809c1814bd 100644
--- a/drivers/soc/tegra/Makefile
+++ b/drivers/soc/tegra/Makefile
@@ -5,3 +5,5 @@ obj-y += common.o
obj-$(CONFIG_SOC_TEGRA_FLOWCTRL) += flowctrl.o
obj-$(CONFIG_SOC_TEGRA_PMC) += pmc.o
obj-$(CONFIG_SOC_TEGRA_POWERGATE_BPMP) += powergate-bpmp.o
+obj-$(CONFIG_SOC_TEGRA20_VOLTAGE_COUPLER) += regulators-tegra20.o
+obj-$(CONFIG_SOC_TEGRA30_VOLTAGE_COUPLER) += regulators-tegra30.o
diff --git a/drivers/soc/tegra/fuse/fuse-tegra.c b/drivers/soc/tegra/fuse/fuse-tegra.c
index 3eb44e65b326..4d719d4b8d5a 100644
--- a/drivers/soc/tegra/fuse/fuse-tegra.c
+++ b/drivers/soc/tegra/fuse/fuse-tegra.c
@@ -8,6 +8,8 @@
#include <linux/kobject.h>
#include <linux/init.h>
#include <linux/io.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
@@ -31,50 +33,6 @@ static const char *tegra_revision_name[TEGRA_REVISION_MAX] = {
[TEGRA_REVISION_A04] = "A04",
};
-static u8 fuse_readb(struct tegra_fuse *fuse, unsigned int offset)
-{
- u32 val;
-
- val = fuse->read(fuse, round_down(offset, 4));
- val >>= (offset % 4) * 8;
- val &= 0xff;
-
- return val;
-}
-
-static ssize_t fuse_read(struct file *fd, struct kobject *kobj,
- struct bin_attribute *attr, char *buf,
- loff_t pos, size_t size)
-{
- struct device *dev = kobj_to_dev(kobj);
- struct tegra_fuse *fuse = dev_get_drvdata(dev);
- int i;
-
- if (pos < 0 || pos >= attr->size)
- return 0;
-
- if (size > attr->size - pos)
- size = attr->size - pos;
-
- for (i = 0; i < size; i++)
- buf[i] = fuse_readb(fuse, pos + i);
-
- return i;
-}
-
-static struct bin_attribute fuse_bin_attr = {
- .attr = { .name = "fuse", .mode = S_IRUGO, },
- .read = fuse_read,
-};
-
-static int tegra_fuse_create_sysfs(struct device *dev, unsigned int size,
- const struct tegra_fuse_info *info)
-{
- fuse_bin_attr.size = size;
-
- return device_create_bin_file(dev, &fuse_bin_attr);
-}
-
static const struct of_device_id car_match[] __initconst = {
{ .compatible = "nvidia,tegra20-car", },
{ .compatible = "nvidia,tegra30-car", },
@@ -115,9 +73,111 @@ static const struct of_device_id tegra_fuse_match[] = {
{ /* sentinel */ }
};
+static int tegra_fuse_read(void *priv, unsigned int offset, void *value,
+ size_t bytes)
+{
+ unsigned int count = bytes / 4, i;
+ struct tegra_fuse *fuse = priv;
+ u32 *buffer = value;
+
+ for (i = 0; i < count; i++)
+ buffer[i] = fuse->read(fuse, offset + i * 4);
+
+ return 0;
+}
+
+static const struct nvmem_cell_info tegra_fuse_cells[] = {
+ {
+ .name = "tsensor-cpu1",
+ .offset = 0x084,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "tsensor-cpu2",
+ .offset = 0x088,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "tsensor-cpu0",
+ .offset = 0x098,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "xusb-pad-calibration",
+ .offset = 0x0f0,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "tsensor-cpu3",
+ .offset = 0x12c,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "sata-calibration",
+ .offset = 0x124,
+ .bytes = 1,
+ .bit_offset = 0,
+ .nbits = 2,
+ }, {
+ .name = "tsensor-gpu",
+ .offset = 0x154,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "tsensor-mem0",
+ .offset = 0x158,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "tsensor-mem1",
+ .offset = 0x15c,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "tsensor-pllx",
+ .offset = 0x160,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "tsensor-common",
+ .offset = 0x180,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "tsensor-realignment",
+ .offset = 0x1fc,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "gpu-calibration",
+ .offset = 0x204,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ }, {
+ .name = "xusb-pad-calibration-ext",
+ .offset = 0x250,
+ .bytes = 4,
+ .bit_offset = 0,
+ .nbits = 32,
+ },
+};
+
static int tegra_fuse_probe(struct platform_device *pdev)
{
void __iomem *base = fuse->base;
+ struct nvmem_config nvmem;
struct resource *res;
int err;
@@ -146,20 +206,42 @@ static int tegra_fuse_probe(struct platform_device *pdev)
if (fuse->soc->probe) {
err = fuse->soc->probe(fuse);
- if (err < 0) {
- fuse->base = base;
- return err;
- }
+ if (err < 0)
+ goto restore;
}
- if (tegra_fuse_create_sysfs(&pdev->dev, fuse->soc->info->size,
- fuse->soc->info))
- return -ENODEV;
+ memset(&nvmem, 0, sizeof(nvmem));
+ nvmem.dev = &pdev->dev;
+ nvmem.name = "fuse";
+ nvmem.id = -1;
+ nvmem.owner = THIS_MODULE;
+ nvmem.cells = tegra_fuse_cells;
+ nvmem.ncells = ARRAY_SIZE(tegra_fuse_cells);
+ nvmem.type = NVMEM_TYPE_OTP;
+ nvmem.read_only = true;
+ nvmem.root_only = true;
+ nvmem.reg_read = tegra_fuse_read;
+ nvmem.size = fuse->soc->info->size;
+ nvmem.word_size = 4;
+ nvmem.stride = 4;
+ nvmem.priv = fuse;
+
+ fuse->nvmem = devm_nvmem_register(&pdev->dev, &nvmem);
+ if (IS_ERR(fuse->nvmem)) {
+ err = PTR_ERR(fuse->nvmem);
+ dev_err(&pdev->dev, "failed to register NVMEM device: %d\n",
+ err);
+ goto restore;
+ }
/* release the early I/O memory mapping */
iounmap(base);
return 0;
+
+restore:
+ fuse->base = base;
+ return err;
}
static struct platform_driver tegra_fuse_driver = {
@@ -186,9 +268,12 @@ u32 __init tegra_fuse_read_early(unsigned int offset)
int tegra_fuse_readl(unsigned long offset, u32 *value)
{
- if (!fuse->read)
+ if (!fuse->read || !fuse->clk)
return -EPROBE_DEFER;
+ if (IS_ERR(fuse->clk))
+ return PTR_ERR(fuse->clk);
+
*value = fuse->read(fuse, offset);
return 0;
@@ -338,6 +423,15 @@ static int __init tegra_init_fuse(void)
pr_debug("Tegra CPU Speedo ID %d, SoC Speedo ID %d\n",
tegra_sku_info.cpu_speedo_id, tegra_sku_info.soc_speedo_id);
+ if (fuse->soc->lookups) {
+ size_t size = sizeof(*fuse->lookups) * fuse->soc->num_lookups;
+
+ fuse->lookups = kmemdup(fuse->soc->lookups, size, GFP_KERNEL);
+ if (!fuse->lookups)
+ return -ENOMEM;
+
+ nvmem_add_cell_lookups(fuse->lookups, fuse->soc->num_lookups);
+ }
return 0;
}
diff --git a/drivers/soc/tegra/fuse/fuse-tegra30.c b/drivers/soc/tegra/fuse/fuse-tegra30.c
index be9424a87173..b8daaf5b7291 100644
--- a/drivers/soc/tegra/fuse/fuse-tegra30.c
+++ b/drivers/soc/tegra/fuse/fuse-tegra30.c
@@ -8,6 +8,7 @@
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
+#include <linux/nvmem-consumer.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
@@ -127,6 +128,70 @@ const struct tegra_fuse_soc tegra114_fuse_soc = {
#endif
#if defined(CONFIG_ARCH_TEGRA_124_SOC) || defined(CONFIG_ARCH_TEGRA_132_SOC)
+static const struct nvmem_cell_lookup tegra124_fuse_lookups[] = {
+ {
+ .nvmem_name = "fuse",
+ .cell_name = "xusb-pad-calibration",
+ .dev_id = "7009f000.padctl",
+ .con_id = "calibration",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "sata-calibration",
+ .dev_id = "70020000.sata",
+ .con_id = "calibration",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-common",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "common",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-realignment",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "realignment",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-cpu0",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "cpu0",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-cpu1",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "cpu1",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-cpu2",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "cpu2",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-cpu3",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "cpu3",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-mem0",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "mem0",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-mem1",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "mem1",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-gpu",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "gpu",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-pllx",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "pllx",
+ },
+};
+
static const struct tegra_fuse_info tegra124_fuse_info = {
.read = tegra30_fuse_read,
.size = 0x300,
@@ -137,10 +202,81 @@ const struct tegra_fuse_soc tegra124_fuse_soc = {
.init = tegra30_fuse_init,
.speedo_init = tegra124_init_speedo_data,
.info = &tegra124_fuse_info,
+ .lookups = tegra124_fuse_lookups,
+ .num_lookups = ARRAY_SIZE(tegra124_fuse_lookups),
};
#endif
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
+static const struct nvmem_cell_lookup tegra210_fuse_lookups[] = {
+ {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-cpu1",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "cpu1",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-cpu2",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "cpu2",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-cpu0",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "cpu0",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "xusb-pad-calibration",
+ .dev_id = "7009f000.padctl",
+ .con_id = "calibration",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-cpu3",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "cpu3",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "sata-calibration",
+ .dev_id = "70020000.sata",
+ .con_id = "calibration",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-gpu",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "gpu",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-mem0",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "mem0",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-mem1",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "mem1",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-pllx",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "pllx",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "tsensor-common",
+ .dev_id = "700e2000.thermal-sensor",
+ .con_id = "common",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "gpu-calibration",
+ .dev_id = "57000000.gpu",
+ .con_id = "calibration",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "xusb-pad-calibration-ext",
+ .dev_id = "7009f000.padctl",
+ .con_id = "calibration-ext",
+ },
+};
+
static const struct tegra_fuse_info tegra210_fuse_info = {
.read = tegra30_fuse_read,
.size = 0x300,
@@ -151,10 +287,26 @@ const struct tegra_fuse_soc tegra210_fuse_soc = {
.init = tegra30_fuse_init,
.speedo_init = tegra210_init_speedo_data,
.info = &tegra210_fuse_info,
+ .lookups = tegra210_fuse_lookups,
+ .num_lookups = ARRAY_SIZE(tegra210_fuse_lookups),
};
#endif
#if defined(CONFIG_ARCH_TEGRA_186_SOC)
+static const struct nvmem_cell_lookup tegra186_fuse_lookups[] = {
+ {
+ .nvmem_name = "fuse",
+ .cell_name = "xusb-pad-calibration",
+ .dev_id = "3520000.padctl",
+ .con_id = "calibration",
+ }, {
+ .nvmem_name = "fuse",
+ .cell_name = "xusb-pad-calibration-ext",
+ .dev_id = "3520000.padctl",
+ .con_id = "calibration-ext",
+ },
+};
+
static const struct tegra_fuse_info tegra186_fuse_info = {
.read = tegra30_fuse_read,
.size = 0x300,
@@ -164,5 +316,7 @@ static const struct tegra_fuse_info tegra186_fuse_info = {
const struct tegra_fuse_soc tegra186_fuse_soc = {
.init = tegra30_fuse_init,
.info = &tegra186_fuse_info,
+ .lookups = tegra186_fuse_lookups,
+ .num_lookups = ARRAY_SIZE(tegra186_fuse_lookups),
};
#endif
diff --git a/drivers/soc/tegra/fuse/fuse.h b/drivers/soc/tegra/fuse/fuse.h
index 7230cb330503..0f74c2c34af0 100644
--- a/drivers/soc/tegra/fuse/fuse.h
+++ b/drivers/soc/tegra/fuse/fuse.h
@@ -13,6 +13,8 @@
#include <linux/dmaengine.h>
#include <linux/types.h>
+struct nvmem_cell_lookup;
+struct nvmem_device;
struct tegra_fuse;
struct tegra_fuse_info {
@@ -27,6 +29,9 @@ struct tegra_fuse_soc {
int (*probe)(struct tegra_fuse *fuse);
const struct tegra_fuse_info *info;
+
+ const struct nvmem_cell_lookup *lookups;
+ unsigned int num_lookups;
};
struct tegra_fuse {
@@ -48,6 +53,9 @@ struct tegra_fuse {
dma_addr_t phys;
u32 *virt;
} apbdma;
+
+ struct nvmem_device *nvmem;
+ struct nvmem_cell_lookup *lookups;
};
void tegra_init_revision(void);
diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 9f9c1c677cf4..8db63cfba833 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -56,8 +56,14 @@
#define PMC_CNTRL_SIDE_EFFECT_LP0 BIT(14) /* LP0 when CPU pwr gated */
#define PMC_CNTRL_SYSCLK_OE BIT(11) /* system clock enable */
#define PMC_CNTRL_SYSCLK_POLARITY BIT(10) /* sys clk polarity */
+#define PMC_CNTRL_PWRREQ_POLARITY BIT(8)
#define PMC_CNTRL_MAIN_RST BIT(4)
+#define PMC_WAKE_MASK 0x0c
+#define PMC_WAKE_LEVEL 0x10
+#define PMC_WAKE_STATUS 0x14
+#define PMC_SW_WAKE_STATUS 0x18
+
#define DPD_SAMPLE 0x020
#define DPD_SAMPLE_ENABLE BIT(0)
#define DPD_SAMPLE_DISABLE (0 << 0)
@@ -82,11 +88,18 @@
#define PMC_CPUPWRGOOD_TIMER 0xc8
#define PMC_CPUPWROFF_TIMER 0xcc
+#define PMC_COREPWRGOOD_TIMER 0x3c
+#define PMC_COREPWROFF_TIMER 0xe0
#define PMC_PWR_DET_VALUE 0xe4
#define PMC_SCRATCH41 0x140
+#define PMC_WAKE2_MASK 0x160
+#define PMC_WAKE2_LEVEL 0x164
+#define PMC_WAKE2_STATUS 0x168
+#define PMC_SW_WAKE2_STATUS 0x16c
+
#define PMC_SENSOR_CTRL 0x1b0
#define PMC_SENSOR_CTRL_SCRATCH_WRITE BIT(2)
#define PMC_SENSOR_CTRL_ENABLE_RST BIT(1)
@@ -226,6 +239,8 @@ struct tegra_pmc_soc {
void (*setup_irq_polarity)(struct tegra_pmc *pmc,
struct device_node *np,
bool invert);
+ int (*irq_set_wake)(struct irq_data *data, unsigned int on);
+ int (*irq_set_type)(struct irq_data *data, unsigned int type);
const char * const *reset_sources;
unsigned int num_reset_sources;
@@ -309,6 +324,7 @@ static const char * const tegra210_reset_sources[] = {
* @pctl_dev: pin controller exposed by the PMC
* @domain: IRQ domain provided by the PMC
* @irq: chip implementation for the IRQ domain
+ * @clk_nb: pclk clock changes handler
*/
struct tegra_pmc {
struct device *dev;
@@ -344,6 +360,8 @@ struct tegra_pmc {
struct irq_domain *domain;
struct irq_chip irq;
+
+ struct notifier_block clk_nb;
};
static struct tegra_pmc *pmc = &(struct tegra_pmc) {
@@ -1192,7 +1210,7 @@ static int tegra_io_pad_prepare(struct tegra_pmc *pmc, enum tegra_io_pad id,
return err;
if (pmc->clk) {
- rate = clk_get_rate(pmc->clk);
+ rate = pmc->rate;
if (!rate) {
dev_err(pmc->dev, "failed to get clock rate\n");
return -ENODEV;
@@ -1433,6 +1451,7 @@ void tegra_pmc_set_suspend_mode(enum tegra_suspend_mode mode)
void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
{
unsigned long long rate = 0;
+ u64 ticks;
u32 value;
switch (mode) {
@@ -1441,7 +1460,7 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
break;
case TEGRA_SUSPEND_LP2:
- rate = clk_get_rate(pmc->clk);
+ rate = pmc->rate;
break;
default:
@@ -1451,21 +1470,13 @@ void tegra_pmc_enter_suspend_mode(enum tegra_suspend_mode mode)
if (WARN_ON_ONCE(rate == 0))
rate = 100000000;
- if (rate != pmc->rate) {
- u64 ticks;
-
- ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1;
- do_div(ticks, USEC_PER_SEC);
- tegra_pmc_writel(pmc, ticks, PMC_CPUPWRGOOD_TIMER);
+ ticks = pmc->cpu_good_time * rate + USEC_PER_SEC - 1;
+ do_div(ticks, USEC_PER_SEC);
+ tegra_pmc_writel(pmc, ticks, PMC_CPUPWRGOOD_TIMER);
- ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1;
- do_div(ticks, USEC_PER_SEC);
- tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER);
-
- wmb();
-
- pmc->rate = rate;
- }
+ ticks = pmc->cpu_off_time * rate + USEC_PER_SEC - 1;
+ do_div(ticks, USEC_PER_SEC);
+ tegra_pmc_writel(pmc, ticks, PMC_CPUPWROFF_TIMER);
value = tegra_pmc_readl(pmc, PMC_CNTRL);
value &= ~PMC_CNTRL_SIDE_EFFECT_LP0;
@@ -1899,6 +1910,20 @@ static int tegra_pmc_irq_alloc(struct irq_domain *domain, unsigned int virq,
event->id,
&pmc->irq, pmc);
+ /*
+ * GPIOs don't have an equivalent interrupt in the
+ * parent controller (GIC). However some code, such
+ * as the one in irq_get_irqchip_state(), require a
+ * valid IRQ chip to be set. Make sure that's the
+ * case by passing NULL here, which will install a
+ * dummy IRQ chip for the interrupt in the parent
+ * domain.
+ */
+ if (domain->parent)
+ irq_domain_set_hwirq_and_chip(domain->parent,
+ virq, 0, NULL,
+ NULL);
+
break;
}
}
@@ -1908,10 +1933,22 @@ static int tegra_pmc_irq_alloc(struct irq_domain *domain, unsigned int virq,
* dummy hardware IRQ number. This is used in the ->irq_set_type()
* and ->irq_set_wake() callbacks to return early for these IRQs.
*/
- if (i == soc->num_wake_events)
+ if (i == soc->num_wake_events) {
err = irq_domain_set_hwirq_and_chip(domain, virq, ULONG_MAX,
&pmc->irq, pmc);
+ /*
+ * Interrupts without a wake event don't have a corresponding
+ * interrupt in the parent controller (GIC). Pass NULL for the
+ * chip here, which causes a dummy IRQ chip to be installed
+ * for the interrupt in the parent domain, to make this
+ * explicit.
+ */
+ if (domain->parent)
+ irq_domain_set_hwirq_and_chip(domain->parent, virq, 0,
+ NULL, NULL);
+ }
+
return err;
}
@@ -1920,7 +1957,87 @@ static const struct irq_domain_ops tegra_pmc_irq_domain_ops = {
.alloc = tegra_pmc_irq_alloc,
};
-static int tegra_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
+static int tegra210_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
+{
+ struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
+ unsigned int offset, bit;
+ u32 value;
+
+ if (data->hwirq == ULONG_MAX)
+ return 0;
+
+ offset = data->hwirq / 32;
+ bit = data->hwirq % 32;
+
+ /* clear wake status */
+ tegra_pmc_writel(pmc, 0, PMC_SW_WAKE_STATUS);
+ tegra_pmc_writel(pmc, 0, PMC_SW_WAKE2_STATUS);
+
+ tegra_pmc_writel(pmc, 0, PMC_WAKE_STATUS);
+ tegra_pmc_writel(pmc, 0, PMC_WAKE2_STATUS);
+
+ /* enable PMC wake */
+ if (data->hwirq >= 32)
+ offset = PMC_WAKE2_MASK;
+ else
+ offset = PMC_WAKE_MASK;
+
+ value = tegra_pmc_readl(pmc, offset);
+
+ if (on)
+ value |= BIT(bit);
+ else
+ value &= ~BIT(bit);
+
+ tegra_pmc_writel(pmc, value, offset);
+
+ return 0;
+}
+
+static int tegra210_pmc_irq_set_type(struct irq_data *data, unsigned int type)
+{
+ struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
+ unsigned int offset, bit;
+ u32 value;
+
+ if (data->hwirq == ULONG_MAX)
+ return 0;
+
+ offset = data->hwirq / 32;
+ bit = data->hwirq % 32;
+
+ if (data->hwirq >= 32)
+ offset = PMC_WAKE2_LEVEL;
+ else
+ offset = PMC_WAKE_LEVEL;
+
+ value = tegra_pmc_readl(pmc, offset);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ case IRQ_TYPE_LEVEL_HIGH:
+ value |= BIT(bit);
+ break;
+
+ case IRQ_TYPE_EDGE_FALLING:
+ case IRQ_TYPE_LEVEL_LOW:
+ value &= ~BIT(bit);
+ break;
+
+ case IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING:
+ value ^= BIT(bit);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ tegra_pmc_writel(pmc, value, offset);
+
+ return 0;
+}
+
+static int tegra186_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
{
struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
unsigned int offset, bit;
@@ -1952,7 +2069,7 @@ static int tegra_pmc_irq_set_wake(struct irq_data *data, unsigned int on)
return 0;
}
-static int tegra_pmc_irq_set_type(struct irq_data *data, unsigned int type)
+static int tegra186_pmc_irq_set_type(struct irq_data *data, unsigned int type)
{
struct tegra_pmc *pmc = irq_data_get_irq_chip_data(data);
u32 value;
@@ -2006,8 +2123,8 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc)
pmc->irq.irq_unmask = irq_chip_unmask_parent;
pmc->irq.irq_eoi = irq_chip_eoi_parent;
pmc->irq.irq_set_affinity = irq_chip_set_affinity_parent;
- pmc->irq.irq_set_type = tegra_pmc_irq_set_type;
- pmc->irq.irq_set_wake = tegra_pmc_irq_set_wake;
+ pmc->irq.irq_set_type = pmc->soc->irq_set_type;
+ pmc->irq.irq_set_wake = pmc->soc->irq_set_wake;
pmc->domain = irq_domain_add_hierarchy(parent, 0, 96, pmc->dev->of_node,
&tegra_pmc_irq_domain_ops, pmc);
@@ -2019,6 +2136,33 @@ static int tegra_pmc_irq_init(struct tegra_pmc *pmc)
return 0;
}
+static int tegra_pmc_clk_notify_cb(struct notifier_block *nb,
+ unsigned long action, void *ptr)
+{
+ struct tegra_pmc *pmc = container_of(nb, struct tegra_pmc, clk_nb);
+ struct clk_notifier_data *data = ptr;
+
+ switch (action) {
+ case PRE_RATE_CHANGE:
+ mutex_lock(&pmc->powergates_lock);
+ break;
+
+ case POST_RATE_CHANGE:
+ pmc->rate = data->new_rate;
+ /* fall through */
+
+ case ABORT_RATE_CHANGE:
+ mutex_unlock(&pmc->powergates_lock);
+ break;
+
+ default:
+ WARN_ON_ONCE(1);
+ return notifier_from_errno(-EINVAL);
+ }
+
+ return NOTIFY_OK;
+}
+
static int tegra_pmc_probe(struct platform_device *pdev)
{
void __iomem *base;
@@ -2082,6 +2226,23 @@ static int tegra_pmc_probe(struct platform_device *pdev)
pmc->clk = NULL;
}
+ /*
+ * PCLK clock rate can't be retrieved using CLK API because it
+ * causes lockup if CPU enters LP2 idle state from some other
+ * CLK notifier, hence we're caching the rate's value locally.
+ */
+ if (pmc->clk) {
+ pmc->clk_nb.notifier_call = tegra_pmc_clk_notify_cb;
+ err = clk_notifier_register(pmc->clk, &pmc->clk_nb);
+ if (err) {
+ dev_err(&pdev->dev,
+ "failed to register clk notifier\n");
+ return err;
+ }
+
+ pmc->rate = clk_get_rate(pmc->clk);
+ }
+
pmc->dev = &pdev->dev;
tegra_pmc_init(pmc);
@@ -2133,6 +2294,8 @@ cleanup_debugfs:
cleanup_sysfs:
device_remove_file(&pdev->dev, &dev_attr_reset_reason);
device_remove_file(&pdev->dev, &dev_attr_reset_level);
+ clk_notifier_unregister(pmc->clk, &pmc->clk_nb);
+
return err;
}
@@ -2184,7 +2347,7 @@ static const struct tegra_pmc_regs tegra20_pmc_regs = {
static void tegra20_pmc_init(struct tegra_pmc *pmc)
{
- u32 value;
+ u32 value, osc, pmu, off;
/* Always enable CPU power request */
value = tegra_pmc_readl(pmc, PMC_CNTRL);
@@ -2198,6 +2361,11 @@ static void tegra20_pmc_init(struct tegra_pmc *pmc)
else
value |= PMC_CNTRL_SYSCLK_POLARITY;
+ if (pmc->corereq_high)
+ value &= ~PMC_CNTRL_PWRREQ_POLARITY;
+ else
+ value |= PMC_CNTRL_PWRREQ_POLARITY;
+
/* configure the output polarity while the request is tristated */
tegra_pmc_writel(pmc, value, PMC_CNTRL);
@@ -2205,6 +2373,16 @@ static void tegra20_pmc_init(struct tegra_pmc *pmc)
value = tegra_pmc_readl(pmc, PMC_CNTRL);
value |= PMC_CNTRL_SYSCLK_OE;
tegra_pmc_writel(pmc, value, PMC_CNTRL);
+
+ /* program core timings which are applicable only for suspend state */
+ if (pmc->suspend_mode != TEGRA_SUSPEND_NONE) {
+ osc = DIV_ROUND_UP(pmc->core_osc_time * 8192, 1000000);
+ pmu = DIV_ROUND_UP(pmc->core_pmu_time * 32768, 1000000);
+ off = DIV_ROUND_UP(pmc->core_off_time * 32768, 1000000);
+ tegra_pmc_writel(pmc, ((osc << 8) & 0xff00) | (pmu & 0xff),
+ PMC_COREPWRGOOD_TIMER);
+ tegra_pmc_writel(pmc, off, PMC_COREPWROFF_TIMER);
+ }
}
static void tegra20_pmc_setup_irq_polarity(struct tegra_pmc *pmc,
@@ -2538,6 +2716,10 @@ static const struct pinctrl_pin_desc tegra210_pin_descs[] = {
TEGRA210_IO_PAD_TABLE(TEGRA_IO_PIN_DESC)
};
+static const struct tegra_wake_event tegra210_wake_events[] = {
+ TEGRA_WAKE_IRQ("rtc", 16, 2),
+};
+
static const struct tegra_pmc_soc tegra210_pmc_soc = {
.num_powergates = ARRAY_SIZE(tegra210_powergates),
.powergates = tegra210_powergates,
@@ -2555,10 +2737,14 @@ static const struct tegra_pmc_soc tegra210_pmc_soc = {
.regs = &tegra20_pmc_regs,
.init = tegra20_pmc_init,
.setup_irq_polarity = tegra20_pmc_setup_irq_polarity,
+ .irq_set_wake = tegra210_pmc_irq_set_wake,
+ .irq_set_type = tegra210_pmc_irq_set_type,
.reset_sources = tegra210_reset_sources,
.num_reset_sources = ARRAY_SIZE(tegra210_reset_sources),
.reset_levels = NULL,
.num_reset_levels = 0,
+ .num_wake_events = ARRAY_SIZE(tegra210_wake_events),
+ .wake_events = tegra210_wake_events,
};
#define TEGRA186_IO_PAD_TABLE(_pad) \
@@ -2680,6 +2866,8 @@ static const struct tegra_pmc_soc tegra186_pmc_soc = {
.regs = &tegra186_pmc_regs,
.init = NULL,
.setup_irq_polarity = tegra186_pmc_setup_irq_polarity,
+ .irq_set_wake = tegra186_pmc_irq_set_wake,
+ .irq_set_type = tegra186_pmc_irq_set_type,
.reset_sources = tegra186_reset_sources,
.num_reset_sources = ARRAY_SIZE(tegra186_reset_sources),
.reset_levels = tegra186_reset_levels,
diff --git a/drivers/soc/tegra/regulators-tegra20.c b/drivers/soc/tegra/regulators-tegra20.c
new file mode 100644
index 000000000000..ea0eede48802
--- /dev/null
+++ b/drivers/soc/tegra/regulators-tegra20.c
@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Voltage regulators coupler for NVIDIA Tegra20
+ * Copyright (C) 2019 GRATE-DRIVER project
+ *
+ * Voltage constraints borrowed from downstream kernel sources
+ * Copyright (C) 2010-2011 NVIDIA Corporation
+ */
+
+#define pr_fmt(fmt) "tegra voltage-coupler: " fmt
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/regulator/coupler.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+
+struct tegra_regulator_coupler {
+ struct regulator_coupler coupler;
+ struct regulator_dev *core_rdev;
+ struct regulator_dev *cpu_rdev;
+ struct regulator_dev *rtc_rdev;
+ int core_min_uV;
+};
+
+static inline struct tegra_regulator_coupler *
+to_tegra_coupler(struct regulator_coupler *coupler)
+{
+ return container_of(coupler, struct tegra_regulator_coupler, coupler);
+}
+
+static int tegra20_core_limit(struct tegra_regulator_coupler *tegra,
+ struct regulator_dev *core_rdev)
+{
+ int core_min_uV = 0;
+ int core_max_uV;
+ int core_cur_uV;
+ int err;
+
+ if (tegra->core_min_uV > 0)
+ return tegra->core_min_uV;
+
+ core_cur_uV = regulator_get_voltage_rdev(core_rdev);
+ if (core_cur_uV < 0)
+ return core_cur_uV;
+
+ core_max_uV = max(core_cur_uV, 1200000);
+
+ err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV);
+ if (err)
+ return err;
+
+ /*
+ * Limit minimum CORE voltage to a value left from bootloader or,
+ * if it's unreasonably low value, to the most common 1.2v or to
+ * whatever maximum value defined via board's device-tree.
+ */
+ tegra->core_min_uV = core_max_uV;
+
+ pr_info("core minimum voltage limited to %duV\n", tegra->core_min_uV);
+
+ return tegra->core_min_uV;
+}
+
+static int tegra20_core_rtc_max_spread(struct regulator_dev *core_rdev,
+ struct regulator_dev *rtc_rdev)
+{
+ struct coupling_desc *c_desc = &core_rdev->coupling_desc;
+ struct regulator_dev *rdev;
+ int max_spread;
+ unsigned int i;
+
+ for (i = 1; i < c_desc->n_coupled; i++) {
+ max_spread = core_rdev->constraints->max_spread[i - 1];
+ rdev = c_desc->coupled_rdevs[i];
+
+ if (rdev == rtc_rdev && max_spread)
+ return max_spread;
+ }
+
+ pr_err_once("rtc-core max-spread is undefined in device-tree\n");
+
+ return 150000;
+}
+
+static int tegra20_core_rtc_update(struct tegra_regulator_coupler *tegra,
+ struct regulator_dev *core_rdev,
+ struct regulator_dev *rtc_rdev,
+ int cpu_uV, int cpu_min_uV)
+{
+ int core_min_uV, core_max_uV = INT_MAX;
+ int rtc_min_uV, rtc_max_uV = INT_MAX;
+ int core_target_uV;
+ int rtc_target_uV;
+ int max_spread;
+ int core_uV;
+ int rtc_uV;
+ int err;
+
+ /*
+ * RTC and CORE voltages should be no more than 170mV from each other,
+ * CPU should be below RTC and CORE by at least 120mV. This applies
+ * to all Tegra20 SoC's.
+ */
+ max_spread = tegra20_core_rtc_max_spread(core_rdev, rtc_rdev);
+
+ /*
+ * The core voltage scaling is currently not hooked up in drivers,
+ * hence we will limit the minimum core voltage to a reasonable value.
+ * This should be good enough for the time being.
+ */
+ core_min_uV = tegra20_core_limit(tegra, core_rdev);
+ if (core_min_uV < 0)
+ return core_min_uV;
+
+ err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV);
+ if (err)
+ return err;
+
+ err = regulator_check_consumers(core_rdev, &core_min_uV, &core_max_uV,
+ PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ core_uV = regulator_get_voltage_rdev(core_rdev);
+ if (core_uV < 0)
+ return core_uV;
+
+ core_min_uV = max(cpu_min_uV + 125000, core_min_uV);
+ if (core_min_uV > core_max_uV)
+ return -EINVAL;
+
+ if (cpu_uV + 120000 > core_uV)
+ pr_err("core-cpu voltage constraint violated: %d %d\n",
+ core_uV, cpu_uV + 120000);
+
+ rtc_uV = regulator_get_voltage_rdev(rtc_rdev);
+ if (rtc_uV < 0)
+ return rtc_uV;
+
+ if (cpu_uV + 120000 > rtc_uV)
+ pr_err("rtc-cpu voltage constraint violated: %d %d\n",
+ rtc_uV, cpu_uV + 120000);
+
+ if (abs(core_uV - rtc_uV) > 170000)
+ pr_err("core-rtc voltage constraint violated: %d %d\n",
+ core_uV, rtc_uV);
+
+ rtc_min_uV = max(cpu_min_uV + 125000, core_min_uV - max_spread);
+
+ err = regulator_check_voltage(rtc_rdev, &rtc_min_uV, &rtc_max_uV);
+ if (err)
+ return err;
+
+ while (core_uV != core_min_uV || rtc_uV != rtc_min_uV) {
+ if (core_uV < core_min_uV) {
+ core_target_uV = min(core_uV + max_spread, core_min_uV);
+ core_target_uV = min(rtc_uV + max_spread, core_target_uV);
+ } else {
+ core_target_uV = max(core_uV - max_spread, core_min_uV);
+ core_target_uV = max(rtc_uV - max_spread, core_target_uV);
+ }
+
+ err = regulator_set_voltage_rdev(core_rdev,
+ core_target_uV,
+ core_max_uV,
+ PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ core_uV = core_target_uV;
+
+ if (rtc_uV < rtc_min_uV) {
+ rtc_target_uV = min(rtc_uV + max_spread, rtc_min_uV);
+ rtc_target_uV = min(core_uV + max_spread, rtc_target_uV);
+ } else {
+ rtc_target_uV = max(rtc_uV - max_spread, rtc_min_uV);
+ rtc_target_uV = max(core_uV - max_spread, rtc_target_uV);
+ }
+
+ err = regulator_set_voltage_rdev(rtc_rdev,
+ rtc_target_uV,
+ rtc_max_uV,
+ PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ rtc_uV = rtc_target_uV;
+ }
+
+ return 0;
+}
+
+static int tegra20_core_voltage_update(struct tegra_regulator_coupler *tegra,
+ struct regulator_dev *cpu_rdev,
+ struct regulator_dev *core_rdev,
+ struct regulator_dev *rtc_rdev)
+{
+ int cpu_uV;
+
+ cpu_uV = regulator_get_voltage_rdev(cpu_rdev);
+ if (cpu_uV < 0)
+ return cpu_uV;
+
+ return tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
+ cpu_uV, cpu_uV);
+}
+
+static int tegra20_cpu_voltage_update(struct tegra_regulator_coupler *tegra,
+ struct regulator_dev *cpu_rdev,
+ struct regulator_dev *core_rdev,
+ struct regulator_dev *rtc_rdev)
+{
+ int cpu_min_uV_consumers = 0;
+ int cpu_max_uV = INT_MAX;
+ int cpu_min_uV = 0;
+ int cpu_uV;
+ int err;
+
+ err = regulator_check_voltage(cpu_rdev, &cpu_min_uV, &cpu_max_uV);
+ if (err)
+ return err;
+
+ err = regulator_check_consumers(cpu_rdev, &cpu_min_uV, &cpu_max_uV,
+ PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ err = regulator_check_consumers(cpu_rdev, &cpu_min_uV_consumers,
+ &cpu_max_uV, PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ cpu_uV = regulator_get_voltage_rdev(cpu_rdev);
+ if (cpu_uV < 0)
+ return cpu_uV;
+
+ /*
+ * CPU's regulator may not have any consumers, hence the voltage
+ * must not be changed in that case because CPU simply won't
+ * survive the voltage drop if it's running on a higher frequency.
+ */
+ if (!cpu_min_uV_consumers)
+ cpu_min_uV = cpu_uV;
+
+ if (cpu_min_uV > cpu_uV) {
+ err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
+ cpu_uV, cpu_min_uV);
+ if (err)
+ return err;
+
+ err = regulator_set_voltage_rdev(cpu_rdev, cpu_min_uV,
+ cpu_max_uV, PM_SUSPEND_ON);
+ if (err)
+ return err;
+ } else if (cpu_min_uV < cpu_uV) {
+ err = regulator_set_voltage_rdev(cpu_rdev, cpu_min_uV,
+ cpu_max_uV, PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ err = tegra20_core_rtc_update(tegra, core_rdev, rtc_rdev,
+ cpu_uV, cpu_min_uV);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int tegra20_regulator_balance_voltage(struct regulator_coupler *coupler,
+ struct regulator_dev *rdev,
+ suspend_state_t state)
+{
+ struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
+ struct regulator_dev *core_rdev = tegra->core_rdev;
+ struct regulator_dev *cpu_rdev = tegra->cpu_rdev;
+ struct regulator_dev *rtc_rdev = tegra->rtc_rdev;
+
+ if ((core_rdev != rdev && cpu_rdev != rdev && rtc_rdev != rdev) ||
+ state != PM_SUSPEND_ON) {
+ pr_err("regulators are not coupled properly\n");
+ return -EINVAL;
+ }
+
+ if (rdev == cpu_rdev)
+ return tegra20_cpu_voltage_update(tegra, cpu_rdev,
+ core_rdev, rtc_rdev);
+
+ if (rdev == core_rdev)
+ return tegra20_core_voltage_update(tegra, cpu_rdev,
+ core_rdev, rtc_rdev);
+
+ pr_err("changing %s voltage not permitted\n", rdev_get_name(rtc_rdev));
+
+ return -EPERM;
+}
+
+static int tegra20_regulator_attach(struct regulator_coupler *coupler,
+ struct regulator_dev *rdev)
+{
+ struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
+ struct device_node *np = rdev->dev.of_node;
+
+ if (of_property_read_bool(np, "nvidia,tegra-core-regulator") &&
+ !tegra->core_rdev) {
+ tegra->core_rdev = rdev;
+ return 0;
+ }
+
+ if (of_property_read_bool(np, "nvidia,tegra-rtc-regulator") &&
+ !tegra->rtc_rdev) {
+ tegra->rtc_rdev = rdev;
+ return 0;
+ }
+
+ if (of_property_read_bool(np, "nvidia,tegra-cpu-regulator") &&
+ !tegra->cpu_rdev) {
+ tegra->cpu_rdev = rdev;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int tegra20_regulator_detach(struct regulator_coupler *coupler,
+ struct regulator_dev *rdev)
+{
+ struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
+
+ if (tegra->core_rdev == rdev) {
+ tegra->core_rdev = NULL;
+ return 0;
+ }
+
+ if (tegra->rtc_rdev == rdev) {
+ tegra->rtc_rdev = NULL;
+ return 0;
+ }
+
+ if (tegra->cpu_rdev == rdev) {
+ tegra->cpu_rdev = NULL;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static struct tegra_regulator_coupler tegra20_coupler = {
+ .coupler = {
+ .attach_regulator = tegra20_regulator_attach,
+ .detach_regulator = tegra20_regulator_detach,
+ .balance_voltage = tegra20_regulator_balance_voltage,
+ },
+};
+
+static int __init tegra_regulator_coupler_init(void)
+{
+ if (!of_machine_is_compatible("nvidia,tegra20"))
+ return 0;
+
+ return regulator_coupler_register(&tegra20_coupler.coupler);
+}
+arch_initcall(tegra_regulator_coupler_init);
diff --git a/drivers/soc/tegra/regulators-tegra30.c b/drivers/soc/tegra/regulators-tegra30.c
new file mode 100644
index 000000000000..8e623ff18e70
--- /dev/null
+++ b/drivers/soc/tegra/regulators-tegra30.c
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Voltage regulators coupler for NVIDIA Tegra30
+ * Copyright (C) 2019 GRATE-DRIVER project
+ *
+ * Voltage constraints borrowed from downstream kernel sources
+ * Copyright (C) 2010-2011 NVIDIA Corporation
+ */
+
+#define pr_fmt(fmt) "tegra voltage-coupler: " fmt
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/regulator/coupler.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+
+#include <soc/tegra/fuse.h>
+
+struct tegra_regulator_coupler {
+ struct regulator_coupler coupler;
+ struct regulator_dev *core_rdev;
+ struct regulator_dev *cpu_rdev;
+ int core_min_uV;
+};
+
+static inline struct tegra_regulator_coupler *
+to_tegra_coupler(struct regulator_coupler *coupler)
+{
+ return container_of(coupler, struct tegra_regulator_coupler, coupler);
+}
+
+static int tegra30_core_limit(struct tegra_regulator_coupler *tegra,
+ struct regulator_dev *core_rdev)
+{
+ int core_min_uV = 0;
+ int core_max_uV;
+ int core_cur_uV;
+ int err;
+
+ if (tegra->core_min_uV > 0)
+ return tegra->core_min_uV;
+
+ core_cur_uV = regulator_get_voltage_rdev(core_rdev);
+ if (core_cur_uV < 0)
+ return core_cur_uV;
+
+ core_max_uV = max(core_cur_uV, 1200000);
+
+ err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV);
+ if (err)
+ return err;
+
+ /*
+ * Limit minimum CORE voltage to a value left from bootloader or,
+ * if it's unreasonably low value, to the most common 1.2v or to
+ * whatever maximum value defined via board's device-tree.
+ */
+ tegra->core_min_uV = core_max_uV;
+
+ pr_info("core minimum voltage limited to %duV\n", tegra->core_min_uV);
+
+ return tegra->core_min_uV;
+}
+
+static int tegra30_core_cpu_limit(int cpu_uV)
+{
+ if (cpu_uV < 800000)
+ return 950000;
+
+ if (cpu_uV < 900000)
+ return 1000000;
+
+ if (cpu_uV < 1000000)
+ return 1100000;
+
+ if (cpu_uV < 1100000)
+ return 1200000;
+
+ if (cpu_uV < 1250000) {
+ switch (tegra_sku_info.cpu_speedo_id) {
+ case 0 ... 1:
+ case 4:
+ case 7 ... 8:
+ return 1200000;
+
+ default:
+ return 1300000;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int tegra30_voltage_update(struct tegra_regulator_coupler *tegra,
+ struct regulator_dev *cpu_rdev,
+ struct regulator_dev *core_rdev)
+{
+ int core_min_uV, core_max_uV = INT_MAX;
+ int cpu_min_uV, cpu_max_uV = INT_MAX;
+ int cpu_min_uV_consumers = 0;
+ int core_min_limited_uV;
+ int core_target_uV;
+ int cpu_target_uV;
+ int core_max_step;
+ int cpu_max_step;
+ int max_spread;
+ int core_uV;
+ int cpu_uV;
+ int err;
+
+ /*
+ * CPU voltage should not got lower than 300mV from the CORE.
+ * CPU voltage should stay below the CORE by 100mV+, depending
+ * by the CORE voltage. This applies to all Tegra30 SoC's.
+ */
+ max_spread = cpu_rdev->constraints->max_spread[0];
+ cpu_max_step = cpu_rdev->constraints->max_uV_step;
+ core_max_step = core_rdev->constraints->max_uV_step;
+
+ if (!max_spread) {
+ pr_err_once("cpu-core max-spread is undefined in device-tree\n");
+ max_spread = 300000;
+ }
+
+ if (!cpu_max_step) {
+ pr_err_once("cpu max-step is undefined in device-tree\n");
+ cpu_max_step = 150000;
+ }
+
+ if (!core_max_step) {
+ pr_err_once("core max-step is undefined in device-tree\n");
+ core_max_step = 150000;
+ }
+
+ /*
+ * The CORE voltage scaling is currently not hooked up in drivers,
+ * hence we will limit the minimum CORE voltage to a reasonable value.
+ * This should be good enough for the time being.
+ */
+ core_min_uV = tegra30_core_limit(tegra, core_rdev);
+ if (core_min_uV < 0)
+ return core_min_uV;
+
+ err = regulator_check_consumers(core_rdev, &core_min_uV, &core_max_uV,
+ PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ core_uV = regulator_get_voltage_rdev(core_rdev);
+ if (core_uV < 0)
+ return core_uV;
+
+ cpu_min_uV = core_min_uV - max_spread;
+
+ err = regulator_check_consumers(cpu_rdev, &cpu_min_uV, &cpu_max_uV,
+ PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ err = regulator_check_consumers(cpu_rdev, &cpu_min_uV_consumers,
+ &cpu_max_uV, PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ err = regulator_check_voltage(cpu_rdev, &cpu_min_uV, &cpu_max_uV);
+ if (err)
+ return err;
+
+ cpu_uV = regulator_get_voltage_rdev(cpu_rdev);
+ if (cpu_uV < 0)
+ return cpu_uV;
+
+ /*
+ * CPU's regulator may not have any consumers, hence the voltage
+ * must not be changed in that case because CPU simply won't
+ * survive the voltage drop if it's running on a higher frequency.
+ */
+ if (!cpu_min_uV_consumers)
+ cpu_min_uV = cpu_uV;
+
+ /*
+ * Bootloader shall set up voltages correctly, but if it
+ * happens that there is a violation, then try to fix it
+ * at first.
+ */
+ core_min_limited_uV = tegra30_core_cpu_limit(cpu_uV);
+ if (core_min_limited_uV < 0)
+ return core_min_limited_uV;
+
+ core_min_uV = max(core_min_uV, tegra30_core_cpu_limit(cpu_min_uV));
+
+ err = regulator_check_voltage(core_rdev, &core_min_uV, &core_max_uV);
+ if (err)
+ return err;
+
+ if (core_min_limited_uV > core_uV) {
+ pr_err("core voltage constraint violated: %d %d %d\n",
+ core_uV, core_min_limited_uV, cpu_uV);
+ goto update_core;
+ }
+
+ while (cpu_uV != cpu_min_uV || core_uV != core_min_uV) {
+ if (cpu_uV < cpu_min_uV) {
+ cpu_target_uV = min(cpu_uV + cpu_max_step, cpu_min_uV);
+ } else {
+ cpu_target_uV = max(cpu_uV - cpu_max_step, cpu_min_uV);
+ cpu_target_uV = max(core_uV - max_spread, cpu_target_uV);
+ }
+
+ err = regulator_set_voltage_rdev(cpu_rdev,
+ cpu_target_uV,
+ cpu_max_uV,
+ PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ cpu_uV = cpu_target_uV;
+update_core:
+ core_min_limited_uV = tegra30_core_cpu_limit(cpu_uV);
+ if (core_min_limited_uV < 0)
+ return core_min_limited_uV;
+
+ core_target_uV = max(core_min_limited_uV, core_min_uV);
+
+ if (core_uV < core_target_uV) {
+ core_target_uV = min(core_target_uV, core_uV + core_max_step);
+ core_target_uV = min(core_target_uV, cpu_uV + max_spread);
+ } else {
+ core_target_uV = max(core_target_uV, core_uV - core_max_step);
+ }
+
+ err = regulator_set_voltage_rdev(core_rdev,
+ core_target_uV,
+ core_max_uV,
+ PM_SUSPEND_ON);
+ if (err)
+ return err;
+
+ core_uV = core_target_uV;
+ }
+
+ return 0;
+}
+
+static int tegra30_regulator_balance_voltage(struct regulator_coupler *coupler,
+ struct regulator_dev *rdev,
+ suspend_state_t state)
+{
+ struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
+ struct regulator_dev *core_rdev = tegra->core_rdev;
+ struct regulator_dev *cpu_rdev = tegra->cpu_rdev;
+
+ if ((core_rdev != rdev && cpu_rdev != rdev) || state != PM_SUSPEND_ON) {
+ pr_err("regulators are not coupled properly\n");
+ return -EINVAL;
+ }
+
+ return tegra30_voltage_update(tegra, cpu_rdev, core_rdev);
+}
+
+static int tegra30_regulator_attach(struct regulator_coupler *coupler,
+ struct regulator_dev *rdev)
+{
+ struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
+ struct device_node *np = rdev->dev.of_node;
+
+ if (of_property_read_bool(np, "nvidia,tegra-core-regulator") &&
+ !tegra->core_rdev) {
+ tegra->core_rdev = rdev;
+ return 0;
+ }
+
+ if (of_property_read_bool(np, "nvidia,tegra-cpu-regulator") &&
+ !tegra->cpu_rdev) {
+ tegra->cpu_rdev = rdev;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int tegra30_regulator_detach(struct regulator_coupler *coupler,
+ struct regulator_dev *rdev)
+{
+ struct tegra_regulator_coupler *tegra = to_tegra_coupler(coupler);
+
+ if (tegra->core_rdev == rdev) {
+ tegra->core_rdev = NULL;
+ return 0;
+ }
+
+ if (tegra->cpu_rdev == rdev) {
+ tegra->cpu_rdev = NULL;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static struct tegra_regulator_coupler tegra30_coupler = {
+ .coupler = {
+ .attach_regulator = tegra30_regulator_attach,
+ .detach_regulator = tegra30_regulator_detach,
+ .balance_voltage = tegra30_regulator_balance_voltage,
+ },
+};
+
+static int __init tegra_regulator_coupler_init(void)
+{
+ if (!of_machine_is_compatible("nvidia,tegra30"))
+ return 0;
+
+ return regulator_coupler_register(&tegra30_coupler.coupler);
+}
+arch_initcall(tegra_regulator_coupler_init);
diff --git a/drivers/soc/ti/Makefile b/drivers/soc/ti/Makefile
index b3868d392d4f..788b5cd1e180 100644
--- a/drivers/soc/ti/Makefile
+++ b/drivers/soc/ti/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_KEYSTONE_NAVIGATOR_QMSS) += knav_qmss.o
knav_qmss-y := knav_qmss_queue.o knav_qmss_acc.o
obj-$(CONFIG_KEYSTONE_NAVIGATOR_DMA) += knav_dma.o
obj-$(CONFIG_AMX3_PM) += pm33xx.o
+obj-$(CONFIG_ARCH_OMAP2PLUS) += omap_prm.o
obj-$(CONFIG_WKUP_M3_IPC) += wkup_m3_ipc.o
obj-$(CONFIG_TI_SCI_PM_DOMAINS) += ti_sci_pm_domains.o
obj-$(CONFIG_TI_SCI_INTA_MSI_DOMAIN) += ti_sci_inta_msi.o
diff --git a/drivers/soc/ti/omap_prm.c b/drivers/soc/ti/omap_prm.c
new file mode 100644
index 000000000000..96c6f777519c
--- /dev/null
+++ b/drivers/soc/ti/omap_prm.c
@@ -0,0 +1,391 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * OMAP2+ PRM driver
+ *
+ * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+ * Tero Kristo <t-kristo@ti.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reset-controller.h>
+#include <linux/delay.h>
+
+#include <linux/platform_data/ti-prm.h>
+
+struct omap_rst_map {
+ s8 rst;
+ s8 st;
+};
+
+struct omap_prm_data {
+ u32 base;
+ const char *name;
+ const char *clkdm_name;
+ u16 rstctrl;
+ u16 rstst;
+ const struct omap_rst_map *rstmap;
+ u8 flags;
+};
+
+struct omap_prm {
+ const struct omap_prm_data *data;
+ void __iomem *base;
+};
+
+struct omap_reset_data {
+ struct reset_controller_dev rcdev;
+ struct omap_prm *prm;
+ u32 mask;
+ spinlock_t lock;
+ struct clockdomain *clkdm;
+ struct device *dev;
+};
+
+#define to_omap_reset_data(p) container_of((p), struct omap_reset_data, rcdev)
+
+#define OMAP_MAX_RESETS 8
+#define OMAP_RESET_MAX_WAIT 10000
+
+#define OMAP_PRM_HAS_RSTCTRL BIT(0)
+#define OMAP_PRM_HAS_RSTST BIT(1)
+#define OMAP_PRM_HAS_NO_CLKDM BIT(2)
+
+#define OMAP_PRM_HAS_RESETS (OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_RSTST)
+
+static const struct omap_rst_map rst_map_0[] = {
+ { .rst = 0, .st = 0 },
+ { .rst = -1 },
+};
+
+static const struct omap_rst_map rst_map_01[] = {
+ { .rst = 0, .st = 0 },
+ { .rst = 1, .st = 1 },
+ { .rst = -1 },
+};
+
+static const struct omap_rst_map rst_map_012[] = {
+ { .rst = 0, .st = 0 },
+ { .rst = 1, .st = 1 },
+ { .rst = 2, .st = 2 },
+ { .rst = -1 },
+};
+
+static const struct omap_prm_data omap4_prm_data[] = {
+ { .name = "tesla", .base = 0x4a306400, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_01 },
+ { .name = "core", .base = 0x4a306700, .rstctrl = 0x210, .rstst = 0x214, .clkdm_name = "ducati", .rstmap = rst_map_012 },
+ { .name = "ivahd", .base = 0x4a306f00, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_012 },
+ { .name = "device", .base = 0x4a307b00, .rstctrl = 0x0, .rstst = 0x4, .rstmap = rst_map_01, .flags = OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_NO_CLKDM },
+ { },
+};
+
+static const struct omap_prm_data omap5_prm_data[] = {
+ { .name = "dsp", .base = 0x4ae06400, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_01 },
+ { .name = "core", .base = 0x4ae06700, .rstctrl = 0x210, .rstst = 0x214, .clkdm_name = "ipu", .rstmap = rst_map_012 },
+ { .name = "iva", .base = 0x4ae07200, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_012 },
+ { .name = "device", .base = 0x4ae07c00, .rstctrl = 0x0, .rstst = 0x4, .rstmap = rst_map_01, .flags = OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_NO_CLKDM },
+ { },
+};
+
+static const struct omap_prm_data dra7_prm_data[] = {
+ { .name = "dsp1", .base = 0x4ae06400, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_01 },
+ { .name = "ipu", .base = 0x4ae06500, .rstctrl = 0x10, .rstst = 0x14, .clkdm_name = "ipu1", .rstmap = rst_map_012 },
+ { .name = "core", .base = 0x4ae06700, .rstctrl = 0x210, .rstst = 0x214, .clkdm_name = "ipu2", .rstmap = rst_map_012 },
+ { .name = "iva", .base = 0x4ae06f00, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_012 },
+ { .name = "dsp2", .base = 0x4ae07b00, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_01 },
+ { .name = "eve1", .base = 0x4ae07b40, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_01 },
+ { .name = "eve2", .base = 0x4ae07b80, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_01 },
+ { .name = "eve3", .base = 0x4ae07bc0, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_01 },
+ { .name = "eve4", .base = 0x4ae07c00, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_01 },
+ { },
+};
+
+static const struct omap_rst_map am3_per_rst_map[] = {
+ { .rst = 1 },
+ { .rst = -1 },
+};
+
+static const struct omap_rst_map am3_wkup_rst_map[] = {
+ { .rst = 3, .st = 5 },
+ { .rst = -1 },
+};
+
+static const struct omap_prm_data am3_prm_data[] = {
+ { .name = "per", .base = 0x44e00c00, .rstctrl = 0x0, .rstmap = am3_per_rst_map, .flags = OMAP_PRM_HAS_RSTCTRL, .clkdm_name = "pruss_ocp" },
+ { .name = "wkup", .base = 0x44e00d00, .rstctrl = 0x0, .rstst = 0xc, .rstmap = am3_wkup_rst_map, .flags = OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_NO_CLKDM },
+ { .name = "device", .base = 0x44e00f00, .rstctrl = 0x0, .rstst = 0x8, .rstmap = rst_map_01, .flags = OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_NO_CLKDM },
+ { .name = "gfx", .base = 0x44e01100, .rstctrl = 0x4, .rstst = 0x14, .rstmap = rst_map_0, .clkdm_name = "gfx_l3" },
+ { },
+};
+
+static const struct omap_rst_map am4_per_rst_map[] = {
+ { .rst = 1, .st = 0 },
+ { .rst = -1 },
+};
+
+static const struct omap_rst_map am4_device_rst_map[] = {
+ { .rst = 0, .st = 1 },
+ { .rst = 1, .st = 0 },
+ { .rst = -1 },
+};
+
+static const struct omap_prm_data am4_prm_data[] = {
+ { .name = "gfx", .base = 0x44df0400, .rstctrl = 0x10, .rstst = 0x14, .rstmap = rst_map_0, .clkdm_name = "gfx_l3" },
+ { .name = "per", .base = 0x44df0800, .rstctrl = 0x10, .rstst = 0x14, .rstmap = am4_per_rst_map, .clkdm_name = "pruss_ocp" },
+ { .name = "wkup", .base = 0x44df2000, .rstctrl = 0x10, .rstst = 0x14, .rstmap = am3_wkup_rst_map, .flags = OMAP_PRM_HAS_NO_CLKDM },
+ { .name = "device", .base = 0x44df4000, .rstctrl = 0x0, .rstst = 0x4, .rstmap = am4_device_rst_map, .flags = OMAP_PRM_HAS_RSTCTRL | OMAP_PRM_HAS_NO_CLKDM },
+ { },
+};
+
+static const struct of_device_id omap_prm_id_table[] = {
+ { .compatible = "ti,omap4-prm-inst", .data = omap4_prm_data },
+ { .compatible = "ti,omap5-prm-inst", .data = omap5_prm_data },
+ { .compatible = "ti,dra7-prm-inst", .data = dra7_prm_data },
+ { .compatible = "ti,am3-prm-inst", .data = am3_prm_data },
+ { .compatible = "ti,am4-prm-inst", .data = am4_prm_data },
+ { },
+};
+
+static bool _is_valid_reset(struct omap_reset_data *reset, unsigned long id)
+{
+ if (reset->mask & BIT(id))
+ return true;
+
+ return false;
+}
+
+static int omap_reset_get_st_bit(struct omap_reset_data *reset,
+ unsigned long id)
+{
+ const struct omap_rst_map *map = reset->prm->data->rstmap;
+
+ while (map->rst >= 0) {
+ if (map->rst == id)
+ return map->st;
+
+ map++;
+ }
+
+ return id;
+}
+
+static int omap_reset_status(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct omap_reset_data *reset = to_omap_reset_data(rcdev);
+ u32 v;
+ int st_bit = omap_reset_get_st_bit(reset, id);
+ bool has_rstst = reset->prm->data->rstst ||
+ (reset->prm->data->flags & OMAP_PRM_HAS_RSTST);
+
+ /* Check if we have rstst */
+ if (!has_rstst)
+ return -ENOTSUPP;
+
+ /* Check if hw reset line is asserted */
+ v = readl_relaxed(reset->prm->base + reset->prm->data->rstctrl);
+ if (v & BIT(id))
+ return 1;
+
+ /*
+ * Check reset status, high value means reset sequence has been
+ * completed successfully so we can return 0 here (reset deasserted)
+ */
+ v = readl_relaxed(reset->prm->base + reset->prm->data->rstst);
+ v >>= st_bit;
+ v &= 1;
+
+ return !v;
+}
+
+static int omap_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct omap_reset_data *reset = to_omap_reset_data(rcdev);
+ u32 v;
+ unsigned long flags;
+
+ /* assert the reset control line */
+ spin_lock_irqsave(&reset->lock, flags);
+ v = readl_relaxed(reset->prm->base + reset->prm->data->rstctrl);
+ v |= 1 << id;
+ writel_relaxed(v, reset->prm->base + reset->prm->data->rstctrl);
+ spin_unlock_irqrestore(&reset->lock, flags);
+
+ return 0;
+}
+
+static int omap_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct omap_reset_data *reset = to_omap_reset_data(rcdev);
+ u32 v;
+ int st_bit;
+ bool has_rstst;
+ unsigned long flags;
+ struct ti_prm_platform_data *pdata = dev_get_platdata(reset->dev);
+ int ret = 0;
+
+ has_rstst = reset->prm->data->rstst ||
+ (reset->prm->data->flags & OMAP_PRM_HAS_RSTST);
+
+ if (has_rstst) {
+ st_bit = omap_reset_get_st_bit(reset, id);
+
+ /* Clear the reset status by writing 1 to the status bit */
+ v = 1 << st_bit;
+ writel_relaxed(v, reset->prm->base + reset->prm->data->rstst);
+ }
+
+ if (reset->clkdm)
+ pdata->clkdm_deny_idle(reset->clkdm);
+
+ /* de-assert the reset control line */
+ spin_lock_irqsave(&reset->lock, flags);
+ v = readl_relaxed(reset->prm->base + reset->prm->data->rstctrl);
+ v &= ~(1 << id);
+ writel_relaxed(v, reset->prm->base + reset->prm->data->rstctrl);
+ spin_unlock_irqrestore(&reset->lock, flags);
+
+ if (!has_rstst)
+ goto exit;
+
+ /* wait for the status to be set */
+ ret = readl_relaxed_poll_timeout(reset->prm->base +
+ reset->prm->data->rstst,
+ v, v & BIT(st_bit), 1,
+ OMAP_RESET_MAX_WAIT);
+ if (ret)
+ pr_err("%s: timedout waiting for %s:%lu\n", __func__,
+ reset->prm->data->name, id);
+
+exit:
+ if (reset->clkdm)
+ pdata->clkdm_allow_idle(reset->clkdm);
+
+ return ret;
+}
+
+static const struct reset_control_ops omap_reset_ops = {
+ .assert = omap_reset_assert,
+ .deassert = omap_reset_deassert,
+ .status = omap_reset_status,
+};
+
+static int omap_prm_reset_xlate(struct reset_controller_dev *rcdev,
+ const struct of_phandle_args *reset_spec)
+{
+ struct omap_reset_data *reset = to_omap_reset_data(rcdev);
+
+ if (!_is_valid_reset(reset, reset_spec->args[0]))
+ return -EINVAL;
+
+ return reset_spec->args[0];
+}
+
+static int omap_prm_reset_init(struct platform_device *pdev,
+ struct omap_prm *prm)
+{
+ struct omap_reset_data *reset;
+ const struct omap_rst_map *map;
+ struct ti_prm_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ char buf[32];
+
+ /*
+ * Check if we have controllable resets. If either rstctrl is non-zero
+ * or OMAP_PRM_HAS_RSTCTRL flag is set, we have reset control register
+ * for the domain.
+ */
+ if (!prm->data->rstctrl && !(prm->data->flags & OMAP_PRM_HAS_RSTCTRL))
+ return 0;
+
+ /* Check if we have the pdata callbacks in place */
+ if (!pdata || !pdata->clkdm_lookup || !pdata->clkdm_deny_idle ||
+ !pdata->clkdm_allow_idle)
+ return -EINVAL;
+
+ map = prm->data->rstmap;
+ if (!map)
+ return -EINVAL;
+
+ reset = devm_kzalloc(&pdev->dev, sizeof(*reset), GFP_KERNEL);
+ if (!reset)
+ return -ENOMEM;
+
+ reset->rcdev.owner = THIS_MODULE;
+ reset->rcdev.ops = &omap_reset_ops;
+ reset->rcdev.of_node = pdev->dev.of_node;
+ reset->rcdev.nr_resets = OMAP_MAX_RESETS;
+ reset->rcdev.of_xlate = omap_prm_reset_xlate;
+ reset->rcdev.of_reset_n_cells = 1;
+ reset->dev = &pdev->dev;
+ spin_lock_init(&reset->lock);
+
+ reset->prm = prm;
+
+ sprintf(buf, "%s_clkdm", prm->data->clkdm_name ? prm->data->clkdm_name :
+ prm->data->name);
+
+ if (!(prm->data->flags & OMAP_PRM_HAS_NO_CLKDM)) {
+ reset->clkdm = pdata->clkdm_lookup(buf);
+ if (!reset->clkdm)
+ return -EINVAL;
+ }
+
+ while (map->rst >= 0) {
+ reset->mask |= BIT(map->rst);
+ map++;
+ }
+
+ return devm_reset_controller_register(&pdev->dev, &reset->rcdev);
+}
+
+static int omap_prm_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ const struct omap_prm_data *data;
+ struct omap_prm *prm;
+ const struct of_device_id *match;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ match = of_match_device(omap_prm_id_table, &pdev->dev);
+ if (!match)
+ return -ENOTSUPP;
+
+ prm = devm_kzalloc(&pdev->dev, sizeof(*prm), GFP_KERNEL);
+ if (!prm)
+ return -ENOMEM;
+
+ data = match->data;
+
+ while (data->base != res->start) {
+ if (!data->base)
+ return -EINVAL;
+ data++;
+ }
+
+ prm->data = data;
+
+ prm->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(prm->base))
+ return PTR_ERR(prm->base);
+
+ return omap_prm_reset_init(pdev, prm);
+}
+
+static struct platform_driver omap_prm_driver = {
+ .probe = omap_prm_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = omap_prm_id_table,
+ },
+};
+builtin_platform_driver(omap_prm_driver);
diff --git a/drivers/soc/xilinx/zynqmp_pm_domains.c b/drivers/soc/xilinx/zynqmp_pm_domains.c
index 600f57cf0c2e..23d90cb12ba9 100644
--- a/drivers/soc/xilinx/zynqmp_pm_domains.c
+++ b/drivers/soc/xilinx/zynqmp_pm_domains.c
@@ -2,7 +2,7 @@
/*
* ZynqMP Generic PM domain support
*
- * Copyright (C) 2015-2018 Xilinx, Inc.
+ * Copyright (C) 2015-2019 Xilinx, Inc.
*
* Davorin Mista <davorin.mista@aggios.com>
* Jolly Shah <jollys@xilinx.com>
@@ -25,6 +25,8 @@
static const struct zynqmp_eemi_ops *eemi_ops;
+static int min_capability;
+
/**
* struct zynqmp_pm_domain - Wrapper around struct generic_pm_domain
* @gpd: Generic power domain
@@ -106,7 +108,7 @@ static int zynqmp_gpd_power_off(struct generic_pm_domain *domain)
int ret;
struct pm_domain_data *pdd, *tmp;
struct zynqmp_pm_domain *pd;
- u32 capabilities = 0;
+ u32 capabilities = min_capability;
bool may_wakeup;
if (!eemi_ops->set_requirement)
@@ -283,6 +285,10 @@ static int zynqmp_gpd_probe(struct platform_device *pdev)
if (!domains)
return -ENOMEM;
+ if (!of_device_is_compatible(dev->parent->of_node,
+ "xlnx,zynqmp-firmware"))
+ min_capability = ZYNQMP_PM_CAPABILITY_UNUSABLE;
+
for (i = 0; i < ZYNQMP_NUM_DOMAINS; i++, pd++) {
pd->node_id = 0;
pd->gpd.name = kasprintf(GFP_KERNEL, "domain%d", i);