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

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

Merge "drivers: regulator_aop: Add snapshot for AOP regulator cdev"

parents a4862e02 c4479a55
Loading
Loading
Loading
Loading
+64 −0
Original line number Diff line number Diff line
Limits Management Hardware - DCVS

The LMH-DCVS block is a hardware IP for every CPU cluster, to handle quick
changes in thermal limits. The hardware responds to thermal variation amongst
the CPUs in the cluster by requesting limits on the clock frequency and
voltage on the OSM hardware.

The LMH DCVS driver exports a virtual sensor that can be used to set the
thermal limits on the hardware. LMH DCVS driver can be a platform CPU Cooling
device, which registers with the CPU cooling device interface. All CPU device
nodes should reference the corresponding LMH DCVS hardware in device tree.
CPUs referencing the same LMH DCVS node will be associated with the
corresponding cooling device as related CPUs.

Properties:

- compatible:
	Usage: required
	Value type: <string>
	Definition: shall be "qcom,msm-hw-limits"
- interrupts:
	Usage: required
	Value type: <interrupt_type interrupt_number interrupt_trigger_type>
	Definition: Should specify interrupt information about the debug
			interrupt generated by the LMH DCVSh hardware. LMH
			DCVSh hardware will generate this interrupt whenever
			it makes a new cpu DCVS decision.
- qcom,affinity:
	Usage: Required
	Value type: <u32>
	Definition: Should specify the cluster affinity this hardware
			corresponds to.

- isens_vref-supply:
	Usage: optional
	Value type: <phandle>
	Definition: Should specify the phandle of the vref regulator used by
			the isens hardware. This active only regulator will be
			enabled by LMH DCVSh.

- isens-vref-settings:
	Usage: optional
	Value type: <u32 array>
	Definition: Should specify the min voltage(uV), max voltage(uV) and
			max load(uA) for the isens vref regulator. This
			property is valid only if there is valid entry for
			isens_vref-supply.

Example:

	lmh_dcvs0: qcom,limits-dcvs@0 {
		compatible = "qcom,msm-hw-limits";
		interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH>;
		qcom,affinity = <0>;
		isens_vref-supply = <&pm8998_l1_ao>;
		isens-vref-settings = <880000 880000 36000>;
	};

	CPU0: cpu@0 {
		device_type = "cpu";
		compatible = "arm,armv8";
		reg = <0x0 0x0>;
		qcom,lmh-dcvs = <&lmh_dcvs0>;;
	};
+44 −0
Original line number Diff line number Diff line
RPMh regulator cooling device.

The RPMh regulator cooling device, will be used to place a voltage floor
restriction on a rail. This cooling device will use a QMP AOP mail box to send
the message to apply and clear voltage floor restriction.

The cooling device node should be a child of the regulator devicetree node,
which it is trying to place the floor restriction.

Properties:

- compatible:
	Usage: required
	Value type: <string>
	Definition: shall be "qcom,rpmh-reg-cdev"

- qcom,reg-resource-name:
	Usage: required
	Value type: <string>
	Definition: The regulator resource name to be used for communicating
			with RPMh. This value should be any of the below
			resource name,
			cx -> For CX rail,
			mx -> For MX rail,
			ebi -> For EBI rail.

- mboxes:
	Usage: required
	Value type: <phandle>
	Definition: A phandle to the QMP AOP mail box, that needs to be used
			for sending the floor restriction message.

- #cooling-cells: Must be 2. Please refer to
			<devicetree/bindings/thermal/thermal.txt> for more
			details.

Example:

	vdd_cx: rpmh-cx-regulator-cdev {
		compatible = "qcom,rpmh-reg-cdev";
		mboxes = <&qmp_aop 0>;
		qcom,reg-resource-name = "cx";
		#cooling-cells = <2>;
	};
+20 −0
Original line number Diff line number Diff line
@@ -9,3 +9,23 @@ config QCOM_TSENS
	  thermal zone device via the mode file results in disabling the sensor.
	  Also able to set threshold temperature for both hot and cold and update
	  when a threshold is reached.

config QTI_THERMAL_LIMITS_DCVS
	bool "QTI LMH DCVS Driver"
	depends on THERMAL_OF
	depends on CPU_THERMAL
	help
	  This enables the driver for Limits Management Hardware - DCVS block
	  for the application processors. The h/w block that is available for
	  each cluster can be used to perform quick thermal mitigations by
	  tracking temperatures of the CPUs and taking thermal action in the
	  hardware without s/w intervention.

config QTI_AOP_REG_COOLING_DEVICE
	bool "QTI AOP Regulator cooling device"
	depends on THERMAL_OF && MSM_QMP
	help
	  This enables the AOP based Regulator cooling device. This cooling
	  device will be used by QTI chipset to place a floor voltage
	  restriction at low temperatures. The cooling device will message
	  the AOP using mail box to establish the floor voltage.
+2 −0
Original line number Diff line number Diff line
obj-$(CONFIG_QCOM_TSENS)	+= qcom_tsens.o
qcom_tsens-y			+= tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-8996.o
obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o lmh_dbg.o
obj-$(CONFIG_QTI_AOP_REG_COOLING_DEVICE) += regulator_aop_cdev.o
+568 −0
Original line number Diff line number Diff line
/* Copyright (c) 2014-2018, 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.
 */

#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__

#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <asm/cacheflush.h>
#include <soc/qcom/scm.h>
#include <linux/dma-mapping.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>

#include "lmh_dbg.h"

#define LMH_MON_NAME			"lmh_monitor"
#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_SCM_PAYLOAD_SIZE		10
#define LMH_READ_LINE_LENGTH            10
#define LMH_DEBUG_READ_TYPE		0x0
#define LMH_DEBUG_CONFIG_TYPE		0x1
#define LMH_DEBUG_SET			0x08
#define LMH_DEBUG_READ_BUF_SIZE		0x09
#define LMH_DEBUG_READ			0x0A
#define LMH_DEBUG_GET_TYPE		0x0B

struct lmh_driver_data {
	struct device			*dev;
	uint32_t			*read_type;
	uint32_t			*config_type;
	uint32_t			read_type_count;
	uint32_t			config_type_count;
	struct dentry			*debugfs_parent;
	struct dentry			*debug_read;
	struct dentry			*debug_config;
	struct dentry			*debug_read_type;
	struct dentry			*debug_config_type;
};

enum lmh_read_type {
	LMH_READ_TYPE = 0,
	LMH_CONFIG_TYPE,
};

static struct lmh_driver_data		*lmh_data;

static int lmh_debug_read(uint32_t **buf)
{
	int ret = 0, size = 0, tz_ret = 0;
	static uint32_t curr_size;
	struct scm_desc desc_arg;
	static uint32_t *payload;

	desc_arg.arginfo = SCM_ARGS(0);
	ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH,
			LMH_DEBUG_READ_BUF_SIZE), &desc_arg);
	size = desc_arg.ret[0];
	if (ret) {
		pr_err("Error in SCM get debug buffer size call. err:%d\n",
				ret);
		goto get_dbg_exit;
	}
	if (!size) {
		pr_err("No Debug data to read\n");
		ret = -ENODEV;
		goto get_dbg_exit;
	}
	size = SCM_BUFFER_SIZE(uint32_t) * size * LMH_READ_LINE_LENGTH;
	if (curr_size != size) {
		if (payload)
			devm_kfree(lmh_data->dev, payload);
		payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(size),
				       GFP_KERNEL);
		if (!payload) {
			ret = -ENOMEM;
			goto get_dbg_exit;
		}
		curr_size = size;
	}

	/* &payload may be a physical address > 4 GB */
	desc_arg.args[0] = SCM_BUFFER_PHYS(payload);
	desc_arg.args[1] = curr_size;
	desc_arg.arginfo = SCM_ARGS(2, SCM_RW, SCM_VAL);
	dmac_flush_range(payload, (void *)payload + curr_size);
	ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_READ),
			&desc_arg);
	dmac_inv_range(payload, (void *)payload + curr_size);
	tz_ret = desc_arg.ret[0];
	/* Have memory barrier before we access the TZ data */
	mb();
	if (ret) {
		pr_err("Error in get debug read. err:%d\n", ret);
		goto get_dbg_exit;
	}
	if (tz_ret) {
		pr_err("TZ API returned error. err:%d\n", tz_ret);
		ret = tz_ret;
		goto get_dbg_exit;
	}

get_dbg_exit:
	if (ret && payload) {
		devm_kfree(lmh_data->dev, payload);
		payload = NULL;
		curr_size = 0;
	}
	*buf = payload;

	return (ret < 0) ? ret : curr_size;
}

static int lmh_debug_config_write(uint32_t cmd_id, uint32_t *buf, int size)
{
	int ret = 0, size_bytes = 0;
	struct scm_desc desc_arg;
	uint32_t *payload = NULL;

	size_bytes = (size - 3) * sizeof(uint32_t);
	payload = devm_kzalloc(lmh_data->dev, PAGE_ALIGN(size_bytes),
			       GFP_KERNEL);
	if (!payload) {
		ret = -ENOMEM;
		goto set_cfg_exit;
	}
	memcpy(payload, &buf[3], size_bytes);

	/* &payload may be a physical address > 4 GB */
	desc_arg.args[0] = SCM_BUFFER_PHYS(payload);
	desc_arg.args[1] = size_bytes;
	desc_arg.args[2] = buf[0];
	desc_arg.args[3] = buf[1];
	desc_arg.args[4] = buf[2];
	desc_arg.arginfo = SCM_ARGS(5, SCM_RO, SCM_VAL, SCM_VAL, SCM_VAL,
					SCM_VAL);
	dmac_flush_range(payload, (void *)payload + size_bytes);
	ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, cmd_id), &desc_arg);
	/* Have memory barrier before we access the TZ data */
	mb();
	if (ret) {
		pr_err("Error in config debug read. err:%d\n", ret);
		goto set_cfg_exit;
	}

set_cfg_exit:
	return ret;
}

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_READ_TYPE:
		case LMH_CONFIG_TYPE:
			ret = lmh_debug_config_write(LMH_DEBUG_SET,
					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_CONFIG_TYPE);
	return count;
}

static int lmh_dbgfs_data_read(struct seq_file *seq_fp, void *data)
{
	static uint32_t *read_buf;
	static int read_buf_size;
	int idx = 0, ret = 0;

	if (!read_buf_size) {
		ret = lmh_debug_read(&read_buf);
		if (ret <= 0)
			goto dfs_read_exit;
		if (!read_buf || ret < sizeof(uint32_t)) {
			ret = -EINVAL;
			goto dfs_read_exit;
		}
		read_buf_size = ret;
		ret = 0;
	}

	do {
		seq_printf(seq_fp, "0x%x ", read_buf[idx]);
		if (seq_has_overflowed(seq_fp)) {
			pr_err("Seq overflow. idx:%d\n", idx);
			goto dfs_read_exit;
		}
		idx++;
		if ((idx % LMH_READ_LINE_LENGTH) == 0) {
			seq_puts(seq_fp, "\n");
			if (seq_has_overflowed(seq_fp)) {
				pr_err("Seq overflow. idx:%d\n", idx);
				goto dfs_read_exit;
			}
		}
	} while (idx < (read_buf_size / sizeof(uint32_t)));
	read_buf_size = 0;
	read_buf = NULL;

dfs_read_exit:
	return ret;
}

static int lmh_get_recurssive_data(struct scm_desc *desc_arg, uint32_t cmd_idx,
		uint32_t *payload, uint32_t *size, uint32_t **dest_buf)
{
	int idx = 0, ret = 0;
	uint32_t next = 0;

	do {
		desc_arg->args[cmd_idx] = next;
		dmac_flush_range(payload, (void *)payload +
				sizeof(*payload) * LMH_SCM_PAYLOAD_SIZE);
		ret = scm_call2(SCM_SIP_FNID(SCM_SVC_LMH, LMH_DEBUG_GET_TYPE),
				desc_arg);
		dmac_inv_range(payload, (void *)payload +
				sizeof(*payload) * LMH_SCM_PAYLOAD_SIZE);
		*size = desc_arg->ret[0];
		/* Have barrier before reading from TZ data */
		mb();
		if (ret) {
			pr_err("Error in SCM get type. cmd:%x err:%d\n",
				LMH_DEBUG_GET_TYPE, ret);
			return ret;
		}
		if (!*size) {
			pr_err("No LMH device supported\n");
			return -ENODEV;
		}
		if (!*dest_buf) {
			*dest_buf = devm_kcalloc(lmh_data->dev, *size,
				sizeof(**dest_buf), GFP_KERNEL);
			if (!*dest_buf)
				return -ENOMEM;
		}

		for (idx = next;
			idx < min((next + LMH_SCM_PAYLOAD_SIZE), *size);
			idx++)
			(*dest_buf)[idx] = payload[idx - next];
		next += LMH_SCM_PAYLOAD_SIZE;
	} while (next < *size);

	return ret;
}

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_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_debug_get_types(bool is_read, uint32_t **buf)
{
	int ret = 0;
	uint32_t size = 0;
	struct scm_desc desc_arg;
	uint32_t *payload = NULL, *dest_buf = NULL;

	if (is_read && lmh_data->read_type) {
		*buf = lmh_data->read_type;
		return lmh_data->read_type_count;
	} else if (!is_read && lmh_data->config_type) {
		*buf = lmh_data->config_type;
		return lmh_data->config_type_count;
	}
	payload = devm_kzalloc(lmh_data->dev,
				PAGE_ALIGN(LMH_SCM_PAYLOAD_SIZE *
				sizeof(*payload)), GFP_KERNEL);
	if (!payload)
		return -ENOMEM;
	/* &payload may be a physical address > 4 GB */
	desc_arg.args[0] = SCM_BUFFER_PHYS(payload);
	desc_arg.args[1] =
		SCM_BUFFER_SIZE(uint32_t) * LMH_SCM_PAYLOAD_SIZE;
	desc_arg.args[2] = (is_read) ?
			LMH_DEBUG_READ_TYPE : LMH_DEBUG_CONFIG_TYPE;
	desc_arg.arginfo = SCM_ARGS(4, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL);
	ret = lmh_get_recurssive_data(&desc_arg, 3, payload, &size, &dest_buf);
	if (ret)
		goto get_type_exit;
	pr_debug("Total %s types:%d\n", (is_read) ? "read" : "config", size);
	if (is_read) {
		lmh_data->read_type = *buf = dest_buf;
		lmh_data->read_type_count = size;
	} else {
		lmh_data->config_type = *buf = dest_buf;
		lmh_data->config_type_count = size;
	}

get_type_exit:
	if (ret) {
		if (lmh_data->read_type_count) {
			devm_kfree(lmh_data->dev, lmh_data->read_type);
			lmh_data->read_type_count = 0;
		}
		if (lmh_data->config_type_count) {
			devm_kfree(lmh_data->dev, lmh_data->config_type);
			lmh_data->config_type_count = 0;
		}
	}
	if (payload)
		devm_kfree(lmh_data->dev, payload);

	return (ret) ? ret : size;
}

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_READ_TYPE:
		ret = lmh_debug_get_types(true, &type_list);
		break;
	case LMH_CONFIG_TYPE:
		ret = lmh_debug_get_types(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_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_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,
};

static int lmh_check_tz_debug_cmds(void)
{
	if (!scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_SET)
		|| !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_READ_BUF_SIZE)
		|| !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_READ)
		|| !scm_is_call_available(SCM_SVC_LMH, LMH_DEBUG_GET_TYPE)) {
		pr_debug("LMH debug scm not available\n");
		return -ENODEV;
	}

	return 0;
}

static int lmh_debug_init(void)
{
	int ret = 0;

	if (lmh_check_tz_debug_cmds()) {
		pr_debug("Debug commands not available\n");
		return -ENODEV;
	}

	lmh_data->debugfs_parent = debugfs_create_dir(LMH_MON_NAME, NULL);
	if (IS_ERR(lmh_data->debugfs_parent)) {
		ret = PTR_ERR(lmh_data->debugfs_parent);
		pr_debug("Error creating debugfs dir:%s. err:%d\n",
					LMH_MON_NAME, ret);
		return ret;
	}
	lmh_data->debug_read = debugfs_create_file(LMH_DBGFS_READ, 0600,
					lmh_data->debugfs_parent, NULL,
					&lmh_dbgfs_read_fops);
	if (IS_ERR(lmh_data->debug_read)) {
		pr_err("Error creating" LMH_DBGFS_READ "entry\n");
		ret = PTR_ERR(lmh_data->debug_read);
		goto dbg_reg_exit;
	}
	lmh_data->debug_config = debugfs_create_file(LMH_DBGFS_CONFIG_READ,
					0200, lmh_data->debugfs_parent, NULL,
					&lmh_dbgfs_config_fops);
	if (IS_ERR(lmh_data->debug_config)) {
		pr_err("Error creating" LMH_DBGFS_CONFIG_READ "entry\n");
		ret = PTR_ERR(lmh_data->debug_config);
		goto dbg_reg_exit;
	}
	lmh_data->debug_read_type = debugfs_create_file(LMH_DBGFS_READ_TYPES,
					0400, lmh_data->debugfs_parent, NULL,
					&lmh_dbgfs_read_type_fops);
	if (IS_ERR(lmh_data->debug_read_type)) {
		pr_err("Error creating" LMH_DBGFS_READ_TYPES "entry\n");
		ret = PTR_ERR(lmh_data->debug_read_type);
		goto dbg_reg_exit;
	}
	lmh_data->debug_read_type = debugfs_create_file(
					LMH_DBGFS_CONFIG_TYPES,
					0400, lmh_data->debugfs_parent, NULL,
					&lmh_dbgfs_config_type_fops);
	if (IS_ERR(lmh_data->debug_config_type)) {
		pr_err("Error creating" LMH_DBGFS_CONFIG_TYPES "entry\n");
		ret = PTR_ERR(lmh_data->debug_config_type);
		goto dbg_reg_exit;
	}

dbg_reg_exit:
	if (ret)
		/*Clean up all the dbg nodes*/
		debugfs_remove_recursive(lmh_data->debugfs_parent);

	return ret;
}

int lmh_debug_register(struct platform_device *pdev)
{
	int ret = 0;

	if (lmh_data) {
		pr_debug("Reinitializing lmh hardware driver\n");
		return -EEXIST;
	}
	lmh_data = devm_kzalloc(&pdev->dev, sizeof(*lmh_data), GFP_KERNEL);
	if (!lmh_data)
		return -ENOMEM;
	lmh_data->dev = &pdev->dev;

	ret = lmh_debug_init();
	if (ret) {
		pr_debug("LMH debug init failed. err:%d\n", ret);
		goto probe_exit;
	}

	return ret;

probe_exit:
	lmh_data = NULL;
	return ret;
}
EXPORT_SYMBOL(lmh_debug_register);
Loading