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

Commit ed0468b8 authored by Subbaraman Narayanamurthy's avatar Subbaraman Narayanamurthy
Browse files

soc: qcom: add initial version of qti_battery_debug driver



QTI battery debug driver helps to get logs and debug information
by communicating with charger firmware (CHGFW) running on the
remote subsystem (e.g. DSP) over PMIC Glink. Currently, it
supports getting the device context dump of battery gauging
software which is part of CHGFW.

Change-Id: I7bb303cf61a97f3087f8c024fc6b37e68e5d1f1e
Signed-off-by: default avatarSubbaraman Narayanamurthy <subbaram@codeaurora.org>
parent 0326bf0e
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -546,6 +546,16 @@ config QTI_PMIC_GLINK
	  charging and gauging.
	  This enables clients to read and write battery charging parameters.

config QTI_BATTERY_GLINK_DEBUG
	tristate "Enable support for QTI battery glink debug driver"
	depends on QTI_PMIC_GLINK
	depends on DEBUG_FS
	help
	  Qualcomm Technologies, Inc. battery glink debug driver helps to
	  obtain debug information for battery charging and gauging over PMIC
	  Glink from charger and gauging firmware running on a remote subsystem
	  (e.g. DSP).

config MSM_CDSP_LOADER
	tristate "CDSP loader support"
	help
+1 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ obj-$(CONFIG_QSEE_IPC_IRQ) += qsee_ipc_irq.o
obj-$(CONFIG_QCOM_GLINK) += glink_probe.o
obj-$(CONFIG_MSM_GLINK_SSR) += msm_glink_ssr.o
obj-$(CONFIG_QTI_PMIC_GLINK) += pmic_glink.o
obj-$(CONFIG_QTI_BATTERY_GLINK_DEBUG) += qti_battery_debug.o
obj-$(CONFIG_QTI_DDR_STATS_LOG) += ddr_stats.o
obj-$(CONFIG_QTI_SYSTEM_PM) += system_pm.o
obj-$(CONFIG_MSM_REMOTEQDSS) += remoteqdss.o
+232 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2020, The Linux Foundation. All rights reserved.
 */

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

#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/soc/qcom/pmic_glink.h>

/* owner/type/opcode for battery debug */
#define MSG_OWNER_BD		32781
#define MSG_TYPE_REQ_RESP	1
#define BD_QBG_DUMP_REQ		0x36

/* Generic definitions */
#define MAX_BUF_LEN		(560 * sizeof(u32))
#define BD_WAIT_TIME_MS		1000

struct qbg_context_req_msg {
	struct pmic_glink_hdr hdr;
};

struct qbg_context_resp_msg {
	struct pmic_glink_hdr		hdr;
	u32				length;
	u8				buf[MAX_BUF_LEN];
};

struct battery_dbg_dev {
	struct device			*dev;
	struct pmic_glink_client	*client;
	struct mutex			lock;
	struct completion		ack;
	struct qbg_context_resp_msg	qbg_dump;
	struct dentry			*debugfs_dir;
	struct debugfs_blob_wrapper	qbg_blob;
};

static int battery_dbg_write(struct battery_dbg_dev *bd, void *data, size_t len)
{
	int rc;

	mutex_lock(&bd->lock);
	reinit_completion(&bd->ack);
	rc = pmic_glink_write(bd->client, data, len);
	if (!rc) {
		rc = wait_for_completion_timeout(&bd->ack,
					msecs_to_jiffies(BD_WAIT_TIME_MS));
		if (!rc) {
			pr_err("Error, timed out sending message\n");
			mutex_unlock(&bd->lock);
			return -ETIMEDOUT;
		}

		rc = 0;
	}
	mutex_unlock(&bd->lock);

	return rc;
}

static void handle_qbg_dump_message(struct battery_dbg_dev *bd,
				    struct qbg_context_resp_msg *resp_msg,
				    size_t len)
{
	u32 buf_len;

	if (len > sizeof(bd->qbg_dump)) {
		pr_err("Incorrect length received: %zu expected: %u\n", len,
			sizeof(bd->qbg_dump));
		return;
	}

	buf_len = resp_msg->length;
	if (buf_len > sizeof(bd->qbg_dump.buf)) {
		pr_err("Incorrect buffer length: %u\n", buf_len);
		return;
	}

	pr_debug("buf length: %u\n", buf_len);
	memcpy(bd->qbg_dump.buf, resp_msg->buf, buf_len);
	bd->qbg_blob.size = buf_len;

	complete(&bd->ack);
}

static int battery_dbg_callback(void *priv, void *data, size_t len)
{
	struct pmic_glink_hdr *hdr = data;
	struct battery_dbg_dev *bd = priv;

	pr_debug("owner: %u type: %u opcode: %#x len: %zu\n", hdr->owner,
		hdr->type, hdr->opcode, len);

	switch (hdr->opcode) {
	case BD_QBG_DUMP_REQ:
		handle_qbg_dump_message(bd, data, len);
		break;
	default:
		pr_err("Unknown opcode %u\n", hdr->opcode);
		break;
	}

	return 0;
}

static int get_qbg_context_write(void *data, u64 val)
{
	struct battery_dbg_dev *bd = data;
	struct qbg_context_req_msg req_msg = { { 0 } };

	req_msg.hdr.owner = MSG_OWNER_BD;
	req_msg.hdr.type = MSG_TYPE_REQ_RESP;
	req_msg.hdr.opcode = BD_QBG_DUMP_REQ;

	return battery_dbg_write(bd, &req_msg, sizeof(req_msg));
}

DEFINE_DEBUGFS_ATTRIBUTE(get_qbg_context_debugfs_ops, NULL,
			get_qbg_context_write, "%llu\n");

static int battery_dbg_add_debugfs(struct battery_dbg_dev *bd)
{
	struct dentry *bd_dir, *file;

	bd_dir = debugfs_create_dir("battery_debug", NULL);
	if (!bd_dir) {
		pr_err("Failed to create battery debugfs directory\n");
		return -ENOMEM;
	}

	file = debugfs_create_file_unsafe("get_qbg_context", 0200, bd_dir, bd,
				&get_qbg_context_debugfs_ops);
	if (!file) {
		pr_err("Failed to create get_qbg_context debugfs file\n");
		goto error;
	}

	bd->qbg_blob.data = bd->qbg_dump.buf;
	bd->qbg_blob.size = 0;
	file = debugfs_create_blob("qbg_context", 0444, bd_dir, &bd->qbg_blob);
	if (!file) {
		pr_err("Failed to create qbg_context debugfs file\n");
		goto error;
	}

	bd->debugfs_dir = bd_dir;

	return 0;
error:
	debugfs_remove_recursive(bd_dir);
	return -ENOMEM;
}

static int battery_dbg_probe(struct platform_device *pdev)
{
	struct battery_dbg_dev *bd;
	struct pmic_glink_client_data client_data = { };
	int rc;

	bd = devm_kzalloc(&pdev->dev, sizeof(*bd), GFP_KERNEL);
	if (!bd)
		return -ENOMEM;

	bd->dev = &pdev->dev;
	client_data.id = MSG_OWNER_BD;
	client_data.name = "battery_debug";
	client_data.callback = battery_dbg_callback;
	client_data.priv = bd;

	bd->client = pmic_glink_register_client(bd->dev, &client_data);
	if (IS_ERR(bd->client)) {
		rc = PTR_ERR(bd->client);
		if (rc != -EPROBE_DEFER)
			dev_err(bd->dev, "Error in registering with pmic_glink %d\n",
				rc);
		return rc;
	}

	mutex_init(&bd->lock);
	init_completion(&bd->ack);
	platform_set_drvdata(pdev, bd);

	rc = battery_dbg_add_debugfs(bd);
	if (rc < 0)
		goto out;

	return 0;
out:
	pmic_glink_unregister_client(bd->client);
	return rc;
}

static int battery_dbg_remove(struct platform_device *pdev)
{
	struct battery_dbg_dev *bd = platform_get_drvdata(pdev);
	int rc;

	debugfs_remove_recursive(bd->debugfs_dir);
	rc = pmic_glink_unregister_client(bd->client);
	if (rc < 0) {
		pr_err("Error unregistering from pmic_glink, rc=%d\n", rc);
		return rc;
	}

	return 0;
}

static const struct of_device_id battery_dbg_match_table[] = {
	{ .compatible = "qcom,battery-debug" },
	{},
};

static struct platform_driver battery_dbg_driver = {
	.driver	= {
		.name = "qti_battery_debug",
		.of_match_table = battery_dbg_match_table,
	},
	.probe	= battery_dbg_probe,
	.remove	= battery_dbg_remove,
};
module_platform_driver(battery_dbg_driver);

MODULE_DESCRIPTION("QTI Glink battery debug driver");
MODULE_LICENSE("GPL v2");