blob: 3411e4ecb822c2a086d2e4c5c29160dd67df5f7c [file] [log] [blame]
/*
* common function for clock framework source file
*
* Copyright (C) 2012 Marvell
* Chao Xie <xiechao.mail@gmail.com>
* Zhoujie Wu <zjwu@marvell.com>
* Lu Cao <lucao@marvell.com>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <soc/asr/asrdcstat.h>
#include <linux/clk/dvfs-dvc.h>
#include <soc/asr/debugfs-asr.h>
#include <linux/gpio.h>
//#include <linux/miscled-rgb.h>
#include <linux/mfd/88pm80x.h>
#include "clk.h"
#include <dt-bindings/power/asr-power.h>
struct vol_op_dcstat_info {
u64 time; /*ms*/
u32 vol; /* mV */
};
struct vol_dc_stat_info {
bool stat_start;
struct vol_op_dcstat_info ops_dcstat[MAX_PMIC_LEVEL];
u32 vc_count[MAX_PMIC_LEVEL][MAX_PMIC_LEVEL]; /* [from][to] */
u32 vc_total_count;
ktime_t breakdown_start;
ktime_t prev_ts;
u32 cur_lvl;
u64 total_time; /* ms */
};
static DEFINE_SPINLOCK(vol_lock);
static struct vol_dc_stat_info vol_dcstat;
static int vol_ledstatus_start;
static ktime_t vol_dcstat_time(void);
/*
* Get platform specified dvc low power modes. Platform power file
* should define an array contains these modes with the same sequence
* in hwdvc_rails.
*/
unsigned int hwdvc_lpm[AP_COMP_MAX] = {POWER_MODE_MAX, POWER_MODE_CORE_POWERDOWN,
POWER_MODE_APPS_IDLE, POWER_MODE_SYS_SLEEP};
static u32 voltage_lvl[AP_COMP_MAX];
/* Platform supported maximum lpms */
u32 max_lpm = POWER_MODE_MAX;
static u32 find_hwdvc_voltage_lvl(u32 lpm)
{
u32 hwlvl;
int i;
for (i = 0; i < AP_COMP_MAX; i++)
if (hwdvc_lpm[i] == lpm) {
hwlvl = voltage_lvl[i];
break;
}
/* Incorrect lpm? Set AP_ACTIVE as default */
if (i == AP_COMP_MAX) {
hwlvl = voltage_lvl[AP_ACTIVE];
}
return hwlvl;
}
static void vol_ledstatus_show(u32 lpm)
{
u32 hwlvl = find_hwdvc_voltage_lvl(lpm);
switch (hwlvl) {
#if 0
case VL3:
/* VL3 : LED RG */
led_rgb_output(LED_R, 1);
led_rgb_output(LED_G, 1);
led_rgb_output(LED_B, 0);
break;
case VL2:
/* VL2 : LED R */
led_rgb_output(LED_R, 1);
led_rgb_output(LED_G, 0);
led_rgb_output(LED_B, 0);
break;
case VL1:
/* VL1 : LED G */
led_rgb_output(LED_R, 0);
led_rgb_output(LED_G, 1);
led_rgb_output(LED_B, 0);
break;
case VL0:
/* VL0 : LED B */
led_rgb_output(LED_R, 0);
led_rgb_output(LED_G, 0);
led_rgb_output(LED_B, 1);
break;
case -1:
/* lpm : LED -- */
led_rgb_output(LED_R, 0);
led_rgb_output(LED_G, 0);
led_rgb_output(LED_B, 0);
break;
#endif
default:
break;
}
}
void vol_ledstatus_event(u32 lpm)
{
spin_lock(&vol_lock);
if (!vol_ledstatus_start)
goto out;
vol_ledstatus_show(lpm);
out:
spin_unlock(&vol_lock);
}
static ssize_t vol_led_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
char *buf;
ssize_t ret, size = 2 * PAGE_SIZE - 1;
u32 len = 0;
buf = (char *)__get_free_pages(GFP_NOIO, get_order(size));
if (!buf)
return -ENOMEM;
len += snprintf(buf + len, size - len,
"Help information :\n");
len += snprintf(buf + len, size - len,
"echo 1 to start voltage led status:\n");
len += snprintf(buf + len, size - len,
"echo 0 to stop voltage led status:\n");
len += snprintf(buf + len, size - len,
"VLevel3: R, G\n");
len += snprintf(buf + len, size - len,
"VLevel2: R\n");
len += snprintf(buf + len, size - len,
"VLevel1: G\n");
len += snprintf(buf + len, size - len,
"VLevel0: B\n");
len += snprintf(buf + len, size - len,
" lpm: off\n");
ret = simple_read_from_buffer(buffer, count, ppos, buf, len);
free_pages((unsigned long)buf, get_order(size));
return ret;
}
static ssize_t vol_led_write(struct file *filp, const char __user *buffer,
size_t count, loff_t *ppos)
{
unsigned int start;
char buf[10] = { 0 };
if (copy_from_user(buf, buffer, count))
return -EFAULT;
sscanf(buf, "%d", &start);
start = !!start;
if (vol_ledstatus_start == start) {
pr_err("[WARNING]Voltage led status is already %s\n",
vol_dcstat.stat_start ? "started" : "stopped");
return -EINVAL;
}
spin_lock(&vol_lock);
vol_ledstatus_start = start;
#if 0
if (vol_ledstatus_start)
vol_ledstatus_show(MAX_LPM_INDEX);
else {
led_rgb_output(LED_R, 0);
led_rgb_output(LED_G, 0);
led_rgb_output(LED_B, 0);
}
#endif
spin_unlock(&vol_lock);
return count;
}
static const struct file_operations vol_led_ops = {
.owner = THIS_MODULE,
.read = vol_led_read,
.write = vol_led_write,
};
static void vol_dcstat_update(u32 lpm)
{
ktime_t cur_ts;
u32 hwlvl;
u64 time_us;
hwlvl = find_hwdvc_voltage_lvl(lpm);
if (vol_dcstat.cur_lvl == hwlvl)
return;
/* update voltage change times */
vol_dcstat.vc_count[vol_dcstat.cur_lvl][hwlvl]++;
/* update voltage dc statistics */
cur_ts = vol_dcstat_time();
time_us = ktime_to_us(ktime_sub(cur_ts, vol_dcstat.prev_ts));
vol_dcstat.ops_dcstat[vol_dcstat.cur_lvl].time += time_us;
vol_dcstat.prev_ts = cur_ts;
vol_dcstat.cur_lvl = hwlvl;
}
void vol_dcstat_event(u32 lpm)
{
spin_lock(&vol_lock);
if (!vol_dcstat.stat_start)
goto out;
vol_dcstat_update(lpm);
out:
spin_unlock(&vol_lock);
}
static ktime_t vol_dcstat_time(void)
{
ktime_t ktime_temp;
/* flag for if timekeeping is suspended */
if (unlikely(timekeeping_suspended))
ktime_temp = ns_to_ktime(suspended_time);
else
ktime_temp = ktime_get();
return ktime_temp;
}
static ssize_t vol_dc_read(struct file *filp, char __user *buffer,
size_t count, loff_t *ppos)
{
char *buf;
ssize_t ret, size = 2 * PAGE_SIZE - 1;
u32 i, j, dc_int = 0, dc_fra = 0, len = 0;
buf = (char *)__get_free_pages(GFP_NOIO, get_order(size));
if (!buf)
return -ENOMEM;
if (!vol_dcstat.total_time) {
len += snprintf(buf + len, size - len,
"No stat information! ");
len += snprintf(buf + len, size - len,
"Help information :\n");
len += snprintf(buf + len, size - len,
"1. echo 1 to start duty cycle stat:\n");
len += snprintf(buf + len, size - len,
"2. echo 0 to stop duty cycle stat:\n");
len += snprintf(buf + len, size - len,
"3. cat to check duty cycle info from start to stop:\n\n");
goto out;
}
if (vol_dcstat.stat_start) {
len += snprintf(buf + len, size - len,
"Please stop the vol duty cycle stats at first\n");
goto out;
}
len += snprintf(buf + len, size - len,
"Total time:%8llums (%6llus)\n",
div64_u64(vol_dcstat.total_time, (u64)(1000)),
div64_u64(vol_dcstat.total_time, (u64)(1000000)));
len += snprintf(buf + len, size - len,
"|Level|Vol(mV)|Time(ms)| %%|\n");
for (i = VL0; i < MAX_PMIC_LEVEL; i++) {
dc_int = calculate_dc(vol_dcstat.ops_dcstat[i].time,
vol_dcstat.total_time, &dc_fra);
len += snprintf(buf + len, size - len,
"| VL_%2d|%7u|%8llu|%3u.%02u%%|\n",
i, vol_dcstat.ops_dcstat[i].vol,
div64_u64(vol_dcstat.ops_dcstat[i].time,
(u64)(1000)),
dc_int, dc_fra);
}
/* show voltage-change times */
len += snprintf(buf + len, size - len,
"\nTotal voltage-change times:%8u",
vol_dcstat.vc_total_count);
len += snprintf(buf + len, size - len, "\n|from\\to|");
for (j = VL0; j < MAX_PMIC_LEVEL; j++)
len += snprintf(buf + len, size - len, " Level%d|", j);
for (i = VL0; i < MAX_PMIC_LEVEL; i++) {
len += snprintf(buf + len, size - len, "\n| Level%2d|", i);
for (j = VL0; j < MAX_PMIC_LEVEL; j++)
if (i == j)
len += snprintf(buf + len, size - len,
" --- |");
else
/* [from][to] */
len += snprintf(buf + len, size - len,
"%7u|",
vol_dcstat.vc_count[i][j]);
}
len += snprintf(buf + len, size - len, "\n");
out:
ret = simple_read_from_buffer(buffer, count, ppos, buf, len);
free_pages((unsigned long)buf, get_order(size));
return ret;
}
static ssize_t vol_dc_write(struct file *filp, const char __user *buffer,
size_t count, loff_t *ppos)
{
unsigned int start, i, j;
char buf[10] = { 0 };
ktime_t cur_ts;
u64 time_us;
struct device_node __maybe_unused *np =
of_find_node_by_name(NULL, "pxa1928_apmu_ver");
if (copy_from_user(buf, buffer, count))
return -EFAULT;
sscanf(buf, "%d", &start);
start = !!start;
if (vol_dcstat.stat_start == start) {
pr_err("[WARNING]Voltage duty-cycle statistics is already %s\n",
vol_dcstat.stat_start ? "started" : "stopped");
return -EINVAL;
}
vol_dcstat.stat_start = start;
if (vol_dcstat.stat_start && np)
for (i = VL0; i < MAX_PMIC_LEVEL; i++)
pm8xx_dvc_getvolt(PM800_ID_BUCK1, i,
&vol_dcstat.ops_dcstat[i].vol);
spin_lock(&vol_lock);
cur_ts = ktime_get();
if (vol_dcstat.stat_start) {
for (i = VL0; i < MAX_PMIC_LEVEL; i++) {
vol_dcstat.ops_dcstat[i].time = 0;
for (j = VL0; j < MAX_PMIC_LEVEL; j++)
vol_dcstat.vc_count[i][j] = 0;
}
vol_dcstat.prev_ts = cur_ts;
vol_dcstat.breakdown_start = cur_ts;
vol_dcstat.cur_lvl = voltage_lvl[AP_ACTIVE];
vol_dcstat.total_time = -1UL;
vol_dcstat.vc_total_count = 0;
} else {
time_us = ktime_to_us(ktime_sub(cur_ts, vol_dcstat.prev_ts));
vol_dcstat.ops_dcstat[vol_dcstat.cur_lvl].time += time_us;
vol_dcstat.total_time = ktime_to_us(ktime_sub(cur_ts,
vol_dcstat.breakdown_start));
for (i = VL0; i < MAX_PMIC_LEVEL; i++)
for (j = VL0; j < MAX_PMIC_LEVEL; j++)
vol_dcstat.vc_total_count +=
vol_dcstat.vc_count[i][j];
}
spin_unlock(&vol_lock);
return count;
}
static const struct file_operations vol_dc_ops = {
.owner = THIS_MODULE,
.read = vol_dc_read,
.write = vol_dc_write,
};
static int hwdvc_stat_notifier_handler(struct notifier_block *nb,
unsigned long rails, void *data)
{
struct hwdvc_notifier_data *vl;
if (rails >= AP_COMP_MAX)
return NOTIFY_OK;
spin_lock(&vol_lock);
vl = (struct hwdvc_notifier_data *)(data);
voltage_lvl[rails] = vl->newlv;
if (!vol_dcstat.stat_start)
goto out_dc;
if (rails == AP_ACTIVE)
vol_dcstat_update(max_lpm);
out_dc:
if (!vol_ledstatus_start)
goto out_led;
if (rails == AP_ACTIVE)
vol_ledstatus_show(max_lpm);
out_led:
spin_unlock(&vol_lock);
return NOTIFY_OK;
}
static struct notifier_block hwdvc_stat_ntf = {
.notifier_call = hwdvc_stat_notifier_handler,
};
static int __init hwdvc_stat_init(void)
{
struct dentry *dvfs_node, *volt_dc_stat, *volt_led;
u32 idx;
#ifdef CONFIG_ARM64
int dvc_map[MAX_PMIC_LEVEL];
int cnt;
#endif
struct device_node __maybe_unused *np =
of_find_node_by_name(NULL, "pxa1928_apmu_ver");
#ifndef CONFIG_ARM64
struct dvc_plat_info platinfo;
/* record voltage lvl when init */
if (dvfs_get_dvcplatinfo(&platinfo))
return -ENOENT;
for (idx = VL0; idx < MAX_PMIC_LEVEL; idx++)
vol_dcstat.ops_dcstat[idx].vol =
platinfo.millivolts[idx];
hwdvc_notifier_register(&hwdvc_stat_ntf);
#else
if (np) {
get_voltage_table_for_cp(dvc_map, &cnt);
for (idx = VL0; idx < MAX_PMIC_LEVEL; idx++)
vol_dcstat.ops_dcstat[idx].vol =
dvc_map[idx];
hwdvc_notify_register(&hwdvc_stat_ntf);
}
#endif
dvfs_node = debugfs_create_dir("vlstat", asr_debug_entry);
if (!dvfs_node)
return -ENOENT;
volt_dc_stat = debugfs_create_file("vol_dc_stat", 0444,
dvfs_node, NULL, &vol_dc_ops);
if (!volt_dc_stat)
goto err_1;
volt_led = debugfs_create_file("vol_led", 0644,
dvfs_node, NULL, &vol_led_ops);
if (!volt_led)
goto err_volt_led;
return 0;
err_volt_led:
debugfs_remove(volt_dc_stat);
err_1:
debugfs_remove(dvfs_node);
return -ENOENT;
}
arch_initcall(hwdvc_stat_init);