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: Subbaraman Narayanamurthy <subbaram@codeaurora.org>
This commit is contained in:
Subbaraman Narayanamurthy 2020-01-16 18:25:02 -08:00 committed by Anjelique Melendez
parent f4ec9d39d0
commit 13fb635d44
3 changed files with 243 additions and 0 deletions

View File

@ -239,6 +239,16 @@ config QTI_PMIC_GLINK_CLIENT_DEBUG
system, which may lead to instability. Therefore, this option should
never be enabled on production devices.
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 QCOM_SECURE_BUFFER
tristate "Helper functions for secure buffers through TZ"
depends on QCOM_SCM

View File

@ -20,6 +20,7 @@ obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
obj-$(CONFIG_QCOM_SMSM) += smsm.o
obj-$(CONFIG_QTI_PMIC_GLINK) += pmic_glink.o
obj-$(CONFIG_QTI_BATTERY_GLINK_DEBUG) += qti_battery_debug.o
obj-$(CONFIG_QCOM_SECURE_BUFFER) += secure_buffer.o
obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o
obj-$(CONFIG_QCOM_SPM) += spm.o

View File

@ -0,0 +1,232 @@
// 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.msg_cb = 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");