435 lines
9.6 KiB
C
435 lines
9.6 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (c) 2020 MediaTek Inc.
|
||
|
* Author: Yu-Chang Wang <Yu-Chang.Wang@mediatek.com>
|
||
|
*/
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_address.h>
|
||
|
#include <linux/of_device.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/string.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#if IS_ENABLED(CONFIG_SEC_FACTORY)
|
||
|
#include <linux/proc_fs.h>
|
||
|
#endif
|
||
|
#include <linux/seq_file.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
|
||
|
#include "clk-fhctl.h"
|
||
|
#include "clk-fhctl-pll.h"
|
||
|
#include "clk-fhctl-util.h"
|
||
|
|
||
|
enum FH_DEBUG_CMD_ID {
|
||
|
FH_DBG_CMD_DVFS_API = 0x1002,
|
||
|
FH_DBG_CMD_SSC_ENABLE = 0x1004,
|
||
|
FH_DBG_CMD_SSC_DISABLE = 0x1005,
|
||
|
};
|
||
|
|
||
|
static bool has_perms;
|
||
|
static int __fh_ctrl_cmd_hdlr(struct pll_dts *array,
|
||
|
unsigned int cmd,
|
||
|
char *pll_name,
|
||
|
unsigned int arg)
|
||
|
{
|
||
|
int i;
|
||
|
struct fh_hdlr *hdlr = NULL;
|
||
|
int num_pll = array->num_pll;
|
||
|
|
||
|
/* pll_name to pll_id */
|
||
|
for (i = 0; i < num_pll; i++, array++) {
|
||
|
FHDBG("<%s,%s>\n", pll_name, array->pll_name);
|
||
|
if (strcmp(pll_name,
|
||
|
array->pll_name) == 0) {
|
||
|
hdlr = array->hdlr;
|
||
|
FHDBG("hdlr<%x>\n", hdlr);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!hdlr) {
|
||
|
FHDBG("\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
FHDBG("perms<%x,%x,%x>\n",
|
||
|
array->perms, PERM_DBG_HOP, PERM_DBG_SSC);
|
||
|
switch (cmd) {
|
||
|
case FH_DBG_CMD_DVFS_API:
|
||
|
if (array->perms & PERM_DBG_HOP)
|
||
|
hdlr->ops->hopping(hdlr->data,
|
||
|
array->domain,
|
||
|
array->fh_id,
|
||
|
arg, 0);
|
||
|
break;
|
||
|
case FH_DBG_CMD_SSC_ENABLE:
|
||
|
if (array->perms & PERM_DBG_SSC)
|
||
|
hdlr->ops->ssc_enable(hdlr->data,
|
||
|
array->domain,
|
||
|
array->fh_id,
|
||
|
arg);
|
||
|
break;
|
||
|
case FH_DBG_CMD_SSC_DISABLE:
|
||
|
if (array->perms & PERM_DBG_SSC)
|
||
|
hdlr->ops->ssc_disable(hdlr->data,
|
||
|
array->domain,
|
||
|
array->fh_id);
|
||
|
break;
|
||
|
default:
|
||
|
FHDBG(" Not Support CMD:%x\n", cmd);
|
||
|
break;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
static bool prop_request(char *kbuf,
|
||
|
struct pll_dts *array)
|
||
|
{
|
||
|
unsigned int n, i, arg;
|
||
|
int num_pll = array->num_pll;
|
||
|
struct pll_dts *entry = NULL;
|
||
|
char pll_name[32];
|
||
|
char prop[32];
|
||
|
|
||
|
/* retrieve prop/pll_name/arg from kbuf */
|
||
|
n = sscanf(kbuf, "%s %31s %x", prop, pll_name, &arg);
|
||
|
FHDBG("prop<%s>, pll_name<%s>, arg<%x>\n",
|
||
|
prop, pll_name, arg);
|
||
|
if (n == -1)
|
||
|
FHDBG("error input format\n");
|
||
|
|
||
|
/* get entry by pll_name */
|
||
|
for (i = 0; i < num_pll; i++, array++) {
|
||
|
if (strcmp(pll_name,
|
||
|
array->pll_name) == 0) {
|
||
|
entry = array;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!entry)
|
||
|
return false;
|
||
|
|
||
|
/* update entry by prop/arg */
|
||
|
if (strstr(prop, "perms"))
|
||
|
entry->perms = arg;
|
||
|
else if (strstr(prop, "ssc-rate"))
|
||
|
entry->ssc_rate = arg;
|
||
|
else
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
static ssize_t fh_ctrl_proc_write(struct file *file,
|
||
|
const char *buffer, size_t count, loff_t *data)
|
||
|
{
|
||
|
int ret, n;
|
||
|
char kbuf[256];
|
||
|
char pll_name[32];
|
||
|
size_t len = 0;
|
||
|
unsigned int cmd, arg;
|
||
|
struct pll_dts *array = file->f_inode->i_private;
|
||
|
|
||
|
FHDBG("array<%x>\n", array);
|
||
|
len = min(count, (sizeof(kbuf) - 1));
|
||
|
|
||
|
FHDBG("count: %ld", count);
|
||
|
if (count == 0)
|
||
|
return -1;
|
||
|
|
||
|
if (count > 255)
|
||
|
count = 255;
|
||
|
|
||
|
ret = copy_from_user(kbuf, buffer, count);
|
||
|
if (ret < 0)
|
||
|
return -1;
|
||
|
|
||
|
kbuf[count] = '\0';
|
||
|
|
||
|
/* permission control */
|
||
|
if (strstr(kbuf, array->comp)) {
|
||
|
has_perms = true;
|
||
|
FHDBG("has_perms to true\n");
|
||
|
return count;
|
||
|
} else if (!has_perms) {
|
||
|
FHDBG("!has_perms\n");
|
||
|
return count;
|
||
|
} else if (prop_request(kbuf, array)) {
|
||
|
FHDBG("prop_request = true\n");
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
n = sscanf(kbuf, "%x %31s %x", &cmd, pll_name, &arg);
|
||
|
if ((n != 3) && (n != 2)) {
|
||
|
FHDBG("error input format\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
FHDBG("pll:%s cmd:0x%x arg:0x%x", pll_name, cmd, arg);
|
||
|
__fh_ctrl_cmd_hdlr(array, cmd, pll_name, arg);
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
static int fh_ctrl_proc_read(struct seq_file *m, void *v)
|
||
|
{
|
||
|
int i;
|
||
|
struct pll_dts *array = m->private;
|
||
|
int num_pll = array->num_pll;
|
||
|
|
||
|
seq_printf(m, "====== FHCTL CTRL, has_perms<%d>, comp<%s>======\n",
|
||
|
has_perms, array->comp);
|
||
|
|
||
|
seq_puts(m, "[Name pll-id fh-id perms ssc-rate]");
|
||
|
seq_puts(m, "[domain method]");
|
||
|
seq_puts(m, "[Hdlr]");
|
||
|
seq_puts(m, "\n");
|
||
|
|
||
|
for (i = 0; i < num_pll; i++, array++) {
|
||
|
seq_printf(m, "<%s,%d,%d,%x,%d>,<%s,%s>,<%lx>\n",
|
||
|
array->pll_name,
|
||
|
array->pll_id, array->fh_id,
|
||
|
array->perms, array->ssc_rate,
|
||
|
array->domain, array->method,
|
||
|
(unsigned long)array->hdlr);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
static int fh_ctrl_proc_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
return single_open(file, fh_ctrl_proc_read, inode->i_private);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations ctrl_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = fh_ctrl_proc_open,
|
||
|
.read = seq_read,
|
||
|
.write = fh_ctrl_proc_write,
|
||
|
.release = single_release,
|
||
|
};
|
||
|
#if IS_ENABLED(CONFIG_SEC_FACTORY)
|
||
|
static int fh_ctrl_proc_open2(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
void *v = PDE_DATA(file_inode(file));
|
||
|
return single_open(file, fh_ctrl_proc_read, v);
|
||
|
}
|
||
|
|
||
|
static ssize_t fh_ctrl_proc_write2(struct file *file,
|
||
|
const char *buffer, size_t count, loff_t *data)
|
||
|
{
|
||
|
int ret, n;
|
||
|
char kbuf[256];
|
||
|
char pll_name[32];
|
||
|
size_t len = 0;
|
||
|
unsigned int cmd, arg;
|
||
|
struct pll_dts *array = PDE_DATA(file_inode(file));
|
||
|
|
||
|
FHDBG("array<%x>\n", array);
|
||
|
len = min(count, (sizeof(kbuf) - 1));
|
||
|
|
||
|
FHDBG("count: %ld", count);
|
||
|
if (count == 0)
|
||
|
return -1;
|
||
|
|
||
|
if (count > 255)
|
||
|
count = 255;
|
||
|
|
||
|
ret = copy_from_user(kbuf, buffer, count);
|
||
|
if (ret < 0)
|
||
|
return -1;
|
||
|
|
||
|
kbuf[count] = '\0';
|
||
|
|
||
|
/* permission control */
|
||
|
if (strstr(kbuf, array->comp)) {
|
||
|
has_perms = true;
|
||
|
FHDBG("has_perms to true\n");
|
||
|
return count;
|
||
|
} else if (!has_perms) {
|
||
|
FHDBG("!has_perms\n");
|
||
|
return count;
|
||
|
} else if (prop_request(kbuf, array)) {
|
||
|
FHDBG("prop_request = true\n");
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
n = sscanf(kbuf, "%x %31s %x", &cmd, pll_name, &arg);
|
||
|
if ((n != 3) && (n != 2)) {
|
||
|
FHDBG("error input format\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
FHDBG("pll:%s cmd:0x%x arg:0x%x", pll_name, cmd, arg);
|
||
|
__fh_ctrl_cmd_hdlr(array, cmd, pll_name, arg);
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static const struct file_operations ctrl_fops2 = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = fh_ctrl_proc_open2,
|
||
|
.read = seq_read,
|
||
|
.write = fh_ctrl_proc_write2,
|
||
|
.release = single_release,
|
||
|
};
|
||
|
#endif
|
||
|
static int __sample_dds(struct fh_pll_regs *regs,
|
||
|
struct fh_pll_data *data,
|
||
|
unsigned int *dds_max,
|
||
|
unsigned int *dds_min)
|
||
|
{
|
||
|
int i, ssc_rate = 0;
|
||
|
unsigned int mon_dds;
|
||
|
unsigned int dds;
|
||
|
|
||
|
mon_dds = readl(regs->reg_mon) & data->dds_mask;
|
||
|
dds = readl(regs->reg_dds) & data->dds_mask;
|
||
|
|
||
|
*dds_max = dds;
|
||
|
*dds_min = mon_dds;
|
||
|
|
||
|
/* Sample 200*10us */
|
||
|
for (i = 0 ; i < 200 ; i++) {
|
||
|
mon_dds = readl(regs->reg_mon) & data->dds_mask;
|
||
|
|
||
|
if (mon_dds > *dds_max)
|
||
|
*dds_max = mon_dds;
|
||
|
|
||
|
if (mon_dds < *dds_min)
|
||
|
*dds_min = mon_dds;
|
||
|
|
||
|
udelay(10);
|
||
|
}
|
||
|
|
||
|
if ((*dds_max == 0) ||
|
||
|
(*dds_min == 0))
|
||
|
ssc_rate = 0;
|
||
|
else {
|
||
|
int diff = (*dds_max - *dds_min);
|
||
|
|
||
|
ssc_rate = (diff * 1000) / *dds_max;
|
||
|
}
|
||
|
|
||
|
return ssc_rate;
|
||
|
}
|
||
|
static int fh_dumpregs_read(struct seq_file *m, void *v)
|
||
|
{
|
||
|
int i;
|
||
|
struct pll_dts *array = m->private;
|
||
|
int num_pll = array->num_pll;
|
||
|
|
||
|
FHDBG("array<%x>\n", array);
|
||
|
seq_puts(m, "FHCTL dumpregs Read\n");
|
||
|
|
||
|
for (i = 0; i < num_pll ; i++, array++) {
|
||
|
struct fh_pll_domain *domain;
|
||
|
struct fh_pll_regs *regs;
|
||
|
struct fh_pll_data *data;
|
||
|
int fh_id, pll_id;
|
||
|
char *pll_name;
|
||
|
int ssc_rate;
|
||
|
unsigned int dds_max, dds_min;
|
||
|
|
||
|
if (!(array->perms & PERM_DBG_DUMP))
|
||
|
continue;
|
||
|
|
||
|
fh_id = array->fh_id;
|
||
|
pll_id = array->pll_id;
|
||
|
pll_name = array->pll_name;
|
||
|
domain = get_fh_domain(array->domain);
|
||
|
|
||
|
if (domain == NULL) {
|
||
|
FHDBG("domain is null!");
|
||
|
WARN_ON(1);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
regs = &domain->regs[fh_id];
|
||
|
data = &domain->data[fh_id];
|
||
|
|
||
|
ssc_rate = __sample_dds(regs, data,
|
||
|
&dds_max, &dds_min);
|
||
|
|
||
|
seq_printf(m, "PLL_ID:%d FH_ID:%d NAME:%s",
|
||
|
pll_id, fh_id, pll_name);
|
||
|
seq_printf(m, " PCW:%x SSC_RATE:%d\n",
|
||
|
readl(regs->reg_con_pcw) & data->dds_mask,
|
||
|
ssc_rate);
|
||
|
|
||
|
seq_puts(m, "HP_EN, CLK_CON, SLOPE0, SLOPE1\n");
|
||
|
seq_printf(m, "0x%08x 0x%08x 0x%08x 0x%08x\n",
|
||
|
readl(regs->reg_hp_en), readl(regs->reg_clk_con),
|
||
|
readl(regs->reg_slope0), readl(regs->reg_slope1));
|
||
|
|
||
|
seq_puts(m, "CFG, UPDNLMT, DDS, DVFS, MON\n");
|
||
|
seq_printf(m, "0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n",
|
||
|
readl(regs->reg_cfg), readl(regs->reg_updnlmt),
|
||
|
readl(regs->reg_dds), readl(regs->reg_dvfs),
|
||
|
readl(regs->reg_mon));
|
||
|
seq_puts(m, "CON_PCW\n");
|
||
|
seq_printf(m, "0x%08x\n",
|
||
|
readl(regs->reg_con_pcw));
|
||
|
|
||
|
seq_printf(m,
|
||
|
"dds_max:0x%08x dds_mix:0x%08x ssc(1/1000):%d\n\n",
|
||
|
dds_max, dds_min, ssc_rate);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
static int fh_dumpregs_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
return single_open(file, fh_dumpregs_read, inode->i_private);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations dumpregs_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = fh_dumpregs_open,
|
||
|
.read = seq_read,
|
||
|
.release = single_release,
|
||
|
};
|
||
|
|
||
|
int fhctl_debugfs_init(struct pll_dts *array)
|
||
|
{
|
||
|
struct dentry *root;
|
||
|
struct dentry *fh_dumpregs_dir;
|
||
|
struct dentry *fh_ctrl_dir;
|
||
|
int i;
|
||
|
int num_pll = array->num_pll;
|
||
|
|
||
|
FHDBG("array<%x>\n", array);
|
||
|
|
||
|
root = debugfs_create_dir("fhctl", NULL);
|
||
|
if (IS_ERR(root)) {
|
||
|
FHDBG("\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* /sys/kernel/debug/fhctl/dumpregs */
|
||
|
fh_dumpregs_dir = debugfs_create_file("dumpregs", 0664,
|
||
|
root, array, &dumpregs_fops);
|
||
|
if (IS_ERR(fh_dumpregs_dir)) {
|
||
|
FHDBG("\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* /sys/kernel/debug/fhctl/ctrl */
|
||
|
fh_ctrl_dir = debugfs_create_file("ctrl", 0664,
|
||
|
root, array, &ctrl_fops);
|
||
|
if (IS_ERR(fh_ctrl_dir)) {
|
||
|
FHDBG("\n");
|
||
|
return -1;
|
||
|
}
|
||
|
#if IS_ENABLED(CONFIG_SEC_FACTORY)
|
||
|
proc_create_data("fhctl", 0644, NULL, &ctrl_fops2, array);
|
||
|
#endif
|
||
|
/* init resources */
|
||
|
for (i = 0; i < num_pll; i++, array++) {
|
||
|
init_fh_domain(array->domain,
|
||
|
array->comp,
|
||
|
array->fhctl_base,
|
||
|
array->apmixed_base);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|