1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2019-2022, The Linux Foundation. All rights reserved. 4 * Copyright (c) 2023, Linaro Ltd 5 */ 6 #include <linux/of_device.h> 7 #include <linux/module.h> 8 #include <linux/platform_device.h> 9 #include <linux/rpmsg.h> 10 #include <linux/slab.h> 11 #include <linux/soc/qcom/pdr.h> 12 #include <linux/debugfs.h> 13 14 #define CREATE_TRACE_POINTS 15 #include "pmic_pdcharger_ulog.h" 16 17 #define MSG_OWNER_CHG_ULOG 32778 18 #define MSG_TYPE_REQ_RESP 1 19 20 #define GET_CHG_ULOG_REQ 0x18 21 #define SET_CHG_ULOG_PROP_REQ 0x19 22 23 #define LOG_DEFAULT_TIME_MS 1000 24 25 #define MAX_ULOG_SIZE 8192 26 27 struct pmic_pdcharger_ulog_hdr { 28 __le32 owner; 29 __le32 type; 30 __le32 opcode; 31 }; 32 33 struct pmic_pdcharger_ulog { 34 struct rpmsg_device *rpdev; 35 struct delayed_work ulog_work; 36 }; 37 38 struct get_ulog_req_msg { 39 struct pmic_pdcharger_ulog_hdr hdr; 40 u32 log_size; 41 }; 42 43 struct get_ulog_resp_msg { 44 struct pmic_pdcharger_ulog_hdr hdr; 45 u8 buf[MAX_ULOG_SIZE]; 46 }; 47 48 static int pmic_pdcharger_ulog_write_async(struct pmic_pdcharger_ulog *pg, void *data, size_t len) 49 { 50 return rpmsg_send(pg->rpdev->ept, data, len); 51 } 52 53 static int pmic_pdcharger_ulog_request(struct pmic_pdcharger_ulog *pg) 54 { 55 struct get_ulog_req_msg req_msg = { 56 .hdr = { 57 .owner = cpu_to_le32(MSG_OWNER_CHG_ULOG), 58 .type = cpu_to_le32(MSG_TYPE_REQ_RESP), 59 .opcode = cpu_to_le32(GET_CHG_ULOG_REQ) 60 }, 61 .log_size = MAX_ULOG_SIZE 62 }; 63 64 return pmic_pdcharger_ulog_write_async(pg, &req_msg, sizeof(req_msg)); 65 } 66 67 static void pmic_pdcharger_ulog_work(struct work_struct *work) 68 { 69 struct pmic_pdcharger_ulog *pg = container_of(work, struct pmic_pdcharger_ulog, 70 ulog_work.work); 71 int rc; 72 73 rc = pmic_pdcharger_ulog_request(pg); 74 if (rc) { 75 dev_err(&pg->rpdev->dev, "Error requesting ulog, rc=%d\n", rc); 76 return; 77 } 78 } 79 80 static void pmic_pdcharger_ulog_handle_message(struct pmic_pdcharger_ulog *pg, 81 struct get_ulog_resp_msg *resp_msg, 82 size_t len) 83 { 84 char *token, *buf = resp_msg->buf; 85 86 if (len != sizeof(*resp_msg)) { 87 dev_err(&pg->rpdev->dev, "Expected data length: %zu, received: %zu\n", 88 sizeof(*resp_msg), len); 89 return; 90 } 91 92 buf[MAX_ULOG_SIZE - 1] = '\0'; 93 94 do { 95 token = strsep((char **)&buf, "\n"); 96 if (token && strlen(token)) 97 trace_pmic_pdcharger_ulog_msg(token); 98 } while (token); 99 } 100 101 static int pmic_pdcharger_ulog_rpmsg_callback(struct rpmsg_device *rpdev, void *data, 102 int len, void *priv, u32 addr) 103 { 104 struct pmic_pdcharger_ulog *pg = dev_get_drvdata(&rpdev->dev); 105 struct pmic_pdcharger_ulog_hdr *hdr = data; 106 u32 opcode; 107 108 opcode = le32_to_cpu(hdr->opcode); 109 110 switch (opcode) { 111 case GET_CHG_ULOG_REQ: 112 schedule_delayed_work(&pg->ulog_work, msecs_to_jiffies(LOG_DEFAULT_TIME_MS)); 113 pmic_pdcharger_ulog_handle_message(pg, data, len); 114 break; 115 default: 116 dev_err(&pg->rpdev->dev, "Unknown opcode %u\n", opcode); 117 break; 118 } 119 120 return 0; 121 } 122 123 static int pmic_pdcharger_ulog_rpmsg_probe(struct rpmsg_device *rpdev) 124 { 125 struct pmic_pdcharger_ulog *pg; 126 struct device *dev = &rpdev->dev; 127 128 pg = devm_kzalloc(dev, sizeof(*pg), GFP_KERNEL); 129 if (!pg) 130 return -ENOMEM; 131 132 pg->rpdev = rpdev; 133 INIT_DELAYED_WORK(&pg->ulog_work, pmic_pdcharger_ulog_work); 134 135 dev_set_drvdata(dev, pg); 136 137 pmic_pdcharger_ulog_request(pg); 138 139 return 0; 140 } 141 142 static void pmic_pdcharger_ulog_rpmsg_remove(struct rpmsg_device *rpdev) 143 { 144 struct pmic_pdcharger_ulog *pg = dev_get_drvdata(&rpdev->dev); 145 146 cancel_delayed_work_sync(&pg->ulog_work); 147 } 148 149 static const struct rpmsg_device_id pmic_pdcharger_ulog_rpmsg_id_match[] = { 150 { "PMIC_LOGS_ADSP_APPS" }, 151 {} 152 }; 153 154 static struct rpmsg_driver pmic_pdcharger_ulog_rpmsg_driver = { 155 .probe = pmic_pdcharger_ulog_rpmsg_probe, 156 .remove = pmic_pdcharger_ulog_rpmsg_remove, 157 .callback = pmic_pdcharger_ulog_rpmsg_callback, 158 .id_table = pmic_pdcharger_ulog_rpmsg_id_match, 159 .drv = { 160 .name = "qcom_pmic_pdcharger_ulog_rpmsg", 161 }, 162 }; 163 164 module_rpmsg_driver(pmic_pdcharger_ulog_rpmsg_driver); 165 MODULE_DESCRIPTION("Qualcomm PMIC ChargerPD ULOG driver"); 166 MODULE_LICENSE("GPL"); 167