summaryrefslogtreecommitdiffstats
path: root/drivers/platform/tegra/pm_debugfs.c
blob: 6269df24cbf2a373898a85eb179d932ba62f08b4 (plain) (blame)
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/*
 * drivers/platform/tegra/pm_debugfs.c
 *
 * Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved.
 *
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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/kernel.h>
#include <linux/debugfs.h>
#include <linux/tegra-pm.h>

/* Debugfs handle for /d/system_states directory */
static struct dentry *system_states_debugfs;

/* Suspend debug flags used in /d/system_states/suspend_debug_flags */
static u32 suspend_debug_flags;

/*
 * Helper function for send_smc that actually makes the smc call
 */
static noinline notrace int __send_smc(u32 smc_func, struct pm_regs *regs)
{
	u32 ret = smc_func;

	asm volatile (
	"       mov     x0, %0\n"
	"       ldp     x1, x2, [%1, #16 * 0]\n"
	"       ldp     x3, x4, [%1, #16 * 1]\n"
	"       ldp     x5, x6, [%1, #16 * 2]\n"
	"       isb\n"
	"       smc     #0\n"
	"       mov     %0, x0\n"
	"       stp     x0, x1, [%1, #16 * 0]\n"
	"       stp     x2, x3, [%1, #16 * 1]\n"
	: "+r" (ret)
	: "r" (regs)
	: "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8",
	"x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17");
	return ret;
}

/*
 * Make an SMC call. Takes in the SMC function to be invoked & registers to be
 * passed along as args.
 */
int send_smc(u32 smc_func, struct pm_regs *regs)
{
	int __ret = __send_smc(smc_func, regs);

	if (__ret) {
		pr_err("%s: failed (ret=%d)\n", __func__, __ret);
		return __ret;
	}

	return __ret;
}

/*
 * Specify debug flags for system suspend.
 */
static int tegra_set_suspend_debug_flags(u32 debug_flags)
{
	struct pm_regs regs;
	u32 smc_func = SMC_FAKE_SYS_SUSPEND |
				(FAKE_SYSTEM_SUSPEND_MODE & SMC_ENUM_MAX);
	regs.args[0] = debug_flags;
	return send_smc(smc_func, &regs);
}
EXPORT_SYMBOL(tegra_set_suspend_debug_flags);

/*
 * Get suspend debug flags. It is used by debugfs ops.
 */
static int suspend_debug_flags_get(void *data, u64 *val)
{
	*val = suspend_debug_flags;
	return 0;
}

/*
 * Set suspend debug flags. It is used by debugfs ops.
 */
static int suspend_debug_flags_set(void *data, u64 val)
{
	int ret;

	if (val == FAKE_SYSTEM_SUSPEND_USER_ARG) {
		suspend_debug_flags = FAKE_SYSTEM_SUSPEND_MODE;
		ret = tegra_set_suspend_debug_flags(suspend_debug_flags);
	} else {
		pr_err("Invalid suspend debug flags\n");
		ret = -1;
	}

	return ret;
}

DEFINE_SIMPLE_ATTRIBUTE(suspend_debug_flags_fops,
		suspend_debug_flags_get, suspend_debug_flags_set, "%llu\n");

/*
 * Interface that returns the debugfs handle for /d/system_states directory.
 * In case its not initialized, it creates it.
 */
struct dentry *return_system_states_dir(void)
{
	if (system_states_debugfs == NULL) {
		system_states_debugfs =
				debugfs_create_dir("system_states", NULL);

		if (system_states_debugfs == NULL)
			pr_err("Cannot create system_states debugfs dir\n");
	}

	return system_states_debugfs;
}
EXPORT_SYMBOL(return_system_states_dir);

static int __init fake_system_suspend_debugfs_init(void)
{
	struct dentry *dfs_file = NULL;
	struct dentry *debugfs_dir = return_system_states_dir();

	if (!debugfs_dir) {
		pr_err("/d/system_states was not created. Aborting\n");
		return -ENOENT;
	}

	dfs_file = debugfs_create_file("suspend_debug_flags", 0644,
			debugfs_dir, NULL, &suspend_debug_flags_fops);

	if (!dfs_file) {
		pr_err("Not able to create suspend_debug_flags debugfs node\n");
		return -ENOENT;
	}

	return 0;
}

late_initcall(fake_system_suspend_debugfs_init);