1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2015, Sony Mobile Communications AB. |
4 | * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. |
5 | */ |
6 | |
7 | #include <linux/module.h> |
8 | #include <linux/platform_device.h> |
9 | #include <linux/of_platform.h> |
10 | #include <linux/io.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/slab.h> |
13 | |
14 | #include <linux/rpmsg.h> |
15 | #include <linux/soc/qcom/smd-rpm.h> |
16 | |
17 | #define RPM_REQUEST_TIMEOUT (5 * HZ) |
18 | |
19 | /** |
20 | * struct qcom_smd_rpm - state of the rpm device driver |
21 | * @rpm_channel: reference to the smd channel |
22 | * @dev: rpm device |
23 | * @ack: completion for acks |
24 | * @lock: mutual exclusion around the send/complete pair |
25 | * @ack_status: result of the rpm request |
26 | */ |
27 | struct qcom_smd_rpm { |
28 | struct rpmsg_endpoint *rpm_channel; |
29 | struct device *dev; |
30 | |
31 | struct completion ack; |
32 | struct mutex lock; |
33 | int ack_status; |
34 | }; |
35 | |
36 | /** |
37 | * struct qcom_rpm_header - header for all rpm requests and responses |
38 | * @service_type: identifier of the service |
39 | * @length: length of the payload |
40 | */ |
41 | struct { |
42 | __le32 ; |
43 | __le32 ; |
44 | }; |
45 | |
46 | /** |
47 | * struct qcom_rpm_request - request message to the rpm |
48 | * @msg_id: identifier of the outgoing message |
49 | * @flags: active/sleep state flags |
50 | * @type: resource type |
51 | * @id: resource id |
52 | * @data_len: length of the payload following this header |
53 | */ |
54 | struct qcom_rpm_request { |
55 | __le32 msg_id; |
56 | __le32 flags; |
57 | __le32 type; |
58 | __le32 id; |
59 | __le32 data_len; |
60 | }; |
61 | |
62 | /** |
63 | * struct qcom_rpm_message - response message from the rpm |
64 | * @msg_type: indicator of the type of message |
65 | * @length: the size of this message, including the message header |
66 | * @msg_id: message id |
67 | * @message: textual message from the rpm |
68 | * |
69 | * Multiple of these messages can be stacked in an rpm message. |
70 | */ |
71 | struct qcom_rpm_message { |
72 | __le32 msg_type; |
73 | __le32 length; |
74 | union { |
75 | __le32 msg_id; |
76 | DECLARE_FLEX_ARRAY(u8, message); |
77 | }; |
78 | }; |
79 | |
80 | #define RPM_SERVICE_TYPE_REQUEST 0x00716572 /* "req\0" */ |
81 | |
82 | #define RPM_MSG_TYPE_ERR 0x00727265 /* "err\0" */ |
83 | #define RPM_MSG_TYPE_MSG_ID 0x2367736d /* "msg#" */ |
84 | |
85 | /** |
86 | * qcom_rpm_smd_write - write @buf to @type:@id |
87 | * @rpm: rpm handle |
88 | * @state: active/sleep state flags |
89 | * @type: resource type |
90 | * @id: resource identifier |
91 | * @buf: the data to be written |
92 | * @count: number of bytes in @buf |
93 | */ |
94 | int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, |
95 | int state, |
96 | u32 type, u32 id, |
97 | void *buf, |
98 | size_t count) |
99 | { |
100 | static unsigned msg_id = 1; |
101 | int left; |
102 | int ret; |
103 | struct { |
104 | struct qcom_rpm_header hdr; |
105 | struct qcom_rpm_request req; |
106 | u8 payload[]; |
107 | } *pkt; |
108 | size_t size = sizeof(*pkt) + count; |
109 | |
110 | /* SMD packets to the RPM may not exceed 256 bytes */ |
111 | if (WARN_ON(size >= 256)) |
112 | return -EINVAL; |
113 | |
114 | pkt = kmalloc(size, GFP_ATOMIC); |
115 | if (!pkt) |
116 | return -ENOMEM; |
117 | |
118 | mutex_lock(&rpm->lock); |
119 | |
120 | pkt->hdr.service_type = cpu_to_le32(RPM_SERVICE_TYPE_REQUEST); |
121 | pkt->hdr.length = cpu_to_le32(sizeof(struct qcom_rpm_request) + count); |
122 | |
123 | pkt->req.msg_id = cpu_to_le32(msg_id++); |
124 | pkt->req.flags = cpu_to_le32(state); |
125 | pkt->req.type = cpu_to_le32(type); |
126 | pkt->req.id = cpu_to_le32(id); |
127 | pkt->req.data_len = cpu_to_le32(count); |
128 | memcpy(pkt->payload, buf, count); |
129 | |
130 | ret = rpmsg_send(ept: rpm->rpm_channel, data: pkt, len: size); |
131 | if (ret) |
132 | goto out; |
133 | |
134 | left = wait_for_completion_timeout(x: &rpm->ack, RPM_REQUEST_TIMEOUT); |
135 | if (!left) |
136 | ret = -ETIMEDOUT; |
137 | else |
138 | ret = rpm->ack_status; |
139 | |
140 | out: |
141 | kfree(objp: pkt); |
142 | mutex_unlock(lock: &rpm->lock); |
143 | return ret; |
144 | } |
145 | EXPORT_SYMBOL_GPL(qcom_rpm_smd_write); |
146 | |
147 | static int qcom_smd_rpm_callback(struct rpmsg_device *rpdev, |
148 | void *data, |
149 | int count, |
150 | void *priv, |
151 | u32 addr) |
152 | { |
153 | const struct qcom_rpm_header *hdr = data; |
154 | size_t hdr_length = le32_to_cpu(hdr->length); |
155 | const struct qcom_rpm_message *msg; |
156 | struct qcom_smd_rpm *rpm = dev_get_drvdata(dev: &rpdev->dev); |
157 | const u8 *buf = data + sizeof(struct qcom_rpm_header); |
158 | const u8 *end = buf + hdr_length; |
159 | char msgbuf[32]; |
160 | int status = 0; |
161 | u32 len, msg_length; |
162 | |
163 | if (le32_to_cpu(hdr->service_type) != RPM_SERVICE_TYPE_REQUEST || |
164 | hdr_length < sizeof(struct qcom_rpm_message)) { |
165 | dev_err(rpm->dev, "invalid request\n" ); |
166 | return 0; |
167 | } |
168 | |
169 | while (buf < end) { |
170 | msg = (struct qcom_rpm_message *)buf; |
171 | msg_length = le32_to_cpu(msg->length); |
172 | switch (le32_to_cpu(msg->msg_type)) { |
173 | case RPM_MSG_TYPE_MSG_ID: |
174 | break; |
175 | case RPM_MSG_TYPE_ERR: |
176 | len = min_t(u32, ALIGN(msg_length, 4), sizeof(msgbuf)); |
177 | memcpy_fromio(msgbuf, msg->message, len); |
178 | msgbuf[len - 1] = 0; |
179 | |
180 | if (!strcmp(msgbuf, "resource does not exist" )) |
181 | status = -ENXIO; |
182 | else |
183 | status = -EINVAL; |
184 | break; |
185 | } |
186 | |
187 | buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg_length, 4); |
188 | } |
189 | |
190 | rpm->ack_status = status; |
191 | complete(&rpm->ack); |
192 | return 0; |
193 | } |
194 | |
195 | static int qcom_smd_rpm_probe(struct rpmsg_device *rpdev) |
196 | { |
197 | struct qcom_smd_rpm *rpm; |
198 | |
199 | if (!rpdev->dev.of_node) |
200 | return -EINVAL; |
201 | |
202 | rpm = devm_kzalloc(dev: &rpdev->dev, size: sizeof(*rpm), GFP_KERNEL); |
203 | if (!rpm) |
204 | return -ENOMEM; |
205 | |
206 | mutex_init(&rpm->lock); |
207 | init_completion(x: &rpm->ack); |
208 | |
209 | rpm->dev = &rpdev->dev; |
210 | rpm->rpm_channel = rpdev->ept; |
211 | dev_set_drvdata(dev: &rpdev->dev, data: rpm); |
212 | |
213 | return of_platform_populate(root: rpdev->dev.of_node, NULL, NULL, parent: &rpdev->dev); |
214 | } |
215 | |
216 | static void qcom_smd_rpm_remove(struct rpmsg_device *rpdev) |
217 | { |
218 | of_platform_depopulate(parent: &rpdev->dev); |
219 | } |
220 | |
221 | static const struct rpmsg_device_id qcom_smd_rpm_id_table[] = { |
222 | { .name = "rpm_requests" , }, |
223 | { /* sentinel */ } |
224 | }; |
225 | MODULE_DEVICE_TABLE(rpmsg, qcom_smd_rpm_id_table); |
226 | |
227 | static struct rpmsg_driver qcom_smd_rpm_driver = { |
228 | .probe = qcom_smd_rpm_probe, |
229 | .remove = qcom_smd_rpm_remove, |
230 | .callback = qcom_smd_rpm_callback, |
231 | .id_table = qcom_smd_rpm_id_table, |
232 | .drv.name = "qcom_smd_rpm" , |
233 | }; |
234 | |
235 | static int __init qcom_smd_rpm_init(void) |
236 | { |
237 | return register_rpmsg_driver(&qcom_smd_rpm_driver); |
238 | } |
239 | arch_initcall(qcom_smd_rpm_init); |
240 | |
241 | static void __exit qcom_smd_rpm_exit(void) |
242 | { |
243 | unregister_rpmsg_driver(drv: &qcom_smd_rpm_driver); |
244 | } |
245 | module_exit(qcom_smd_rpm_exit); |
246 | |
247 | MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>" ); |
248 | MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver" ); |
249 | MODULE_LICENSE("GPL v2" ); |
250 | |