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