summaryrefslogtreecommitdiff
path: root/drivers/soc/qcom/pasr.c
blob: da85dd50529e244b267ab981f5866eb055510627 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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);