1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2016, Linaro Ltd. |
4 | * Copyright (c) 2015, Sony Mobile Communications Inc. |
5 | */ |
6 | #include <linux/firmware.h> |
7 | #include <linux/module.h> |
8 | #include <linux/slab.h> |
9 | #include <linux/io.h> |
10 | #include <linux/of.h> |
11 | #include <linux/of_platform.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/rpmsg.h> |
14 | #include <linux/soc/qcom/wcnss_ctrl.h> |
15 | |
16 | #define WCNSS_REQUEST_TIMEOUT (5 * HZ) |
17 | #define WCNSS_CBC_TIMEOUT (10 * HZ) |
18 | |
19 | #define WCNSS_ACK_DONE_BOOTING 1 |
20 | #define WCNSS_ACK_COLD_BOOTING 2 |
21 | |
22 | #define NV_FRAGMENT_SIZE 3072 |
23 | #define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin" |
24 | |
25 | /** |
26 | * struct wcnss_ctrl - driver context |
27 | * @dev: device handle |
28 | * @channel: SMD channel handle |
29 | * @ack: completion for outstanding requests |
30 | * @cbc: completion for cbc complete indication |
31 | * @ack_status: status of the outstanding request |
32 | * @probe_work: worker for uploading nv binary |
33 | */ |
34 | struct wcnss_ctrl { |
35 | struct device *dev; |
36 | struct rpmsg_endpoint *channel; |
37 | |
38 | struct completion ack; |
39 | struct completion cbc; |
40 | int ack_status; |
41 | |
42 | struct work_struct probe_work; |
43 | }; |
44 | |
45 | /* message types */ |
46 | enum { |
47 | WCNSS_VERSION_REQ = 0x01000000, |
48 | WCNSS_VERSION_RESP, |
49 | WCNSS_DOWNLOAD_NV_REQ, |
50 | WCNSS_DOWNLOAD_NV_RESP, |
51 | WCNSS_UPLOAD_CAL_REQ, |
52 | WCNSS_UPLOAD_CAL_RESP, |
53 | WCNSS_DOWNLOAD_CAL_REQ, |
54 | WCNSS_DOWNLOAD_CAL_RESP, |
55 | WCNSS_VBAT_LEVEL_IND, |
56 | WCNSS_BUILD_VERSION_REQ, |
57 | WCNSS_BUILD_VERSION_RESP, |
58 | WCNSS_PM_CONFIG_REQ, |
59 | WCNSS_CBC_COMPLETE_IND, |
60 | }; |
61 | |
62 | /** |
63 | * struct wcnss_msg_hdr - common packet header for requests and responses |
64 | * @type: packet message type |
65 | * @len: total length of the packet, including this header |
66 | */ |
67 | struct wcnss_msg_hdr { |
68 | u32 type; |
69 | u32 len; |
70 | } __packed; |
71 | |
72 | /* |
73 | * struct wcnss_version_resp - version request response |
74 | */ |
75 | struct wcnss_version_resp { |
76 | struct wcnss_msg_hdr hdr; |
77 | u8 major; |
78 | u8 minor; |
79 | u8 version; |
80 | u8 revision; |
81 | } __packed; |
82 | |
83 | /** |
84 | * struct wcnss_download_nv_req - firmware fragment request |
85 | * @hdr: common packet wcnss_msg_hdr header |
86 | * @seq: sequence number of this fragment |
87 | * @last: boolean indicator of this being the last fragment of the binary |
88 | * @frag_size: length of this fragment |
89 | * @fragment: fragment data |
90 | */ |
91 | struct wcnss_download_nv_req { |
92 | struct wcnss_msg_hdr hdr; |
93 | u16 seq; |
94 | u16 last; |
95 | u32 frag_size; |
96 | u8 fragment[]; |
97 | } __packed; |
98 | |
99 | /** |
100 | * struct wcnss_download_nv_resp - firmware download response |
101 | * @hdr: common packet wcnss_msg_hdr header |
102 | * @status: boolean to indicate success of the download |
103 | */ |
104 | struct wcnss_download_nv_resp { |
105 | struct wcnss_msg_hdr hdr; |
106 | u8 status; |
107 | } __packed; |
108 | |
109 | /** |
110 | * wcnss_ctrl_smd_callback() - handler from SMD responses |
111 | * @rpdev: remote processor message device pointer |
112 | * @data: pointer to the incoming data packet |
113 | * @count: size of the incoming data packet |
114 | * @priv: unused |
115 | * @addr: unused |
116 | * |
117 | * Handles any incoming packets from the remote WCNSS_CTRL service. |
118 | */ |
119 | static int wcnss_ctrl_smd_callback(struct rpmsg_device *rpdev, |
120 | void *data, |
121 | int count, |
122 | void *priv, |
123 | u32 addr) |
124 | { |
125 | struct wcnss_ctrl *wcnss = dev_get_drvdata(dev: &rpdev->dev); |
126 | const struct wcnss_download_nv_resp *nvresp; |
127 | const struct wcnss_version_resp *version; |
128 | const struct wcnss_msg_hdr *hdr = data; |
129 | |
130 | switch (hdr->type) { |
131 | case WCNSS_VERSION_RESP: |
132 | if (count != sizeof(*version)) { |
133 | dev_err(wcnss->dev, |
134 | "invalid size of version response\n" ); |
135 | break; |
136 | } |
137 | |
138 | version = data; |
139 | dev_info(wcnss->dev, "WCNSS Version %d.%d %d.%d\n" , |
140 | version->major, version->minor, |
141 | version->version, version->revision); |
142 | |
143 | complete(&wcnss->ack); |
144 | break; |
145 | case WCNSS_DOWNLOAD_NV_RESP: |
146 | if (count != sizeof(*nvresp)) { |
147 | dev_err(wcnss->dev, |
148 | "invalid size of download response\n" ); |
149 | break; |
150 | } |
151 | |
152 | nvresp = data; |
153 | wcnss->ack_status = nvresp->status; |
154 | complete(&wcnss->ack); |
155 | break; |
156 | case WCNSS_CBC_COMPLETE_IND: |
157 | dev_dbg(wcnss->dev, "cold boot complete\n" ); |
158 | complete(&wcnss->cbc); |
159 | break; |
160 | default: |
161 | dev_info(wcnss->dev, "unknown message type %d\n" , hdr->type); |
162 | break; |
163 | } |
164 | |
165 | return 0; |
166 | } |
167 | |
168 | /** |
169 | * wcnss_request_version() - send a version request to WCNSS |
170 | * @wcnss: wcnss ctrl driver context |
171 | */ |
172 | static int wcnss_request_version(struct wcnss_ctrl *wcnss) |
173 | { |
174 | struct wcnss_msg_hdr msg; |
175 | int ret; |
176 | |
177 | msg.type = WCNSS_VERSION_REQ; |
178 | msg.len = sizeof(msg); |
179 | ret = rpmsg_send(ept: wcnss->channel, data: &msg, len: sizeof(msg)); |
180 | if (ret < 0) |
181 | return ret; |
182 | |
183 | ret = wait_for_completion_timeout(x: &wcnss->ack, WCNSS_CBC_TIMEOUT); |
184 | if (!ret) { |
185 | dev_err(wcnss->dev, "timeout waiting for version response\n" ); |
186 | return -ETIMEDOUT; |
187 | } |
188 | |
189 | return 0; |
190 | } |
191 | |
192 | /** |
193 | * wcnss_download_nv() - send nv binary to WCNSS |
194 | * @wcnss: wcnss_ctrl state handle |
195 | * @expect_cbc: indicator to caller that an cbc event is expected |
196 | * |
197 | * Returns 0 on success. Negative errno on failure. |
198 | */ |
199 | static int wcnss_download_nv(struct wcnss_ctrl *wcnss, bool *expect_cbc) |
200 | { |
201 | struct wcnss_download_nv_req *req; |
202 | const struct firmware *fw; |
203 | struct device *dev = wcnss->dev; |
204 | const char *nvbin = NVBIN_FILE; |
205 | const void *data; |
206 | ssize_t left; |
207 | int ret; |
208 | |
209 | req = kzalloc(size: sizeof(*req) + NV_FRAGMENT_SIZE, GFP_KERNEL); |
210 | if (!req) |
211 | return -ENOMEM; |
212 | |
213 | ret = of_property_read_string(np: dev->of_node, propname: "firmware-name" , out_string: &nvbin); |
214 | if (ret < 0 && ret != -EINVAL) |
215 | goto free_req; |
216 | |
217 | ret = request_firmware(fw: &fw, name: nvbin, device: dev); |
218 | if (ret < 0) { |
219 | dev_err(dev, "Failed to load nv file %s: %d\n" , nvbin, ret); |
220 | goto free_req; |
221 | } |
222 | |
223 | data = fw->data; |
224 | left = fw->size; |
225 | |
226 | req->hdr.type = WCNSS_DOWNLOAD_NV_REQ; |
227 | req->hdr.len = sizeof(*req) + NV_FRAGMENT_SIZE; |
228 | |
229 | req->last = 0; |
230 | req->frag_size = NV_FRAGMENT_SIZE; |
231 | |
232 | req->seq = 0; |
233 | do { |
234 | if (left <= NV_FRAGMENT_SIZE) { |
235 | req->last = 1; |
236 | req->frag_size = left; |
237 | req->hdr.len = sizeof(*req) + left; |
238 | } |
239 | |
240 | memcpy(req->fragment, data, req->frag_size); |
241 | |
242 | ret = rpmsg_send(ept: wcnss->channel, data: req, len: req->hdr.len); |
243 | if (ret < 0) { |
244 | dev_err(dev, "failed to send smd packet\n" ); |
245 | goto release_fw; |
246 | } |
247 | |
248 | /* Increment for next fragment */ |
249 | req->seq++; |
250 | |
251 | data += NV_FRAGMENT_SIZE; |
252 | left -= NV_FRAGMENT_SIZE; |
253 | } while (left > 0); |
254 | |
255 | ret = wait_for_completion_timeout(x: &wcnss->ack, WCNSS_REQUEST_TIMEOUT); |
256 | if (!ret) { |
257 | dev_err(dev, "timeout waiting for nv upload ack\n" ); |
258 | ret = -ETIMEDOUT; |
259 | } else { |
260 | *expect_cbc = wcnss->ack_status == WCNSS_ACK_COLD_BOOTING; |
261 | ret = 0; |
262 | } |
263 | |
264 | release_fw: |
265 | release_firmware(fw); |
266 | free_req: |
267 | kfree(objp: req); |
268 | |
269 | return ret; |
270 | } |
271 | |
272 | /** |
273 | * qcom_wcnss_open_channel() - open additional SMD channel to WCNSS |
274 | * @wcnss: wcnss handle, retrieved from drvdata |
275 | * @name: SMD channel name |
276 | * @cb: callback to handle incoming data on the channel |
277 | * @priv: private data for use in the call-back |
278 | */ |
279 | struct rpmsg_endpoint *qcom_wcnss_open_channel(void *wcnss, const char *name, rpmsg_rx_cb_t cb, void *priv) |
280 | { |
281 | struct rpmsg_channel_info chinfo; |
282 | struct wcnss_ctrl *_wcnss = wcnss; |
283 | |
284 | strscpy(chinfo.name, name, sizeof(chinfo.name)); |
285 | chinfo.src = RPMSG_ADDR_ANY; |
286 | chinfo.dst = RPMSG_ADDR_ANY; |
287 | |
288 | return rpmsg_create_ept(_wcnss->channel->rpdev, cb, priv, chinfo); |
289 | } |
290 | EXPORT_SYMBOL_GPL(qcom_wcnss_open_channel); |
291 | |
292 | static void wcnss_async_probe(struct work_struct *work) |
293 | { |
294 | struct wcnss_ctrl *wcnss = container_of(work, struct wcnss_ctrl, probe_work); |
295 | bool expect_cbc; |
296 | int ret; |
297 | |
298 | ret = wcnss_request_version(wcnss); |
299 | if (ret < 0) |
300 | return; |
301 | |
302 | ret = wcnss_download_nv(wcnss, expect_cbc: &expect_cbc); |
303 | if (ret < 0) |
304 | return; |
305 | |
306 | /* Wait for pending cold boot completion if indicated by the nv downloader */ |
307 | if (expect_cbc) { |
308 | ret = wait_for_completion_timeout(x: &wcnss->cbc, WCNSS_REQUEST_TIMEOUT); |
309 | if (!ret) |
310 | dev_err(wcnss->dev, "expected cold boot completion\n" ); |
311 | } |
312 | |
313 | of_platform_populate(root: wcnss->dev->of_node, NULL, NULL, parent: wcnss->dev); |
314 | } |
315 | |
316 | static int wcnss_ctrl_probe(struct rpmsg_device *rpdev) |
317 | { |
318 | struct wcnss_ctrl *wcnss; |
319 | |
320 | wcnss = devm_kzalloc(dev: &rpdev->dev, size: sizeof(*wcnss), GFP_KERNEL); |
321 | if (!wcnss) |
322 | return -ENOMEM; |
323 | |
324 | wcnss->dev = &rpdev->dev; |
325 | wcnss->channel = rpdev->ept; |
326 | |
327 | init_completion(x: &wcnss->ack); |
328 | init_completion(x: &wcnss->cbc); |
329 | INIT_WORK(&wcnss->probe_work, wcnss_async_probe); |
330 | |
331 | dev_set_drvdata(dev: &rpdev->dev, data: wcnss); |
332 | |
333 | schedule_work(work: &wcnss->probe_work); |
334 | |
335 | return 0; |
336 | } |
337 | |
338 | static void wcnss_ctrl_remove(struct rpmsg_device *rpdev) |
339 | { |
340 | struct wcnss_ctrl *wcnss = dev_get_drvdata(dev: &rpdev->dev); |
341 | |
342 | cancel_work_sync(work: &wcnss->probe_work); |
343 | of_platform_depopulate(parent: &rpdev->dev); |
344 | } |
345 | |
346 | static const struct of_device_id wcnss_ctrl_of_match[] = { |
347 | { .compatible = "qcom,wcnss" , }, |
348 | {} |
349 | }; |
350 | MODULE_DEVICE_TABLE(of, wcnss_ctrl_of_match); |
351 | |
352 | static struct rpmsg_driver wcnss_ctrl_driver = { |
353 | .probe = wcnss_ctrl_probe, |
354 | .remove = wcnss_ctrl_remove, |
355 | .callback = wcnss_ctrl_smd_callback, |
356 | .drv = { |
357 | .name = "qcom_wcnss_ctrl" , |
358 | .of_match_table = wcnss_ctrl_of_match, |
359 | }, |
360 | }; |
361 | |
362 | module_rpmsg_driver(wcnss_ctrl_driver); |
363 | |
364 | MODULE_DESCRIPTION("Qualcomm WCNSS control driver" ); |
365 | MODULE_LICENSE("GPL v2" ); |
366 | |