// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #define pr_fmt(fmt) "[clkdbg] " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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_PM_DOMAIN_API_4_19 1 #define CLKDBG_CCF_API_4_4 1 #define CLKDBG_HACK_CLK 0 #define CLKDBG_HACK_CLK_CORE 1 #define MAX_CLK_NUM 1024 #define MAX_PD_NUM 32 #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 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; } EXPORT_SYMBOL(set_clkdbg_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); } EXPORT_SYMBOL(print_fmeter_all); 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); } EXPORT_SYMBOL(print_regs); static void seq_print_reg(const struct regname *rn, void *data) { struct seq_file *s = data; const char *pg = rn->base->pg; struct clk *clk; struct clk_hw *c_hw; bool is_pwr_on = true; if (!is_valid_reg(ADDR(rn))) return; if (pg) { clk = __clk_lookup(pg); if (!clk) return; c_hw = __clk_get_hw(clk); if (c_hw) is_pwr_on = clk_hw_is_prepared(c_hw); } if (is_pwr_on) seq_printf(s, "%-21s: [0x%08x][0x%p] = 0x%08x\n", rn->name, PHYSADDR(rn), ADDR(rn), clk_readl(ADDR(rn))); else seq_printf(s, "%-21s: [0x%08x][0x%p] cannot read, pwr_off\n", rn->name, PHYSADDR(rn), 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; if (scpsys_base == NULL) scpsys_base = ioremap(0x10006000, PAGE_SIZE); return clk_readl(scpsys_base + 0x60c); } 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 = 0; 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[MAX_CLK_NUM]; struct device_node *node = NULL; unsigned 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 && n < MAX_CLK_NUM); return provider_clks; } EXPORT_SYMBOL(get_all_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; } EXPORT_SYMBOL(get_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); } } } EXPORT_SYMBOL(prepare_enable_provider); 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); } } EXPORT_SYMBOL(disable_unprepare_provider); 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 = 0; 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; } #if defined(CONFIG_MTK_ENG_BUILD) 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 = 0; 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 = 0; 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 = 0; 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; } #endif /* CONFIG_MTK_ENG_BUILD */ 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 = 0; 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 = 0; 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[MAX_PD_NUM]; static unsigned int num_pds; const size_t maxpd = ARRAY_SIZE(pds); struct device_node *node; #if CLKDBG_PM_DOMAIN_API_4_9 || CLKDBG_PM_DOMAIN_API_4_19 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 || CLKDBG_PM_DOMAIN_API_4_19 pdev = platform_device_alloc("traverse", 0); if (!pdev) return NULL; #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 || CLKDBG_PM_DOMAIN_API_4_19 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); #if CLKDBG_PM_DOMAIN_API_4_19 r = pm_genpd_remove_device(&pdev->dev); #else /* < v4.19 */ r = pm_genpd_remove_device(pds[num_pds], &pdev->dev); #endif 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 || CLKDBG_PM_DOMAIN_API_4_19 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 != 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 != 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 != 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[MAX_PD_NUM]; 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 *genpd); 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 != 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"), PDEV_DRV("clkdbg-pd16"), PDEV_DRV("clkdbg-pd17"), PDEV_DRV("clkdbg-pd18"), PDEV_DRV("clkdbg-pd19"), PDEV_DRV("clkdbg-pd20"), PDEV_DRV("clkdbg-pd21"), PDEV_DRV("clkdbg-pd22"), PDEV_DRV("clkdbg-pd23"), PDEV_DRV("clkdbg-pd24"), PDEV_DRV("clkdbg-pd25"), PDEV_DRV("clkdbg-pd26"), PDEV_DRV("clkdbg-pd27"), PDEV_DRV("clkdbg-pd28"), PDEV_DRV("clkdbg-pd29"), PDEV_DRV("clkdbg-pd30"), PDEV_DRV("clkdbg-pd31"), }; 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 && *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; #if CLKDBG_PM_DOMAIN_API_4_19 r = pm_genpd_remove_device(&pderv[i].pdev->dev); #else r = pm_genpd_remove_device(pd, &pderv[i].pdev->dev); #endif 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 } EXPORT_SYMBOL(reg_pdrv); void unreg_pdrv(const char *pdname) { #if CLKDBG_PM_DOMAIN unreg_pdev_drv(pdname, NULL); #endif } EXPORT_SYMBOL(unreg_pdrv); /* * Suspend / resume handler */ #include #include 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[MAX_CLK_NUM]; #if CLKDBG_PM_DOMAIN struct genpd_state genpd_states[MAX_PD_NUM]; 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_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; } EXPORT_SYMBOL(set_custom_cmds); static int clkdbg_cmds(struct seq_file *s, void *v); static const struct cmd_fn common_cmds[] = { #if !defined(CONFIG_MACH_MT6781) CMDFN("dump_regs", seq_print_regs), CMDFN("dump_regs2", clkdbg_dump_regs2), CMDFN("dump_clks", clkdbg_dump_provider_clks), #endif CMDFN("dump_state", clkdbg_dump_state_all), 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), #if defined(CONFIG_MTK_ENG_BUILD) CMDFN("reg_read", clkdbg_reg_read), CMDFN("reg_write", clkdbg_reg_write), CMDFN("reg_set", clkdbg_reg_set), CMDFN("reg_clr", clkdbg_reg_clr), #endif /* CONFIG_MTK_ENG_BUILD */ 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 (len >= 1 && 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", 0, 0, &clkdbg_fops); if (entry == 0) return -ENOMEM; set_clkdbg_flag(CLKDBG_EN_SUSPEND_SAVE_3); return 0; } 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; } static int __init clkdbg_module_init(void) { int r = 0; r = clkdbg_debug_init(); if (r != 0) goto err; r = clkdbg_pm_init(); if (r != 0) goto err; err: return r; } static void __exit clkdbg_module_exit(void) { } module_init(clkdbg_module_init); module_exit(clkdbg_module_exit); MODULE_LICENSE("GPL");