[Feature]add MT2731_MP2_MR2_SVN388 baseline version
Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/kernel/linux/v4.14/drivers/clk/mediatek/clkdbg.c b/src/kernel/linux/v4.14/drivers/clk/mediatek/clkdbg.c
new file mode 100644
index 0000000..f4a65ed
--- /dev/null
+++ b/src/kernel/linux/v4.14/drivers/clk/mediatek/clkdbg.c
@@ -0,0 +1,2242 @@
+/*
+ * Copyright (C) 2015 MediaTek Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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.
+ */
+
+#define pr_fmt(fmt) "[clkdbg] " fmt
+
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <linux/proc_fs.h>
+#include <linux/fs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/module.h>
+#include <linux/version.h>
+
+#include "clkdbg.h"
+
+#if defined(CONFIG_PM_DEBUG)
+#define CLKDBG_PM_DOMAIN 1
+#else
+#define CLKDBG_PM_DOMAIN 0
+#endif
+#define CLKDBG_PM_DOMAIN_API_4_9 1
+#define CLKDBG_CCF_API_4_4 1
+#define CLKDBG_HACK_CLK 0
+#define CLKDBG_HACK_CLK_CORE 1
+
+
+#if !CLKDBG_CCF_API_4_4
+
+/* backward compatible */
+
+static const char *clk_hw_get_name(const struct clk_hw *hw)
+{
+ return __clk_get_name(hw->clk);
+}
+
+static bool clk_hw_is_prepared(const struct clk_hw *hw)
+{
+ return __clk_is_prepared(hw->clk);
+}
+
+static bool clk_hw_is_enabled(const struct clk_hw *hw)
+{
+ return __clk_is_enabled(hw->clk);
+}
+
+static unsigned long clk_hw_get_rate(const struct clk_hw *hw)
+{
+ return __clk_get_rate(hw->clk);
+}
+
+static unsigned int clk_hw_get_num_parents(const struct clk_hw *hw)
+{
+ return __clk_get_num_parents(hw->clk);
+}
+
+static struct clk_hw *clk_hw_get_parent_by_index(const struct clk_hw *hw,
+ unsigned int index)
+{
+ return __clk_get_hw(clk_get_parent_by_index(hw->clk, index));
+}
+
+#endif /* !CLKDBG_CCF_API_4_4 */
+
+#if CLKDBG_HACK_CLK
+
+#include <linux/clk-private.h>
+
+static bool clk_hw_is_on(struct clk_hw *hw)
+{
+ const struct clk_ops *ops = hw->clk->ops;
+
+ if (ops->is_enabled)
+ return clk_hw_is_enabled(hw);
+ else if (ops->is_prepared)
+ return clk_hw_is_prepared(hw);
+ return clk_hw_is_enabled(hw) || clk_hw_is_prepared(hw);
+}
+
+#elif CLKDBG_HACK_CLK_CORE
+
+struct clk_core {
+ const char *name;
+ const struct clk_ops *ops;
+ struct clk_hw *hw;
+};
+
+static bool clk_hw_is_on(struct clk_hw *hw)
+{
+ const struct clk_ops *ops = hw->core->ops;
+
+ if (ops->is_enabled)
+ return clk_hw_is_enabled(hw);
+ else if (ops->is_prepared)
+ return clk_hw_is_prepared(hw);
+ return clk_hw_is_enabled(hw) || clk_hw_is_prepared(hw);
+}
+
+#else
+
+static bool clk_hw_is_on(struct clk_hw *hw)
+{
+ return __clk_get_enable_count(hw->clk) || clk_hw_is_prepared(hw);
+}
+
+#endif /* !CLKDBG_HACK_CLK && !CLKDBG_HACK_CLK_CORE */
+
+static const struct clkdbg_ops *clkdbg_ops;
+
+void set_clkdbg_ops(const struct clkdbg_ops *ops)
+{
+ clkdbg_ops = ops;
+}
+
+static const struct fmeter_clk *get_all_fmeter_clks(void)
+{
+ if (clkdbg_ops == NULL || clkdbg_ops->get_all_fmeter_clks == NULL)
+ return NULL;
+
+ return clkdbg_ops->get_all_fmeter_clks();
+}
+
+static void *prepare_fmeter(void)
+{
+ if (clkdbg_ops == NULL || clkdbg_ops->prepare_fmeter == NULL)
+ return NULL;
+
+ return clkdbg_ops->prepare_fmeter();
+}
+
+static void unprepare_fmeter(void *data)
+{
+ if (clkdbg_ops == NULL || clkdbg_ops->unprepare_fmeter == NULL)
+ return;
+
+ clkdbg_ops->unprepare_fmeter(data);
+}
+
+static u32 fmeter_freq(const struct fmeter_clk *fclk)
+{
+ if (clkdbg_ops == NULL || clkdbg_ops->fmeter_freq == NULL)
+ return 0;
+
+ return clkdbg_ops->fmeter_freq(fclk);
+}
+
+static const struct regname *get_all_regnames(void)
+{
+ if (clkdbg_ops == NULL || clkdbg_ops->get_all_regnames == NULL)
+ return NULL;
+
+ return clkdbg_ops->get_all_regnames();
+}
+
+static const char * const *get_all_clk_names(void)
+{
+ if (clkdbg_ops == NULL || clkdbg_ops->get_all_clk_names == NULL)
+ return NULL;
+
+ return clkdbg_ops->get_all_clk_names();
+}
+
+static const char * const *get_pwr_names(void)
+{
+ static const char * const default_pwr_names[] = {
+ [0] = "(MD)",
+ [1] = "(CONN)",
+ [2] = "(DDRPHY)",
+ [3] = "(DISP)",
+ [4] = "(MFG)",
+ [5] = "(ISP)",
+ [6] = "(INFRA)",
+ [7] = "(VDEC)",
+ [8] = "(CPU, CA7_CPUTOP)",
+ [9] = "(FC3, CA7_CPU0, CPUTOP)",
+ [10] = "(FC2, CA7_CPU1, CPU3)",
+ [11] = "(FC1, CA7_CPU2, CPU2)",
+ [12] = "(FC0, CA7_CPU3, CPU1)",
+ [13] = "(MCUSYS, CA7_DBG, CPU0)",
+ [14] = "(MCUSYS, VEN, BDP)",
+ [15] = "(CA15_CPUTOP, ETH, MCUSYS)",
+ [16] = "(CA15_CPU0, HIF)",
+ [17] = "(CA15_CPU1, CA15-CX0, INFRA_MISC)",
+ [18] = "(CA15_CPU2, CA15-CX1)",
+ [19] = "(CA15_CPU3, CA15-CPU0)",
+ [20] = "(VEN2, MJC, CA15-CPU1)",
+ [21] = "(VEN, CA15-CPUTOP)",
+ [22] = "(MFG_2D)",
+ [23] = "(MFG_ASYNC, DBG)",
+ [24] = "(AUDIO, MFG_2D)",
+ [25] = "(USB, VCORE_PDN, MFG_ASYNC)",
+ [26] = "(ARMPLL_DIV, CPUTOP_SRM_SLPB)",
+ [27] = "(MD2, CPUTOP_SRM_PDN)",
+ [28] = "(CPU3_SRM_PDN)",
+ [29] = "(CPU2_SRM_PDN)",
+ [30] = "(CPU1_SRM_PDN)",
+ [31] = "(CPU0_SRM_PDN)",
+ };
+
+ if (clkdbg_ops == NULL || clkdbg_ops->get_pwr_names == NULL)
+ return default_pwr_names;
+
+ return clkdbg_ops->get_pwr_names();
+}
+
+static void setup_provider_clk(struct provider_clk *pvdck)
+{
+ if (clkdbg_ops == NULL || clkdbg_ops->setup_provider_clk == NULL)
+ return;
+
+ clkdbg_ops->setup_provider_clk(pvdck);
+}
+
+static bool is_valid_reg(void __iomem *addr)
+{
+#ifdef CONFIG_64BIT
+ return ((u64)addr & 0xf0000000) != 0UL ||
+ (((u64)addr >> 32U) & 0xf0000000) != 0UL;
+#else
+ return ((u32)addr & 0xf0000000) != 0U;
+#endif
+}
+
+enum clkdbg_opt {
+ CLKDBG_EN_SUSPEND_SAVE_1,
+ CLKDBG_EN_SUSPEND_SAVE_2,
+ CLKDBG_EN_SUSPEND_SAVE_3,
+ CLKDBG_EN_LOG_SAVE_POINTS,
+};
+
+static u32 clkdbg_flags;
+
+static void set_clkdbg_flag(enum clkdbg_opt opt)
+{
+ clkdbg_flags |= BIT(opt);
+}
+
+static void clr_clkdbg_flag(enum clkdbg_opt opt)
+{
+ clkdbg_flags &= ~BIT(opt);
+}
+
+static bool has_clkdbg_flag(enum clkdbg_opt opt)
+{
+ return (clkdbg_flags & BIT(opt)) != 0U;
+}
+
+typedef void (*fn_fclk_freq_proc)(const struct fmeter_clk *fclk,
+ u32 freq, void *data);
+
+static void proc_all_fclk_freq(fn_fclk_freq_proc proc, void *data)
+{
+ void *fmeter_data;
+ const struct fmeter_clk *fclk;
+
+ fclk = get_all_fmeter_clks();
+
+ if (fclk == NULL || proc == NULL)
+ return;
+
+ fmeter_data = prepare_fmeter();
+
+ for (; fclk->type != FT_NULL; fclk++) {
+ u32 freq;
+
+ freq = fmeter_freq(fclk);
+ proc(fclk, freq, data);
+ }
+
+ unprepare_fmeter(fmeter_data);
+}
+
+static void print_fclk_freq(const struct fmeter_clk *fclk, u32 freq, void *data)
+{
+ pr_info("%2d: %-29s: %u\n", fclk->id, fclk->name, freq);
+}
+
+void print_fmeter_all(void)
+{
+ proc_all_fclk_freq(print_fclk_freq, NULL);
+}
+
+static void seq_print_fclk_freq(const struct fmeter_clk *fclk,
+ u32 freq, void *data)
+{
+ struct seq_file *s = data;
+
+ seq_printf(s, "%2d: %-29s: %u\n", fclk->id, fclk->name, freq);
+}
+
+static int seq_print_fmeter_all(struct seq_file *s, void *v)
+{
+ proc_all_fclk_freq(seq_print_fclk_freq, s);
+
+ return 0;
+}
+
+typedef void (*fn_regname_proc)(const struct regname *rn, void *data);
+
+static void proc_all_regname(fn_regname_proc proc, void *data)
+{
+ const struct regname *rn = get_all_regnames();
+
+ if (rn == NULL)
+ return;
+
+ for (; rn->base != NULL; rn++)
+ proc(rn, data);
+}
+
+static void print_reg(const struct regname *rn, void *data)
+{
+ if (!is_valid_reg(ADDR(rn)))
+ return;
+
+ pr_info("%-21s: [0x%08x][0x%p] = 0x%08x\n",
+ rn->name, PHYSADDR(rn), ADDR(rn), clk_readl(ADDR(rn)));
+}
+
+void print_regs(void)
+{
+ proc_all_regname(print_reg, NULL);
+}
+
+static void seq_print_reg(const struct regname *rn, void *data)
+{
+ struct seq_file *s = data;
+
+ if (!is_valid_reg(ADDR(rn)))
+ return;
+
+ seq_printf(s, "%-21s: [0x%08x][0x%p] = 0x%08x\n",
+ rn->name, PHYSADDR(rn), ADDR(rn), clk_readl(ADDR(rn)));
+}
+
+static int seq_print_regs(struct seq_file *s, void *v)
+{
+ proc_all_regname(seq_print_reg, s);
+
+ return 0;
+}
+
+static void print_reg2(const struct regname *rn, void *data)
+{
+ if (!is_valid_reg(ADDR(rn)))
+ return;
+
+ pr_info("%-21s: [0x%08x][0x%p] = 0x%08x\n",
+ rn->name, PHYSADDR(rn), ADDR(rn), clk_readl(ADDR(rn)));
+
+ msleep(20);
+}
+
+static int clkdbg_dump_regs2(struct seq_file *s, void *v)
+{
+ proc_all_regname(print_reg2, s);
+
+ return 0;
+}
+
+static u32 read_spm_pwr_status(void)
+{
+ static void __iomem *scpsys_base, *pwr_sta, *pwr_sta_2nd;
+
+ if (clkdbg_ops == NULL || clkdbg_ops->get_spm_pwr_status == NULL) {
+ if (scpsys_base == NULL ||
+ pwr_sta == NULL || pwr_sta_2nd == NULL) {
+ scpsys_base = ioremap(0x10006000, PAGE_SIZE);
+ pwr_sta = scpsys_base + 0x60c;
+ pwr_sta_2nd = scpsys_base + 0x610;
+ }
+
+ return clk_readl(pwr_sta) & clk_readl(pwr_sta_2nd);
+ } else
+ return clkdbg_ops->get_spm_pwr_status();
+}
+
+static bool clk_hw_pwr_is_on(struct clk_hw *c_hw,
+ u32 spm_pwr_status, u32 pwr_mask)
+{
+ if ((spm_pwr_status & pwr_mask) != pwr_mask)
+ return false;
+
+ return clk_hw_is_on(c_hw);
+}
+
+static bool pvdck_pwr_is_on(struct provider_clk *pvdck, u32 spm_pwr_status)
+{
+ struct clk *c = pvdck->ck;
+ struct clk_hw *c_hw = __clk_get_hw(c);
+
+ return clk_hw_pwr_is_on(c_hw, spm_pwr_status, pvdck->pwr_mask);
+}
+
+static bool pvdck_is_on(struct provider_clk *pvdck)
+{
+ u32 spm_pwr_status = 0;
+
+ if (pvdck->pwr_mask != 0U)
+ spm_pwr_status = read_spm_pwr_status();
+
+ return pvdck_pwr_is_on(pvdck, spm_pwr_status);
+}
+
+static const char *ccf_state(struct clk_hw *hw)
+{
+ if (__clk_get_enable_count(hw->clk))
+ return "enabled";
+
+ if (clk_hw_is_prepared(hw))
+ return "prepared";
+
+ return "disabled";
+}
+
+static void dump_clk_state(const char *clkname, struct seq_file *s)
+{
+ struct clk *c = __clk_lookup(clkname);
+ struct clk *p = IS_ERR_OR_NULL(c) ? NULL : clk_get_parent(c);
+ struct clk_hw *c_hw = __clk_get_hw(c);
+ struct clk_hw *p_hw = __clk_get_hw(p);
+
+ if (IS_ERR_OR_NULL(c)) {
+ seq_printf(s, "[%17s: NULL]\n", clkname);
+ return;
+ }
+
+ seq_printf(s, "[%-17s: %8s, %3d, %3d, %10ld, %17s]\n",
+ clk_hw_get_name(c_hw),
+ ccf_state(c_hw),
+ clk_hw_is_prepared(c_hw),
+ __clk_get_enable_count(c),
+ clk_hw_get_rate(c_hw),
+ p != NULL ? clk_hw_get_name(p_hw) : "- ");
+}
+
+static int clkdbg_dump_state_all(struct seq_file *s, void *v)
+{
+ const char * const *ckn = get_all_clk_names();
+
+ if (ckn == NULL)
+ return 0;
+
+ for (; *ckn != NULL; ckn++)
+ dump_clk_state(*ckn, s);
+
+ return 0;
+}
+
+static const char *get_provider_name(struct device_node *node, u32 *cells)
+{
+ const char *name;
+ const char *p;
+ u32 cc;
+
+ if (of_property_read_u32(node, "#clock-cells", &cc) != 0)
+ cc = 0;
+
+ if (cells != NULL)
+ *cells = cc;
+
+ if (cc == 0U) {
+ if (of_property_read_string(node,
+ "clock-output-names", &name) < 0)
+ name = node->name;
+
+ return name;
+ }
+
+ if (of_property_read_string(node, "compatible", &name) < 0)
+ name = node->name;
+
+ p = strchr(name, (int)'-');
+
+ if (p != NULL)
+ return p + 1;
+ else
+ return name;
+}
+
+struct provider_clk *get_all_provider_clks(void)
+{
+ static struct provider_clk provider_clks[512];
+ struct device_node *node = NULL;
+ int n = 0;
+
+ if (provider_clks[0].ck != NULL)
+ return provider_clks;
+
+ do {
+ const char *node_name;
+ u32 cells;
+
+ node = of_find_node_with_property(node, "#clock-cells");
+
+ if (node == NULL)
+ break;
+
+ node_name = get_provider_name(node, &cells);
+
+ if (cells == 0U) {
+ struct clk *ck = __clk_lookup(node_name);
+
+ if (IS_ERR_OR_NULL(ck))
+ continue;
+
+ provider_clks[n].ck = ck;
+ setup_provider_clk(&provider_clks[n]);
+ ++n;
+ } else {
+ unsigned int i;
+
+ for (i = 0; i < 256; i++) {
+ struct of_phandle_args pa;
+ struct clk *ck;
+
+ pa.np = node;
+ pa.args[0] = i;
+ pa.args_count = 1;
+ ck = of_clk_get_from_provider(&pa);
+
+ if (PTR_ERR(ck) == -EINVAL)
+ break;
+ else if (IS_ERR_OR_NULL(ck))
+ continue;
+
+ provider_clks[n].ck = ck;
+ provider_clks[n].idx = i;
+ provider_clks[n].provider_name = node_name;
+ setup_provider_clk(&provider_clks[n]);
+ ++n;
+ }
+ }
+ } while (node != NULL);
+
+ return provider_clks;
+}
+
+static void dump_provider_clk(struct provider_clk *pvdck, struct seq_file *s)
+{
+ struct clk *c = pvdck->ck;
+ struct clk *p = IS_ERR_OR_NULL(c) ? NULL : clk_get_parent(c);
+ struct clk_hw *c_hw = __clk_get_hw(c);
+ struct clk_hw *p_hw = __clk_get_hw(p);
+
+ seq_printf(s, "[%10s: %-17s: %3s, %3d, %3d, %10ld, %17s]\n",
+ pvdck->provider_name != NULL ? pvdck->provider_name : "/ ",
+ clk_hw_get_name(c_hw),
+ pvdck_is_on(pvdck) ? "ON" : "off",
+ clk_hw_is_prepared(c_hw),
+ __clk_get_enable_count(c),
+ clk_hw_get_rate(c_hw),
+ p != NULL ? clk_hw_get_name(p_hw) : "- ");
+}
+
+static int clkdbg_dump_provider_clks(struct seq_file *s, void *v)
+{
+ struct provider_clk *pvdck = get_all_provider_clks();
+
+ for (; pvdck->ck != NULL; pvdck++)
+ dump_provider_clk(pvdck, s);
+
+ return 0;
+}
+
+static void dump_provider_mux(struct provider_clk *pvdck, struct seq_file *s)
+{
+ unsigned int i;
+ struct clk *c = pvdck->ck;
+ struct clk_hw *c_hw = __clk_get_hw(c);
+ unsigned int np = clk_hw_get_num_parents(c_hw);
+
+ if (np <= 1U)
+ return;
+
+ dump_provider_clk(pvdck, s);
+
+ for (i = 0; i < np; i++) {
+ struct clk_hw *p_hw = clk_hw_get_parent_by_index(c_hw, i);
+
+ if (IS_ERR_OR_NULL(p_hw))
+ continue;
+
+ seq_printf(s, "\t\t\t(%2d: %-17s: %8s, %10ld)\n",
+ i,
+ clk_hw_get_name(p_hw),
+ ccf_state(p_hw),
+ clk_hw_get_rate(p_hw));
+ }
+}
+
+static int clkdbg_dump_muxes(struct seq_file *s, void *v)
+{
+ struct provider_clk *pvdck = get_all_provider_clks();
+
+ for (; pvdck->ck != NULL; pvdck++)
+ dump_provider_mux(pvdck, s);
+
+ return 0;
+}
+
+static void show_pwr_status(u32 spm_pwr_status)
+{
+ unsigned int i;
+ const char * const *pwr_name = get_pwr_names();
+
+ pr_info("SPM_PWR_STATUS: 0x%08x\n\n", spm_pwr_status);
+
+ for (i = 0; i < 32; i++) {
+ const char *st = (spm_pwr_status & BIT(i)) != 0U ? "ON" : "off";
+
+ pr_info("[%2d]: %3s: %s\n", i, st, pwr_name[i]);
+ mdelay(20);
+ }
+}
+
+static int dump_pwr_status(u32 spm_pwr_status, struct seq_file *s)
+{
+ unsigned int i;
+ const char * const *pwr_name = get_pwr_names();
+
+ seq_printf(s, "SPM_PWR_STATUS: 0x%08x\n\n", spm_pwr_status);
+
+ for (i = 0; i < 32; i++) {
+ const char *st = (spm_pwr_status & BIT(i)) != 0U ? "ON" : "off";
+
+ seq_printf(s, "[%2d]: %3s: %s\n", i, st, pwr_name[i]);
+ }
+
+ return 0;
+}
+
+static int clkdbg_pwr_status(struct seq_file *s, void *v)
+{
+ return dump_pwr_status(read_spm_pwr_status(), s);
+}
+
+static char last_cmd[128] = "null";
+
+const char *get_last_cmd(void)
+{
+ return last_cmd;
+}
+
+static int clkop_int_ckname(int (*clkop)(struct clk *clk),
+ const char *clkop_name, const char *clk_name,
+ struct clk *ck, struct seq_file *s)
+{
+ struct clk *clk;
+
+ if (!IS_ERR_OR_NULL(ck)) {
+ clk = ck;
+ } else {
+ clk = __clk_lookup(clk_name);
+ if (IS_ERR_OR_NULL(clk)) {
+ seq_printf(s, "clk_lookup(%s): 0x%p\n", clk_name, clk);
+ return PTR_ERR(clk);
+ }
+ }
+
+ return clkop(clk);
+}
+
+static int clkdbg_clkop_int_ckname(int (*clkop)(struct clk *clk),
+ const char *clkop_name, struct seq_file *s, void *v)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *clk_name;
+ int r = 0;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ clk_name = strsep(&c, " ");
+
+ if (clk_name == NULL)
+ return 0;
+
+ if (strcmp(clk_name, "all") == 0) {
+ struct provider_clk *pvdck = get_all_provider_clks();
+
+ for (; pvdck->ck != NULL; pvdck++) {
+ r |= clkop_int_ckname(clkop, clkop_name, NULL,
+ pvdck->ck, s);
+ }
+
+ seq_printf(s, "%s(%s): %d\n", clkop_name, clk_name, r);
+
+ return r;
+ }
+
+ r = clkop_int_ckname(clkop, clkop_name, clk_name, NULL, s);
+ seq_printf(s, "%s(%s): %d\n", clkop_name, clk_name, r);
+
+ return r;
+}
+
+static void clkop_void_ckname(void (*clkop)(struct clk *clk),
+ const char *clkop_name, const char *clk_name,
+ struct clk *ck, struct seq_file *s)
+{
+ struct clk *clk;
+
+ if (!IS_ERR_OR_NULL(ck)) {
+ clk = ck;
+ } else {
+ clk = __clk_lookup(clk_name);
+ if (IS_ERR_OR_NULL(clk)) {
+ seq_printf(s, "clk_lookup(%s): 0x%p\n", clk_name, clk);
+ return;
+ }
+ }
+
+ clkop(clk);
+}
+
+static int clkdbg_clkop_void_ckname(void (*clkop)(struct clk *clk),
+ const char *clkop_name, struct seq_file *s, void *v)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *clk_name;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ clk_name = strsep(&c, " ");
+
+ if (clk_name == NULL)
+ return 0;
+
+ if (strcmp(clk_name, "all") == 0) {
+ struct provider_clk *pvdck = get_all_provider_clks();
+
+ for (; pvdck->ck != NULL; pvdck++) {
+ clkop_void_ckname(clkop, clkop_name, NULL,
+ pvdck->ck, s);
+ }
+
+ seq_printf(s, "%s(%s)\n", clkop_name, clk_name);
+
+ return 0;
+ }
+
+ clkop_void_ckname(clkop, clkop_name, clk_name, NULL, s);
+ seq_printf(s, "%s(%s)\n", clkop_name, clk_name);
+
+ return 0;
+}
+
+static int clkdbg_prepare(struct seq_file *s, void *v)
+{
+ return clkdbg_clkop_int_ckname(clk_prepare,
+ "clk_prepare", s, v);
+}
+
+static int clkdbg_unprepare(struct seq_file *s, void *v)
+{
+ return clkdbg_clkop_void_ckname(clk_unprepare,
+ "clk_unprepare", s, v);
+}
+
+static int clkdbg_enable(struct seq_file *s, void *v)
+{
+ return clkdbg_clkop_int_ckname(clk_enable,
+ "clk_enable", s, v);
+}
+
+static int clkdbg_disable(struct seq_file *s, void *v)
+{
+ return clkdbg_clkop_void_ckname(clk_disable,
+ "clk_disable", s, v);
+}
+
+static int clkdbg_prepare_enable(struct seq_file *s, void *v)
+{
+ return clkdbg_clkop_int_ckname(clk_prepare_enable,
+ "clk_prepare_enable", s, v);
+}
+
+static int clkdbg_disable_unprepare(struct seq_file *s, void *v)
+{
+ return clkdbg_clkop_void_ckname(clk_disable_unprepare,
+ "clk_disable_unprepare", s, v);
+}
+
+void prepare_enable_provider(const char *pvd)
+{
+ bool allpvd = (pvd == NULL || strcmp(pvd, "all") == 0);
+ struct provider_clk *pvdck = get_all_provider_clks();
+
+ for (; pvdck->ck != NULL; pvdck++) {
+ if (allpvd || (pvdck->provider_name != NULL &&
+ strcmp(pvd, pvdck->provider_name) == 0)) {
+ int r = clk_prepare_enable(pvdck->ck);
+
+ if (r != 0)
+ pr_info("clk_prepare_enable(): %d\n", r);
+ }
+ }
+}
+
+void disable_unprepare_provider(const char *pvd)
+{
+ bool allpvd = (pvd == NULL || strcmp(pvd, "all") == 0);
+ struct provider_clk *pvdck = get_all_provider_clks();
+
+ for (; pvdck->ck != NULL; pvdck++) {
+ if (allpvd || (pvdck->provider_name != NULL &&
+ strcmp(pvd, pvdck->provider_name) == 0))
+ clk_disable_unprepare(pvdck->ck);
+ }
+}
+
+static void clkpvdop(void (*pvdop)(const char *), const char *clkpvdop_name,
+ struct seq_file *s)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *pvd_name;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ pvd_name = strsep(&c, " ");
+
+ if (pvd_name == NULL)
+ return;
+
+ pvdop(pvd_name);
+ seq_printf(s, "%s(%s)\n", clkpvdop_name, pvd_name);
+}
+
+static int clkdbg_prepare_enable_provider(struct seq_file *s, void *v)
+{
+ clkpvdop(prepare_enable_provider, "prepare_enable_provider", s);
+ return 0;
+}
+
+static int clkdbg_disable_unprepare_provider(struct seq_file *s, void *v)
+{
+ clkpvdop(disable_unprepare_provider, "disable_unprepare_provider", s);
+ return 0;
+}
+
+static int clkdbg_set_parent(struct seq_file *s, void *v)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *clk_name;
+ char *parent_name;
+ struct clk *clk;
+ struct clk *parent;
+ int r;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ clk_name = strsep(&c, " ");
+ parent_name = strsep(&c, " ");
+
+ if (clk_name == NULL || parent_name == NULL)
+ return 0;
+
+ seq_printf(s, "clk_set_parent(%s, %s): ", clk_name, parent_name);
+
+ clk = __clk_lookup(clk_name);
+ if (IS_ERR_OR_NULL(clk)) {
+ seq_printf(s, "__clk_lookup(): 0x%p\n", clk);
+ return PTR_ERR(clk);
+ }
+
+ parent = __clk_lookup(parent_name);
+ if (IS_ERR_OR_NULL(parent)) {
+ seq_printf(s, "__clk_lookup(): 0x%p\n", parent);
+ return PTR_ERR(parent);
+ }
+
+ r = clk_prepare_enable(clk);
+ if (r != 0) {
+ seq_printf(s, "clk_prepare_enable(): %d\n", r);
+ return r;
+ }
+
+ r = clk_set_parent(clk, parent);
+ seq_printf(s, "%d\n", r);
+
+ clk_disable_unprepare(clk);
+
+ return r;
+}
+
+static int clkdbg_set_rate(struct seq_file *s, void *v)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *clk_name;
+ char *rate_str;
+ struct clk *clk;
+ unsigned long rate;
+ int r;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ clk_name = strsep(&c, " ");
+ rate_str = strsep(&c, " ");
+
+ if (clk_name == NULL || rate_str == NULL)
+ return 0;
+
+ r = kstrtoul(rate_str, 0, &rate);
+
+ seq_printf(s, "clk_set_rate(%s, %lu): %d: ", clk_name, rate, r);
+
+ clk = __clk_lookup(clk_name);
+ if (IS_ERR_OR_NULL(clk)) {
+ seq_printf(s, "__clk_lookup(): 0x%p\n", clk);
+ return PTR_ERR(clk);
+ }
+
+ r = clk_set_rate(clk, rate);
+ seq_printf(s, "%d\n", r);
+
+ return r;
+}
+
+static void *reg_from_str(const char *str)
+{
+ static phys_addr_t phys;
+ static void __iomem *virt;
+
+ if (sizeof(void *) == sizeof(unsigned long)) {
+ unsigned long v;
+
+ if (kstrtoul(str, 0, &v) == 0U) {
+ if ((0xf0000000 & v) < 0x20000000) {
+ if (virt != NULL && v > phys
+ && v < phys + PAGE_SIZE)
+ return virt + v - phys;
+
+ if (virt != NULL)
+ iounmap(virt);
+
+ phys = v & ~(PAGE_SIZE - 1U);
+ virt = ioremap(phys, PAGE_SIZE);
+
+ return virt + v - phys;
+ }
+
+ return (void *)((uintptr_t)v);
+ }
+ } else if (sizeof(void *) == sizeof(unsigned long long)) {
+ unsigned long long v;
+
+ if (kstrtoull(str, 0, &v) == 0) {
+ if ((0xfffffffff0000000ULL & v) < 0x20000000) {
+ if (virt && v > phys && v < phys + PAGE_SIZE)
+ return virt + v - phys;
+
+ if (virt != NULL)
+ iounmap(virt);
+
+ phys = v & ~(PAGE_SIZE - 1);
+ virt = ioremap(phys, PAGE_SIZE);
+
+ return virt + v - phys;
+ }
+
+ return (void *)((uintptr_t)v);
+ }
+ } else {
+ pr_warn("unexpected pointer size: sizeof(void *): %zu\n",
+ sizeof(void *));
+ }
+
+ pr_warn("%s(): parsing error: %s\n", __func__, str);
+
+ return NULL;
+}
+
+static int parse_reg_val_from_cmd(void __iomem **preg, unsigned long *pval)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *reg_str;
+ char *val_str;
+ int r = 0;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ reg_str = strsep(&c, " ");
+ val_str = strsep(&c, " ");
+
+ if (preg != NULL && reg_str != NULL) {
+ *preg = reg_from_str(reg_str);
+ if (*preg != NULL)
+ r++;
+ }
+
+ if (pval != NULL && val_str != NULL && kstrtoul(val_str, 0, pval) == 0)
+ r++;
+
+ return r;
+}
+
+static int clkdbg_reg_read(struct seq_file *s, void *v)
+{
+ void __iomem *reg;
+ unsigned long val;
+
+ if (parse_reg_val_from_cmd(®, NULL) != 1)
+ return 0;
+
+ seq_printf(s, "readl(0x%p): ", reg);
+
+ val = clk_readl(reg);
+ seq_printf(s, "0x%08x\n", (u32)val);
+
+ return 0;
+}
+
+static int clkdbg_reg_write(struct seq_file *s, void *v)
+{
+ void __iomem *reg;
+ unsigned long val;
+
+ if (parse_reg_val_from_cmd(®, &val) != 2)
+ return 0;
+
+ seq_printf(s, "writel(0x%p, 0x%08x): ", reg, (u32)val);
+
+ clk_writel(reg, val);
+ val = clk_readl(reg);
+ seq_printf(s, "0x%08x\n", (u32)val);
+
+ return 0;
+}
+
+static int clkdbg_reg_set(struct seq_file *s, void *v)
+{
+ void __iomem *reg;
+ unsigned long val;
+
+ if (parse_reg_val_from_cmd(®, &val) != 2)
+ return 0;
+
+ seq_printf(s, "writel(0x%p, 0x%08x): ", reg, (u32)val);
+
+ clk_setl(reg, val);
+ val = clk_readl(reg);
+ seq_printf(s, "0x%08x\n", (u32)val);
+
+ return 0;
+}
+
+static int clkdbg_reg_clr(struct seq_file *s, void *v)
+{
+ void __iomem *reg;
+ unsigned long val;
+
+ if (parse_reg_val_from_cmd(®, &val) != 2)
+ return 0;
+
+ seq_printf(s, "writel(0x%p, 0x%08x): ", reg, (u32)val);
+
+ clk_clrl(reg, val);
+ val = clk_readl(reg);
+ seq_printf(s, "0x%08x\n", (u32)val);
+
+ return 0;
+}
+
+static int parse_val_from_cmd(unsigned long *pval)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *val_str;
+ int r = 0;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ val_str = strsep(&c, " ");
+
+ if (pval != NULL && val_str != NULL && kstrtoul(val_str, 0, pval) == 0)
+ r++;
+
+ return r;
+}
+
+static int clkdbg_show_flags(struct seq_file *s, void *v)
+{
+ static const char * const clkdbg_opt_name[] = {
+ "CLKDBG_EN_SUSPEND_SAVE_1",
+ "CLKDBG_EN_SUSPEND_SAVE_2",
+ "CLKDBG_EN_SUSPEND_SAVE_3",
+ "CLKDBG_EN_LOG_SAVE_POINTS",
+ };
+
+ size_t i;
+
+ seq_printf(s, "clkdbg_flags: 0x%08x\n", clkdbg_flags);
+
+ for (i = 0; i < ARRAY_SIZE(clkdbg_opt_name); i++) {
+ const char *onff =
+ has_clkdbg_flag((enum clkdbg_opt)i) ? "ON" : "off";
+
+ seq_printf(s, "[%2zd]: %3s: %s\n", i, onff, clkdbg_opt_name[i]);
+ }
+
+ return 0;
+}
+
+static int clkdbg_set_flag(struct seq_file *s, void *v)
+{
+ unsigned long val;
+
+ if (parse_val_from_cmd(&val) != 1)
+ return 0;
+
+ set_clkdbg_flag((enum clkdbg_opt)val);
+
+ seq_printf(s, "clkdbg_flags: 0x%08x\n", clkdbg_flags);
+
+ return 0;
+}
+
+static int clkdbg_clr_flag(struct seq_file *s, void *v)
+{
+ unsigned long val;
+
+ if (parse_val_from_cmd(&val) != 1)
+ return 0;
+
+ clr_clkdbg_flag((enum clkdbg_opt)val);
+
+ seq_printf(s, "clkdbg_flags: 0x%08x\n", clkdbg_flags);
+
+ return 0;
+}
+
+#if CLKDBG_PM_DOMAIN
+
+/*
+ * pm_domain support
+ */
+
+static struct generic_pm_domain **get_all_genpd(void)
+{
+ static struct generic_pm_domain *pds[20];
+ static int num_pds;
+ const size_t maxpd = ARRAY_SIZE(pds);
+ struct device_node *node;
+#if CLKDBG_PM_DOMAIN_API_4_9
+ struct platform_device *pdev;
+ int r;
+#endif
+
+ if (num_pds != 0)
+ goto out;
+
+ node = of_find_node_with_property(NULL, "#power-domain-cells");
+
+ if (node == NULL)
+ return NULL;
+
+#if CLKDBG_PM_DOMAIN_API_4_9
+ pdev = platform_device_alloc("traverse", 0);
+#endif
+
+ for (num_pds = 0; num_pds < maxpd; num_pds++) {
+ struct of_phandle_args pa;
+
+ pa.np = node;
+ pa.args[0] = num_pds;
+ pa.args_count = 1;
+
+#if CLKDBG_PM_DOMAIN_API_4_9
+ r = of_genpd_add_device(&pa, &pdev->dev);
+ if (r == -EINVAL)
+ continue;
+ else if (r != 0)
+ pr_warn("%s(): of_genpd_add_device(%d)\n", __func__, r);
+ pds[num_pds] = pd_to_genpd(pdev->dev.pm_domain);
+ r = pm_genpd_remove_device(pds[num_pds], &pdev->dev);
+ if (r != 0)
+ pr_warn("%s(): pm_genpd_remove_device(%d)\n",
+ __func__, r);
+#else
+ pds[num_pds] = of_genpd_get_from_provider(&pa);
+#endif
+
+ if (IS_ERR(pds[num_pds])) {
+ pds[num_pds] = NULL;
+ break;
+ }
+ }
+
+#if CLKDBG_PM_DOMAIN_API_4_9
+ platform_device_put(pdev);
+#endif
+
+out:
+ return pds;
+}
+
+static struct platform_device *pdev_from_name(const char *name)
+{
+ struct generic_pm_domain **pds = get_all_genpd();
+
+ for (; *pds != NULL; pds++) {
+ struct pm_domain_data *pdd;
+ struct generic_pm_domain *pd = *pds;
+
+ if (IS_ERR_OR_NULL(pd))
+ continue;
+
+ list_for_each_entry(pdd, &pd->dev_list, list_node) {
+ struct device *dev = pdd->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+
+ if (strcmp(name, pdev->name) == 0)
+ return pdev;
+ }
+ }
+
+ return NULL;
+}
+
+static struct generic_pm_domain *genpd_from_name(const char *name)
+{
+ struct generic_pm_domain **pds = get_all_genpd();
+
+ for (; *pds != NULL; pds++) {
+ struct generic_pm_domain *pd = *pds;
+
+ if (IS_ERR_OR_NULL(pd))
+ continue;
+
+ if (strcmp(name, pd->name) == 0)
+ return pd;
+ }
+
+ return NULL;
+}
+
+struct genpd_dev_state {
+ struct device *dev;
+ bool active;
+ atomic_t usage_count;
+ unsigned int disable_depth;
+ enum rpm_status runtime_status;
+};
+
+struct genpd_state {
+ struct generic_pm_domain *pd;
+ enum gpd_status status;
+ struct genpd_dev_state *dev_state;
+ int num_dev_state;
+};
+
+static void save_all_genpd_state(struct genpd_state *genpd_states,
+ struct genpd_dev_state *genpd_dev_states)
+{
+ struct genpd_state *pdst = genpd_states;
+ struct genpd_dev_state *devst = genpd_dev_states;
+ struct generic_pm_domain **pds = get_all_genpd();
+
+ for (; *pds != NULL; pds++) {
+ struct pm_domain_data *pdd;
+ struct generic_pm_domain *pd = *pds;
+
+ if (IS_ERR_OR_NULL(pd))
+ continue;
+
+ pdst->pd = pd;
+ pdst->status = pd->status;
+ pdst->dev_state = devst;
+ pdst->num_dev_state = 0;
+
+ list_for_each_entry(pdd, &pd->dev_list, list_node) {
+ struct device *d = pdd->dev;
+
+ devst->dev = d;
+ devst->active = pm_runtime_active(d);
+ devst->usage_count = d->power.usage_count;
+ devst->disable_depth = d->power.disable_depth;
+ devst->runtime_status = d->power.runtime_status;
+
+ devst++;
+ pdst->num_dev_state++;
+ }
+
+ pdst++;
+ }
+
+ pdst->pd = NULL;
+ devst->dev = NULL;
+}
+
+static void show_genpd_state(struct genpd_state *pdst)
+{
+ static const char * const gpd_status_name[] = {
+ "ACTIVE",
+ "POWER_OFF",
+ };
+
+ static const char * const prm_status_name[] = {
+ "active",
+ "resuming",
+ "suspended",
+ "suspending",
+ };
+
+ pr_info("domain_on [pmd_name status]\n");
+ pr_info("\tdev_on (dev_name usage_count, disable, status)\n");
+ pr_info("------------------------------------------------------\n");
+
+ for (; pdst->pd != NULL; pdst++) {
+ int i;
+ struct generic_pm_domain *pd = pdst->pd;
+
+ if (IS_ERR_OR_NULL(pd)) {
+ pr_info("pd: 0x%p\n", pd);
+ continue;
+ }
+
+ pr_info("%c [%-9s %11s]\n",
+ (pdst->status == GPD_STATE_ACTIVE) ? '+' : '-',
+ pd->name, gpd_status_name[pdst->status]);
+
+ for (i = 0; i < pdst->num_dev_state; i++) {
+ struct genpd_dev_state *devst = &pdst->dev_state[i];
+ struct device *dev = devst->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+
+ pr_info("\t%c (%-19s %3d, %d, %10s)\n",
+ devst->active ? '+' : '-',
+ pdev->name,
+ atomic_read(&dev->power.usage_count),
+ devst->disable_depth,
+ prm_status_name[devst->runtime_status]);
+ mdelay(20);
+ }
+ }
+}
+
+static void dump_genpd_state(struct genpd_state *pdst, struct seq_file *s)
+{
+ static const char * const gpd_status_name[] = {
+ "ACTIVE",
+ "POWER_OFF",
+ };
+
+ static const char * const prm_status_name[] = {
+ "active",
+ "resuming",
+ "suspended",
+ "suspending",
+ };
+
+ seq_puts(s, "domain_on [pmd_name status]\n");
+ seq_puts(s, "\tdev_on (dev_name usage_count, disable, status)\n");
+ seq_puts(s, "------------------------------------------------------\n");
+
+ for (; pdst->pd != NULL; pdst++) {
+ int i;
+ struct generic_pm_domain *pd = pdst->pd;
+
+ if (IS_ERR_OR_NULL(pd)) {
+ seq_printf(s, "pd: 0x%p\n", pd);
+ continue;
+ }
+
+ seq_printf(s, "%c [%-9s %11s]\n",
+ (pdst->status == GPD_STATE_ACTIVE) ? '+' : '-',
+ pd->name, gpd_status_name[pdst->status]);
+
+ for (i = 0; i < pdst->num_dev_state; i++) {
+ struct genpd_dev_state *devst = &pdst->dev_state[i];
+ struct device *dev = devst->dev;
+ struct platform_device *pdev = to_platform_device(dev);
+
+ seq_printf(s, "\t%c (%-19s %3d, %d, %10s)\n",
+ devst->active ? '+' : '-',
+ pdev->name,
+ atomic_read(&dev->power.usage_count),
+ devst->disable_depth,
+ prm_status_name[devst->runtime_status]);
+ }
+ }
+}
+
+static void seq_print_all_genpd(struct seq_file *s)
+{
+ static struct genpd_dev_state devst[100];
+ static struct genpd_state pdst[20];
+
+ save_all_genpd_state(pdst, devst);
+ dump_genpd_state(pdst, s);
+}
+
+static int clkdbg_dump_genpd(struct seq_file *s, void *v)
+{
+ seq_print_all_genpd(s);
+
+ return 0;
+}
+
+static int clkdbg_pm_runtime_enable(struct seq_file *s, void *v)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *dev_name;
+ struct platform_device *pdev;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ dev_name = strsep(&c, " ");
+
+ if (dev_name == NULL)
+ return 0;
+
+ seq_printf(s, "pm_runtime_enable(%s): ", dev_name);
+
+ pdev = pdev_from_name(dev_name);
+ if (pdev != NULL) {
+ pm_runtime_enable(&pdev->dev);
+ seq_puts(s, "\n");
+ } else {
+ seq_puts(s, "NULL\n");
+ }
+
+ return 0;
+}
+
+static int clkdbg_pm_runtime_disable(struct seq_file *s, void *v)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *dev_name;
+ struct platform_device *pdev;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ dev_name = strsep(&c, " ");
+
+ if (dev_name == NULL)
+ return 0;
+
+ seq_printf(s, "pm_runtime_disable(%s): ", dev_name);
+
+ pdev = pdev_from_name(dev_name);
+ if (pdev != NULL) {
+ pm_runtime_disable(&pdev->dev);
+ seq_puts(s, "\n");
+ } else {
+ seq_puts(s, "NULL\n");
+ }
+
+ return 0;
+}
+
+static int clkdbg_pm_runtime_get_sync(struct seq_file *s, void *v)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *dev_name;
+ struct platform_device *pdev;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ dev_name = strsep(&c, " ");
+
+ if (dev_name == NULL)
+ return 0;
+
+ seq_printf(s, "pm_runtime_get_sync(%s): ", dev_name);
+
+ pdev = pdev_from_name(dev_name);
+ if (pdev != NULL) {
+ int r = pm_runtime_get_sync(&pdev->dev);
+
+ seq_printf(s, "%d\n", r);
+ } else {
+ seq_puts(s, "NULL\n");
+ }
+
+ return 0;
+}
+
+static int clkdbg_pm_runtime_put_sync(struct seq_file *s, void *v)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *dev_name;
+ struct platform_device *pdev;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ dev_name = strsep(&c, " ");
+
+ if (dev_name == NULL)
+ return 0;
+
+ seq_printf(s, "pm_runtime_put_sync(%s): ", dev_name);
+
+ pdev = pdev_from_name(dev_name);
+ if (pdev != NULL) {
+ int r = pm_runtime_put_sync(&pdev->dev);
+
+ seq_printf(s, "%d\n", r);
+ } else {
+ seq_puts(s, "NULL\n");
+ }
+
+ return 0;
+}
+
+static int genpd_op(const char *gpd_op_name, struct seq_file *s)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *pd_name;
+ struct generic_pm_domain *genpd;
+ int gpd_op_id;
+ int (*gpd_op)(struct generic_pm_domain *);
+ int r = 0;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ pd_name = strsep(&c, " ");
+
+ if (pd_name == NULL)
+ return 0;
+
+ if (strcmp(gpd_op_name, "power_on") == 0)
+ gpd_op_id = 1;
+ else
+ gpd_op_id = 0;
+
+ if (strcmp(pd_name, "all") == 0) {
+ struct generic_pm_domain **pds = get_all_genpd();
+
+ for (; *pds != NULL; pds++) {
+ genpd = *pds;
+
+ if (IS_ERR_OR_NULL(genpd))
+ continue;
+
+ gpd_op = (gpd_op_id == 1) ?
+ genpd->power_on : genpd->power_off;
+ r |= gpd_op(genpd);
+ }
+
+ seq_printf(s, "%s(%s): %d\n", gpd_op_name, pd_name, r);
+
+ return 0;
+ }
+
+ genpd = genpd_from_name(pd_name);
+ if (genpd != NULL) {
+ gpd_op = (gpd_op_id == 1) ? genpd->power_on : genpd->power_off;
+ r = gpd_op(genpd);
+
+ seq_printf(s, "%s(%s): %d\n", gpd_op_name, pd_name, r);
+ } else {
+ seq_printf(s, "genpd_from_name(%s): NULL\n", pd_name);
+ }
+
+ return 0;
+}
+
+static int clkdbg_pwr_on(struct seq_file *s, void *v)
+{
+ return genpd_op("power_on", s);
+}
+
+static int clkdbg_pwr_off(struct seq_file *s, void *v)
+{
+ return genpd_op("power_off", s);
+}
+
+/*
+ * clkdbg reg_pdrv/runeg_pdrv support
+ */
+
+static int clkdbg_probe(struct platform_device *pdev)
+{
+ int r;
+
+ pm_runtime_enable(&pdev->dev);
+ r = pm_runtime_get_sync(&pdev->dev);
+ if (r != 0)
+ pr_warn("%s(): pm_runtime_get_sync(%d)\n", __func__, r);
+
+ return r;
+}
+
+static int clkdbg_remove(struct platform_device *pdev)
+{
+ int r;
+
+ r = pm_runtime_put_sync(&pdev->dev);
+ if (r != 0)
+ pr_warn("%s(): pm_runtime_put_sync(%d)\n", __func__, r);
+ pm_runtime_disable(&pdev->dev);
+
+ return r;
+}
+
+struct pdev_drv {
+ struct platform_driver pdrv;
+ struct platform_device *pdev;
+ struct generic_pm_domain *genpd;
+};
+
+#define PDEV_DRV(_name) { \
+ .pdrv = { \
+ .probe = clkdbg_probe, \
+ .remove = clkdbg_remove, \
+ .driver = { \
+ .name = _name, \
+ }, \
+ }, \
+}
+
+static struct pdev_drv pderv[] = {
+ PDEV_DRV("clkdbg-pd0"),
+ PDEV_DRV("clkdbg-pd1"),
+ PDEV_DRV("clkdbg-pd2"),
+ PDEV_DRV("clkdbg-pd3"),
+ PDEV_DRV("clkdbg-pd4"),
+ PDEV_DRV("clkdbg-pd5"),
+ PDEV_DRV("clkdbg-pd6"),
+ PDEV_DRV("clkdbg-pd7"),
+ PDEV_DRV("clkdbg-pd8"),
+ PDEV_DRV("clkdbg-pd9"),
+ PDEV_DRV("clkdbg-pd10"),
+ PDEV_DRV("clkdbg-pd11"),
+ PDEV_DRV("clkdbg-pd12"),
+ PDEV_DRV("clkdbg-pd13"),
+ PDEV_DRV("clkdbg-pd14"),
+ PDEV_DRV("clkdbg-pd15"),
+};
+
+static void reg_pdev_drv(const char *pdname, struct seq_file *s)
+{
+ size_t i;
+ struct generic_pm_domain **pds = get_all_genpd();
+ bool allpd = (pdname == NULL || strcmp(pdname, "all") == 0);
+ int r;
+
+ for (i = 0; i < ARRAY_SIZE(pderv) && *pds != NULL; i++, pds++) {
+ const char *name = pderv[i].pdrv.driver.name;
+ struct generic_pm_domain *pd = *pds;
+
+ if (IS_ERR_OR_NULL(pd) || pderv[i].genpd != NULL)
+ continue;
+
+ if (!allpd && strcmp(pdname, pd->name) != 0)
+ continue;
+
+ pderv[i].genpd = pd;
+
+ pderv[i].pdev = platform_device_alloc(name, 0);
+ r = platform_device_add(pderv[i].pdev);
+ if (r != 0 && s != NULL)
+ seq_printf(s, "%s(): platform_device_add(%d)\n",
+ __func__, r);
+
+ r = pm_genpd_add_device(pd, &pderv[i].pdev->dev);
+ if (r != 0 && s != NULL)
+ seq_printf(s, "%s(): pm_genpd_add_device(%d)\n",
+ __func__, r);
+ r = platform_driver_register(&pderv[i].pdrv);
+ if (r != 0 && s != NULL)
+ seq_printf(s, "%s(): platform_driver_register(%d)\n",
+ __func__, r);
+
+ if (s != NULL)
+ seq_printf(s, "%s --> %s\n", name, pd->name);
+ }
+}
+
+static void unreg_pdev_drv(const char *pdname, struct seq_file *s)
+{
+ ssize_t i;
+ bool allpd = (pdname == NULL || strcmp(pdname, "all") == 0);
+ int r;
+
+ for (i = ARRAY_SIZE(pderv) - 1L; i >= 0L; i--) {
+ const char *name = pderv[i].pdrv.driver.name;
+ struct generic_pm_domain *pd = pderv[i].genpd;
+
+ if (IS_ERR_OR_NULL(pd))
+ continue;
+
+ if (!allpd && strcmp(pdname, pd->name) != 0)
+ continue;
+
+ r = pm_genpd_remove_device(pd, &pderv[i].pdev->dev);
+ if (r != 0 && s != NULL)
+ seq_printf(s, "%s(): pm_genpd_remove_device(%d)\n",
+ __func__, r);
+
+ platform_driver_unregister(&pderv[i].pdrv);
+ platform_device_unregister(pderv[i].pdev);
+
+ pderv[i].genpd = NULL;
+
+ if (s != NULL)
+ seq_printf(s, "%s -x- %s\n", name, pd->name);
+ }
+}
+
+static int clkdbg_reg_pdrv(struct seq_file *s, void *v)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *pd_name;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ pd_name = strsep(&c, " ");
+
+ if (pd_name == NULL)
+ return 0;
+
+ reg_pdev_drv(pd_name, s);
+
+ return 0;
+}
+
+static int clkdbg_unreg_pdrv(struct seq_file *s, void *v)
+{
+ char cmd[sizeof(last_cmd)];
+ char *c = cmd;
+ char *ign;
+ char *pd_name;
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ ign = strsep(&c, " ");
+ pd_name = strsep(&c, " ");
+
+ if (pd_name == NULL)
+ return 0;
+
+ unreg_pdev_drv(pd_name, s);
+
+ return 0;
+}
+
+#endif /* CLKDBG_PM_DOMAIN */
+
+void reg_pdrv(const char *pdname)
+{
+#if CLKDBG_PM_DOMAIN
+ reg_pdev_drv(pdname, NULL);
+#endif
+}
+
+void unreg_pdrv(const char *pdname)
+{
+#if CLKDBG_PM_DOMAIN
+ unreg_pdev_drv(pdname, NULL);
+#endif
+}
+
+/*
+ * Suspend / resume handler
+ */
+
+#include <linux/suspend.h>
+#include <linux/syscore_ops.h>
+
+struct provider_clk_state {
+ struct provider_clk *pvdck;
+ bool prepared;
+ bool enabled;
+ unsigned int enable_count;
+ unsigned long rate;
+ struct clk *parent;
+};
+
+struct save_point {
+ u32 spm_pwr_status;
+ struct provider_clk_state clks_states[512];
+#if CLKDBG_PM_DOMAIN
+ struct genpd_state genpd_states[20];
+ struct genpd_dev_state genpd_dev_states[100];
+#endif
+};
+
+static struct save_point save_point_1;
+static struct save_point save_point_2;
+static struct save_point save_point_3;
+
+static void save_pwr_status(u32 *spm_pwr_status)
+{
+ *spm_pwr_status = read_spm_pwr_status();
+}
+
+static void save_all_clks_state(struct provider_clk_state *clks_states,
+ u32 spm_pwr_status)
+{
+ struct provider_clk *pvdck = get_all_provider_clks();
+ struct provider_clk_state *st = clks_states;
+
+ for (; pvdck->ck != NULL; pvdck++, st++) {
+ struct clk *c = pvdck->ck;
+ struct clk_hw *c_hw = __clk_get_hw(c);
+
+ st->pvdck = pvdck;
+ st->prepared = clk_hw_is_prepared(c_hw);
+ st->enabled = clk_hw_pwr_is_on(c_hw, spm_pwr_status,
+ pvdck->pwr_mask);
+ st->enable_count = __clk_get_enable_count(c);
+ st->rate = clk_hw_get_rate(c_hw);
+ st->parent = IS_ERR_OR_NULL(c) ? NULL : clk_get_parent(c);
+ }
+}
+
+static void show_provider_clk_state(struct provider_clk_state *st)
+{
+ struct provider_clk *pvdck = st->pvdck;
+ struct clk_hw *c_hw = __clk_get_hw(pvdck->ck);
+
+ pr_info("[%10s: %-17s: %3s, %3d, %3d, %10ld, %17s]\n",
+ pvdck->provider_name != NULL ? pvdck->provider_name : "/ ",
+ clk_hw_get_name(c_hw),
+ st->enabled ? "ON" : "off",
+ st->prepared,
+ st->enable_count,
+ st->rate,
+ st->parent != NULL ?
+ clk_hw_get_name(__clk_get_hw(st->parent)) : "- ");
+ mdelay(20);
+}
+
+static void dump_provider_clk_state(struct provider_clk_state *st,
+ struct seq_file *s)
+{
+ struct provider_clk *pvdck = st->pvdck;
+ struct clk_hw *c_hw = __clk_get_hw(pvdck->ck);
+
+ seq_printf(s, "[%10s: %-17s: %3s, %3d, %3d, %10ld, %17s]\n",
+ pvdck->provider_name != NULL ? pvdck->provider_name : "/ ",
+ clk_hw_get_name(c_hw),
+ st->enabled ? "ON" : "off",
+ st->prepared,
+ st->enable_count,
+ st->rate,
+ st->parent != NULL ?
+ clk_hw_get_name(__clk_get_hw(st->parent)) : "- ");
+}
+
+static void show_save_point(struct save_point *sp)
+{
+ struct provider_clk_state *st = sp->clks_states;
+
+ for (; st->pvdck != NULL; st++)
+ show_provider_clk_state(st);
+
+ pr_info("\n");
+ show_pwr_status(sp->spm_pwr_status);
+
+#if CLKDBG_PM_DOMAIN
+ pr_info("\n");
+ show_genpd_state(sp->genpd_states);
+#endif
+}
+
+static void store_save_point(struct save_point *sp)
+{
+ save_pwr_status(&sp->spm_pwr_status);
+ save_all_clks_state(sp->clks_states, sp->spm_pwr_status);
+
+#if CLKDBG_PM_DOMAIN
+ save_all_genpd_state(sp->genpd_states, sp->genpd_dev_states);
+#endif
+
+ if (has_clkdbg_flag(CLKDBG_EN_LOG_SAVE_POINTS))
+ show_save_point(sp);
+}
+
+static void dump_save_point(struct save_point *sp, struct seq_file *s)
+{
+ struct provider_clk_state *st = sp->clks_states;
+
+ for (; st->pvdck != NULL; st++)
+ dump_provider_clk_state(st, s);
+
+ seq_puts(s, "\n");
+ dump_pwr_status(sp->spm_pwr_status, s);
+
+#if CLKDBG_PM_DOMAIN
+ seq_puts(s, "\n");
+ dump_genpd_state(sp->genpd_states, s);
+#endif
+}
+
+static int clkdbg_dump_suspend_clks_1(struct seq_file *s, void *v)
+{
+ dump_save_point(&save_point_1, s);
+ return 0;
+}
+
+static int clkdbg_dump_suspend_clks_2(struct seq_file *s, void *v)
+{
+ dump_save_point(&save_point_2, s);
+ return 0;
+}
+
+static int clkdbg_dump_suspend_clks_3(struct seq_file *s, void *v)
+{
+ dump_save_point(&save_point_3, s);
+ return 0;
+}
+
+static int clkdbg_dump_suspend_clks(struct seq_file *s, void *v)
+{
+ if (has_clkdbg_flag(CLKDBG_EN_SUSPEND_SAVE_3) &&
+ save_point_3.spm_pwr_status != 0U)
+ return clkdbg_dump_suspend_clks_3(s, v);
+ else if (has_clkdbg_flag(CLKDBG_EN_SUSPEND_SAVE_2) &&
+ save_point_2.spm_pwr_status != 0U)
+ return clkdbg_dump_suspend_clks_2(s, v);
+ else if (has_clkdbg_flag(CLKDBG_EN_SUSPEND_SAVE_1) &&
+ save_point_1.spm_pwr_status != 0U)
+ return clkdbg_dump_suspend_clks_1(s, v);
+
+ return 0;
+}
+
+static int clkdbg_pm_event_handler(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ switch (event) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ /* suspend */
+ if (has_clkdbg_flag(CLKDBG_EN_SUSPEND_SAVE_1)) {
+ store_save_point(&save_point_1);
+ return NOTIFY_OK;
+ }
+
+ break;
+ case PM_POST_HIBERNATION:
+ case PM_POST_SUSPEND:
+ /* resume */
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block clkdbg_pm_notifier = {
+ .notifier_call = clkdbg_pm_event_handler,
+};
+
+static int clkdbg_syscore_suspend(void)
+{
+ if (has_clkdbg_flag(CLKDBG_EN_SUSPEND_SAVE_2))
+ store_save_point(&save_point_2);
+
+ return 0;
+}
+
+static void clkdbg_syscore_resume(void)
+{
+}
+
+static struct syscore_ops clkdbg_syscore_ops = {
+ .suspend = clkdbg_syscore_suspend,
+ .resume = clkdbg_syscore_resume,
+};
+
+static int __init clkdbg_pm_init(void)
+{
+ int r;
+
+ register_syscore_ops(&clkdbg_syscore_ops);
+ r = register_pm_notifier(&clkdbg_pm_notifier);
+ if (r != 0)
+ pr_warn("%s(): register_pm_notifier(%d)\n", __func__, r);
+
+ return r;
+}
+subsys_initcall(clkdbg_pm_init);
+
+static int clkdbg_suspend_ops_valid(suspend_state_t state)
+{
+ return state == PM_SUSPEND_MEM ? 1 : 0;
+}
+
+static int clkdbg_suspend_ops_begin(suspend_state_t state)
+{
+ return 0;
+}
+
+static int clkdbg_suspend_ops_prepare(void)
+{
+ return 0;
+}
+
+static int clkdbg_suspend_ops_enter(suspend_state_t state)
+{
+ if (has_clkdbg_flag(CLKDBG_EN_SUSPEND_SAVE_3))
+ store_save_point(&save_point_3);
+
+ return 0;
+}
+
+static void clkdbg_suspend_ops_finish(void)
+{
+}
+
+static void clkdbg_suspend_ops_end(void)
+{
+}
+
+static const struct platform_suspend_ops clkdbg_suspend_ops = {
+ .valid = clkdbg_suspend_ops_valid,
+ .begin = clkdbg_suspend_ops_begin,
+ .prepare = clkdbg_suspend_ops_prepare,
+ .enter = clkdbg_suspend_ops_enter,
+ .finish = clkdbg_suspend_ops_finish,
+ .end = clkdbg_suspend_ops_end,
+};
+
+static int clkdbg_suspend_set_ops(struct seq_file *s, void *v)
+{
+ suspend_set_ops(&clkdbg_suspend_ops);
+
+ return 0;
+}
+
+static const struct cmd_fn *custom_cmds;
+
+void set_custom_cmds(const struct cmd_fn *cmds)
+{
+ custom_cmds = cmds;
+}
+
+static int clkdbg_cmds(struct seq_file *s, void *v);
+
+static const struct cmd_fn common_cmds[] = {
+ CMDFN("dump_regs", seq_print_regs),
+ CMDFN("dump_regs2", clkdbg_dump_regs2),
+ CMDFN("dump_state", clkdbg_dump_state_all),
+ CMDFN("dump_clks", clkdbg_dump_provider_clks),
+ CMDFN("dump_muxes", clkdbg_dump_muxes),
+ CMDFN("fmeter", seq_print_fmeter_all),
+ CMDFN("pwr_status", clkdbg_pwr_status),
+ CMDFN("prepare", clkdbg_prepare),
+ CMDFN("unprepare", clkdbg_unprepare),
+ CMDFN("enable", clkdbg_enable),
+ CMDFN("disable", clkdbg_disable),
+ CMDFN("prepare_enable", clkdbg_prepare_enable),
+ CMDFN("disable_unprepare", clkdbg_disable_unprepare),
+ CMDFN("prepare_enable_provider", clkdbg_prepare_enable_provider),
+ CMDFN("disable_unprepare_provider", clkdbg_disable_unprepare_provider),
+ CMDFN("set_parent", clkdbg_set_parent),
+ CMDFN("set_rate", clkdbg_set_rate),
+ CMDFN("reg_read", clkdbg_reg_read),
+ CMDFN("reg_write", clkdbg_reg_write),
+ CMDFN("reg_set", clkdbg_reg_set),
+ CMDFN("reg_clr", clkdbg_reg_clr),
+ CMDFN("show_flags", clkdbg_show_flags),
+ CMDFN("set_flag", clkdbg_set_flag),
+ CMDFN("clr_flag", clkdbg_clr_flag),
+#if CLKDBG_PM_DOMAIN
+ CMDFN("dump_genpd", clkdbg_dump_genpd),
+ CMDFN("pm_runtime_enable", clkdbg_pm_runtime_enable),
+ CMDFN("pm_runtime_disable", clkdbg_pm_runtime_disable),
+ CMDFN("pm_runtime_get_sync", clkdbg_pm_runtime_get_sync),
+ CMDFN("pm_runtime_put_sync", clkdbg_pm_runtime_put_sync),
+ CMDFN("pwr_on", clkdbg_pwr_on),
+ CMDFN("pwr_off", clkdbg_pwr_off),
+ CMDFN("reg_pdrv", clkdbg_reg_pdrv),
+ CMDFN("unreg_pdrv", clkdbg_unreg_pdrv),
+#endif /* CLKDBG_PM_DOMAIN */
+ CMDFN("suspend_set_ops", clkdbg_suspend_set_ops),
+ CMDFN("dump_suspend_clks", clkdbg_dump_suspend_clks),
+ CMDFN("dump_suspend_clks_1", clkdbg_dump_suspend_clks_1),
+ CMDFN("dump_suspend_clks_2", clkdbg_dump_suspend_clks_2),
+ CMDFN("dump_suspend_clks_3", clkdbg_dump_suspend_clks_3),
+ CMDFN("cmds", clkdbg_cmds),
+ {}
+};
+
+static int clkdbg_cmds(struct seq_file *s, void *v)
+{
+ const struct cmd_fn *cf;
+
+ for (cf = common_cmds; cf->cmd != NULL; cf++)
+ seq_printf(s, "%s\n", cf->cmd);
+
+ for (cf = custom_cmds; cf != NULL && cf->cmd != NULL; cf++)
+ seq_printf(s, "%s\n", cf->cmd);
+
+ seq_puts(s, "\n");
+
+ return 0;
+}
+
+static int clkdbg_show(struct seq_file *s, void *v)
+{
+ const struct cmd_fn *cf;
+ char cmd[sizeof(last_cmd)];
+
+ strncpy(cmd, last_cmd, sizeof(cmd));
+ cmd[sizeof(cmd) - 1UL] = '\0';
+
+ for (cf = custom_cmds; cf != NULL && cf->cmd != NULL; cf++) {
+ char *c = cmd;
+ char *token = strsep(&c, " ");
+
+ if (strcmp(cf->cmd, token) == 0)
+ return cf->fn(s, v);
+ }
+
+ for (cf = common_cmds; cf->cmd != NULL; cf++) {
+ char *c = cmd;
+ char *token = strsep(&c, " ");
+
+ if (strcmp(cf->cmd, token) == 0)
+ return cf->fn(s, v);
+ }
+
+ return 0;
+}
+
+static int clkdbg_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, clkdbg_show, NULL);
+}
+
+static ssize_t clkdbg_write(
+ struct file *file,
+ const char __user *buffer,
+ size_t count,
+ loff_t *data)
+{
+ size_t len = 0;
+
+ len = (count < (sizeof(last_cmd) - 1UL)) ?
+ count : (sizeof(last_cmd) - 1UL);
+ if (copy_from_user(last_cmd, buffer, len) != 0UL)
+ return 0;
+
+ last_cmd[len] = '\0';
+
+ if (last_cmd[len - 1UL] == '\n')
+ last_cmd[len - 1UL] = '\0';
+
+ return (ssize_t)len;
+}
+
+static const struct file_operations clkdbg_fops = {
+ .owner = THIS_MODULE,
+ .open = clkdbg_open,
+ .read = seq_read,
+ .write = clkdbg_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/*
+ * init functions
+ */
+
+static int __init clkdbg_debug_init(void)
+{
+ struct proc_dir_entry *entry;
+
+ entry = proc_create("clkdbg", 0644, NULL, &clkdbg_fops);
+ if (entry == 0)
+ return -ENOMEM;
+
+ set_clkdbg_flag(CLKDBG_EN_SUSPEND_SAVE_3);
+
+ return 0;
+}
+module_init(clkdbg_debug_init);