// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. * Author: Yu-Chang Wang */ #include #include #include #include #include #include #include #include #if IS_ENABLED(CONFIG_SEC_FACTORY) #include #endif #include #include #include #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; }