blob: 3411e4ecb822c2a086d2e4c5c29160dd67df5f7c [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001/*
2 * common function for clock framework source file
3 *
4 * Copyright (C) 2012 Marvell
5 * Chao Xie <xiechao.mail@gmail.com>
6 * Zhoujie Wu <zjwu@marvell.com>
7 * Lu Cao <lucao@marvell.com>
8 *
9 * This file is licensed under the terms of the GNU General Public
10 * License version 2. This program is licensed "as is" without any
11 * warranty of any kind, whether express or implied.
12 */
13
14#include <linux/module.h>
15#include <linux/kernel.h>
16#include <soc/asr/asrdcstat.h>
17#include <linux/clk/dvfs-dvc.h>
18#include <soc/asr/debugfs-asr.h>
19#include <linux/gpio.h>
20//#include <linux/miscled-rgb.h>
21#include <linux/mfd/88pm80x.h>
22#include "clk.h"
23#include <dt-bindings/power/asr-power.h>
24
25struct vol_op_dcstat_info {
26 u64 time; /*ms*/
27 u32 vol; /* mV */
28};
29
30struct vol_dc_stat_info {
31 bool stat_start;
32 struct vol_op_dcstat_info ops_dcstat[MAX_PMIC_LEVEL];
33 u32 vc_count[MAX_PMIC_LEVEL][MAX_PMIC_LEVEL]; /* [from][to] */
34 u32 vc_total_count;
35 ktime_t breakdown_start;
36 ktime_t prev_ts;
37 u32 cur_lvl;
38 u64 total_time; /* ms */
39};
40
41static DEFINE_SPINLOCK(vol_lock);
42static struct vol_dc_stat_info vol_dcstat;
43static int vol_ledstatus_start;
44
45static ktime_t vol_dcstat_time(void);
46
47/*
48 * Get platform specified dvc low power modes. Platform power file
49 * should define an array contains these modes with the same sequence
50 * in hwdvc_rails.
51 */
52unsigned int hwdvc_lpm[AP_COMP_MAX] = {POWER_MODE_MAX, POWER_MODE_CORE_POWERDOWN,
53 POWER_MODE_APPS_IDLE, POWER_MODE_SYS_SLEEP};
54
55static u32 voltage_lvl[AP_COMP_MAX];
56/* Platform supported maximum lpms */
57u32 max_lpm = POWER_MODE_MAX;
58
59static u32 find_hwdvc_voltage_lvl(u32 lpm)
60{
61 u32 hwlvl;
62 int i;
63
64 for (i = 0; i < AP_COMP_MAX; i++)
65 if (hwdvc_lpm[i] == lpm) {
66 hwlvl = voltage_lvl[i];
67 break;
68 }
69
70 /* Incorrect lpm? Set AP_ACTIVE as default */
71 if (i == AP_COMP_MAX) {
72 hwlvl = voltage_lvl[AP_ACTIVE];
73 }
74
75 return hwlvl;
76}
77
78static void vol_ledstatus_show(u32 lpm)
79{
80 u32 hwlvl = find_hwdvc_voltage_lvl(lpm);
81
82 switch (hwlvl) {
83#if 0
84 case VL3:
85 /* VL3 : LED RG */
86 led_rgb_output(LED_R, 1);
87 led_rgb_output(LED_G, 1);
88 led_rgb_output(LED_B, 0);
89 break;
90 case VL2:
91 /* VL2 : LED R */
92 led_rgb_output(LED_R, 1);
93 led_rgb_output(LED_G, 0);
94 led_rgb_output(LED_B, 0);
95 break;
96 case VL1:
97 /* VL1 : LED G */
98 led_rgb_output(LED_R, 0);
99 led_rgb_output(LED_G, 1);
100 led_rgb_output(LED_B, 0);
101 break;
102 case VL0:
103 /* VL0 : LED B */
104 led_rgb_output(LED_R, 0);
105 led_rgb_output(LED_G, 0);
106 led_rgb_output(LED_B, 1);
107 break;
108 case -1:
109 /* lpm : LED -- */
110 led_rgb_output(LED_R, 0);
111 led_rgb_output(LED_G, 0);
112 led_rgb_output(LED_B, 0);
113 break;
114#endif
115 default:
116 break;
117 }
118}
119
120void vol_ledstatus_event(u32 lpm)
121{
122 spin_lock(&vol_lock);
123 if (!vol_ledstatus_start)
124 goto out;
125 vol_ledstatus_show(lpm);
126out:
127 spin_unlock(&vol_lock);
128}
129
130static ssize_t vol_led_read(struct file *filp, char __user *buffer,
131 size_t count, loff_t *ppos)
132{
133 char *buf;
134 ssize_t ret, size = 2 * PAGE_SIZE - 1;
135 u32 len = 0;
136
137 buf = (char *)__get_free_pages(GFP_NOIO, get_order(size));
138 if (!buf)
139 return -ENOMEM;
140
141 len += snprintf(buf + len, size - len,
142 "Help information :\n");
143 len += snprintf(buf + len, size - len,
144 "echo 1 to start voltage led status:\n");
145 len += snprintf(buf + len, size - len,
146 "echo 0 to stop voltage led status:\n");
147 len += snprintf(buf + len, size - len,
148 "VLevel3: R, G\n");
149 len += snprintf(buf + len, size - len,
150 "VLevel2: R\n");
151 len += snprintf(buf + len, size - len,
152 "VLevel1: G\n");
153 len += snprintf(buf + len, size - len,
154 "VLevel0: B\n");
155 len += snprintf(buf + len, size - len,
156 " lpm: off\n");
157
158 ret = simple_read_from_buffer(buffer, count, ppos, buf, len);
159 free_pages((unsigned long)buf, get_order(size));
160 return ret;
161}
162
163static ssize_t vol_led_write(struct file *filp, const char __user *buffer,
164 size_t count, loff_t *ppos)
165{
166 unsigned int start;
167 char buf[10] = { 0 };
168
169 if (copy_from_user(buf, buffer, count))
170 return -EFAULT;
171 sscanf(buf, "%d", &start);
172 start = !!start;
173
174 if (vol_ledstatus_start == start) {
175 pr_err("[WARNING]Voltage led status is already %s\n",
176 vol_dcstat.stat_start ? "started" : "stopped");
177 return -EINVAL;
178 }
179
180 spin_lock(&vol_lock);
181 vol_ledstatus_start = start;
182#if 0
183 if (vol_ledstatus_start)
184 vol_ledstatus_show(MAX_LPM_INDEX);
185 else {
186 led_rgb_output(LED_R, 0);
187 led_rgb_output(LED_G, 0);
188 led_rgb_output(LED_B, 0);
189 }
190#endif
191 spin_unlock(&vol_lock);
192
193 return count;
194}
195
196static const struct file_operations vol_led_ops = {
197 .owner = THIS_MODULE,
198 .read = vol_led_read,
199 .write = vol_led_write,
200};
201
202static void vol_dcstat_update(u32 lpm)
203{
204 ktime_t cur_ts;
205 u32 hwlvl;
206 u64 time_us;
207
208 hwlvl = find_hwdvc_voltage_lvl(lpm);
209 if (vol_dcstat.cur_lvl == hwlvl)
210 return;
211
212 /* update voltage change times */
213 vol_dcstat.vc_count[vol_dcstat.cur_lvl][hwlvl]++;
214
215 /* update voltage dc statistics */
216 cur_ts = vol_dcstat_time();
217 time_us = ktime_to_us(ktime_sub(cur_ts, vol_dcstat.prev_ts));
218 vol_dcstat.ops_dcstat[vol_dcstat.cur_lvl].time += time_us;
219 vol_dcstat.prev_ts = cur_ts;
220 vol_dcstat.cur_lvl = hwlvl;
221}
222
223void vol_dcstat_event(u32 lpm)
224{
225 spin_lock(&vol_lock);
226 if (!vol_dcstat.stat_start)
227 goto out;
228 vol_dcstat_update(lpm);
229out:
230 spin_unlock(&vol_lock);
231}
232
233static ktime_t vol_dcstat_time(void)
234{
235 ktime_t ktime_temp;
236
237 /* flag for if timekeeping is suspended */
238 if (unlikely(timekeeping_suspended))
239 ktime_temp = ns_to_ktime(suspended_time);
240 else
241 ktime_temp = ktime_get();
242
243 return ktime_temp;
244}
245
246static ssize_t vol_dc_read(struct file *filp, char __user *buffer,
247 size_t count, loff_t *ppos)
248{
249 char *buf;
250 ssize_t ret, size = 2 * PAGE_SIZE - 1;
251 u32 i, j, dc_int = 0, dc_fra = 0, len = 0;
252
253 buf = (char *)__get_free_pages(GFP_NOIO, get_order(size));
254 if (!buf)
255 return -ENOMEM;
256
257 if (!vol_dcstat.total_time) {
258 len += snprintf(buf + len, size - len,
259 "No stat information! ");
260 len += snprintf(buf + len, size - len,
261 "Help information :\n");
262 len += snprintf(buf + len, size - len,
263 "1. echo 1 to start duty cycle stat:\n");
264 len += snprintf(buf + len, size - len,
265 "2. echo 0 to stop duty cycle stat:\n");
266 len += snprintf(buf + len, size - len,
267 "3. cat to check duty cycle info from start to stop:\n\n");
268 goto out;
269 }
270
271 if (vol_dcstat.stat_start) {
272 len += snprintf(buf + len, size - len,
273 "Please stop the vol duty cycle stats at first\n");
274 goto out;
275 }
276
277 len += snprintf(buf + len, size - len,
278 "Total time:%8llums (%6llus)\n",
279 div64_u64(vol_dcstat.total_time, (u64)(1000)),
280 div64_u64(vol_dcstat.total_time, (u64)(1000000)));
281 len += snprintf(buf + len, size - len,
282 "|Level|Vol(mV)|Time(ms)| %%|\n");
283 for (i = VL0; i < MAX_PMIC_LEVEL; i++) {
284 dc_int = calculate_dc(vol_dcstat.ops_dcstat[i].time,
285 vol_dcstat.total_time, &dc_fra);
286 len += snprintf(buf + len, size - len,
287 "| VL_%2d|%7u|%8llu|%3u.%02u%%|\n",
288 i, vol_dcstat.ops_dcstat[i].vol,
289 div64_u64(vol_dcstat.ops_dcstat[i].time,
290 (u64)(1000)),
291 dc_int, dc_fra);
292 }
293
294 /* show voltage-change times */
295 len += snprintf(buf + len, size - len,
296 "\nTotal voltage-change times:%8u",
297 vol_dcstat.vc_total_count);
298 len += snprintf(buf + len, size - len, "\n|from\\to|");
299 for (j = VL0; j < MAX_PMIC_LEVEL; j++)
300 len += snprintf(buf + len, size - len, " Level%d|", j);
301 for (i = VL0; i < MAX_PMIC_LEVEL; i++) {
302 len += snprintf(buf + len, size - len, "\n| Level%2d|", i);
303 for (j = VL0; j < MAX_PMIC_LEVEL; j++)
304 if (i == j)
305 len += snprintf(buf + len, size - len,
306 " --- |");
307 else
308 /* [from][to] */
309 len += snprintf(buf + len, size - len,
310 "%7u|",
311 vol_dcstat.vc_count[i][j]);
312 }
313 len += snprintf(buf + len, size - len, "\n");
314out:
315 ret = simple_read_from_buffer(buffer, count, ppos, buf, len);
316 free_pages((unsigned long)buf, get_order(size));
317 return ret;
318}
319
320static ssize_t vol_dc_write(struct file *filp, const char __user *buffer,
321 size_t count, loff_t *ppos)
322{
323 unsigned int start, i, j;
324 char buf[10] = { 0 };
325 ktime_t cur_ts;
326 u64 time_us;
327 struct device_node __maybe_unused *np =
328 of_find_node_by_name(NULL, "pxa1928_apmu_ver");
329
330 if (copy_from_user(buf, buffer, count))
331 return -EFAULT;
332 sscanf(buf, "%d", &start);
333 start = !!start;
334
335 if (vol_dcstat.stat_start == start) {
336 pr_err("[WARNING]Voltage duty-cycle statistics is already %s\n",
337 vol_dcstat.stat_start ? "started" : "stopped");
338 return -EINVAL;
339 }
340 vol_dcstat.stat_start = start;
341 if (vol_dcstat.stat_start && np)
342 for (i = VL0; i < MAX_PMIC_LEVEL; i++)
343 pm8xx_dvc_getvolt(PM800_ID_BUCK1, i,
344 &vol_dcstat.ops_dcstat[i].vol);
345
346 spin_lock(&vol_lock);
347 cur_ts = ktime_get();
348 if (vol_dcstat.stat_start) {
349 for (i = VL0; i < MAX_PMIC_LEVEL; i++) {
350 vol_dcstat.ops_dcstat[i].time = 0;
351 for (j = VL0; j < MAX_PMIC_LEVEL; j++)
352 vol_dcstat.vc_count[i][j] = 0;
353 }
354
355 vol_dcstat.prev_ts = cur_ts;
356 vol_dcstat.breakdown_start = cur_ts;
357 vol_dcstat.cur_lvl = voltage_lvl[AP_ACTIVE];
358 vol_dcstat.total_time = -1UL;
359 vol_dcstat.vc_total_count = 0;
360 } else {
361 time_us = ktime_to_us(ktime_sub(cur_ts, vol_dcstat.prev_ts));
362 vol_dcstat.ops_dcstat[vol_dcstat.cur_lvl].time += time_us;
363 vol_dcstat.total_time = ktime_to_us(ktime_sub(cur_ts,
364 vol_dcstat.breakdown_start));
365 for (i = VL0; i < MAX_PMIC_LEVEL; i++)
366 for (j = VL0; j < MAX_PMIC_LEVEL; j++)
367 vol_dcstat.vc_total_count +=
368 vol_dcstat.vc_count[i][j];
369 }
370 spin_unlock(&vol_lock);
371 return count;
372}
373
374static const struct file_operations vol_dc_ops = {
375 .owner = THIS_MODULE,
376 .read = vol_dc_read,
377 .write = vol_dc_write,
378};
379
380static int hwdvc_stat_notifier_handler(struct notifier_block *nb,
381 unsigned long rails, void *data)
382{
383 struct hwdvc_notifier_data *vl;
384
385 if (rails >= AP_COMP_MAX)
386 return NOTIFY_OK;
387
388 spin_lock(&vol_lock);
389 vl = (struct hwdvc_notifier_data *)(data);
390 voltage_lvl[rails] = vl->newlv;
391
392 if (!vol_dcstat.stat_start)
393 goto out_dc;
394 if (rails == AP_ACTIVE)
395 vol_dcstat_update(max_lpm);
396out_dc:
397 if (!vol_ledstatus_start)
398 goto out_led;
399 if (rails == AP_ACTIVE)
400 vol_ledstatus_show(max_lpm);
401out_led:
402 spin_unlock(&vol_lock);
403 return NOTIFY_OK;
404}
405
406static struct notifier_block hwdvc_stat_ntf = {
407 .notifier_call = hwdvc_stat_notifier_handler,
408};
409
410static int __init hwdvc_stat_init(void)
411{
412 struct dentry *dvfs_node, *volt_dc_stat, *volt_led;
413 u32 idx;
414#ifdef CONFIG_ARM64
415 int dvc_map[MAX_PMIC_LEVEL];
416 int cnt;
417#endif
418 struct device_node __maybe_unused *np =
419 of_find_node_by_name(NULL, "pxa1928_apmu_ver");
420#ifndef CONFIG_ARM64
421 struct dvc_plat_info platinfo;
422 /* record voltage lvl when init */
423 if (dvfs_get_dvcplatinfo(&platinfo))
424 return -ENOENT;
425
426 for (idx = VL0; idx < MAX_PMIC_LEVEL; idx++)
427 vol_dcstat.ops_dcstat[idx].vol =
428 platinfo.millivolts[idx];
429 hwdvc_notifier_register(&hwdvc_stat_ntf);
430#else
431 if (np) {
432 get_voltage_table_for_cp(dvc_map, &cnt);
433 for (idx = VL0; idx < MAX_PMIC_LEVEL; idx++)
434 vol_dcstat.ops_dcstat[idx].vol =
435 dvc_map[idx];
436 hwdvc_notify_register(&hwdvc_stat_ntf);
437 }
438#endif
439
440 dvfs_node = debugfs_create_dir("vlstat", asr_debug_entry);
441 if (!dvfs_node)
442 return -ENOENT;
443
444 volt_dc_stat = debugfs_create_file("vol_dc_stat", 0444,
445 dvfs_node, NULL, &vol_dc_ops);
446 if (!volt_dc_stat)
447 goto err_1;
448
449 volt_led = debugfs_create_file("vol_led", 0644,
450 dvfs_node, NULL, &vol_led_ops);
451 if (!volt_led)
452 goto err_volt_led;
453
454 return 0;
455
456err_volt_led:
457 debugfs_remove(volt_dc_stat);
458err_1:
459 debugfs_remove(dvfs_node);
460 return -ENOENT;
461}
462arch_initcall(hwdvc_stat_init);