summaryrefslogtreecommitdiff
path: root/drivers/soc/qcom/pasr.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc/qcom/pasr.c')
-rw-r--r--drivers/soc/qcom/pasr.c95
1 files changed, 95 insertions, 0 deletions
diff --git a/drivers/soc/qcom/pasr.c b/drivers/soc/qcom/pasr.c
new file mode 100644
index 000000000000..da85dd50529e
--- /dev/null
+++ b/drivers/soc/qcom/pasr.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/memory.h>
+#include <linux/module.h>
+#include <soc/qcom/rpm-smd.h>
+
+struct memory_refresh_request {
+ u64 start; /* Lower bit signifies action
+ * 0 - disable self-refresh
+ * 1 - enable self-refresh
+ * upper bits are for base address
+ */
+ size_t size; /* size of memory region */
+};
+#define RPM_DDR_REQ 0x726464
+
+static void mem_region_refresh_control(unsigned long pfn,
+ unsigned long nr_pages, bool enable)
+{
+ struct memory_refresh_request mem_req;
+ struct msm_rpm_kvp rpm_kvp;
+ int ret;
+
+ mem_req.start = enable;
+ mem_req.start |= pfn << PAGE_SHIFT;
+ mem_req.size = nr_pages * PAGE_SIZE;
+
+ rpm_kvp.key = RPM_DDR_REQ;
+ rpm_kvp.data = (void *)&mem_req;
+ rpm_kvp.length = sizeof(mem_req);
+
+ ret = msm_rpm_send_message(MSM_RPM_CTX_ACTIVE_SET, RPM_DDR_REQ, 0,
+ &rpm_kvp, 1);
+ if (ret)
+ pr_err("PASR: Failed to send rpm message\n");
+}
+
+static int pasr_callback(struct notifier_block *self,
+ unsigned long action, void *arg)
+{
+ struct memory_notify *mn = arg;
+ unsigned long start, end;
+
+ start = SECTION_ALIGN_DOWN(mn->start_pfn);
+ end = SECTION_ALIGN_UP(mn->start_pfn + mn->nr_pages);
+
+ if ((start != mn->start_pfn) || (end != mn->start_pfn + mn->nr_pages)) {
+ pr_err("PASR: %s pfn not aligned to section\n", __func__);
+ pr_err("PASR: start pfn = %lu end pfn = %lu\n",
+ mn->start_pfn, mn->start_pfn + mn->nr_pages);
+ return -EINVAL;
+ }
+
+ switch (action) {
+ case MEM_GOING_ONLINE:
+ pr_debug("PASR: MEM_GOING_ONLINE : start = %lx end = %lx",
+ mn->start_pfn << PAGE_SHIFT,
+ (mn->start_pfn + mn->nr_pages) << PAGE_SHIFT);
+ mem_region_refresh_control(mn->start_pfn, mn->nr_pages, true);
+ break;
+ case MEM_OFFLINE:
+ pr_debug("PASR: MEM_OFFLINE: start = %lx end = %lx",
+ mn->start_pfn << PAGE_SHIFT,
+ (mn->start_pfn + mn->nr_pages) << PAGE_SHIFT);
+ mem_region_refresh_control(mn->start_pfn, mn->nr_pages, false);
+ break;
+ case MEM_CANCEL_ONLINE:
+ pr_debug("PASR: MEM_CANCEL_ONLINE: start = %lx end = %lx",
+ mn->start_pfn << PAGE_SHIFT,
+ (mn->start_pfn + mn->nr_pages) << PAGE_SHIFT);
+ mem_region_refresh_control(mn->start_pfn, mn->nr_pages, false);
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int __init pasr_module_init(void)
+{
+ return hotplug_memory_notifier(pasr_callback, 0);
+}
+late_initcall(pasr_module_init);