soc: qcom: add ADSP PDCharger ULOG driver
authorNeil Armstrong <neil.armstrong@linaro.org>
Fri, 8 Sep 2023 10:53:49 +0000 (12:53 +0200)
committerBjorn Andersson <andersson@kernel.org>
Tue, 14 Nov 2023 17:06:24 +0000 (11:06 -0600)
The Qualcomm PMIC PDCharger ULOG driver provides access to logs of
the ADSP firmware PDCharger module in charge of Battery and Power
Delivery on modern systems.

Implement trace events as a simple rpmsg driver with an 1s interval
to retrieve the messages.

The interface allows filtering the messages by subsystem and priority
level, this could be implemented later on.

Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Tested-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Link: https://lore.kernel.org/r/20230908-topic-sm8550-upstream-pdcharge-ulog-v1-1-d1b16b02ced2@linaro.org
Signed-off-by: Bjorn Andersson <andersson@kernel.org>
drivers/soc/qcom/Kconfig
drivers/soc/qcom/Makefile
drivers/soc/qcom/pmic_pdcharger_ulog.c [new file with mode: 0644]
drivers/soc/qcom/pmic_pdcharger_ulog.h [new file with mode: 0644]

index b3634e10f6f5ec5c4476fa6ef6145220c7efa36e..2686fda9fe27775ea5ae7acfe9a55bea3cc5993b 100644 (file)
@@ -77,6 +77,18 @@ config QCOM_PDR_HELPERS
        select QCOM_QMI_HELPERS
        depends on NET
 
+config QCOM_PMIC_PDCHARGER_ULOG
+       tristate "Qualcomm PMIC PDCharger ULOG driver"
+       depends on RPMSG
+       depends on EVENT_TRACING
+       help
+         The Qualcomm PMIC PDCharger ULOG driver provides access to logs of
+         the ADSP firmware PDCharger module in charge of Battery and Power
+         Delivery on modern systems.
+
+         Say yes here to support PDCharger ULOG event tracing on modern
+         Qualcomm platforms.
+
 config QCOM_PMIC_GLINK
        tristate "Qualcomm PMIC GLINK driver"
        depends on RPMSG
index bbca2e1e55bb712049584d61ffc993ef7f8b1397..110108e23669d6b85e119c8712caa9c4752ed0c8 100644 (file)
@@ -9,6 +9,7 @@ obj-$(CONFIG_QCOM_OCMEM)        += ocmem.o
 obj-$(CONFIG_QCOM_PDR_HELPERS) += pdr_interface.o
 obj-$(CONFIG_QCOM_PMIC_GLINK)  += pmic_glink.o
 obj-$(CONFIG_QCOM_PMIC_GLINK)  += pmic_glink_altmode.o
+obj-$(CONFIG_QCOM_PMIC_PDCHARGER_ULOG) += pmic_pdcharger_ulog.o
 obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o
 qmi_helpers-y  += qmi_encdec.o qmi_interface.o
 obj-$(CONFIG_QCOM_RAMP_CTRL)   += ramp_controller.o
diff --git a/drivers/soc/qcom/pmic_pdcharger_ulog.c b/drivers/soc/qcom/pmic_pdcharger_ulog.c
new file mode 100644 (file)
index 0000000..f1aaacf
--- /dev/null
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2019-2022, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2023, Linaro Ltd
+ */
+#include <linux/of_device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rpmsg.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/pdr.h>
+#include <linux/debugfs.h>
+
+#define CREATE_TRACE_POINTS
+#include "pmic_pdcharger_ulog.h"
+
+#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 LOG_DEFAULT_TIME_MS            1000
+
+#define MAX_ULOG_SIZE                  8192
+
+struct pmic_pdcharger_ulog_hdr {
+       __le32 owner;
+       __le32 type;
+       __le32 opcode;
+};
+
+struct pmic_pdcharger_ulog {
+       struct rpmsg_device *rpdev;
+       struct delayed_work ulog_work;
+};
+
+struct get_ulog_req_msg {
+       struct pmic_pdcharger_ulog_hdr  hdr;
+       u32                             log_size;
+};
+
+struct get_ulog_resp_msg {
+       struct pmic_pdcharger_ulog_hdr  hdr;
+       u8                              buf[MAX_ULOG_SIZE];
+};
+
+static int pmic_pdcharger_ulog_write_async(struct pmic_pdcharger_ulog *pg, void *data, size_t len)
+{
+       return rpmsg_send(pg->rpdev->ept, data, len);
+}
+
+static int pmic_pdcharger_ulog_request(struct pmic_pdcharger_ulog *pg)
+{
+       struct get_ulog_req_msg req_msg = {
+               .hdr = {
+                       .owner = MSG_OWNER_CHG_ULOG,
+                       .type = MSG_TYPE_REQ_RESP,
+                       .opcode = GET_CHG_ULOG_REQ
+               },
+               .log_size = MAX_ULOG_SIZE
+       };
+
+       return pmic_pdcharger_ulog_write_async(pg, &req_msg, sizeof(req_msg));
+}
+
+static void pmic_pdcharger_ulog_work(struct work_struct *work)
+{
+       struct pmic_pdcharger_ulog *pg = container_of(work, struct pmic_pdcharger_ulog,
+                                                     ulog_work.work);
+       int rc;
+
+       rc = pmic_pdcharger_ulog_request(pg);
+       if (rc) {
+               dev_err(&pg->rpdev->dev, "Error requesting ulog, rc=%d\n", rc);
+               return;
+       }
+}
+
+static void pmic_pdcharger_ulog_handle_message(struct pmic_pdcharger_ulog *pg,
+                                              struct get_ulog_resp_msg *resp_msg,
+                                              size_t len)
+{
+       char *token, *buf = resp_msg->buf;
+
+       if (len != sizeof(*resp_msg)) {
+               dev_err(&pg->rpdev->dev, "Expected data length: %zu, received: %zu\n",
+                       sizeof(*resp_msg), len);
+               return;
+       }
+
+       buf[MAX_ULOG_SIZE - 1] = '\0';
+
+       do {
+               token = strsep((char **)&buf, "\n");
+               if (token && strlen(token))
+                       trace_pmic_pdcharger_ulog_msg(token);
+       } while (token);
+}
+
+static int pmic_pdcharger_ulog_rpmsg_callback(struct rpmsg_device *rpdev, void *data,
+                                             int len, void *priv, u32 addr)
+{
+       struct pmic_pdcharger_ulog *pg = dev_get_drvdata(&rpdev->dev);
+       struct pmic_pdcharger_ulog_hdr *hdr = data;
+       u32 opcode;
+
+       opcode = le32_to_cpu(hdr->opcode);
+
+       switch (opcode) {
+       case GET_CHG_ULOG_REQ:
+               schedule_delayed_work(&pg->ulog_work, msecs_to_jiffies(LOG_DEFAULT_TIME_MS));
+               pmic_pdcharger_ulog_handle_message(pg, data, len);
+               break;
+       default:
+               dev_err(&pg->rpdev->dev, "Unknown opcode %u\n", opcode);
+               break;
+       }
+
+       return 0;
+}
+
+static int pmic_pdcharger_ulog_rpmsg_probe(struct rpmsg_device *rpdev)
+{
+       struct pmic_pdcharger_ulog *pg;
+       struct device *dev = &rpdev->dev;
+
+       pg = devm_kzalloc(dev, sizeof(*pg), GFP_KERNEL);
+       if (!pg)
+               return -ENOMEM;
+
+       pg->rpdev = rpdev;
+       INIT_DELAYED_WORK(&pg->ulog_work, pmic_pdcharger_ulog_work);
+
+       dev_set_drvdata(dev, pg);
+
+       pmic_pdcharger_ulog_request(pg);
+
+       return 0;
+}
+
+static void pmic_pdcharger_ulog_rpmsg_remove(struct rpmsg_device *rpdev)
+{
+       struct pmic_pdcharger_ulog *pg = dev_get_drvdata(&rpdev->dev);
+
+       cancel_delayed_work_sync(&pg->ulog_work);
+}
+
+static const struct rpmsg_device_id pmic_pdcharger_ulog_rpmsg_id_match[] = {
+       { "PMIC_LOGS_ADSP_APPS" },
+       {}
+};
+
+static struct rpmsg_driver pmic_pdcharger_ulog_rpmsg_driver = {
+       .probe = pmic_pdcharger_ulog_rpmsg_probe,
+       .remove = pmic_pdcharger_ulog_rpmsg_remove,
+       .callback = pmic_pdcharger_ulog_rpmsg_callback,
+       .id_table = pmic_pdcharger_ulog_rpmsg_id_match,
+       .drv  = {
+               .name  = "qcom_pmic_pdcharger_ulog_rpmsg",
+       },
+};
+
+module_rpmsg_driver(pmic_pdcharger_ulog_rpmsg_driver);
+MODULE_DESCRIPTION("Qualcomm PMIC ChargerPD ULOG driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/soc/qcom/pmic_pdcharger_ulog.h b/drivers/soc/qcom/pmic_pdcharger_ulog.h
new file mode 100644 (file)
index 0000000..9d5d9af
--- /dev/null
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2023, Linaro Ltd
+ */
+
+#if !defined(_TRACE_PMIC_PDCHARGER_ULOG_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_PMIC_PDCHARGER_ULOG_H
+
+#include <linux/tracepoint.h>
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM pmic_pdcharger_ulog
+
+TRACE_EVENT(pmic_pdcharger_ulog_msg,
+       TP_PROTO(char *msg),
+       TP_ARGS(msg),
+       TP_STRUCT__entry(
+               __string(msg, msg)
+       ),
+       TP_fast_assign(
+               __assign_str(msg, msg);
+       ),
+       TP_printk("%s", __get_str(msg))
+);
+
+#endif /* _TRACE_PMIC_PDCHARGER_ULOG_H */
+
+/* This part must be outside protection */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE pmic_pdcharger_ulog
+
+#include <trace/define_trace.h>