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
27struct pmic_pdcharger_ulog_hdr {
28 __le32 owner;
29 __le32 type;
30 __le32 opcode;
31};
32
33struct pmic_pdcharger_ulog {
34 struct rpmsg_device *rpdev;
35 struct delayed_work ulog_work;
36};
37
38struct get_ulog_req_msg {
39 struct pmic_pdcharger_ulog_hdr hdr;
40 u32 log_size;
41};
42
43struct get_ulog_resp_msg {
44 struct pmic_pdcharger_ulog_hdr hdr;
45 u8 buf[MAX_ULOG_SIZE];
46};
47
48static int pmic_pdcharger_ulog_write_async(struct pmic_pdcharger_ulog *pg, void *data, size_t len)
49{
50 return rpmsg_send(ept: pg->rpdev->ept, data, len);
51}
52
53static 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, data: &req_msg, len: sizeof(req_msg));
65}
66
67static 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
80static 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(msg: token);
98 } while (token);
99}
100
101static 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(dev: &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(dwork: &pg->ulog_work, delay: msecs_to_jiffies(LOG_DEFAULT_TIME_MS));
113 pmic_pdcharger_ulog_handle_message(pg, resp_msg: 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
123static 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, size: 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, data: pg);
136
137 pmic_pdcharger_ulog_request(pg);
138
139 return 0;
140}
141
142static void pmic_pdcharger_ulog_rpmsg_remove(struct rpmsg_device *rpdev)
143{
144 struct pmic_pdcharger_ulog *pg = dev_get_drvdata(dev: &rpdev->dev);
145
146 cancel_delayed_work_sync(dwork: &pg->ulog_work);
147}
148
149static const struct rpmsg_device_id pmic_pdcharger_ulog_rpmsg_id_match[] = {
150 { "PMIC_LOGS_ADSP_APPS" },
151 {}
152};
153
154static 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
164module_rpmsg_driver(pmic_pdcharger_ulog_rpmsg_driver);
165MODULE_DESCRIPTION("Qualcomm PMIC ChargerPD ULOG driver");
166MODULE_LICENSE("GPL");
167

source code of linux/drivers/soc/qcom/pmic_pdcharger_ulog.c