|
@@ -13,7 +13,14 @@
|
|
|
#include <linux/io.h>
|
|
|
#include <linux/err.h>
|
|
|
#include <linux/export.h>
|
|
|
+
|
|
|
+#include <linux/genalloc.h>
|
|
|
+#include <linux/of.h>
|
|
|
+#include <linux/of_address.h>
|
|
|
+#include <linux/of_platform.h>
|
|
|
+
|
|
|
#include <asm/cacheflush.h>
|
|
|
+#include <asm/fncpy.h>
|
|
|
#include <asm/system_misc.h>
|
|
|
#include <asm/tlbflush.h>
|
|
|
|
|
@@ -49,10 +56,50 @@
|
|
|
*/
|
|
|
#define IMX5_DEFAULT_CPU_IDLE_STATE WAIT_UNCLOCKED_POWER_OFF
|
|
|
|
|
|
+struct imx5_suspend_io_state {
|
|
|
+ u32 offset;
|
|
|
+ u32 clear;
|
|
|
+ u32 set;
|
|
|
+ u32 saved_value;
|
|
|
+};
|
|
|
+
|
|
|
struct imx5_pm_data {
|
|
|
phys_addr_t ccm_addr;
|
|
|
phys_addr_t cortex_addr;
|
|
|
phys_addr_t gpc_addr;
|
|
|
+ phys_addr_t m4if_addr;
|
|
|
+ phys_addr_t iomuxc_addr;
|
|
|
+ void (*suspend_asm)(void __iomem *ocram_vbase);
|
|
|
+ const u32 *suspend_asm_sz;
|
|
|
+ const struct imx5_suspend_io_state *suspend_io_config;
|
|
|
+ int suspend_io_count;
|
|
|
+};
|
|
|
+
|
|
|
+static const struct imx5_suspend_io_state imx53_suspend_io_config[] = {
|
|
|
+#define MX53_DSE_HIGHZ_MASK (0x7 << 19)
|
|
|
+ {.offset = 0x584, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM0 */
|
|
|
+ {.offset = 0x594, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM1 */
|
|
|
+ {.offset = 0x560, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM2 */
|
|
|
+ {.offset = 0x554, .clear = MX53_DSE_HIGHZ_MASK}, /* DQM3 */
|
|
|
+ {.offset = 0x574, .clear = MX53_DSE_HIGHZ_MASK}, /* CAS */
|
|
|
+ {.offset = 0x588, .clear = MX53_DSE_HIGHZ_MASK}, /* RAS */
|
|
|
+ {.offset = 0x578, .clear = MX53_DSE_HIGHZ_MASK}, /* SDCLK_0 */
|
|
|
+ {.offset = 0x570, .clear = MX53_DSE_HIGHZ_MASK}, /* SDCLK_1 */
|
|
|
+
|
|
|
+ {.offset = 0x580, .clear = MX53_DSE_HIGHZ_MASK}, /* SDODT0 */
|
|
|
+ {.offset = 0x564, .clear = MX53_DSE_HIGHZ_MASK}, /* SDODT1 */
|
|
|
+ {.offset = 0x57c, .clear = MX53_DSE_HIGHZ_MASK}, /* SDQS0 */
|
|
|
+ {.offset = 0x590, .clear = MX53_DSE_HIGHZ_MASK}, /* SDQS1 */
|
|
|
+ {.offset = 0x568, .clear = MX53_DSE_HIGHZ_MASK}, /* SDQS2 */
|
|
|
+ {.offset = 0x558, .clear = MX53_DSE_HIGHZ_MASK}, /* SDSQ3 */
|
|
|
+ {.offset = 0x6f0, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_ADDS */
|
|
|
+ {.offset = 0x718, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_BODS */
|
|
|
+ {.offset = 0x71c, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_B1DS */
|
|
|
+ {.offset = 0x728, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_B2DS */
|
|
|
+ {.offset = 0x72c, .clear = MX53_DSE_HIGHZ_MASK}, /* GRP_B3DS */
|
|
|
+
|
|
|
+ /* Controls the CKE signal which is required to leave self refresh */
|
|
|
+ {.offset = 0x720, .clear = MX53_DSE_HIGHZ_MASK, .set = 1 << 19}, /* CTLDS */
|
|
|
};
|
|
|
|
|
|
static const struct imx5_pm_data imx51_pm_data __initconst = {
|
|
@@ -65,11 +112,35 @@ static const struct imx5_pm_data imx53_pm_data __initconst = {
|
|
|
.ccm_addr = 0x53fd4000,
|
|
|
.cortex_addr = 0x63fa0000,
|
|
|
.gpc_addr = 0x53fd8000,
|
|
|
+ .m4if_addr = 0x63fd8000,
|
|
|
+ .iomuxc_addr = 0x53fa8000,
|
|
|
+ .suspend_asm = &imx53_suspend,
|
|
|
+ .suspend_asm_sz = &imx53_suspend_sz,
|
|
|
+ .suspend_io_config = imx53_suspend_io_config,
|
|
|
+ .suspend_io_count = ARRAY_SIZE(imx53_suspend_io_config),
|
|
|
};
|
|
|
|
|
|
+#define MX5_MAX_SUSPEND_IOSTATE ARRAY_SIZE(imx53_suspend_io_config)
|
|
|
+
|
|
|
+/*
|
|
|
+ * This structure is for passing necessary data for low level ocram
|
|
|
+ * suspend code(arch/arm/mach-imx/suspend-imx53.S), if this struct
|
|
|
+ * definition is changed, the offset definition in that file
|
|
|
+ * must be also changed accordingly otherwise, the suspend to ocram
|
|
|
+ * function will be broken!
|
|
|
+ */
|
|
|
+struct imx5_cpu_suspend_info {
|
|
|
+ void __iomem *m4if_base;
|
|
|
+ void __iomem *iomuxc_base;
|
|
|
+ u32 io_count;
|
|
|
+ struct imx5_suspend_io_state io_state[MX5_MAX_SUSPEND_IOSTATE];
|
|
|
+} __aligned(8);
|
|
|
+
|
|
|
static void __iomem *ccm_base;
|
|
|
static void __iomem *cortex_base;
|
|
|
static void __iomem *gpc_base;
|
|
|
+static void __iomem *suspend_ocram_base;
|
|
|
+static void (*imx5_suspend_in_ocram_fn)(void __iomem *ocram_vbase);
|
|
|
|
|
|
/*
|
|
|
* set cpu low power mode before WFI instruction. This function is called
|
|
@@ -159,8 +230,15 @@ static int mx5_suspend_enter(suspend_state_t state)
|
|
|
/*clear the EMPGC0/1 bits */
|
|
|
__raw_writel(0, gpc_base + MXC_SRPG_EMPGC0_SRPGCR);
|
|
|
__raw_writel(0, gpc_base + MXC_SRPG_EMPGC1_SRPGCR);
|
|
|
+
|
|
|
+ if (imx5_suspend_in_ocram_fn)
|
|
|
+ imx5_suspend_in_ocram_fn(suspend_ocram_base);
|
|
|
+ else
|
|
|
+ cpu_do_idle();
|
|
|
+
|
|
|
+ } else {
|
|
|
+ cpu_do_idle();
|
|
|
}
|
|
|
- cpu_do_idle();
|
|
|
|
|
|
/* return registers to default idle state */
|
|
|
mx5_cpu_lp_set(IMX5_DEFAULT_CPU_IDLE_STATE);
|
|
@@ -192,6 +270,111 @@ static void imx5_pm_idle(void)
|
|
|
imx5_cpu_do_idle();
|
|
|
}
|
|
|
|
|
|
+static int __init imx_suspend_alloc_ocram(
|
|
|
+ size_t size,
|
|
|
+ void __iomem **virt_out,
|
|
|
+ phys_addr_t *phys_out)
|
|
|
+{
|
|
|
+ struct device_node *node;
|
|
|
+ struct platform_device *pdev;
|
|
|
+ struct gen_pool *ocram_pool;
|
|
|
+ unsigned long ocram_base;
|
|
|
+ void __iomem *virt;
|
|
|
+ phys_addr_t phys;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ /* Copied from imx6: TODO factorize */
|
|
|
+ node = of_find_compatible_node(NULL, NULL, "mmio-sram");
|
|
|
+ if (!node) {
|
|
|
+ pr_warn("%s: failed to find ocram node!\n", __func__);
|
|
|
+ return -ENODEV;
|
|
|
+ }
|
|
|
+
|
|
|
+ pdev = of_find_device_by_node(node);
|
|
|
+ if (!pdev) {
|
|
|
+ pr_warn("%s: failed to find ocram device!\n", __func__);
|
|
|
+ ret = -ENODEV;
|
|
|
+ goto put_node;
|
|
|
+ }
|
|
|
+
|
|
|
+ ocram_pool = dev_get_gen_pool(&pdev->dev);
|
|
|
+ if (!ocram_pool) {
|
|
|
+ pr_warn("%s: ocram pool unavailable!\n", __func__);
|
|
|
+ ret = -ENODEV;
|
|
|
+ goto put_node;
|
|
|
+ }
|
|
|
+
|
|
|
+ ocram_base = gen_pool_alloc(ocram_pool, size);
|
|
|
+ if (!ocram_base) {
|
|
|
+ pr_warn("%s: unable to alloc ocram!\n", __func__);
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto put_node;
|
|
|
+ }
|
|
|
+
|
|
|
+ phys = gen_pool_virt_to_phys(ocram_pool, ocram_base);
|
|
|
+ virt = __arm_ioremap_exec(phys, size, false);
|
|
|
+ if (phys_out)
|
|
|
+ *phys_out = phys;
|
|
|
+ if (virt_out)
|
|
|
+ *virt_out = virt;
|
|
|
+
|
|
|
+put_node:
|
|
|
+ of_node_put(node);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int __init imx5_suspend_init(const struct imx5_pm_data *soc_data)
|
|
|
+{
|
|
|
+ struct imx5_cpu_suspend_info *suspend_info;
|
|
|
+ int ret;
|
|
|
+ /* Need this to avoid compile error due to const typeof in fncpy.h */
|
|
|
+ void (*suspend_asm)(void __iomem *) = soc_data->suspend_asm;
|
|
|
+
|
|
|
+ if (!suspend_asm)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (!soc_data->suspend_asm_sz || !*soc_data->suspend_asm_sz)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ ret = imx_suspend_alloc_ocram(
|
|
|
+ *soc_data->suspend_asm_sz + sizeof(*suspend_info),
|
|
|
+ &suspend_ocram_base, NULL);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ suspend_info = suspend_ocram_base;
|
|
|
+
|
|
|
+ suspend_info->io_count = soc_data->suspend_io_count;
|
|
|
+ memcpy(suspend_info->io_state, soc_data->suspend_io_config,
|
|
|
+ sizeof(*suspend_info->io_state) * soc_data->suspend_io_count);
|
|
|
+
|
|
|
+ suspend_info->m4if_base = ioremap(soc_data->m4if_addr, SZ_16K);
|
|
|
+ if (!suspend_info->m4if_base) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto failed_map_m4if;
|
|
|
+ }
|
|
|
+
|
|
|
+ suspend_info->iomuxc_base = ioremap(soc_data->iomuxc_addr, SZ_16K);
|
|
|
+ if (!suspend_info->iomuxc_base) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto failed_map_iomuxc;
|
|
|
+ }
|
|
|
+
|
|
|
+ imx5_suspend_in_ocram_fn = fncpy(
|
|
|
+ suspend_ocram_base + sizeof(*suspend_info),
|
|
|
+ suspend_asm,
|
|
|
+ *soc_data->suspend_asm_sz);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+failed_map_iomuxc:
|
|
|
+ iounmap(suspend_info->m4if_base);
|
|
|
+
|
|
|
+failed_map_m4if:
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
static int __init imx5_pm_common_init(const struct imx5_pm_data *data)
|
|
|
{
|
|
|
int ret;
|
|
@@ -218,6 +401,11 @@ static int __init imx5_pm_common_init(const struct imx5_pm_data *data)
|
|
|
if (ret)
|
|
|
pr_warn("%s: cpuidle init failed %d\n", __func__, ret);
|
|
|
|
|
|
+ ret = imx5_suspend_init(data);
|
|
|
+ if (ret)
|
|
|
+ pr_warn("%s: No DDR LPM support with suspend %d!\n",
|
|
|
+ __func__, ret);
|
|
|
+
|
|
|
suspend_set_ops(&mx5_suspend_ops);
|
|
|
|
|
|
return 0;
|