Loading Documentation/devicetree/bindings/arm/msm/limits_lmh.txt +5 −3 Original line number Diff line number Diff line Loading @@ -14,7 +14,9 @@ Device/Asic specific properties: trim error in LMH hardware. Required parameters: - compatible: Must be "qcom,lmh" - compatible: Must be either "qcom,lmh" or "qcom,lmh_v1". The driver based on the compatible string will decide the default profile. - interrupts: LMH Lite hardware interrupt details. Example: Loading @@ -29,6 +31,6 @@ qcom,lmh { Or for asics that don't have trim err: qcom,lmh { compatible = "qcom,lmh"; compatible = "qcom,lmh_v1"; interrupts = <0 332 4>; }; arch/arm/boot/dts/qcom/msm8996.dtsi +1 −1 Original line number Diff line number Diff line Loading @@ -3464,7 +3464,7 @@ }; lmh: qcom,lmh { compatible = "qcom,lmh"; compatible = "qcom,lmh_v1"; interrupts = <0 23 4>; }; Loading drivers/thermal/lmh_interface.c +380 −73 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ #define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/device.h> #include <linux/interrupt.h> #include <linux/workqueue.h> #include <linux/kernel.h> Loading @@ -26,11 +26,18 @@ #include <linux/thermal.h> #include <linux/slab.h> #include "lmh_interface.h" #include <linux/string.h> #include <linux/uaccess.h> #define LMH_MON_NAME "lmh_monitor" #define LMH_ISR_POLL_DELAY "interrupt_poll_delay_msec" #define LMH_TRACE_ENABLE "hw_trace_enable" #define LMH_TRACE_INTERVAL "hw_trace_interval" #define LMH_DBGFS_DIR "debug" #define LMH_DBGFS_READ "data" #define LMH_DBGFS_CONFIG_READ "config" #define LMH_DBGFS_READ_TYPES "data_types" #define LMH_DBGFS_CONFIG_TYPES "config_types" #define LMH_TRACE_INTERVAL_XO_TICKS 250 struct lmh_mon_threshold { Loading @@ -50,6 +57,7 @@ struct lmh_device_data { struct dentry *avail_lvl_fs; struct list_head list_ptr; struct rw_semaphore lock; struct device dev; }; struct lmh_mon_sensor_data { Loading @@ -71,9 +79,24 @@ struct lmh_mon_driver_data { struct dentry *hw_log_delay; uint32_t hw_log_enable; uint64_t hw_log_interval; struct dentry *debug_dir; struct dentry *debug_read; struct dentry *debug_config; struct dentry *debug_read_type; struct dentry *debug_config_type; struct lmh_debug_ops *debug_ops; }; enum lmh_read_type { LMH_DEBUG_READ_TYPE, LMH_DEBUG_CONFIG_TYPE, LMH_PROFILES, }; static struct lmh_mon_driver_data *lmh_mon_data; static struct class lmh_class_info = { .name = "msm_limits", }; static DECLARE_RWSEM(lmh_mon_access_lock); static LIST_HEAD(lmh_sensor_list); static DECLARE_RWSEM(lmh_dev_access_lock); Loading Loading @@ -123,104 +146,117 @@ DEFINE_SIMPLE_ATTRIBUTE(_name##_fops, _name##_get, _name##_set, \ "%llu\n"); #define LMH_DEV_GET(_name) \ static int _name##_get(void *data, u64 *val) \ static ssize_t _name##_get(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ struct lmh_device_data *lmh_dev = (struct lmh_device_data *) data; \ *val = lmh_dev->_name; \ return 0; \ } struct lmh_device_data *lmh_dev = container_of(dev, \ struct lmh_device_data, dev); \ return snprintf(buf, LMH_NAME_MAX, "%d", lmh_dev->_name); \ } \ LMH_HW_LOG_FS(hw_log_enable); LMH_HW_LOG_FS(hw_log_interval); LMH_DEV_GET(max_level); LMH_DEV_GET(curr_level); static int curr_level_set(void *data, u64 val) static ssize_t curr_level_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct lmh_device_data *lmh_dev = (struct lmh_device_data *) data; struct lmh_device_data *lmh_dev = container_of(dev, struct lmh_device_data, dev); int val = 0, ret = 0; ret = kstrtouint(buf, 0, &val); if (ret < 0) { pr_err("Invalid input [%s]. err:%d\n", buf, ret); return ret; } return lmh_set_dev_level(lmh_dev->device_name, val); } static ssize_t avail_level_get(struct file *fp, char __user *user_buffer, size_t buffer_length, loff_t *position) static ssize_t avail_level_get(struct device *dev, struct device_attribute *attr, char *buf) { struct lmh_device_data *lmh_dev = list_first_entry(&lmh_device_list, struct lmh_device_data, list_ptr); int idx = 0, count = 0, ret = 0; char *profile_buf = NULL; char *buf_start = NULL; struct lmh_device_data *lmh_dev = container_of(dev, struct lmh_device_data, dev); uint32_t *type_list = NULL; int ret = 0, count = 0, lvl_buf_count = 0, idx = 0; char *lvl_buf = NULL; if (!lmh_dev || !lmh_dev->max_level) return ret; profile_buf = kzalloc(LMH_NAME_MAX * lmh_dev->max_level, GFP_KERNEL); if (!profile_buf) { if (!lmh_dev || !lmh_dev->levels || !lmh_dev->max_level) { pr_err("Invalid input\n"); return -EINVAL; } type_list = lmh_dev->levels; lvl_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!lvl_buf) { pr_err("Error allocating memory\n"); return -ENOMEM; } buf_start = profile_buf; for (idx = 0; idx < lmh_dev->max_level; idx++) { count = snprintf(buf_start, LMH_NAME_MAX, "%d ", lmh_dev->levels[idx]); if (count <= 0) { pr_err("Error getting freq value idx:%d. err:%d\n", idx, count); return count; for (idx = 0; (idx < lmh_dev->max_level) && (lvl_buf_count < PAGE_SIZE) ; idx++) { count = snprintf(lvl_buf + lvl_buf_count, PAGE_SIZE - lvl_buf_count, "%d ", type_list[idx]); if (count + lvl_buf_count >= PAGE_SIZE) { pr_err("overflow.\n"); break; } buf_start += count; } if (buf_start != profile_buf) { buf_start[-1] = '\n'; ret = simple_read_from_buffer(user_buffer, buffer_length, position, profile_buf, buf_start - profile_buf - 1); if (ret) pr_err("populating the buffer failed. err:%d\n", ret); } else { ret = -ENODEV; lvl_buf_count += count; } count = snprintf(lvl_buf + lvl_buf_count, PAGE_SIZE - lvl_buf_count, "\n"); if (count + lvl_buf_count < PAGE_SIZE) lvl_buf_count += count; kfree(profile_buf); return ret; count = snprintf(buf, lvl_buf_count + 1, lvl_buf); if (count > PAGE_SIZE) { pr_err("copy to user buf failed\n"); ret = -EFAULT; goto lvl_get_exit; } DEFINE_SIMPLE_ATTRIBUTE(curr_level_fops, curr_level_get, curr_level_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(max_level_fops, max_level_get, NULL, "%llu\n"); static const struct file_operations avail_level_fops = { .read = avail_level_get, }; lvl_get_exit: kfree(lvl_buf); return (ret) ? ret : count; } static int lmh_create_dev_debugfs(struct lmh_device_data *lmh_dev) static int lmh_create_dev_sysfs(struct lmh_device_data *lmh_dev) { int ret = 0; static DEVICE_ATTR(level, 0600, curr_level_get, curr_level_set); static DEVICE_ATTR(available_levels, 0400, avail_level_get, NULL); static DEVICE_ATTR(total_levels, 0400, max_level_get, NULL); LMH_CREATE_DEBUGFS_DIR(lmh_dev->dev_parent, lmh_dev->device_name, lmh_mon_data->debugfs_parent, ret); if (ret) goto debugfs_exit; LMH_CREATE_DEBUGFS_FILE(lmh_dev->curr_lvl_fs, "level", 0600, lmh_dev->dev_parent, (void *)lmh_dev, &curr_level_fops, ret); if (ret) goto debugfs_exit; LMH_CREATE_DEBUGFS_FILE(lmh_dev->max_lvl_fs, "total_levels", 0400, lmh_dev->dev_parent, (void *)lmh_dev, &max_level_fops, ret); if (ret) goto debugfs_exit; LMH_CREATE_DEBUGFS_FILE(lmh_dev->avail_lvl_fs, "available_levels", 0400, lmh_dev->dev_parent, (void *)lmh_dev, &avail_level_fops, ret); if (ret) goto debugfs_exit; lmh_dev->dev.class = &lmh_class_info; dev_set_name(&lmh_dev->dev, "%s", lmh_dev->device_name); ret = device_register(&lmh_dev->dev); if (ret) { pr_err("Error registering profile device. err:%d\n", ret); return ret; } ret = device_create_file(&lmh_dev->dev, &dev_attr_level); if (ret) { pr_err("Error creating profile level sysfs node. err:%d\n", ret); goto dev_sysfs_exit; } ret = device_create_file(&lmh_dev->dev, &dev_attr_total_levels); if (ret) { pr_err("Error creating total level sysfs node. err:%d\n", ret); goto dev_sysfs_exit; } ret = device_create_file(&lmh_dev->dev, &dev_attr_available_levels); if (ret) { pr_err("Error creating available level sysfs node. err:%d\n", ret); goto dev_sysfs_exit; } debugfs_exit: dev_sysfs_exit: if (ret) debugfs_remove_recursive(lmh_dev->dev_parent); device_unregister(&lmh_dev->dev); return ret; } Loading Loading @@ -688,7 +724,7 @@ static int lmh_device_init(struct lmh_device_data *lmh_device, lmh_device->device_ops = ops; strlcpy(lmh_device->device_name, device_name, LMH_NAME_MAX); list_add_tail(&lmh_device->list_ptr, &lmh_device_list); lmh_create_dev_debugfs(lmh_device); lmh_create_dev_sysfs(lmh_device); dev_init_exit: if (ret) Loading Loading @@ -877,6 +913,262 @@ void lmh_device_deregister(struct lmh_device_ops *ops) return; } static int lmh_parse_and_extract(const char __user *user_buf, size_t count, enum lmh_read_type type) { char *local_buf = NULL, *token = NULL, *curr_ptr = NULL, *token1 = NULL; char *next_line = NULL; int ret = 0, data_ct = 0, i = 0, size = 0; uint32_t *config_buf = NULL; /* Allocate two extra space to add ';' character and NULL terminate */ local_buf = kzalloc(count + 2, GFP_KERNEL); if (!local_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } if (copy_from_user(local_buf, user_buf, count)) { pr_err("user buf error\n"); ret = -EFAULT; goto dfs_cfg_write_exit; } size = count + (strnchr(local_buf, count, '\n') ? 1 : 2); local_buf[size - 2] = ';'; local_buf[size - 1] = '\0'; curr_ptr = next_line = local_buf; while ((token1 = strnchr(next_line, local_buf + size - next_line, ';')) != NULL) { data_ct = 0; *token1 = '\0'; curr_ptr = next_line; next_line = token1 + 1; for (token = (char *)curr_ptr; token && ((token = strnchr(token, next_line - token, ' ')) != NULL); token++) data_ct++; if (data_ct < 2) { pr_err("Invalid format string:[%s]\n", curr_ptr); ret = -EINVAL; goto dfs_cfg_write_exit; } config_buf = kzalloc((++data_ct) * sizeof(uint32_t), GFP_KERNEL); if (!config_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } pr_debug("Input:%s data_ct:%d\n", curr_ptr, data_ct); for (i = 0, token = (char *)curr_ptr; token && (i < data_ct); i++) { token = strnchr(token, next_line - token, ' '); if (token) *token = '\0'; ret = kstrtouint(curr_ptr, 0, &config_buf[i]); if (ret < 0) { pr_err("Data[%s] scan error. err:%d\n", curr_ptr, ret); kfree(config_buf); goto dfs_cfg_write_exit; } if (token) curr_ptr = ++token; } switch (type) { case LMH_DEBUG_READ_TYPE: ret = lmh_mon_data->debug_ops->debug_config_read( lmh_mon_data->debug_ops, config_buf, data_ct); break; case LMH_DEBUG_CONFIG_TYPE: ret = lmh_mon_data->debug_ops->debug_config_lmh( lmh_mon_data->debug_ops, config_buf, data_ct); break; default: ret = -EINVAL; break; } kfree(config_buf); if (ret) { pr_err("Config error. type:%d err:%d\n", type, ret); goto dfs_cfg_write_exit; } } dfs_cfg_write_exit: kfree(local_buf); return ret; } static ssize_t lmh_dbgfs_config_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_DEBUG_CONFIG_TYPE); return count; } static int lmh_dbgfs_data_read(struct seq_file *seq_fp, void *data) { uint32_t *read_buf = NULL; int ret = 0, i = 0; ret = lmh_mon_data->debug_ops->debug_read(lmh_mon_data->debug_ops, &read_buf); if (ret <= 0 || !read_buf) goto dfs_read_exit; do { seq_printf(seq_fp, "0x%x ", read_buf[i]); i++; if ((i % LMH_READ_LINE_LENGTH) == 0) seq_puts(seq_fp, "\n"); } while (i < (ret / sizeof(uint32_t))); dfs_read_exit: return (ret < 0) ? ret : 0; } static ssize_t lmh_dbgfs_data_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_DEBUG_READ_TYPE); return count; } static int lmh_dbgfs_data_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_data_read, inode->i_private); } static int lmh_get_types(struct seq_file *seq_fp, enum lmh_read_type type) { int ret = 0, idx = 0, size = 0; uint32_t *type_list = NULL; switch (type) { case LMH_DEBUG_READ_TYPE: ret = lmh_mon_data->debug_ops->debug_get_types( lmh_mon_data->debug_ops, true, &type_list); break; case LMH_DEBUG_CONFIG_TYPE: ret = lmh_mon_data->debug_ops->debug_get_types( lmh_mon_data->debug_ops, false, &type_list); break; default: return -EINVAL; } if (ret <= 0 || !type_list) { pr_err("No device information. err:%d\n", ret); return -ENODEV; } size = ret; for (idx = 0; idx < size; idx++) seq_printf(seq_fp, "0x%x ", type_list[idx]); seq_puts(seq_fp, "\n"); return 0; } static int lmh_dbgfs_read_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_DEBUG_READ_TYPE); } static int lmh_dbgfs_read_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_read_type, inode->i_private); } static int lmh_dbgfs_config_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_DEBUG_CONFIG_TYPE); } static int lmh_dbgfs_config_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_config_type, inode->i_private); } static const struct file_operations lmh_dbgfs_config_fops = { .write = lmh_dbgfs_config_write, }; static const struct file_operations lmh_dbgfs_read_fops = { .open = lmh_dbgfs_data_open, .read = seq_read, .write = lmh_dbgfs_data_write, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_read_type_fops = { .open = lmh_dbgfs_read_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_config_type_fops = { .open = lmh_dbgfs_config_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; int lmh_debug_register(struct lmh_debug_ops *ops) { int ret = 0; if (!ops || !ops->debug_read || !ops->debug_config_read || !ops->debug_get_types) { pr_err("Invalid input"); ret = -EINVAL; goto dbg_reg_exit; } lmh_mon_data->debug_ops = ops; LMH_CREATE_DEBUGFS_DIR(lmh_mon_data->debug_dir, LMH_DBGFS_DIR, lmh_mon_data->debugfs_parent, ret); if (ret) goto dbg_reg_exit; LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_read, LMH_DBGFS_READ, 0600, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_read_fops, ret); if (!lmh_mon_data->debug_read) { pr_err("Error creating" LMH_DBGFS_READ "entry.\n"); ret = -ENODEV; goto dbg_reg_exit; } LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_config, LMH_DBGFS_CONFIG_READ, 0200, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_config_fops, ret); if (!lmh_mon_data->debug_config) { pr_err("Error creating" LMH_DBGFS_CONFIG_READ "entry\n"); ret = -ENODEV; goto dbg_reg_exit; } LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_read_type, LMH_DBGFS_READ_TYPES, 0400, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_read_type_fops, ret); if (!lmh_mon_data->debug_read_type) { pr_err("Error creating" LMH_DBGFS_READ_TYPES "entry\n"); ret = -ENODEV; goto dbg_reg_exit; } LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_config_type, LMH_DBGFS_CONFIG_TYPES, 0400, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_config_type_fops, ret); if (!lmh_mon_data->debug_config_type) { pr_err("Error creating" LMH_DBGFS_CONFIG_TYPES "entry\n"); ret = -ENODEV; goto dbg_reg_exit; } dbg_reg_exit: if (ret) { /*Clean up all the dbg nodes*/ debugfs_remove_recursive(lmh_mon_data->debug_dir); lmh_mon_data->debug_ops = NULL; } return ret; } static int lmh_mon_init_driver(void) { int ret = 0; Loading Loading @@ -913,8 +1205,13 @@ static int __init lmh_mon_init_call(void) pr_err("Error initializing the debugfs. err:%d\n", ret); goto lmh_init_exit; } ret = class_register(&lmh_class_info); if (ret) goto lmh_init_exit; lmh_init_exit: if (ret) class_unregister(&lmh_class_info); return ret; } Loading @@ -940,10 +1237,20 @@ static void lmh_device_cleanup(void) up_write(&lmh_dev_access_lock); } static void lmh_debug_cleanup(void) { if (lmh_mon_data->debug_ops) { debugfs_remove_recursive(lmh_mon_data->debug_dir); lmh_mon_data->debug_ops = NULL; } } static void __exit lmh_mon_exit(void) { lmh_mon_cleanup(); lmh_device_cleanup(); lmh_debug_cleanup(); class_unregister(&lmh_class_info); } module_init(lmh_mon_init_call); Loading drivers/thermal/lmh_interface.h +19 −1 Original line number Diff line number Diff line /* * Copyright (c) 2014, The Linux Foundation. All rights reserved. * Copyright (c) 2014-2015, 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 Loading @@ -16,6 +16,7 @@ #define LMH_NAME_MAX 20 #define LMH_POLLING_MSEC 30 #define LMH_READ_LINE_LENGTH 10 enum lmh_trip_type { LMH_LOW_TRIP, Loading Loading @@ -44,6 +45,13 @@ struct lmh_device_ops { int (*set_level) (struct lmh_device_ops *, int); }; struct lmh_debug_ops { int (*debug_read)(struct lmh_debug_ops *, uint32_t **); int (*debug_config_read)(struct lmh_debug_ops *, uint32_t *, int); int (*debug_config_lmh)(struct lmh_debug_ops *, uint32_t *, int); int (*debug_get_types)(struct lmh_debug_ops *, bool, uint32_t **); }; static int lmh_poll_interval = LMH_POLLING_MSEC; #ifdef CONFIG_LIMITS_MONITOR int lmh_get_all_dev_levels(char *, int *); Loading @@ -53,6 +61,8 @@ int lmh_sensor_register(char *, struct lmh_sensor_ops *); void lmh_sensor_deregister(struct lmh_sensor_ops *); int lmh_device_register(char *, struct lmh_device_ops *); void lmh_device_deregister(struct lmh_device_ops *); int lmh_debug_register(struct lmh_debug_ops *); void lmh_debug_deregister(struct lmh_debug_ops *ops); #else static inline int lmh_get_all_dev_levels(char *device_name, int *level) { Loading Loading @@ -90,6 +100,14 @@ static inline void lmh_device_deregister(struct lmh_device_ops *ops) { return; } static inline int lmh_debug_register(struct lmh_debug_ops *) { return -ENOSYS; } static inline void lmh_debug_deregister(struct lmh_debug_ops *ops) { } #endif #endif /*__LMH_INTERFACE_H*/ drivers/thermal/lmh_lite.c +372 −50 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
Documentation/devicetree/bindings/arm/msm/limits_lmh.txt +5 −3 Original line number Diff line number Diff line Loading @@ -14,7 +14,9 @@ Device/Asic specific properties: trim error in LMH hardware. Required parameters: - compatible: Must be "qcom,lmh" - compatible: Must be either "qcom,lmh" or "qcom,lmh_v1". The driver based on the compatible string will decide the default profile. - interrupts: LMH Lite hardware interrupt details. Example: Loading @@ -29,6 +31,6 @@ qcom,lmh { Or for asics that don't have trim err: qcom,lmh { compatible = "qcom,lmh"; compatible = "qcom,lmh_v1"; interrupts = <0 332 4>; };
arch/arm/boot/dts/qcom/msm8996.dtsi +1 −1 Original line number Diff line number Diff line Loading @@ -3464,7 +3464,7 @@ }; lmh: qcom,lmh { compatible = "qcom,lmh"; compatible = "qcom,lmh_v1"; interrupts = <0 23 4>; }; Loading
drivers/thermal/lmh_interface.c +380 −73 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ #define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/device.h> #include <linux/interrupt.h> #include <linux/workqueue.h> #include <linux/kernel.h> Loading @@ -26,11 +26,18 @@ #include <linux/thermal.h> #include <linux/slab.h> #include "lmh_interface.h" #include <linux/string.h> #include <linux/uaccess.h> #define LMH_MON_NAME "lmh_monitor" #define LMH_ISR_POLL_DELAY "interrupt_poll_delay_msec" #define LMH_TRACE_ENABLE "hw_trace_enable" #define LMH_TRACE_INTERVAL "hw_trace_interval" #define LMH_DBGFS_DIR "debug" #define LMH_DBGFS_READ "data" #define LMH_DBGFS_CONFIG_READ "config" #define LMH_DBGFS_READ_TYPES "data_types" #define LMH_DBGFS_CONFIG_TYPES "config_types" #define LMH_TRACE_INTERVAL_XO_TICKS 250 struct lmh_mon_threshold { Loading @@ -50,6 +57,7 @@ struct lmh_device_data { struct dentry *avail_lvl_fs; struct list_head list_ptr; struct rw_semaphore lock; struct device dev; }; struct lmh_mon_sensor_data { Loading @@ -71,9 +79,24 @@ struct lmh_mon_driver_data { struct dentry *hw_log_delay; uint32_t hw_log_enable; uint64_t hw_log_interval; struct dentry *debug_dir; struct dentry *debug_read; struct dentry *debug_config; struct dentry *debug_read_type; struct dentry *debug_config_type; struct lmh_debug_ops *debug_ops; }; enum lmh_read_type { LMH_DEBUG_READ_TYPE, LMH_DEBUG_CONFIG_TYPE, LMH_PROFILES, }; static struct lmh_mon_driver_data *lmh_mon_data; static struct class lmh_class_info = { .name = "msm_limits", }; static DECLARE_RWSEM(lmh_mon_access_lock); static LIST_HEAD(lmh_sensor_list); static DECLARE_RWSEM(lmh_dev_access_lock); Loading Loading @@ -123,104 +146,117 @@ DEFINE_SIMPLE_ATTRIBUTE(_name##_fops, _name##_get, _name##_set, \ "%llu\n"); #define LMH_DEV_GET(_name) \ static int _name##_get(void *data, u64 *val) \ static ssize_t _name##_get(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ struct lmh_device_data *lmh_dev = (struct lmh_device_data *) data; \ *val = lmh_dev->_name; \ return 0; \ } struct lmh_device_data *lmh_dev = container_of(dev, \ struct lmh_device_data, dev); \ return snprintf(buf, LMH_NAME_MAX, "%d", lmh_dev->_name); \ } \ LMH_HW_LOG_FS(hw_log_enable); LMH_HW_LOG_FS(hw_log_interval); LMH_DEV_GET(max_level); LMH_DEV_GET(curr_level); static int curr_level_set(void *data, u64 val) static ssize_t curr_level_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct lmh_device_data *lmh_dev = (struct lmh_device_data *) data; struct lmh_device_data *lmh_dev = container_of(dev, struct lmh_device_data, dev); int val = 0, ret = 0; ret = kstrtouint(buf, 0, &val); if (ret < 0) { pr_err("Invalid input [%s]. err:%d\n", buf, ret); return ret; } return lmh_set_dev_level(lmh_dev->device_name, val); } static ssize_t avail_level_get(struct file *fp, char __user *user_buffer, size_t buffer_length, loff_t *position) static ssize_t avail_level_get(struct device *dev, struct device_attribute *attr, char *buf) { struct lmh_device_data *lmh_dev = list_first_entry(&lmh_device_list, struct lmh_device_data, list_ptr); int idx = 0, count = 0, ret = 0; char *profile_buf = NULL; char *buf_start = NULL; struct lmh_device_data *lmh_dev = container_of(dev, struct lmh_device_data, dev); uint32_t *type_list = NULL; int ret = 0, count = 0, lvl_buf_count = 0, idx = 0; char *lvl_buf = NULL; if (!lmh_dev || !lmh_dev->max_level) return ret; profile_buf = kzalloc(LMH_NAME_MAX * lmh_dev->max_level, GFP_KERNEL); if (!profile_buf) { if (!lmh_dev || !lmh_dev->levels || !lmh_dev->max_level) { pr_err("Invalid input\n"); return -EINVAL; } type_list = lmh_dev->levels; lvl_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!lvl_buf) { pr_err("Error allocating memory\n"); return -ENOMEM; } buf_start = profile_buf; for (idx = 0; idx < lmh_dev->max_level; idx++) { count = snprintf(buf_start, LMH_NAME_MAX, "%d ", lmh_dev->levels[idx]); if (count <= 0) { pr_err("Error getting freq value idx:%d. err:%d\n", idx, count); return count; for (idx = 0; (idx < lmh_dev->max_level) && (lvl_buf_count < PAGE_SIZE) ; idx++) { count = snprintf(lvl_buf + lvl_buf_count, PAGE_SIZE - lvl_buf_count, "%d ", type_list[idx]); if (count + lvl_buf_count >= PAGE_SIZE) { pr_err("overflow.\n"); break; } buf_start += count; } if (buf_start != profile_buf) { buf_start[-1] = '\n'; ret = simple_read_from_buffer(user_buffer, buffer_length, position, profile_buf, buf_start - profile_buf - 1); if (ret) pr_err("populating the buffer failed. err:%d\n", ret); } else { ret = -ENODEV; lvl_buf_count += count; } count = snprintf(lvl_buf + lvl_buf_count, PAGE_SIZE - lvl_buf_count, "\n"); if (count + lvl_buf_count < PAGE_SIZE) lvl_buf_count += count; kfree(profile_buf); return ret; count = snprintf(buf, lvl_buf_count + 1, lvl_buf); if (count > PAGE_SIZE) { pr_err("copy to user buf failed\n"); ret = -EFAULT; goto lvl_get_exit; } DEFINE_SIMPLE_ATTRIBUTE(curr_level_fops, curr_level_get, curr_level_set, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(max_level_fops, max_level_get, NULL, "%llu\n"); static const struct file_operations avail_level_fops = { .read = avail_level_get, }; lvl_get_exit: kfree(lvl_buf); return (ret) ? ret : count; } static int lmh_create_dev_debugfs(struct lmh_device_data *lmh_dev) static int lmh_create_dev_sysfs(struct lmh_device_data *lmh_dev) { int ret = 0; static DEVICE_ATTR(level, 0600, curr_level_get, curr_level_set); static DEVICE_ATTR(available_levels, 0400, avail_level_get, NULL); static DEVICE_ATTR(total_levels, 0400, max_level_get, NULL); LMH_CREATE_DEBUGFS_DIR(lmh_dev->dev_parent, lmh_dev->device_name, lmh_mon_data->debugfs_parent, ret); if (ret) goto debugfs_exit; LMH_CREATE_DEBUGFS_FILE(lmh_dev->curr_lvl_fs, "level", 0600, lmh_dev->dev_parent, (void *)lmh_dev, &curr_level_fops, ret); if (ret) goto debugfs_exit; LMH_CREATE_DEBUGFS_FILE(lmh_dev->max_lvl_fs, "total_levels", 0400, lmh_dev->dev_parent, (void *)lmh_dev, &max_level_fops, ret); if (ret) goto debugfs_exit; LMH_CREATE_DEBUGFS_FILE(lmh_dev->avail_lvl_fs, "available_levels", 0400, lmh_dev->dev_parent, (void *)lmh_dev, &avail_level_fops, ret); if (ret) goto debugfs_exit; lmh_dev->dev.class = &lmh_class_info; dev_set_name(&lmh_dev->dev, "%s", lmh_dev->device_name); ret = device_register(&lmh_dev->dev); if (ret) { pr_err("Error registering profile device. err:%d\n", ret); return ret; } ret = device_create_file(&lmh_dev->dev, &dev_attr_level); if (ret) { pr_err("Error creating profile level sysfs node. err:%d\n", ret); goto dev_sysfs_exit; } ret = device_create_file(&lmh_dev->dev, &dev_attr_total_levels); if (ret) { pr_err("Error creating total level sysfs node. err:%d\n", ret); goto dev_sysfs_exit; } ret = device_create_file(&lmh_dev->dev, &dev_attr_available_levels); if (ret) { pr_err("Error creating available level sysfs node. err:%d\n", ret); goto dev_sysfs_exit; } debugfs_exit: dev_sysfs_exit: if (ret) debugfs_remove_recursive(lmh_dev->dev_parent); device_unregister(&lmh_dev->dev); return ret; } Loading Loading @@ -688,7 +724,7 @@ static int lmh_device_init(struct lmh_device_data *lmh_device, lmh_device->device_ops = ops; strlcpy(lmh_device->device_name, device_name, LMH_NAME_MAX); list_add_tail(&lmh_device->list_ptr, &lmh_device_list); lmh_create_dev_debugfs(lmh_device); lmh_create_dev_sysfs(lmh_device); dev_init_exit: if (ret) Loading Loading @@ -877,6 +913,262 @@ void lmh_device_deregister(struct lmh_device_ops *ops) return; } static int lmh_parse_and_extract(const char __user *user_buf, size_t count, enum lmh_read_type type) { char *local_buf = NULL, *token = NULL, *curr_ptr = NULL, *token1 = NULL; char *next_line = NULL; int ret = 0, data_ct = 0, i = 0, size = 0; uint32_t *config_buf = NULL; /* Allocate two extra space to add ';' character and NULL terminate */ local_buf = kzalloc(count + 2, GFP_KERNEL); if (!local_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } if (copy_from_user(local_buf, user_buf, count)) { pr_err("user buf error\n"); ret = -EFAULT; goto dfs_cfg_write_exit; } size = count + (strnchr(local_buf, count, '\n') ? 1 : 2); local_buf[size - 2] = ';'; local_buf[size - 1] = '\0'; curr_ptr = next_line = local_buf; while ((token1 = strnchr(next_line, local_buf + size - next_line, ';')) != NULL) { data_ct = 0; *token1 = '\0'; curr_ptr = next_line; next_line = token1 + 1; for (token = (char *)curr_ptr; token && ((token = strnchr(token, next_line - token, ' ')) != NULL); token++) data_ct++; if (data_ct < 2) { pr_err("Invalid format string:[%s]\n", curr_ptr); ret = -EINVAL; goto dfs_cfg_write_exit; } config_buf = kzalloc((++data_ct) * sizeof(uint32_t), GFP_KERNEL); if (!config_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } pr_debug("Input:%s data_ct:%d\n", curr_ptr, data_ct); for (i = 0, token = (char *)curr_ptr; token && (i < data_ct); i++) { token = strnchr(token, next_line - token, ' '); if (token) *token = '\0'; ret = kstrtouint(curr_ptr, 0, &config_buf[i]); if (ret < 0) { pr_err("Data[%s] scan error. err:%d\n", curr_ptr, ret); kfree(config_buf); goto dfs_cfg_write_exit; } if (token) curr_ptr = ++token; } switch (type) { case LMH_DEBUG_READ_TYPE: ret = lmh_mon_data->debug_ops->debug_config_read( lmh_mon_data->debug_ops, config_buf, data_ct); break; case LMH_DEBUG_CONFIG_TYPE: ret = lmh_mon_data->debug_ops->debug_config_lmh( lmh_mon_data->debug_ops, config_buf, data_ct); break; default: ret = -EINVAL; break; } kfree(config_buf); if (ret) { pr_err("Config error. type:%d err:%d\n", type, ret); goto dfs_cfg_write_exit; } } dfs_cfg_write_exit: kfree(local_buf); return ret; } static ssize_t lmh_dbgfs_config_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_DEBUG_CONFIG_TYPE); return count; } static int lmh_dbgfs_data_read(struct seq_file *seq_fp, void *data) { uint32_t *read_buf = NULL; int ret = 0, i = 0; ret = lmh_mon_data->debug_ops->debug_read(lmh_mon_data->debug_ops, &read_buf); if (ret <= 0 || !read_buf) goto dfs_read_exit; do { seq_printf(seq_fp, "0x%x ", read_buf[i]); i++; if ((i % LMH_READ_LINE_LENGTH) == 0) seq_puts(seq_fp, "\n"); } while (i < (ret / sizeof(uint32_t))); dfs_read_exit: return (ret < 0) ? ret : 0; } static ssize_t lmh_dbgfs_data_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_DEBUG_READ_TYPE); return count; } static int lmh_dbgfs_data_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_data_read, inode->i_private); } static int lmh_get_types(struct seq_file *seq_fp, enum lmh_read_type type) { int ret = 0, idx = 0, size = 0; uint32_t *type_list = NULL; switch (type) { case LMH_DEBUG_READ_TYPE: ret = lmh_mon_data->debug_ops->debug_get_types( lmh_mon_data->debug_ops, true, &type_list); break; case LMH_DEBUG_CONFIG_TYPE: ret = lmh_mon_data->debug_ops->debug_get_types( lmh_mon_data->debug_ops, false, &type_list); break; default: return -EINVAL; } if (ret <= 0 || !type_list) { pr_err("No device information. err:%d\n", ret); return -ENODEV; } size = ret; for (idx = 0; idx < size; idx++) seq_printf(seq_fp, "0x%x ", type_list[idx]); seq_puts(seq_fp, "\n"); return 0; } static int lmh_dbgfs_read_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_DEBUG_READ_TYPE); } static int lmh_dbgfs_read_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_read_type, inode->i_private); } static int lmh_dbgfs_config_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_DEBUG_CONFIG_TYPE); } static int lmh_dbgfs_config_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_config_type, inode->i_private); } static const struct file_operations lmh_dbgfs_config_fops = { .write = lmh_dbgfs_config_write, }; static const struct file_operations lmh_dbgfs_read_fops = { .open = lmh_dbgfs_data_open, .read = seq_read, .write = lmh_dbgfs_data_write, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_read_type_fops = { .open = lmh_dbgfs_read_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_config_type_fops = { .open = lmh_dbgfs_config_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; int lmh_debug_register(struct lmh_debug_ops *ops) { int ret = 0; if (!ops || !ops->debug_read || !ops->debug_config_read || !ops->debug_get_types) { pr_err("Invalid input"); ret = -EINVAL; goto dbg_reg_exit; } lmh_mon_data->debug_ops = ops; LMH_CREATE_DEBUGFS_DIR(lmh_mon_data->debug_dir, LMH_DBGFS_DIR, lmh_mon_data->debugfs_parent, ret); if (ret) goto dbg_reg_exit; LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_read, LMH_DBGFS_READ, 0600, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_read_fops, ret); if (!lmh_mon_data->debug_read) { pr_err("Error creating" LMH_DBGFS_READ "entry.\n"); ret = -ENODEV; goto dbg_reg_exit; } LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_config, LMH_DBGFS_CONFIG_READ, 0200, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_config_fops, ret); if (!lmh_mon_data->debug_config) { pr_err("Error creating" LMH_DBGFS_CONFIG_READ "entry\n"); ret = -ENODEV; goto dbg_reg_exit; } LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_read_type, LMH_DBGFS_READ_TYPES, 0400, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_read_type_fops, ret); if (!lmh_mon_data->debug_read_type) { pr_err("Error creating" LMH_DBGFS_READ_TYPES "entry\n"); ret = -ENODEV; goto dbg_reg_exit; } LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_config_type, LMH_DBGFS_CONFIG_TYPES, 0400, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_config_type_fops, ret); if (!lmh_mon_data->debug_config_type) { pr_err("Error creating" LMH_DBGFS_CONFIG_TYPES "entry\n"); ret = -ENODEV; goto dbg_reg_exit; } dbg_reg_exit: if (ret) { /*Clean up all the dbg nodes*/ debugfs_remove_recursive(lmh_mon_data->debug_dir); lmh_mon_data->debug_ops = NULL; } return ret; } static int lmh_mon_init_driver(void) { int ret = 0; Loading Loading @@ -913,8 +1205,13 @@ static int __init lmh_mon_init_call(void) pr_err("Error initializing the debugfs. err:%d\n", ret); goto lmh_init_exit; } ret = class_register(&lmh_class_info); if (ret) goto lmh_init_exit; lmh_init_exit: if (ret) class_unregister(&lmh_class_info); return ret; } Loading @@ -940,10 +1237,20 @@ static void lmh_device_cleanup(void) up_write(&lmh_dev_access_lock); } static void lmh_debug_cleanup(void) { if (lmh_mon_data->debug_ops) { debugfs_remove_recursive(lmh_mon_data->debug_dir); lmh_mon_data->debug_ops = NULL; } } static void __exit lmh_mon_exit(void) { lmh_mon_cleanup(); lmh_device_cleanup(); lmh_debug_cleanup(); class_unregister(&lmh_class_info); } module_init(lmh_mon_init_call); Loading
drivers/thermal/lmh_interface.h +19 −1 Original line number Diff line number Diff line /* * Copyright (c) 2014, The Linux Foundation. All rights reserved. * Copyright (c) 2014-2015, 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 Loading @@ -16,6 +16,7 @@ #define LMH_NAME_MAX 20 #define LMH_POLLING_MSEC 30 #define LMH_READ_LINE_LENGTH 10 enum lmh_trip_type { LMH_LOW_TRIP, Loading Loading @@ -44,6 +45,13 @@ struct lmh_device_ops { int (*set_level) (struct lmh_device_ops *, int); }; struct lmh_debug_ops { int (*debug_read)(struct lmh_debug_ops *, uint32_t **); int (*debug_config_read)(struct lmh_debug_ops *, uint32_t *, int); int (*debug_config_lmh)(struct lmh_debug_ops *, uint32_t *, int); int (*debug_get_types)(struct lmh_debug_ops *, bool, uint32_t **); }; static int lmh_poll_interval = LMH_POLLING_MSEC; #ifdef CONFIG_LIMITS_MONITOR int lmh_get_all_dev_levels(char *, int *); Loading @@ -53,6 +61,8 @@ int lmh_sensor_register(char *, struct lmh_sensor_ops *); void lmh_sensor_deregister(struct lmh_sensor_ops *); int lmh_device_register(char *, struct lmh_device_ops *); void lmh_device_deregister(struct lmh_device_ops *); int lmh_debug_register(struct lmh_debug_ops *); void lmh_debug_deregister(struct lmh_debug_ops *ops); #else static inline int lmh_get_all_dev_levels(char *device_name, int *level) { Loading Loading @@ -90,6 +100,14 @@ static inline void lmh_device_deregister(struct lmh_device_ops *ops) { return; } static inline int lmh_debug_register(struct lmh_debug_ops *) { return -ENOSYS; } static inline void lmh_debug_deregister(struct lmh_debug_ops *ops) { } #endif #endif /*__LMH_INTERFACE_H*/
drivers/thermal/lmh_lite.c +372 −50 File changed.Preview size limit exceeded, changes collapsed. Show changes