Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 20d4a655 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "msm: lmh_lite: Add ftrace events for LMH debug interface"

parents 01839c0b de3da028
Loading
Loading
Loading
Loading
+5 −3
Original line number Diff line number Diff line
@@ -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:
@@ -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>;
};
+1 −1
Original line number Diff line number Diff line
@@ -3464,7 +3464,7 @@
	};

	lmh: qcom,lmh {
		compatible = "qcom,lmh";
		compatible = "qcom,lmh_v1";
		interrupts = <0 23 4>;
	};

+380 −73
Original line number Diff line number Diff line
@@ -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>
@@ -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 {
@@ -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 {
@@ -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);
@@ -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;
}

@@ -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)
@@ -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;
@@ -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;
}

@@ -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);
+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
@@ -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,
@@ -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 *);
@@ -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)
{
@@ -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*/
+372 −50

File changed.

Preview size limit exceeded, changes collapsed.

Loading