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");