In IO+DDR the DDR is kept in self-refresh while the SoC cores are
powered off completely. During boot the normal initialization routine of
DDR is slightly different to exit self-refresh and keep the DDR contents.

Signed-off-by: Markus Schneider-Pargmann (TI.com) <[email protected]>
---
 drivers/ram/k3-ddrss/Makefile       |   1 +
 drivers/ram/k3-ddrss/k3-ddrss-lpm.c | 181 ++++++++++++++++++++++++++++++++++++
 drivers/ram/k3-ddrss/k3-ddrss-lpm.h |  20 ++++
 drivers/ram/k3-ddrss/k3-ddrss.c     |  26 +++++-
 4 files changed, 227 insertions(+), 1 deletion(-)

diff --git a/drivers/ram/k3-ddrss/Makefile b/drivers/ram/k3-ddrss/Makefile
index 
823d1887178174021778296e6626cadfd11170de..8a2ece1c27eeae51593467259337483a852f5f85
 100644
--- a/drivers/ram/k3-ddrss/Makefile
+++ b/drivers/ram/k3-ddrss/Makefile
@@ -4,6 +4,7 @@
 #
 
 obj-$(CONFIG_K3_DDRSS) += k3-ddrss.o
+obj-$(CONFIG_K3_DDRSS) += k3-ddrss-lpm.o
 obj-$(CONFIG_K3_DDRSS) += lpddr4_obj_if.o
 obj-$(CONFIG_K3_DDRSS) += lpddr4.o
 ccflags-$(CONFIG_K3_DDRSS) += -Idrivers/ram/k3-ddrss/
diff --git a/drivers/ram/k3-ddrss/k3-ddrss-lpm.c 
b/drivers/ram/k3-ddrss/k3-ddrss-lpm.c
new file mode 100644
index 
0000000000000000000000000000000000000000..20181d0a876dbb723920efb78ed980948eec117f
--- /dev/null
+++ b/drivers/ram/k3-ddrss/k3-ddrss-lpm.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * K3 DDRSS LPM specific functions
+ *
+ * Copyright (C) 2025 Texas Instruments Incorporated - https://www.ti.com/
+ * Copyright (C) 2025 BayLibre, SAS
+ */
+
+#include <asm/io.h>
+#include <linux/compiler_types.h>
+#include <linux/types.h>
+#include <time.h>
+#include <vsprintf.h>
+#include <wait_bit.h>
+
+#include "k3-ddrss-lpm.h"
+
+#define AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL                           
0x430080d0
+#define AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL_DATA_RET_LD               BIT(31)
+#define AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL_DATA_RETENTION_MASK       
GENMASK(3, 0)
+
+#define AM62XX_WKUP_CTRL_MMR_CANUART_WAKE_STAT1                                
0x4301830c
+#define AM62XX_WKUP_CTRL_MMR_CANUART_WAKE_STAT1_CANUART_IO_MODE                
BIT(0)
+
+#define AM62XX_WKUP_CTRL_MMR_CANUART_WAKE_OFF_MODE_STAT                        
0x43018318
+#define AM62XX_WKUP_CTRL_MMR_CANUART_WAKE_OFF_MODE_STAT_MW             0x555555
+
+#define K3_DDRSS_CFG_DENALI_CTL_20                             0x0050
+#define K3_DDRSS_CFG_DENALI_CTL_20_PHY_INDEP_TRAIN_MODE                BIT(24)
+#define K3_DDRSS_CFG_DENALI_CTL_21                             0x0054
+#define K3_DDRSS_CFG_DENALI_CTL_21_PHY_INDEP_INIT_MODE         BIT(8)
+#define K3_DDRSS_CFG_DENALI_CTL_106                            0x01a8
+#define K3_DDRSS_CFG_DENALI_CTL_106_PWRUP_SREFRESH_EXIT                BIT(16)
+#define K3_DDRSS_CFG_DENALI_CTL_160                            0x0280
+#define K3_DDRSS_CFG_DENALI_CTL_160_LP_CMD_MASK                        
GENMASK(14, 8)
+#define K3_DDRSS_CFG_DENALI_CTL_160_LP_CMD_ENTRY               BIT(9)
+#define K3_DDRSS_CFG_DENALI_CTL_169                            0x02a4
+#define K3_DDRSS_CFG_DENALI_CTL_169_LP_AUTO_EXIT_EN_MASK       GENMASK(27, 24)
+#define K3_DDRSS_CFG_DENALI_CTL_169_LP_AUTO_ENTRY_EN_MASK      GENMASK(19, 16)
+#define K3_DDRSS_CFG_DENALI_CTL_169_LP_STATE_MASK              GENMASK(14, 8)
+#define K3_DDRSS_CFG_DENALI_CTL_169_LP_STATE_SHIFT             8
+#define K3_DDRSS_CFG_DENALI_CTL_345                            0x0564
+#define K3_DDRSS_CFG_DENALI_CTL_345_INT_STATUS_LOWPOWER_SHIFT  16
+#define K3_DDRSS_CFG_DENALI_CTL_353                            0x0584
+#define K3_DDRSS_CFG_DENALI_CTL_353_INT_ACK_LOWPOWER_SHIFT     16
+#define K3_DDRSS_CFG_DENALI_PI_6                               0x2018
+#define K3_DDRSS_CFG_DENALI_PI_6_PI_DFI_PHYMSTR_STATE_SEL_R    BIT(8)
+#define K3_DDRSS_CFG_DENALI_PI_146                             0x2248
+#define K3_DDRSS_CFG_DENALI_PI_150                             0x2258
+#define K3_DDRSS_CFG_DENALI_PI_150_PI_DRAM_INIT_EN             BIT(8)
+#define K3_DDRSS_CFG_DENALI_PHY_1820                           0x5C70
+#define K3_DDRSS_CFG_DENALI_PHY_1820_SET_DFI_INPUT_2_SHIFT     16
+
+#define AM62XX_WKUP_CTRL_DDRSS_RETENTION_TIMEOUT_MS    5000
+#define K3_DDRSS_LPM_TIMEOUT_MS                                5000
+
+static void k3_ddrss_reg_update_bits(void __iomem *addr, u32 offset, u32 mask, 
u32 set)
+{
+       u32 val = readl(addr + offset);
+
+       val &= ~mask;
+       val |= set;
+       writel(val, addr + offset);
+}
+
+void k3_ddrss_self_refresh_exit(void __iomem *ddrss_ctl_cfg)
+{
+       k3_ddrss_reg_update_bits(ddrss_ctl_cfg,
+                                K3_DDRSS_CFG_DENALI_CTL_169,
+                                
K3_DDRSS_CFG_DENALI_CTL_169_LP_AUTO_EXIT_EN_MASK |
+                                
K3_DDRSS_CFG_DENALI_CTL_169_LP_AUTO_ENTRY_EN_MASK,
+                                0x0);
+       k3_ddrss_reg_update_bits(ddrss_ctl_cfg,
+                                K3_DDRSS_CFG_DENALI_PHY_1820,
+                                0,
+                                BIT(2) << 
K3_DDRSS_CFG_DENALI_PHY_1820_SET_DFI_INPUT_2_SHIFT);
+       k3_ddrss_reg_update_bits(ddrss_ctl_cfg,
+                                K3_DDRSS_CFG_DENALI_CTL_106,
+                                0,
+                                
K3_DDRSS_CFG_DENALI_CTL_106_PWRUP_SREFRESH_EXIT);
+       writel(0, ddrss_ctl_cfg + K3_DDRSS_CFG_DENALI_PI_146);
+       k3_ddrss_reg_update_bits(ddrss_ctl_cfg,
+                                K3_DDRSS_CFG_DENALI_PI_150,
+                                K3_DDRSS_CFG_DENALI_PI_150_PI_DRAM_INIT_EN,
+                                0x0);
+       k3_ddrss_reg_update_bits(ddrss_ctl_cfg,
+                                K3_DDRSS_CFG_DENALI_PI_6,
+                                0,
+                                
K3_DDRSS_CFG_DENALI_PI_6_PI_DFI_PHYMSTR_STATE_SEL_R);
+       k3_ddrss_reg_update_bits(ddrss_ctl_cfg,
+                                K3_DDRSS_CFG_DENALI_CTL_21,
+                                K3_DDRSS_CFG_DENALI_CTL_21_PHY_INDEP_INIT_MODE,
+                                0);
+       k3_ddrss_reg_update_bits(ddrss_ctl_cfg,
+                                K3_DDRSS_CFG_DENALI_CTL_20,
+                                0,
+                                
K3_DDRSS_CFG_DENALI_CTL_20_PHY_INDEP_TRAIN_MODE);
+}
+
+void k3_ddrss_lpm_resume(void __iomem *ddrss_ctl_cfg)
+{
+       int ret;
+       unsigned long timeout_start;
+
+       k3_ddrss_reg_update_bits(ddrss_ctl_cfg,
+                                K3_DDRSS_CFG_DENALI_CTL_160,
+                                K3_DDRSS_CFG_DENALI_CTL_160_LP_CMD_MASK,
+                                K3_DDRSS_CFG_DENALI_CTL_160_LP_CMD_ENTRY);
+       ret = wait_for_bit_32(ddrss_ctl_cfg + K3_DDRSS_CFG_DENALI_CTL_345,
+                             (1 << 
K3_DDRSS_CFG_DENALI_CTL_345_INT_STATUS_LOWPOWER_SHIFT),
+                             true, 5000, false);
+       if (ret)
+               panic("Failed waiting for low power command %d\n", ret);
+
+       k3_ddrss_reg_update_bits(ddrss_ctl_cfg,
+                                K3_DDRSS_CFG_DENALI_CTL_353,
+                                0,
+                                1 << 
K3_DDRSS_CFG_DENALI_CTL_353_INT_ACK_LOWPOWER_SHIFT);
+
+       timeout_start = get_timer(0);
+       while (1) {
+               if (get_timer(timeout_start) > K3_DDRSS_LPM_TIMEOUT_MS)
+                       panic("Failed waiting for low power state\n");
+
+               if ((readl(ddrss_ctl_cfg + K3_DDRSS_CFG_DENALI_CTL_169) &
+                    K3_DDRSS_CFG_DENALI_CTL_169_LP_STATE_MASK) ==
+                   0x40 << K3_DDRSS_CFG_DENALI_CTL_169_LP_STATE_SHIFT)
+                       break;
+       }
+}
+
+void am62xx_ddrss_deassert_retention(void)
+{
+       int ret;
+
+       k3_ddrss_reg_update_bits((void *)AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL,
+                                0x0,
+                                
AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL_DATA_RET_LD |
+                                
AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL_DATA_RETENTION_MASK,
+                                0);
+       k3_ddrss_reg_update_bits((void *)AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL,
+                                0x0,
+                                
AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL_DATA_RET_LD,
+                                
AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL_DATA_RET_LD);
+
+       ret = wait_for_bit_32((void *)AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL,
+                             AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL_DATA_RET_LD,
+                             true, 
AM62XX_WKUP_CTRL_DDRSS_RETENTION_TIMEOUT_MS, false);
+       if (ret)
+               panic("Failed waiting for latching of retention %d\n", ret);
+
+       k3_ddrss_reg_update_bits((void *)AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL,
+                                0x0,
+                                
AM62XX_WKUP_CTRL_MMR0_DDR16SS_PMCTRL_DATA_RET_LD,
+                                0);
+}
+
+static bool am62xx_wkup_conf_canuart_wakeup_active(void)
+{
+       u32 active;
+
+       active = readl((void *)AM62XX_WKUP_CTRL_MMR_CANUART_WAKE_STAT1);
+
+       return !!(active & 
AM62XX_WKUP_CTRL_MMR_CANUART_WAKE_STAT1_CANUART_IO_MODE);
+}
+
+static bool am62xx_wkup_conf_canuart_magic_word_set(void)
+{
+       u32 magic_word;
+
+       magic_word = readl((void 
*)AM62XX_WKUP_CTRL_MMR_CANUART_WAKE_OFF_MODE_STAT);
+
+       return magic_word == AM62XX_WKUP_CTRL_MMR_CANUART_WAKE_OFF_MODE_STAT_MW;
+}
+
+bool am62xx_wkup_conf_boot_is_resume(void)
+{
+       return IS_ENABLED(CONFIG_K3_IODDR) &&
+               am62xx_wkup_conf_canuart_wakeup_active() &&
+               am62xx_wkup_conf_canuart_magic_word_set();
+}
diff --git a/drivers/ram/k3-ddrss/k3-ddrss-lpm.h 
b/drivers/ram/k3-ddrss/k3-ddrss-lpm.h
new file mode 100644
index 
0000000000000000000000000000000000000000..742d224327e45b926508cf6b1fb14f4f7323cd76
--- /dev/null
+++ b/drivers/ram/k3-ddrss/k3-ddrss-lpm.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * K3 DDRSS LPM specific functions
+ *
+ * Copyright (C) 2025 Texas Instruments Incorporated - https://www.ti.com/
+ * Copyright (C) 2025 BayLibre, SAS
+ */
+
+#ifndef _K3_DDRSS_LPM_H_
+#define _K3_DDRSS_LPM_H_
+
+#include <linux/compiler_types.h>
+#include <linux/types.h>
+
+void k3_ddrss_self_refresh_exit(void __iomem *ddrss_ctl_cfg);
+void k3_ddrss_lpm_resume(void __iomem *ddrss_ctl_cfg);
+void am62xx_ddrss_deassert_retention(void);
+bool am62xx_wkup_conf_boot_is_resume(void);
+
+#endif  /* _K3_DDRSS_LPM_H_ */
diff --git a/drivers/ram/k3-ddrss/k3-ddrss.c b/drivers/ram/k3-ddrss/k3-ddrss.c
index 
5144470b931384534e78fbfcea5e66b7083c9f07..27823e91d5efbd7e6629f60921aebc6c834f6989
 100644
--- a/drivers/ram/k3-ddrss/k3-ddrss.c
+++ b/drivers/ram/k3-ddrss/k3-ddrss.c
@@ -20,6 +20,7 @@
 #include <wait_bit.h>
 #include <power/regulator.h>
 
+#include "k3-ddrss-lpm.h"
 #include "lpddr4_obj_if.h"
 #include "lpddr4_if.h"
 #include "lpddr4_structs_if.h"
@@ -119,6 +120,8 @@ enum intrlv_size {
 
 struct k3_ddrss_data {
        u32 flags;
+       bool (*is_lpm_resume)(void);
+       void (*ddrss_deassert_retention)(void);
 };
 
 enum ecc_enable {
@@ -866,6 +869,7 @@ static void k3_ddrss_lpddr4_ecc_init(struct k3_ddrss_desc 
*ddrss)
 
 static int k3_ddrss_probe(struct udevice *dev)
 {
+       struct k3_ddrss_data *ddrss_data = (struct k3_ddrss_data 
*)dev_get_driver_data(dev);
        u64 end, bank0, bank1, bank0_size;
        int ret;
        struct k3_ddrss_desc *ddrss = dev_get_priv(dev);
@@ -873,6 +877,7 @@ static int k3_ddrss_probe(struct udevice *dev)
        __maybe_unused u32 inst;
        __maybe_unused struct k3_ddrss_ecc_region *range = ddrss->ecc_ranges;
        __maybe_unused struct k3_msmc *msmc_parent = NULL;
+       bool is_lpm_resume;
 
        debug("%s(dev=%p)\n", __func__, dev);
 
@@ -880,6 +885,10 @@ static int k3_ddrss_probe(struct udevice *dev)
        if (ret)
                return ret;
 
+       is_lpm_resume = ddrss_data->is_lpm_resume && 
ddrss_data->is_lpm_resume();
+       if (is_lpm_resume)
+               dev_info(dev, "Detected IO+DDR resume\n");
+
        ddrss->dev = dev;
        ret = k3_ddrss_power_on(ddrss);
        if (ret)
@@ -895,12 +904,21 @@ static int k3_ddrss_probe(struct udevice *dev)
        k3_lpddr4_init(ddrss);
        k3_lpddr4_hardware_reg_init(ddrss);
 
+       if (is_lpm_resume)
+               k3_ddrss_self_refresh_exit(ddrss->ddrss_ctl_cfg);
+
        ret = k3_ddrss_init_freq(ddrss);
        if (ret)
                return ret;
 
+       if (is_lpm_resume && ddrss_data->ddrss_deassert_retention)
+               ddrss_data->ddrss_deassert_retention();
+
        k3_lpddr4_start(ddrss);
 
+       if (is_lpm_resume)
+               k3_ddrss_lpm_resume(ddrss->ddrss_ctl_cfg);
+
        if (IS_ENABLED(CONFIG_K3_INLINE_ECC)) {
                if (!ddrss->ddrss_ss_cfg) {
                        printf("%s: ss_cfg is required if ecc is enabled but 
not provided.",
@@ -1020,12 +1038,18 @@ static const struct k3_ddrss_data k3_data = {
        .flags = SINGLE_DDR_SUBSYSTEM,
 };
 
+static const struct k3_ddrss_data am62xx_data = {
+       .flags = SINGLE_DDR_SUBSYSTEM,
+       .is_lpm_resume = am62xx_wkup_conf_boot_is_resume,
+       .ddrss_deassert_retention = am62xx_ddrss_deassert_retention,
+};
+
 static const struct k3_ddrss_data j721s2_data = {
        .flags = MULTI_DDR_SUBSYSTEM,
 };
 
 static const struct udevice_id k3_ddrss_ids[] = {
-       {.compatible = "ti,am62a-ddrss", .data = (ulong)&k3_data, },
+       {.compatible = "ti,am62a-ddrss", .data = (ulong)&am62xx_data, },
        {.compatible = "ti,am64-ddrss", .data = (ulong)&k3_data, },
        {.compatible = "ti,j721e-ddrss", .data = (ulong)&k3_data, },
        {.compatible = "ti,j721s2-ddrss", .data = (ulong)&j721s2_data, },

-- 
2.51.0

Reply via email to