1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * OMAP Power Management debug routines |
4 | * |
5 | * Copyright (C) 2005 Texas Instruments, Inc. |
6 | * Copyright (C) 2006-2008 Nokia Corporation |
7 | * |
8 | * Written by: |
9 | * Richard Woodruff <r-woodruff2@ti.com> |
10 | * Tony Lindgren |
11 | * Juha Yrjola |
12 | * Amit Kucheria <amit.kucheria@nokia.com> |
13 | * Igor Stoppa <igor.stoppa@nokia.com> |
14 | * Jouni Hogander |
15 | * |
16 | * Based on pm.c for omap2 |
17 | */ |
18 | |
19 | #include <linux/kernel.h> |
20 | #include <linux/sched.h> |
21 | #include <linux/sched/clock.h> |
22 | #include <linux/clk.h> |
23 | #include <linux/err.h> |
24 | #include <linux/io.h> |
25 | #include <linux/module.h> |
26 | #include <linux/slab.h> |
27 | |
28 | #include "clock.h" |
29 | #include "powerdomain.h" |
30 | #include "clockdomain.h" |
31 | |
32 | #include "soc.h" |
33 | #include "cm2xxx_3xxx.h" |
34 | #include "prm2xxx_3xxx.h" |
35 | #include "pm.h" |
36 | |
37 | #ifdef CONFIG_DEBUG_FS |
38 | #include <linux/debugfs.h> |
39 | #include <linux/seq_file.h> |
40 | |
41 | static int pm_dbg_init_done; |
42 | |
43 | static int pm_dbg_init(void); |
44 | |
45 | static const char pwrdm_state_names[][PWRDM_MAX_PWRSTS] = { |
46 | "OFF" , |
47 | "RET" , |
48 | "INA" , |
49 | "ON" |
50 | }; |
51 | |
52 | void pm_dbg_update_time(struct powerdomain *pwrdm, int prev) |
53 | { |
54 | s64 t; |
55 | |
56 | if (!pm_dbg_init_done) |
57 | return ; |
58 | |
59 | /* Update timer for previous state */ |
60 | t = sched_clock(); |
61 | |
62 | pwrdm->state_timer[prev] += t - pwrdm->timer; |
63 | |
64 | pwrdm->timer = t; |
65 | } |
66 | |
67 | static int clkdm_dbg_show_counter(struct clockdomain *clkdm, void *user) |
68 | { |
69 | struct seq_file *s = (struct seq_file *)user; |
70 | |
71 | if (strcmp(clkdm->name, "emu_clkdm" ) == 0 || |
72 | strcmp(clkdm->name, "wkup_clkdm" ) == 0 || |
73 | strncmp(clkdm->name, "dpll" , 4) == 0) |
74 | return 0; |
75 | |
76 | seq_printf(m: s, fmt: "%s->%s (%d)\n" , clkdm->name, clkdm->pwrdm.ptr->name, |
77 | clkdm->usecount); |
78 | |
79 | return 0; |
80 | } |
81 | |
82 | static int pwrdm_dbg_show_counter(struct powerdomain *pwrdm, void *user) |
83 | { |
84 | struct seq_file *s = (struct seq_file *)user; |
85 | int i; |
86 | |
87 | if (strcmp(pwrdm->name, "emu_pwrdm" ) == 0 || |
88 | strcmp(pwrdm->name, "wkup_pwrdm" ) == 0 || |
89 | strncmp(pwrdm->name, "dpll" , 4) == 0) |
90 | return 0; |
91 | |
92 | if (pwrdm->state != pwrdm_read_pwrst(pwrdm)) |
93 | printk(KERN_ERR "pwrdm state mismatch(%s) %d != %d\n" , |
94 | pwrdm->name, pwrdm->state, pwrdm_read_pwrst(pwrdm)); |
95 | |
96 | seq_printf(m: s, fmt: "%s (%s)" , pwrdm->name, |
97 | pwrdm_state_names[pwrdm->state]); |
98 | for (i = 0; i < PWRDM_MAX_PWRSTS; i++) |
99 | seq_printf(m: s, fmt: ",%s:%d" , pwrdm_state_names[i], |
100 | pwrdm->state_counter[i]); |
101 | |
102 | seq_printf(m: s, fmt: ",RET-LOGIC-OFF:%d" , pwrdm->ret_logic_off_counter); |
103 | for (i = 0; i < pwrdm->banks; i++) |
104 | seq_printf(m: s, fmt: ",RET-MEMBANK%d-OFF:%d" , i + 1, |
105 | pwrdm->ret_mem_off_counter[i]); |
106 | |
107 | seq_putc(m: s, c: '\n'); |
108 | return 0; |
109 | } |
110 | |
111 | static int pwrdm_dbg_show_timer(struct powerdomain *pwrdm, void *user) |
112 | { |
113 | struct seq_file *s = (struct seq_file *)user; |
114 | int i; |
115 | |
116 | if (strcmp(pwrdm->name, "emu_pwrdm" ) == 0 || |
117 | strcmp(pwrdm->name, "wkup_pwrdm" ) == 0 || |
118 | strncmp(pwrdm->name, "dpll" , 4) == 0) |
119 | return 0; |
120 | |
121 | pwrdm_state_switch(pwrdm); |
122 | |
123 | seq_printf(m: s, fmt: "%s (%s)" , pwrdm->name, |
124 | pwrdm_state_names[pwrdm->state]); |
125 | |
126 | for (i = 0; i < 4; i++) |
127 | seq_printf(m: s, fmt: ",%s:%lld" , pwrdm_state_names[i], |
128 | pwrdm->state_timer[i]); |
129 | |
130 | seq_putc(m: s, c: '\n'); |
131 | return 0; |
132 | } |
133 | |
134 | static int pm_dbg_counters_show(struct seq_file *s, void *unused) |
135 | { |
136 | pwrdm_for_each(fn: pwrdm_dbg_show_counter, user: s); |
137 | clkdm_for_each(fn: clkdm_dbg_show_counter, user: s); |
138 | |
139 | return 0; |
140 | } |
141 | DEFINE_SHOW_ATTRIBUTE(pm_dbg_counters); |
142 | |
143 | static int pm_dbg_timers_show(struct seq_file *s, void *unused) |
144 | { |
145 | pwrdm_for_each(fn: pwrdm_dbg_show_timer, user: s); |
146 | return 0; |
147 | } |
148 | DEFINE_SHOW_ATTRIBUTE(pm_dbg_timers); |
149 | |
150 | static int pwrdm_suspend_get(void *data, u64 *val) |
151 | { |
152 | int ret = -EINVAL; |
153 | |
154 | if (cpu_is_omap34xx()) |
155 | ret = omap3_pm_get_suspend_state(pwrdm: (struct powerdomain *)data); |
156 | *val = ret; |
157 | |
158 | if (ret >= 0) |
159 | return 0; |
160 | return *val; |
161 | } |
162 | |
163 | static int pwrdm_suspend_set(void *data, u64 val) |
164 | { |
165 | if (cpu_is_omap34xx()) |
166 | return omap3_pm_set_suspend_state( |
167 | pwrdm: (struct powerdomain *)data, state: (int)val); |
168 | return -EINVAL; |
169 | } |
170 | |
171 | DEFINE_DEBUGFS_ATTRIBUTE(pwrdm_suspend_fops, pwrdm_suspend_get, |
172 | pwrdm_suspend_set, "%llu\n" ); |
173 | |
174 | static int __init pwrdms_setup(struct powerdomain *pwrdm, void *dir) |
175 | { |
176 | int i; |
177 | s64 t; |
178 | struct dentry *d; |
179 | |
180 | t = sched_clock(); |
181 | |
182 | for (i = 0; i < 4; i++) |
183 | pwrdm->state_timer[i] = 0; |
184 | |
185 | pwrdm->timer = t; |
186 | |
187 | if (strncmp(pwrdm->name, "dpll" , 4) == 0) |
188 | return 0; |
189 | |
190 | d = debugfs_create_dir(name: pwrdm->name, parent: (struct dentry *)dir); |
191 | debugfs_create_file(name: "suspend" , S_IRUGO|S_IWUSR, parent: d, data: pwrdm, |
192 | fops: &pwrdm_suspend_fops); |
193 | |
194 | return 0; |
195 | } |
196 | |
197 | static int option_get(void *data, u64 *val) |
198 | { |
199 | u32 *option = data; |
200 | |
201 | *val = *option; |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | static int option_set(void *data, u64 val) |
207 | { |
208 | u32 *option = data; |
209 | |
210 | *option = val; |
211 | |
212 | if (option == &enable_off_mode) { |
213 | if (cpu_is_omap34xx()) |
214 | omap3_pm_off_mode_enable(val); |
215 | } |
216 | |
217 | return 0; |
218 | } |
219 | |
220 | DEFINE_SIMPLE_ATTRIBUTE(pm_dbg_option_fops, option_get, option_set, "%llu\n" ); |
221 | |
222 | static int __init pm_dbg_init(void) |
223 | { |
224 | struct dentry *d; |
225 | |
226 | if (pm_dbg_init_done) |
227 | return 0; |
228 | |
229 | d = debugfs_create_dir(name: "pm_debug" , NULL); |
230 | |
231 | debugfs_create_file(name: "count" , mode: 0444, parent: d, NULL, fops: &pm_dbg_counters_fops); |
232 | debugfs_create_file(name: "time" , mode: 0444, parent: d, NULL, fops: &pm_dbg_timers_fops); |
233 | |
234 | pwrdm_for_each(fn: pwrdms_setup, user: (void *)d); |
235 | |
236 | debugfs_create_file(name: "enable_off_mode" , S_IRUGO | S_IWUSR, parent: d, |
237 | data: &enable_off_mode, fops: &pm_dbg_option_fops); |
238 | pm_dbg_init_done = 1; |
239 | |
240 | return 0; |
241 | } |
242 | omap_arch_initcall(pm_dbg_init); |
243 | |
244 | #endif |
245 | |