From 652a68d2f2c42ea060c71c452613dadd87734560 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Tue, 13 Apr 2021 20:25:46 -0700 Subject: [PATCH] soc: qcom: add QTI charger-ulog-glink driver Qualcomm Technologies, Inc. charger-ulog-glink driver helps to get ulogs from charging and gauging stack by communicating with charger firmware running on the remote subsystem (e.g. DSP) over PMIC Glink. To get charger ulogs runtime, cd /sys/kernel/debug/charger_ulog echo 3 > level echo 0x20000 > category echo 1 > enable cat /sys/kernel/debug/ipc_logging/charger_ulog/log To get charger init ulogs once, cd /sys/kernel/debug/charger_ulog echo 5 > level echo 0x100000000 > category echo 1 > enable cat /sys/kernel/debug/ipc_logging/charger_init_ulog/log Minimum allowed value for time_ms is 500 ms and is set to 1000 ms by default. Change-Id: I64a4cdd9d50f7f683b8da54ab7fd6b4b7c7943b3 Signed-off-by: Subbaraman Narayanamurthy --- drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/charger-ulog-glink.c | 469 ++++++++++++++++++++++++++ 3 files changed, 479 insertions(+) create mode 100644 drivers/soc/qcom/charger-ulog-glink.c diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 66fb29dfefaf..5b68941952c3 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -248,6 +248,15 @@ config QTI_BATTERY_GLINK_DEBUG Glink from charger and gauging firmware running on a remote subsystem (e.g. DSP). +config QTI_CHARGER_ULOG_GLINK + tristate "Enable support for QTI charger ulog glink driver" + depends on QTI_PMIC_GLINK && DEBUG_FS + help + Qualcomm Technologies, Inc. charger ulog glink driver helps to + obtain ulogs from battery charging and gauging stack over PMIC + Glink from the charger firmware running on a remote subsystem + (e.g. DSP). + config QTI_ALTMODE_GLINK tristate "Type-C alternate mode over GLINK" depends on QTI_PMIC_GLINK diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 00156a188c9f..cfc26ea18a37 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -21,6 +21,7 @@ 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_QTI_CHARGER_ULOG_GLINK) += charger-ulog-glink.o obj-$(CONFIG_QTI_ALTMODE_GLINK) += altmode-glink.o obj-$(CONFIG_QCOM_SECURE_BUFFER) += secure_buffer.o obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o diff --git a/drivers/soc/qcom/charger-ulog-glink.c b/drivers/soc/qcom/charger-ulog-glink.c new file mode 100644 index 000000000000..756ddded3d8d --- /dev/null +++ b/drivers/soc/qcom/charger-ulog-glink.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "CHARGER_ULOG: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MSG_OWNER_CHG_ULOG 32778 +#define MSG_TYPE_REQ_RESP 1 +#define GET_CHG_ULOG_REQ 0x18 +#define SET_CHG_ULOG_PROP_REQ 0x19 +#define GET_CHG_INIT_ULOG_REQ 0x23 + +#define LOG_CATEGORY_INIT (1ULL << 32) +#define LOG_MIN_TIME_MS 500 +#define LOG_DEFAULT_TIME_MS 1000 + +#define MAX_ULOG_SIZE 8192 +#define NUM_LOG_PAGES 10 +#define NUM_INIT_LOG_PAGES 4 + +struct set_ulog_prop_req_msg { + struct pmic_glink_hdr hdr; + u64 log_category; + u32 log_level; +}; + +struct get_ulog_req_msg { + struct pmic_glink_hdr hdr; + u32 log_size; +}; + +struct get_ulog_resp_msg { + struct pmic_glink_hdr hdr; + u8 buf[MAX_ULOG_SIZE]; +}; + +struct chg_ulog_glink_dev { + struct device *dev; + struct pmic_glink_client *client; + struct dentry *debugfs_dir; + void *ipc_log; + void *ipc_init_log; + struct mutex lock; + struct completion ack; + struct delayed_work ulog_work; + u8 ulog_buf[MAX_ULOG_SIZE]; + u64 log_category; + u32 log_level; + u32 log_time_ms; + bool log_enable; + bool init_log_enable; +}; + +#define WAIT_TIME_MS 1000 +static int chg_ulog_write(struct chg_ulog_glink_dev *cd, void *data, + size_t len) +{ + int rc; + + mutex_lock(&cd->lock); + reinit_completion(&cd->ack); + rc = pmic_glink_write(cd->client, data, len); + if (!rc) { + rc = wait_for_completion_timeout(&cd->ack, + msecs_to_jiffies(WAIT_TIME_MS)); + if (!rc) { + pr_err("Error, timed out sending message\n"); + mutex_unlock(&cd->lock); + return -ETIMEDOUT; + } + + rc = 0; + } + mutex_unlock(&cd->lock); + + return rc; +} + +static int chg_ulog_request(struct chg_ulog_glink_dev *cd, bool init) +{ + struct get_ulog_req_msg req_msg = { { 0 } }; + + req_msg.hdr.owner = MSG_OWNER_CHG_ULOG; + req_msg.hdr.type = MSG_TYPE_REQ_RESP; + req_msg.hdr.opcode = init ? GET_CHG_INIT_ULOG_REQ : GET_CHG_ULOG_REQ; + req_msg.log_size = MAX_ULOG_SIZE; + + return chg_ulog_write(cd, &req_msg, sizeof(req_msg)); +} + +static int chg_ulog_set_log_type(struct chg_ulog_glink_dev *cd, u64 category, + u32 level) +{ + struct set_ulog_prop_req_msg req_msg = { { 0 } }; + int rc; + + req_msg.hdr.owner = MSG_OWNER_CHG_ULOG; + req_msg.hdr.type = MSG_TYPE_REQ_RESP; + req_msg.hdr.opcode = SET_CHG_ULOG_PROP_REQ; + req_msg.log_category = category; + req_msg.log_level = level; + + rc = chg_ulog_write(cd, &req_msg, sizeof(req_msg)); + if (!rc) + pr_debug("Set log category %llu log level %u\n", category, + level); + + return rc; +} + +static void chg_ulog_work(struct work_struct *work) +{ + struct chg_ulog_glink_dev *cd = container_of(work, + struct chg_ulog_glink_dev, + ulog_work.work); + int rc; + + rc = chg_ulog_request(cd, cd->init_log_enable); + if (rc) + pr_err("Error requesting ulog, rc=%d\n", rc); + else if (cd->log_enable || cd->init_log_enable) + schedule_delayed_work(&cd->ulog_work, + msecs_to_jiffies(cd->log_time_ms)); +} + +static void ulog_store(struct chg_ulog_glink_dev *cd, void *ipc_ctxt, + size_t len) +{ + char *buf = cd->ulog_buf, *token = NULL; + + if (buf[0] == '\0') { + pr_debug("buffer is NULL\n"); + if (cd->init_log_enable) + cd->init_log_enable = false; + return; + } + + buf[len - 1] = '\0'; + if (len >= MAX_MSG_SIZE) { + do { + token = strsep((char **)&buf, "\n"); + if (token) + ipc_log_string(ipc_ctxt, "%s", token); + } while (token); + } else { + ipc_log_string(ipc_ctxt, "%s", buf); + } +} + +static void handle_ulog_message(struct chg_ulog_glink_dev *cd, + struct get_ulog_resp_msg *resp_msg, + size_t len) +{ + void *ipc_ctxt; + + if (len != sizeof(*resp_msg)) { + pr_err("Expected data length: %zu, received: %zu\n", + sizeof(*resp_msg), len); + return; + } + + memcpy(cd->ulog_buf, resp_msg->buf, sizeof(cd->ulog_buf)); + ipc_ctxt = (resp_msg->hdr.opcode == GET_CHG_INIT_ULOG_REQ) + ? cd->ipc_init_log : cd->ipc_log; + + ulog_store(cd, ipc_ctxt, len - sizeof(resp_msg->hdr)); +} + +static int chg_ulog_callback(void *priv, void *data, size_t len) +{ + struct pmic_glink_hdr *hdr = data; + struct chg_ulog_glink_dev *cd = priv; + + pr_debug("owner: %u type: %u opcode: %#x len: %zu\n", hdr->owner, + hdr->type, hdr->opcode, len); + + switch (hdr->opcode) { + case SET_CHG_ULOG_PROP_REQ: + complete(&cd->ack); + break; + case GET_CHG_ULOG_REQ: + case GET_CHG_INIT_ULOG_REQ: + handle_ulog_message(cd, data, len); + complete(&cd->ack); + break; + default: + pr_err("Unknown opcode %u\n", hdr->opcode); + break; + } + + return 0; +} + +static int ulog_cat_get(void *data, u64 *val) +{ + struct chg_ulog_glink_dev *cd = data; + + *val = cd->log_category; + + return 0; +} + +static int ulog_cat_set(void *data, u64 val) +{ + int rc; + struct chg_ulog_glink_dev *cd = data; + + if (cd->log_enable) { + pr_err("Disable ulog before changing log category\n"); + return -EINVAL; + } + + if (val == cd->log_category) + return 0; + + rc = chg_ulog_set_log_type(cd, val, cd->log_level); + if (rc) + pr_err("Couldn't set log_category rc=%d\n", rc); + else + cd->log_category = val; + + return rc; +} +DEFINE_DEBUGFS_ATTRIBUTE(ulog_cat_fops, ulog_cat_get, ulog_cat_set, + "%llu\n"); + +static int ulog_level_get(void *data, u64 *val) +{ + struct chg_ulog_glink_dev *cd = data; + + *val = cd->log_level; + + return 0; +} + +static int ulog_level_set(void *data, u64 val) +{ + int rc; + struct chg_ulog_glink_dev *cd = data; + u32 level = val; + + if (cd->log_enable) { + pr_err("Disable ulog before changing log level\n"); + return -EINVAL; + } + + if (level == cd->log_level) + return 0; + + rc = chg_ulog_set_log_type(cd, cd->log_category, level); + if (rc) + pr_err("Couldn't set log_level rc=%d\n", rc); + else + cd->log_level = level; + + return rc; +} +DEFINE_DEBUGFS_ATTRIBUTE(ulog_level_fops, ulog_level_get, ulog_level_set, + "%llu\n"); + +static int ulog_en_get(void *data, u64 *val) +{ + struct chg_ulog_glink_dev *cd = data; + + *val = cd->log_enable; + + return 0; +} + +static int ulog_en_set(void *data, u64 val) +{ + struct chg_ulog_glink_dev *cd = data; + bool en = val; + + if (en == cd->log_enable) + return 0; + + if (cd->log_category == LOG_CATEGORY_INIT) + cd->init_log_enable = en; + else + cd->log_enable = en; + + if (en) + schedule_delayed_work(&cd->ulog_work, + msecs_to_jiffies(cd->log_time_ms)); + else + cancel_delayed_work_sync(&cd->ulog_work); + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ulog_en_fops, ulog_en_get, ulog_en_set, "%llu\n"); + +static int ulog_time_get(void *data, u64 *val) +{ + struct chg_ulog_glink_dev *cd = data; + + *val = cd->log_time_ms; + + return 0; +} + +static int ulog_time_set(void *data, u64 val) +{ + struct chg_ulog_glink_dev *cd = data; + + if (val == cd->log_time_ms) + return 0; + + if (val < LOG_MIN_TIME_MS) + return -EINVAL; + + cd->log_time_ms = val; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(ulog_time_fops, ulog_time_get, ulog_time_set, + "%llu\n"); + +static int chg_ulog_add_debugfs(struct chg_ulog_glink_dev *cd) +{ + struct dentry *dir, *file; + int rc; + + dir = debugfs_create_dir("charger_ulog", NULL); + if (IS_ERR(dir)) { + rc = PTR_ERR(dir); + pr_err("Failed to create charger_ulog debugfs directory: %d\n", + rc); + return rc; + } + + file = debugfs_create_file_unsafe("category", 0600, dir, cd, + &ulog_cat_fops); + if (IS_ERR(file)) { + rc = PTR_ERR(file); + pr_err("Failed to create category %d\n", rc); + goto out; + } + + file = debugfs_create_file_unsafe("level", 0600, dir, cd, + &ulog_level_fops); + if (IS_ERR(file)) { + rc = PTR_ERR(file); + pr_err("Failed to create level %d\n", rc); + goto out; + } + + file = debugfs_create_file_unsafe("enable", 0600, dir, cd, + &ulog_en_fops); + if (IS_ERR(file)) { + rc = PTR_ERR(file); + pr_err("Failed to create enable %d\n", rc); + goto out; + } + + file = debugfs_create_file_unsafe("time_ms", 0600, dir, cd, + &ulog_time_fops); + if (IS_ERR(file)) { + rc = PTR_ERR(file); + pr_err("Failed to create time_ms %d\n", rc); + goto out; + } + cd->debugfs_dir = dir; + + return 0; +out: + debugfs_remove_recursive(dir); + return rc; +} + +static int chg_ulog_probe(struct platform_device *pdev) +{ + struct chg_ulog_glink_dev *cd; + struct pmic_glink_client_data client_data = { }; + int rc; + + cd = devm_kzalloc(&pdev->dev, sizeof(*cd), GFP_KERNEL); + if (!cd) + return -ENOMEM; + + mutex_init(&cd->lock); + init_completion(&cd->ack); + INIT_DELAYED_WORK(&cd->ulog_work, chg_ulog_work); + cd->log_time_ms = LOG_DEFAULT_TIME_MS; + platform_set_drvdata(pdev, cd); + + cd->dev = &pdev->dev; + client_data.id = MSG_OWNER_CHG_ULOG; + client_data.name = "chg_ulog"; + client_data.msg_cb = chg_ulog_callback; + client_data.priv = cd; + + cd->client = pmic_glink_register_client(cd->dev, &client_data); + if (IS_ERR(cd->client)) + return dev_err_probe(cd->dev, PTR_ERR(cd->client), + "Error in registering with pmic_glink %d\n"); + + rc = chg_ulog_add_debugfs(cd); + if (rc) { + pmic_glink_unregister_client(cd->client); + return dev_err_probe(cd->dev, -EINVAL, "Error in creating debugfs\n"); + } + + cd->ipc_log = ipc_log_context_create(NUM_LOG_PAGES, "charger_ulog", 0); + if (!cd->ipc_log) { + pmic_glink_unregister_client(cd->client); + debugfs_remove_recursive(cd->debugfs_dir); + return dev_err_probe(cd->dev, -ENODEV, "Error in creating charger_ulog\n"); + } + + cd->ipc_init_log = ipc_log_context_create(NUM_INIT_LOG_PAGES, + "charger_ulog_init", 0); + if (!cd->ipc_init_log) { + pmic_glink_unregister_client(cd->client); + ipc_log_context_destroy(cd->ipc_log); + debugfs_remove_recursive(cd->debugfs_dir); + return dev_err_probe(cd->dev, -ENODEV, "Error in creating charger_ulog_init\n"); + } + + return 0; +} + +static int chg_ulog_remove(struct platform_device *pdev) +{ + struct chg_ulog_glink_dev *cd = platform_get_drvdata(pdev); + int rc; + + debugfs_remove_recursive(cd->debugfs_dir); + cancel_delayed_work_sync(&cd->ulog_work); + + rc = pmic_glink_unregister_client(cd->client); + if (rc < 0) + pr_err("Error unregistering from pmic_glink, rc=%d\n", rc); + + ipc_log_context_destroy(cd->ipc_log); + ipc_log_context_destroy(cd->ipc_init_log); + + return 0; +} + +static const struct of_device_id chg_ulog_match_table[] = { + { .compatible = "qcom,charger-ulog-glink" }, + {}, +}; + +static struct platform_driver chg_ulog_driver = { + .driver = { + .name = "charger_ulog_glink", + .of_match_table = chg_ulog_match_table, + }, + .probe = chg_ulog_probe, + .remove = chg_ulog_remove, +}; +module_platform_driver(chg_ulog_driver); + +MODULE_DESCRIPTION("QTI charger ulog glink driver"); +MODULE_LICENSE("GPL v2");