From 13fb635d446e2d1f0e28ecce048fb55e2048cfc7 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Thu, 16 Jan 2020 18:25:02 -0800 Subject: [PATCH] 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 --- drivers/soc/qcom/Kconfig | 10 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/qti_battery_debug.c | 232 +++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 drivers/soc/qcom/qti_battery_debug.c diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 2121ebfffeb5..7ba223837447 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -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 diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index f00df8b590a5..3a840a883413 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -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 diff --git a/drivers/soc/qcom/qti_battery_debug.c b/drivers/soc/qcom/qti_battery_debug.c new file mode 100644 index 000000000000..a54930861d41 --- /dev/null +++ b/drivers/soc/qcom/qti_battery_debug.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +/* 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");