| /* |
| * 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 |
| |
| #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 <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; |
| 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, *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 val = 0; |
| |
| if (clkdbg_ops == NULL || clkdbg_ops->is_pwr_on == NULL) { |
| if (pvdck->pwr_mask != 0U) |
| val = read_spm_pwr_status(); |
| |
| return pvdck_pwr_is_on(pvdck, val); |
| } |
| |
| val = clkdbg_ops->is_pwr_on(pvdck); |
| |
| return val; |
| } |
| |
| 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[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; |
| } |
| |
| 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 = 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; |
| } |
| |
| 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 = 0; |
| |
| 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 = 0; |
| |
| 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 = 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 |
| 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); |
| 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 |
| 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 != 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; |
| |
| 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[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_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; |
| char *nl; |
| |
| 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'; |
| |
| nl = strchr(last_cmd, '\n'); |
| if (nl != NULL) |
| *nl = '\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); |