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