Loading Documentation/devicetree/bindings/arm/msm/rpm_stats.txt 0 → 100644 +33 −0 Original line number Diff line number Diff line * RPM Stats RPM maintains a counter of the number of times the SoC entered a deeper sleep mode involving lowering or powering down the backbone rails - Cx and Mx and the oscillator clock, XO. PROPERTIES - compatible: Usage: required Value type: <string> Definition: Should be "qcom,rpm-stats". - reg: Usage: required Value type: <prop-encoded-array> Definition: The address on the RPM RAM from where the stats are read should be provided as "phys_addr_base". The offset from which the stats are available should be provided as "offset_addr". - reg-names: Usage: required Value type: <prop-encoded-array> Definition: Provides labels for the reg property. EXAMPLE: qcom,rpm-stats@c000000 { compatible = "qcom,rpm-stats"; reg = <0xC000000 0x1000>, <0x3F0000 0x4>; reg-names = "phys_addr_base", "offset_addr"; }; drivers/soc/qcom/Kconfig +10 −0 Original line number Diff line number Diff line Loading @@ -645,3 +645,13 @@ config QCOM_DCC_V2 This option enables driver for Data Capture and Compare engine. DCC driver provides interface to configure DCC block and read back captured data from DCC's internal SRAM. config QTI_RPM_STATS_LOG bool "Qualcomm Technologies RPM Stats Driver" depends on DEBUG_FS default n help This option enables a driver which reads RPM messages from a shared memory location. These messages provide statistical information about the low power modes that RPM enters. The drivers outputs the message via a debugfs node. drivers/soc/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -68,3 +68,4 @@ obj-$(CONFIG_MSM_EVENT_TIMER) += event_timer.o obj-$(CONFIG_MSM_IDLE_STATS) += lpm-stats.o obj-$(CONFIG_APSS_CORE_EA) += msm-core.o debug_core.o obj-$(CONFIG_QCOM_DCC_V2) += dcc_v2.o obj-$(CONFIG_QTI_RPM_STATS_LOG) += rpm_stats.o drivers/soc/qcom/rpm_stats.c 0 → 100644 +381 −0 Original line number Diff line number Diff line /* Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only 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. * */ #include <linux/debugfs.h> #include <linux/init.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/of.h> #include <linux/uaccess.h> #include <asm/arch_timer.h> #define RPM_STATS_NUM_REC 2 #define MSM_ARCH_TIMER_FREQ 19200000 #define GET_PDATA_OF_ATTR(attr) \ (container_of(attr, struct msm_rpmstats_kobj_attr, ka)->pd) struct msm_rpmstats_record { char name[32]; u32 id; u32 val; }; struct msm_rpmstats_platform_data { phys_addr_t phys_addr_base; u32 phys_size; }; struct msm_rpmstats_private_data { void __iomem *reg_base; u32 num_records; u32 read_idx; u32 len; char buf[320]; struct msm_rpmstats_platform_data *platform_data; }; struct msm_rpm_stats_data { u32 stat_type; u32 count; u64 last_entered_at; u64 last_exited_at; u64 accumulated; }; struct msm_rpmstats_kobj_attr { struct kobj_attribute ka; struct msm_rpmstats_platform_data *pd; }; static inline u64 get_time_in_sec(u64 counter) { do_div(counter, MSM_ARCH_TIMER_FREQ); return counter; } static inline u64 get_time_in_msec(u64 counter) { do_div(counter, MSM_ARCH_TIMER_FREQ); counter *= MSEC_PER_SEC; return counter; } static inline int msm_rpmstats_append_data_to_buf(char *buf, struct msm_rpm_stats_data *data, int buflength) { char stat_type[5]; u64 time_in_last_mode; u64 time_since_last_mode; u64 actual_last_sleep; stat_type[4] = 0; memcpy(stat_type, &data->stat_type, sizeof(u32)); time_in_last_mode = data->last_exited_at - data->last_entered_at; time_in_last_mode = get_time_in_msec(time_in_last_mode); time_since_last_mode = arch_counter_get_cntvct() - data->last_exited_at; time_since_last_mode = get_time_in_sec(time_since_last_mode); actual_last_sleep = get_time_in_msec(data->accumulated); return snprintf(buf, buflength, "RPM Mode:%s\n\t count:%d\ntime in last mode(msec):%llu\n" "time since last mode(sec):%llu\nactual last sleep(msec):%llu\n\n", stat_type, data->count, time_in_last_mode, time_since_last_mode, actual_last_sleep); } static inline u32 msm_rpmstats_read_long_register(void __iomem *regbase, int index, int offset) { return readl_relaxed(regbase + offset + index * sizeof(struct msm_rpm_stats_data)); } static inline u64 msm_rpmstats_read_quad_register(void __iomem *regbase, int index, int offset) { u64 dst; memcpy_fromio(&dst, regbase + offset + index * sizeof(struct msm_rpm_stats_data), 8); return dst; } static inline int msm_rpmstats_copy_stats( struct msm_rpmstats_private_data *prvdata) { void __iomem *reg; struct msm_rpm_stats_data data; int i, length; reg = prvdata->reg_base; for (i = 0, length = 0; i < prvdata->num_records; i++) { data.stat_type = msm_rpmstats_read_long_register(reg, i, offsetof(struct msm_rpm_stats_data, stat_type)); data.count = msm_rpmstats_read_long_register(reg, i, offsetof(struct msm_rpm_stats_data, count)); data.last_entered_at = msm_rpmstats_read_quad_register(reg, i, offsetof(struct msm_rpm_stats_data, last_entered_at)); data.last_exited_at = msm_rpmstats_read_quad_register(reg, i, offsetof(struct msm_rpm_stats_data, last_exited_at)); data.accumulated = msm_rpmstats_read_quad_register(reg, i, offsetof(struct msm_rpm_stats_data, accumulated)); length += msm_rpmstats_append_data_to_buf(prvdata->buf + length, &data, sizeof(prvdata->buf) - length); prvdata->read_idx++; } return length; } static inline unsigned long msm_rpmstats_read_register(void __iomem *regbase, int index, int offset) { return readl_relaxed(regbase + index * 12 + (offset + 1) * 4); } static ssize_t msm_rpmstats_file_read(struct file *file, char __user *bufu, size_t count, loff_t *ppos) { struct msm_rpmstats_private_data *prvdata; prvdata = file->private_data; if (!prvdata) return -EINVAL; if (!bufu || count == 0) return -EINVAL; if ((*ppos >= prvdata->len) && (prvdata->read_idx < prvdata->num_records)) { prvdata->len = msm_rpmstats_copy_stats(prvdata); *ppos = 0; } return simple_read_from_buffer(bufu, count, ppos, prvdata->buf, prvdata->len); } static int msm_rpmstats_file_open(struct inode *inode, struct file *file) { struct msm_rpmstats_private_data *prvdata; struct msm_rpmstats_platform_data *pdata; pdata = inode->i_private; file->private_data = kzalloc(sizeof(*prvdata), GFP_KERNEL); if (!file->private_data) return -ENOMEM; prvdata = file->private_data; prvdata->reg_base = ioremap_nocache(pdata->phys_addr_base, pdata->phys_size); if (!prvdata->reg_base) { kfree(file->private_data); prvdata = NULL; pr_err("%s: ERROR could not ioremap start=%pa, len=%u\n", __func__, &pdata->phys_addr_base, pdata->phys_size); return -EBUSY; } prvdata->read_idx = prvdata->len = 0; prvdata->platform_data = pdata; prvdata->num_records = RPM_STATS_NUM_REC; return 0; } static int msm_rpmstats_file_close(struct inode *inode, struct file *file) { struct msm_rpmstats_private_data *private = file->private_data; if (private->reg_base) iounmap(private->reg_base); kfree(file->private_data); return 0; } static const struct file_operations msm_rpmstats_fops = { .owner = THIS_MODULE, .open = msm_rpmstats_file_open, .read = msm_rpmstats_file_read, .release = msm_rpmstats_file_close, .llseek = no_llseek, }; static ssize_t rpmstats_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct msm_rpmstats_private_data *prvdata = NULL; struct msm_rpmstats_platform_data *pdata = NULL; pdata = GET_PDATA_OF_ATTR(attr); prvdata = kmalloc(sizeof(*prvdata), GFP_KERNEL); if (!prvdata) return -ENOMEM; prvdata->reg_base = ioremap_nocache(pdata->phys_addr_base, pdata->phys_size); if (!prvdata->reg_base) { kfree(prvdata); pr_err("%s: ERROR could not ioremap start=%pa, len=%u\n", __func__, &pdata->phys_addr_base, pdata->phys_size); return -EBUSY; } prvdata->read_idx = prvdata->len = 0; prvdata->platform_data = pdata; prvdata->num_records = RPM_STATS_NUM_REC; if (prvdata->read_idx < prvdata->num_records) prvdata->len = msm_rpmstats_copy_stats(prvdata); return snprintf(buf, prvdata->len, prvdata->buf); } static int msm_rpmstats_create_sysfs(struct msm_rpmstats_platform_data *pd) { struct kobject *module_kobj = NULL; struct kobject *rpmstats_kobj = NULL; struct msm_rpmstats_kobj_attr *rpms_ka = NULL; int ret = 0; module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); if (!module_kobj) { pr_err("%s: Cannot find module_kset\n", __func__); return -ENODEV; } rpmstats_kobj = kobject_create_and_add("rpmstats", module_kobj); if (!rpmstats_kobj) { pr_err("%s: Cannot create rpmstats kobject\n", __func__); ret = -ENOMEM; goto fail; } rpms_ka = kzalloc(sizeof(*rpms_ka), GFP_KERNEL); if (!rpms_ka) { kobject_put(rpmstats_kobj); ret = -ENOMEM; goto fail; } sysfs_attr_init(&rpms_ka->ka.attr); rpms_ka->pd = pd; rpms_ka->ka.attr.mode = 0444; rpms_ka->ka.attr.name = "stats"; rpms_ka->ka.show = rpmstats_show; rpms_ka->ka.store = NULL; ret = sysfs_create_file(rpmstats_kobj, &rpms_ka->ka.attr); fail: return ret; } static int msm_rpmstats_probe(struct platform_device *pdev) { struct dentry *dent = NULL; struct msm_rpmstats_platform_data *pdata; struct msm_rpmstats_platform_data *pd; struct resource *res = NULL, *offset = NULL; u32 offset_addr = 0; void __iomem *phys_ptr = NULL; pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phys_addr_base"); if (!res) return -EINVAL; offset = platform_get_resource_byname(pdev, IORESOURCE_MEM, "offset_addr"); if (offset) { /* Remap the rpm-stats pointer */ phys_ptr = ioremap_nocache(offset->start, SZ_4); if (!phys_ptr) { pr_err("%s: Failed to ioremap address: %x\n", __func__, offset_addr); return -ENODEV; } offset_addr = readl_relaxed(phys_ptr); iounmap(phys_ptr); } pdata->phys_addr_base = res->start + offset_addr; pdata->phys_size = resource_size(res); if (pdev->dev.platform_data) pd = pdev->dev.platform_data; dent = debugfs_create_file("rpm_stats", 0444, NULL, pdata, &msm_rpmstats_fops); if (!dent) { pr_err("%s: ERROR rpm_stats debugfs_create_file fail\n", __func__); return -ENOMEM; } msm_rpmstats_create_sysfs(pdata); platform_set_drvdata(pdev, dent); return 0; } static int msm_rpmstats_remove(struct platform_device *pdev) { struct dentry *dent; dent = platform_get_drvdata(pdev); debugfs_remove(dent); platform_set_drvdata(pdev, NULL); return 0; } static const struct of_device_id rpm_stats_table[] = { { .compatible = "qcom,rpm-stats" }, { }, }; static struct platform_driver msm_rpmstats_driver = { .probe = msm_rpmstats_probe, .remove = msm_rpmstats_remove, .driver = { .name = "msm_rpm_stat", .owner = THIS_MODULE, .of_match_table = rpm_stats_table, }, }; builtin_platform_driver(msm_rpmstats_driver); Loading
Documentation/devicetree/bindings/arm/msm/rpm_stats.txt 0 → 100644 +33 −0 Original line number Diff line number Diff line * RPM Stats RPM maintains a counter of the number of times the SoC entered a deeper sleep mode involving lowering or powering down the backbone rails - Cx and Mx and the oscillator clock, XO. PROPERTIES - compatible: Usage: required Value type: <string> Definition: Should be "qcom,rpm-stats". - reg: Usage: required Value type: <prop-encoded-array> Definition: The address on the RPM RAM from where the stats are read should be provided as "phys_addr_base". The offset from which the stats are available should be provided as "offset_addr". - reg-names: Usage: required Value type: <prop-encoded-array> Definition: Provides labels for the reg property. EXAMPLE: qcom,rpm-stats@c000000 { compatible = "qcom,rpm-stats"; reg = <0xC000000 0x1000>, <0x3F0000 0x4>; reg-names = "phys_addr_base", "offset_addr"; };
drivers/soc/qcom/Kconfig +10 −0 Original line number Diff line number Diff line Loading @@ -645,3 +645,13 @@ config QCOM_DCC_V2 This option enables driver for Data Capture and Compare engine. DCC driver provides interface to configure DCC block and read back captured data from DCC's internal SRAM. config QTI_RPM_STATS_LOG bool "Qualcomm Technologies RPM Stats Driver" depends on DEBUG_FS default n help This option enables a driver which reads RPM messages from a shared memory location. These messages provide statistical information about the low power modes that RPM enters. The drivers outputs the message via a debugfs node.
drivers/soc/qcom/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -68,3 +68,4 @@ obj-$(CONFIG_MSM_EVENT_TIMER) += event_timer.o obj-$(CONFIG_MSM_IDLE_STATS) += lpm-stats.o obj-$(CONFIG_APSS_CORE_EA) += msm-core.o debug_core.o obj-$(CONFIG_QCOM_DCC_V2) += dcc_v2.o obj-$(CONFIG_QTI_RPM_STATS_LOG) += rpm_stats.o
drivers/soc/qcom/rpm_stats.c 0 → 100644 +381 −0 Original line number Diff line number Diff line /* Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only 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. * */ #include <linux/debugfs.h> #include <linux/init.h> #include <linux/io.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/of.h> #include <linux/uaccess.h> #include <asm/arch_timer.h> #define RPM_STATS_NUM_REC 2 #define MSM_ARCH_TIMER_FREQ 19200000 #define GET_PDATA_OF_ATTR(attr) \ (container_of(attr, struct msm_rpmstats_kobj_attr, ka)->pd) struct msm_rpmstats_record { char name[32]; u32 id; u32 val; }; struct msm_rpmstats_platform_data { phys_addr_t phys_addr_base; u32 phys_size; }; struct msm_rpmstats_private_data { void __iomem *reg_base; u32 num_records; u32 read_idx; u32 len; char buf[320]; struct msm_rpmstats_platform_data *platform_data; }; struct msm_rpm_stats_data { u32 stat_type; u32 count; u64 last_entered_at; u64 last_exited_at; u64 accumulated; }; struct msm_rpmstats_kobj_attr { struct kobj_attribute ka; struct msm_rpmstats_platform_data *pd; }; static inline u64 get_time_in_sec(u64 counter) { do_div(counter, MSM_ARCH_TIMER_FREQ); return counter; } static inline u64 get_time_in_msec(u64 counter) { do_div(counter, MSM_ARCH_TIMER_FREQ); counter *= MSEC_PER_SEC; return counter; } static inline int msm_rpmstats_append_data_to_buf(char *buf, struct msm_rpm_stats_data *data, int buflength) { char stat_type[5]; u64 time_in_last_mode; u64 time_since_last_mode; u64 actual_last_sleep; stat_type[4] = 0; memcpy(stat_type, &data->stat_type, sizeof(u32)); time_in_last_mode = data->last_exited_at - data->last_entered_at; time_in_last_mode = get_time_in_msec(time_in_last_mode); time_since_last_mode = arch_counter_get_cntvct() - data->last_exited_at; time_since_last_mode = get_time_in_sec(time_since_last_mode); actual_last_sleep = get_time_in_msec(data->accumulated); return snprintf(buf, buflength, "RPM Mode:%s\n\t count:%d\ntime in last mode(msec):%llu\n" "time since last mode(sec):%llu\nactual last sleep(msec):%llu\n\n", stat_type, data->count, time_in_last_mode, time_since_last_mode, actual_last_sleep); } static inline u32 msm_rpmstats_read_long_register(void __iomem *regbase, int index, int offset) { return readl_relaxed(regbase + offset + index * sizeof(struct msm_rpm_stats_data)); } static inline u64 msm_rpmstats_read_quad_register(void __iomem *regbase, int index, int offset) { u64 dst; memcpy_fromio(&dst, regbase + offset + index * sizeof(struct msm_rpm_stats_data), 8); return dst; } static inline int msm_rpmstats_copy_stats( struct msm_rpmstats_private_data *prvdata) { void __iomem *reg; struct msm_rpm_stats_data data; int i, length; reg = prvdata->reg_base; for (i = 0, length = 0; i < prvdata->num_records; i++) { data.stat_type = msm_rpmstats_read_long_register(reg, i, offsetof(struct msm_rpm_stats_data, stat_type)); data.count = msm_rpmstats_read_long_register(reg, i, offsetof(struct msm_rpm_stats_data, count)); data.last_entered_at = msm_rpmstats_read_quad_register(reg, i, offsetof(struct msm_rpm_stats_data, last_entered_at)); data.last_exited_at = msm_rpmstats_read_quad_register(reg, i, offsetof(struct msm_rpm_stats_data, last_exited_at)); data.accumulated = msm_rpmstats_read_quad_register(reg, i, offsetof(struct msm_rpm_stats_data, accumulated)); length += msm_rpmstats_append_data_to_buf(prvdata->buf + length, &data, sizeof(prvdata->buf) - length); prvdata->read_idx++; } return length; } static inline unsigned long msm_rpmstats_read_register(void __iomem *regbase, int index, int offset) { return readl_relaxed(regbase + index * 12 + (offset + 1) * 4); } static ssize_t msm_rpmstats_file_read(struct file *file, char __user *bufu, size_t count, loff_t *ppos) { struct msm_rpmstats_private_data *prvdata; prvdata = file->private_data; if (!prvdata) return -EINVAL; if (!bufu || count == 0) return -EINVAL; if ((*ppos >= prvdata->len) && (prvdata->read_idx < prvdata->num_records)) { prvdata->len = msm_rpmstats_copy_stats(prvdata); *ppos = 0; } return simple_read_from_buffer(bufu, count, ppos, prvdata->buf, prvdata->len); } static int msm_rpmstats_file_open(struct inode *inode, struct file *file) { struct msm_rpmstats_private_data *prvdata; struct msm_rpmstats_platform_data *pdata; pdata = inode->i_private; file->private_data = kzalloc(sizeof(*prvdata), GFP_KERNEL); if (!file->private_data) return -ENOMEM; prvdata = file->private_data; prvdata->reg_base = ioremap_nocache(pdata->phys_addr_base, pdata->phys_size); if (!prvdata->reg_base) { kfree(file->private_data); prvdata = NULL; pr_err("%s: ERROR could not ioremap start=%pa, len=%u\n", __func__, &pdata->phys_addr_base, pdata->phys_size); return -EBUSY; } prvdata->read_idx = prvdata->len = 0; prvdata->platform_data = pdata; prvdata->num_records = RPM_STATS_NUM_REC; return 0; } static int msm_rpmstats_file_close(struct inode *inode, struct file *file) { struct msm_rpmstats_private_data *private = file->private_data; if (private->reg_base) iounmap(private->reg_base); kfree(file->private_data); return 0; } static const struct file_operations msm_rpmstats_fops = { .owner = THIS_MODULE, .open = msm_rpmstats_file_open, .read = msm_rpmstats_file_read, .release = msm_rpmstats_file_close, .llseek = no_llseek, }; static ssize_t rpmstats_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct msm_rpmstats_private_data *prvdata = NULL; struct msm_rpmstats_platform_data *pdata = NULL; pdata = GET_PDATA_OF_ATTR(attr); prvdata = kmalloc(sizeof(*prvdata), GFP_KERNEL); if (!prvdata) return -ENOMEM; prvdata->reg_base = ioremap_nocache(pdata->phys_addr_base, pdata->phys_size); if (!prvdata->reg_base) { kfree(prvdata); pr_err("%s: ERROR could not ioremap start=%pa, len=%u\n", __func__, &pdata->phys_addr_base, pdata->phys_size); return -EBUSY; } prvdata->read_idx = prvdata->len = 0; prvdata->platform_data = pdata; prvdata->num_records = RPM_STATS_NUM_REC; if (prvdata->read_idx < prvdata->num_records) prvdata->len = msm_rpmstats_copy_stats(prvdata); return snprintf(buf, prvdata->len, prvdata->buf); } static int msm_rpmstats_create_sysfs(struct msm_rpmstats_platform_data *pd) { struct kobject *module_kobj = NULL; struct kobject *rpmstats_kobj = NULL; struct msm_rpmstats_kobj_attr *rpms_ka = NULL; int ret = 0; module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); if (!module_kobj) { pr_err("%s: Cannot find module_kset\n", __func__); return -ENODEV; } rpmstats_kobj = kobject_create_and_add("rpmstats", module_kobj); if (!rpmstats_kobj) { pr_err("%s: Cannot create rpmstats kobject\n", __func__); ret = -ENOMEM; goto fail; } rpms_ka = kzalloc(sizeof(*rpms_ka), GFP_KERNEL); if (!rpms_ka) { kobject_put(rpmstats_kobj); ret = -ENOMEM; goto fail; } sysfs_attr_init(&rpms_ka->ka.attr); rpms_ka->pd = pd; rpms_ka->ka.attr.mode = 0444; rpms_ka->ka.attr.name = "stats"; rpms_ka->ka.show = rpmstats_show; rpms_ka->ka.store = NULL; ret = sysfs_create_file(rpmstats_kobj, &rpms_ka->ka.attr); fail: return ret; } static int msm_rpmstats_probe(struct platform_device *pdev) { struct dentry *dent = NULL; struct msm_rpmstats_platform_data *pdata; struct msm_rpmstats_platform_data *pd; struct resource *res = NULL, *offset = NULL; u32 offset_addr = 0; void __iomem *phys_ptr = NULL; pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) return -ENOMEM; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phys_addr_base"); if (!res) return -EINVAL; offset = platform_get_resource_byname(pdev, IORESOURCE_MEM, "offset_addr"); if (offset) { /* Remap the rpm-stats pointer */ phys_ptr = ioremap_nocache(offset->start, SZ_4); if (!phys_ptr) { pr_err("%s: Failed to ioremap address: %x\n", __func__, offset_addr); return -ENODEV; } offset_addr = readl_relaxed(phys_ptr); iounmap(phys_ptr); } pdata->phys_addr_base = res->start + offset_addr; pdata->phys_size = resource_size(res); if (pdev->dev.platform_data) pd = pdev->dev.platform_data; dent = debugfs_create_file("rpm_stats", 0444, NULL, pdata, &msm_rpmstats_fops); if (!dent) { pr_err("%s: ERROR rpm_stats debugfs_create_file fail\n", __func__); return -ENOMEM; } msm_rpmstats_create_sysfs(pdata); platform_set_drvdata(pdev, dent); return 0; } static int msm_rpmstats_remove(struct platform_device *pdev) { struct dentry *dent; dent = platform_get_drvdata(pdev); debugfs_remove(dent); platform_set_drvdata(pdev, NULL); return 0; } static const struct of_device_id rpm_stats_table[] = { { .compatible = "qcom,rpm-stats" }, { }, }; static struct platform_driver msm_rpmstats_driver = { .probe = msm_rpmstats_probe, .remove = msm_rpmstats_remove, .driver = { .name = "msm_rpm_stat", .owner = THIS_MODULE, .of_match_table = rpm_stats_table, }, }; builtin_platform_driver(msm_rpmstats_driver);