From 55675aeb1b43ff8a7dd0533ed7f1330a293608c1 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Thu, 18 Mar 2021 19:48:25 -0700 Subject: [PATCH 1/2] soc: qcom: altmode-glink: add ipc logging support Add ipc logging support so that the essential log information can be stored and looked upon especially when dynamic_debug log filter is not set. This is useful for debugging. Change-Id: I33c8eb1733ab1ecc541abb86f4f351ce9c8ff394 Signed-off-by: Subbaraman Narayanamurthy --- drivers/soc/qcom/altmode-glink.c | 33 ++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/drivers/soc/qcom/altmode-glink.c b/drivers/soc/qcom/altmode-glink.c index ecf1e7a9af09..2697fd7ffda5 100644 --- a/drivers/soc/qcom/altmode-glink.c +++ b/drivers/soc/qcom/altmode-glink.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2020 The Linux Foundation. All rights reserved. + * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "altmode-glink: %s: " fmt, __func__ @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -27,10 +28,18 @@ #define MAX_NUM_PORTS 4 +#define NUM_LOG_PAGES 10 + #define IDR_KEY_GEN(svid, ind) (((svid) << 8) | (ind)) #define IDR_KEY(client) \ IDR_KEY_GEN((client)->data.svid, (client)->port_index) +#define altmode_dbg(fmt, ...) \ + do { \ + ipc_log_string(altmode_ipc_log, fmt, ##__VA_ARGS__); \ + pr_debug(fmt, ##__VA_ARGS__); \ + } while (0) + struct usbc_notify_ind_msg { struct pmic_glink_hdr hdr; u8 payload[NOTIFY_PAYLOAD_SIZE]; @@ -125,6 +134,7 @@ static LIST_HEAD(probe_notify_list); static DEFINE_MUTEX(notify_lock); static void altmode_send_pan_ack(struct work_struct *work); +static void *altmode_ipc_log; static struct altmode_dev *to_altmode_device(struct device_node *amdev_node) { @@ -462,7 +472,7 @@ static void altmode_send_pan_en(struct work_struct *work) } atomic_set(&amdev->pan_en_sent, 1); - pr_debug("Sent PAN EN\n"); + altmode_dbg("Sent PAN EN\n"); } static int altmode_send_ack(struct altmode_dev *amdev, u8 port_index) @@ -479,7 +489,7 @@ static int altmode_send_ack(struct altmode_dev *amdev, u8 port_index) return rc; } - pr_debug("port %u: Sent PAN ACK\n", port_index); + altmode_dbg("port %u: Sent PAN ACK\n", port_index); return rc; } @@ -488,7 +498,7 @@ static void altmode_state_cb(void *priv, enum pmic_glink_state state) { struct altmode_dev *amdev = priv; - pr_debug("state: %d\n", state); + altmode_dbg("state: %d\n", state); switch (state) { case PMIC_GLINK_STATE_DOWN: @@ -527,7 +537,7 @@ static int altmode_callback(void *priv, void *data, size_t len) struct altmode_client *amclient; u8 port_index; - pr_debug("len: %zu owner: %u type: %u opcode %04x\n", len, hdr->owner, + altmode_dbg("len: %zu owner: %u type: %u opcode %04x\n", len, hdr->owner, hdr->type, hdr->opcode); /* @@ -544,7 +554,7 @@ static int altmode_callback(void *priv, void *data, size_t len) break; case USBC_NOTIFY_IND: if (len != sizeof(*notify_msg)) { - pr_debug("Expected length %u, got: %zu\n", + altmode_dbg("Expected length %u, got: %zu\n", sizeof(*notify_msg), len); return -EINVAL; } @@ -558,7 +568,7 @@ static int altmode_callback(void *priv, void *data, size_t len) mutex_unlock(&amdev->client_lock); if (!amclient) { - pr_debug("No client associated with SVID %#x port %u\n", + altmode_dbg("No client associated with SVID %#x port %u\n", svid, port_index); amdev->ack_port_index = port_index; schedule_delayed_work(&amdev->send_pan_ack_work, @@ -566,7 +576,7 @@ static int altmode_callback(void *priv, void *data, size_t len) return 0; } - pr_debug("Payload: %*ph\n", NOTIFY_PAYLOAD_SIZE, + altmode_dbg("Payload: %*ph\n", NOTIFY_PAYLOAD_SIZE, notify_msg->payload); cancel_work_sync(&amclient->client_cb_work); @@ -727,6 +737,10 @@ static int altmode_probe(struct platform_device *pdev) goto unreg_pmic_glink; } + altmode_ipc_log = ipc_log_context_create(NUM_LOG_PAGES, "altmode", 0); + if (!altmode_ipc_log) + dev_warn(dev, "Error in creating ipc_log_context\n"); + altmode_notify_clients(amdev); return 0; @@ -765,6 +779,9 @@ static int altmode_remove(struct platform_device *pdev) list_del(&client->c_node); mutex_unlock(&amdev->client_lock); + ipc_log_context_destroy(altmode_ipc_log); + altmode_ipc_log = NULL; + rc = pmic_glink_unregister_client(amdev->pgclient); if (rc < 0) dev_err(amdev->dev, "Error in pmic_glink de-registration: %d\n", From 652a68d2f2c42ea060c71c452613dadd87734560 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Tue, 13 Apr 2021 20:25:46 -0700 Subject: [PATCH 2/2] 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");