416 lines
12 KiB
C
416 lines
12 KiB
C
/* Copyright (c) 2012, 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/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/types.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/sort.h>
|
|
#include <asm/uaccess.h>
|
|
#include <mach/msm_iomap.h>
|
|
#include "timer.h"
|
|
#include "rpm_rbcpr_stats.h"
|
|
|
|
#define RBCPR_USER_BUF (2000)
|
|
#define STR(a) (#a)
|
|
#define GETFIELD(a) ((strnstr(STR(a), "->", 80) + 2))
|
|
#define PRINTFIELD(buf, buf_size, pos, format, ...) \
|
|
((pos < buf_size) ? snprintf((buf + pos), (buf_size - pos), format,\
|
|
## __VA_ARGS__) : 0)
|
|
|
|
enum {
|
|
RBCPR_CORNER_SVS = 0,
|
|
RBCPR_CORNER_NOMINAL,
|
|
RBCPR_CORNER_TURBO,
|
|
RBCPR_CORNERS_COUNT,
|
|
RBCPR_CORNER_INVALID = 0x7FFFFFFF,
|
|
};
|
|
|
|
struct msm_rpmrbcpr_recmnd {
|
|
uint32_t voltage;
|
|
uint32_t timestamp;
|
|
};
|
|
|
|
struct msm_rpmrbcpr_corners {
|
|
int efuse_adjustment;
|
|
struct msm_rpmrbcpr_recmnd *rpm_rcmnd;
|
|
uint32_t programmed_voltage;
|
|
uint32_t isr_counter;
|
|
uint32_t min_counter;
|
|
uint32_t max_counter;
|
|
};
|
|
|
|
struct msm_rpmrbcpr_stats {
|
|
uint32_t status_count;
|
|
uint32_t num_corners;
|
|
uint32_t num_latest_recommends;
|
|
struct msm_rpmrbcpr_corners *rbcpr_corners;
|
|
uint32_t current_corner;
|
|
uint32_t railway_voltage;
|
|
uint32_t enable;
|
|
};
|
|
|
|
struct msm_rpmrbcpr_stats_internal {
|
|
void __iomem *regbase;
|
|
uint32_t len;
|
|
char buf[RBCPR_USER_BUF];
|
|
};
|
|
|
|
static DEFINE_SPINLOCK(rpm_rbcpr_lock);
|
|
static struct msm_rpmrbcpr_design_data rbcpr_design_data;
|
|
static struct msm_rpmrbcpr_stats rbcpr_stats;
|
|
static struct msm_rpmrbcpr_stats_internal pvtdata;
|
|
|
|
static inline unsigned long msm_rpmrbcpr_read_data(void __iomem *regbase,
|
|
int offset)
|
|
{
|
|
return readl_relaxed(regbase + (offset * 4));
|
|
}
|
|
|
|
static int msm_rpmrbcpr_cmp_func(const void *a, const void *b)
|
|
{
|
|
struct msm_rpmrbcpr_recmnd *pa = (struct msm_rpmrbcpr_recmnd *)(a);
|
|
struct msm_rpmrbcpr_recmnd *pb = (struct msm_rpmrbcpr_recmnd *)(b);
|
|
return pa->timestamp - pb->timestamp;
|
|
}
|
|
|
|
static char *msm_rpmrbcpr_corner_string(uint32_t corner)
|
|
{
|
|
switch (corner) {
|
|
case RBCPR_CORNER_SVS:
|
|
return STR(RBCPR_CORNER_SVS);
|
|
break;
|
|
case RBCPR_CORNER_NOMINAL:
|
|
return STR(RBCPR_CORNER_NOMINAL);
|
|
break;
|
|
case RBCPR_CORNER_TURBO:
|
|
return STR(RBCPR_CORNER_TURBO);
|
|
break;
|
|
case RBCPR_CORNERS_COUNT:
|
|
case RBCPR_CORNER_INVALID:
|
|
default:
|
|
return STR(RBCPR_CORNER_INVALID);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int msm_rpmrbcpr_print_buf(struct msm_rpmrbcpr_stats *pdata,
|
|
struct msm_rpmrbcpr_design_data *pdesdata,
|
|
char *buf)
|
|
{
|
|
int pos = 0;
|
|
struct msm_rpmrbcpr_corners *corners;
|
|
struct msm_rpmrbcpr_recmnd *rcmnd;
|
|
int i, j;
|
|
int current_timestamp = msm_timer_get_sclk_ticks();
|
|
|
|
if (!pdata->enable) {
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
"RBCPR Stats not enabled at RPM");
|
|
return pos;
|
|
}
|
|
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
":RBCPR Platform Data");
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %u)", GETFIELD(pdesdata->upside_steps),
|
|
pdesdata->upside_steps);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s:%u)", GETFIELD(pdesdata->downside_steps),
|
|
pdesdata->downside_steps);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %d)", GETFIELD(pdesdata->svs_voltage),
|
|
pdesdata->svs_voltage);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %d)", GETFIELD(pdesdata->nominal_voltage),
|
|
pdesdata->nominal_voltage);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %d)\n", GETFIELD(pdesdata->turbo_voltage),
|
|
pdesdata->turbo_voltage);
|
|
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
":RBCPR Stats");
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %u)", GETFIELD(pdata->status_counter),
|
|
pdata->status_count);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %s)", GETFIELD(pdata->current_corner),
|
|
msm_rpmrbcpr_corner_string(pdata->current_corner));
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (current_timestamp: 0x%x)",
|
|
current_timestamp);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %u)\n", GETFIELD(pdata->railway_voltage),
|
|
pdata->railway_voltage);
|
|
|
|
for (i = 0; i < pdata->num_corners; i++) {
|
|
corners = &pdata->rbcpr_corners[i];
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
":\tRBCPR Corner Data");
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (name: %s)", msm_rpmrbcpr_corner_string(i));
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %d)", GETFIELD(corners->efuse_adjustment),
|
|
corners->efuse_adjustment);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %u)", GETFIELD(corners->programmed_voltage),
|
|
corners->programmed_voltage);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %u)", GETFIELD(corners->isr_counter),
|
|
corners->isr_counter);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
"(%s: %u)", GETFIELD(corners->min_counter),
|
|
corners->min_counter);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
"(%s:%u)\n", GETFIELD(corners->max_counter),
|
|
corners->max_counter);
|
|
for (j = 0; j < pdata->num_latest_recommends; j++) {
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
":\t\tVoltage History[%d]", j);
|
|
rcmnd = &corners->rpm_rcmnd[j];
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: %u)", GETFIELD(rcmnd->voltage),
|
|
rcmnd->voltage);
|
|
pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
|
|
" (%s: 0x%x)\n", GETFIELD(rcmnd->timestamp),
|
|
rcmnd->timestamp);
|
|
}
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
|
|
static void msm_rpmrbcpr_copy_data(struct msm_rpmrbcpr_stats_internal *pdata,
|
|
struct msm_rpmrbcpr_stats *prbcpr_stats)
|
|
{
|
|
struct msm_rpmrbcpr_corners *corners;
|
|
struct msm_rpmrbcpr_recmnd *rcmnd;
|
|
int i, j;
|
|
int offset = (offsetof(struct msm_rpmrbcpr_stats, rbcpr_corners) / 4);
|
|
|
|
if (!prbcpr_stats)
|
|
return;
|
|
|
|
for (i = 0; i < prbcpr_stats->num_corners; i++) {
|
|
corners = &prbcpr_stats->rbcpr_corners[i];
|
|
corners->efuse_adjustment = msm_rpmrbcpr_read_data(
|
|
pdata->regbase, offset++);
|
|
for (j = 0; j < prbcpr_stats->num_latest_recommends; j++) {
|
|
rcmnd = &corners->rpm_rcmnd[j];
|
|
rcmnd->voltage = msm_rpmrbcpr_read_data(
|
|
pdata->regbase, offset++);
|
|
rcmnd->timestamp = msm_rpmrbcpr_read_data(
|
|
pdata->regbase, offset++);
|
|
}
|
|
sort(&corners->rpm_rcmnd[0],
|
|
prbcpr_stats->num_latest_recommends,
|
|
sizeof(struct msm_rpmrbcpr_recmnd),
|
|
msm_rpmrbcpr_cmp_func, NULL);
|
|
corners->programmed_voltage = msm_rpmrbcpr_read_data(
|
|
pdata->regbase, offset++);
|
|
corners->isr_counter = msm_rpmrbcpr_read_data(pdata->regbase,
|
|
offset++);
|
|
corners->min_counter = msm_rpmrbcpr_read_data(pdata->regbase,
|
|
offset++);
|
|
corners->max_counter = msm_rpmrbcpr_read_data(pdata->regbase,
|
|
offset++);
|
|
}
|
|
prbcpr_stats->current_corner = msm_rpmrbcpr_read_data(pdata->regbase,
|
|
offset++);
|
|
prbcpr_stats->railway_voltage = msm_rpmrbcpr_read_data
|
|
(pdata->regbase, offset++);
|
|
prbcpr_stats->enable = msm_rpmrbcpr_read_data(pdata->regbase, offset++);
|
|
}
|
|
|
|
static int msm_rpmrbcpr_file_read(struct file *file, char __user *bufu,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct msm_rpmrbcpr_stats_internal *pdata = file->private_data;
|
|
int ret;
|
|
int status_counter;
|
|
|
|
if (!pdata) {
|
|
pr_info("%s pdata is null", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!bufu || count < 0) {
|
|
pr_info("%s count %d ", __func__, count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (*ppos > pdata->len || !pdata->len) {
|
|
/* Read RPM stats */
|
|
status_counter = readl_relaxed(pdata->regbase +
|
|
offsetof(struct msm_rpmrbcpr_stats, status_count));
|
|
if (status_counter != rbcpr_stats.status_count) {
|
|
spin_lock(&rpm_rbcpr_lock);
|
|
msm_rpmrbcpr_copy_data(pdata, &rbcpr_stats);
|
|
rbcpr_stats.status_count = status_counter;
|
|
spin_unlock(&rpm_rbcpr_lock);
|
|
}
|
|
pdata->len = msm_rpmrbcpr_print_buf(&rbcpr_stats,
|
|
&rbcpr_design_data, pdata->buf);
|
|
*ppos = 0;
|
|
}
|
|
/* copy to user data */
|
|
ret = simple_read_from_buffer(bufu, count, ppos, pdata->buf,
|
|
pdata->len);
|
|
return ret;
|
|
}
|
|
|
|
static void msm_rpmrbcpr_free_mem(struct msm_rpmrbcpr_stats_internal *pvtdata,
|
|
struct msm_rpmrbcpr_stats *prbcpr_stats)
|
|
{
|
|
int i;
|
|
if (pvtdata->regbase)
|
|
iounmap(pvtdata->regbase);
|
|
|
|
|
|
if (prbcpr_stats) {
|
|
for (i = 0; i < prbcpr_stats->num_corners; i++) {
|
|
kfree(prbcpr_stats->rbcpr_corners[i].rpm_rcmnd);
|
|
prbcpr_stats->rbcpr_corners[i].rpm_rcmnd = NULL;
|
|
}
|
|
|
|
kfree(prbcpr_stats->rbcpr_corners);
|
|
prbcpr_stats->rbcpr_corners = NULL;
|
|
}
|
|
}
|
|
|
|
static int msm_rpmrbcpr_allocate_mem(struct msm_rpmrbcpr_platform_data *pdata,
|
|
struct resource *res)
|
|
{
|
|
int i;
|
|
|
|
pvtdata.regbase = ioremap(res->start, (res->end - res->start + 1));
|
|
memcpy(&rbcpr_design_data, &pdata->rbcpr_data,
|
|
sizeof(struct msm_rpmrbcpr_design_data));
|
|
|
|
|
|
rbcpr_stats.num_corners = readl_relaxed(pvtdata.regbase +
|
|
offsetof(struct msm_rpmrbcpr_stats, num_corners));
|
|
rbcpr_stats.num_latest_recommends = readl_relaxed(pvtdata.regbase +
|
|
offsetof(struct msm_rpmrbcpr_stats,
|
|
num_latest_recommends));
|
|
|
|
rbcpr_stats.rbcpr_corners = kzalloc(
|
|
sizeof(struct msm_rpmrbcpr_corners)
|
|
* rbcpr_stats.num_corners, GFP_KERNEL);
|
|
|
|
if (!rbcpr_stats.rbcpr_corners) {
|
|
msm_rpmrbcpr_free_mem(&pvtdata, &rbcpr_stats);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < rbcpr_stats.num_corners; i++) {
|
|
rbcpr_stats.rbcpr_corners[i].rpm_rcmnd =
|
|
kzalloc(sizeof(struct msm_rpmrbcpr_corners)
|
|
* rbcpr_stats.num_latest_recommends,
|
|
GFP_KERNEL);
|
|
|
|
if (!rbcpr_stats.rbcpr_corners[i].rpm_rcmnd) {
|
|
msm_rpmrbcpr_free_mem(&pvtdata, &rbcpr_stats);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int msm_rpmrbcpr_file_open(struct inode *inode, struct file *file)
|
|
{
|
|
file->private_data = &pvtdata;
|
|
pvtdata.len = 0;
|
|
|
|
if (!pvtdata.regbase)
|
|
return -EBUSY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int msm_rpmrbcpr_file_close(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations msm_rpmrbcpr_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = msm_rpmrbcpr_file_open,
|
|
.read = msm_rpmrbcpr_file_read,
|
|
.release = msm_rpmrbcpr_file_close,
|
|
.llseek = no_llseek,
|
|
};
|
|
|
|
static int __devinit msm_rpmrbcpr_probe(struct platform_device *pdev)
|
|
{
|
|
struct dentry *dent;
|
|
struct msm_rpmrbcpr_platform_data *pdata;
|
|
int ret = 0;
|
|
|
|
pdata = pdev->dev.platform_data;
|
|
if (!pdata)
|
|
return -EINVAL;
|
|
dent = debugfs_create_file("rpm_rbcpr", S_IRUGO, NULL,
|
|
pdev->dev.platform_data, &msm_rpmrbcpr_fops);
|
|
|
|
if (!dent) {
|
|
pr_err("%s: ERROR debugfs_create_file failed\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
platform_set_drvdata(pdev, dent);
|
|
ret = msm_rpmrbcpr_allocate_mem(pdata, pdev->resource);
|
|
return ret;
|
|
}
|
|
|
|
static int __devexit msm_rpmrbcpr_remove(struct platform_device *pdev)
|
|
{
|
|
struct dentry *dent;
|
|
|
|
msm_rpmrbcpr_free_mem(&pvtdata, &rbcpr_stats);
|
|
dent = platform_get_drvdata(pdev);
|
|
debugfs_remove(dent);
|
|
platform_set_drvdata(pdev, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver msm_rpmrbcpr_driver = {
|
|
.probe = msm_rpmrbcpr_probe,
|
|
.remove = __devexit_p(msm_rpmrbcpr_remove),
|
|
.driver = {
|
|
.name = "msm_rpm_rbcpr",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init msm_rpmrbcpr_init(void)
|
|
{
|
|
return platform_driver_register(&msm_rpmrbcpr_driver);
|
|
}
|
|
|
|
static void __exit msm_rpmrbcpr_exit(void)
|
|
{
|
|
platform_driver_unregister(&msm_rpmrbcpr_driver);
|
|
}
|
|
|
|
module_init(msm_rpmrbcpr_init);
|
|
module_exit(msm_rpmrbcpr_exit);
|