// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2019-2022, The Linux Foundation. All rights reserved. * Copyright (c) 2023, Linaro Ltd */ #include #include #include #include #include #include #include #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 = cpu_to_le32(MSG_OWNER_CHG_ULOG), .type = cpu_to_le32(MSG_TYPE_REQ_RESP), .opcode = cpu_to_le32(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");