diff options
author | Bjorn Andersson <bjorn.andersson@linaro.org> | 2016-06-06 19:58:20 -0400 |
---|---|---|
committer | Andy Gross <andy.gross@linaro.org> | 2016-06-24 14:34:00 -0400 |
commit | 6be2b3d0848d1ed3e78e416cc4ae9007e85c7533 (patch) | |
tree | 3b1d507ec15248fd3b41449531072999f4682734 | |
parent | 6b1751a86ce2eb6ebbffa426a703a12f15bcea28 (diff) |
soc: qcom: wcnss_ctrl: Make wcnss_ctrl parent the other components
We need the signal from wcnss_ctrl indicating that the firmware is up
and running before we can communicate with the other components of the
chip. So make these other components children of the wcnss_ctrl device,
so they can be probed in order.
The process seems to take between 1/2-5 seconds, so this is done in a
worker, instead of holding up the probe.
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
Signed-off-by: Andy Gross <andy.gross@linaro.org>
-rw-r--r-- | drivers/soc/qcom/wcnss_ctrl.c | 125 | ||||
-rw-r--r-- | include/linux/soc/qcom/wcnss_ctrl.h | 8 |
2 files changed, 114 insertions, 19 deletions
diff --git a/drivers/soc/qcom/wcnss_ctrl.c b/drivers/soc/qcom/wcnss_ctrl.c index c544f3d2c6ee..520aedd29965 100644 --- a/drivers/soc/qcom/wcnss_ctrl.c +++ b/drivers/soc/qcom/wcnss_ctrl.c | |||
@@ -1,4 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (c) 2016, Linaro Ltd. | ||
2 | * Copyright (c) 2015, Sony Mobile Communications Inc. | 3 | * Copyright (c) 2015, Sony Mobile Communications Inc. |
3 | * | 4 | * |
4 | * This program is free software; you can redistribute it and/or modify | 5 | * This program is free software; you can redistribute it and/or modify |
@@ -14,8 +15,16 @@ | |||
14 | #include <linux/module.h> | 15 | #include <linux/module.h> |
15 | #include <linux/slab.h> | 16 | #include <linux/slab.h> |
16 | #include <linux/soc/qcom/smd.h> | 17 | #include <linux/soc/qcom/smd.h> |
18 | #include <linux/io.h> | ||
19 | #include <linux/of_platform.h> | ||
20 | #include <linux/platform_device.h> | ||
21 | #include <linux/soc/qcom/wcnss_ctrl.h> | ||
17 | 22 | ||
18 | #define WCNSS_REQUEST_TIMEOUT (5 * HZ) | 23 | #define WCNSS_REQUEST_TIMEOUT (5 * HZ) |
24 | #define WCNSS_CBC_TIMEOUT (10 * HZ) | ||
25 | |||
26 | #define WCNSS_ACK_DONE_BOOTING 1 | ||
27 | #define WCNSS_ACK_COLD_BOOTING 2 | ||
19 | 28 | ||
20 | #define NV_FRAGMENT_SIZE 3072 | 29 | #define NV_FRAGMENT_SIZE 3072 |
21 | #define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin" | 30 | #define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin" |
@@ -25,17 +34,19 @@ | |||
25 | * @dev: device handle | 34 | * @dev: device handle |
26 | * @channel: SMD channel handle | 35 | * @channel: SMD channel handle |
27 | * @ack: completion for outstanding requests | 36 | * @ack: completion for outstanding requests |
37 | * @cbc: completion for cbc complete indication | ||
28 | * @ack_status: status of the outstanding request | 38 | * @ack_status: status of the outstanding request |
29 | * @download_nv_work: worker for uploading nv binary | 39 | * @probe_work: worker for uploading nv binary |
30 | */ | 40 | */ |
31 | struct wcnss_ctrl { | 41 | struct wcnss_ctrl { |
32 | struct device *dev; | 42 | struct device *dev; |
33 | struct qcom_smd_channel *channel; | 43 | struct qcom_smd_channel *channel; |
34 | 44 | ||
35 | struct completion ack; | 45 | struct completion ack; |
46 | struct completion cbc; | ||
36 | int ack_status; | 47 | int ack_status; |
37 | 48 | ||
38 | struct work_struct download_nv_work; | 49 | struct work_struct probe_work; |
39 | }; | 50 | }; |
40 | 51 | ||
41 | /* message types */ | 52 | /* message types */ |
@@ -48,6 +59,11 @@ enum { | |||
48 | WCNSS_UPLOAD_CAL_RESP, | 59 | WCNSS_UPLOAD_CAL_RESP, |
49 | WCNSS_DOWNLOAD_CAL_REQ, | 60 | WCNSS_DOWNLOAD_CAL_REQ, |
50 | WCNSS_DOWNLOAD_CAL_RESP, | 61 | WCNSS_DOWNLOAD_CAL_RESP, |
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, | ||
51 | }; | 67 | }; |
52 | 68 | ||
53 | /** | 69 | /** |
@@ -128,7 +144,7 @@ static int wcnss_ctrl_smd_callback(struct qcom_smd_channel *channel, | |||
128 | version->major, version->minor, | 144 | version->major, version->minor, |
129 | version->version, version->revision); | 145 | version->version, version->revision); |
130 | 146 | ||
131 | schedule_work(&wcnss->download_nv_work); | 147 | complete(&wcnss->ack); |
132 | break; | 148 | break; |
133 | case WCNSS_DOWNLOAD_NV_RESP: | 149 | case WCNSS_DOWNLOAD_NV_RESP: |
134 | if (count != sizeof(*nvresp)) { | 150 | if (count != sizeof(*nvresp)) { |
@@ -141,6 +157,10 @@ static int wcnss_ctrl_smd_callback(struct qcom_smd_channel *channel, | |||
141 | wcnss->ack_status = nvresp->status; | 157 | wcnss->ack_status = nvresp->status; |
142 | complete(&wcnss->ack); | 158 | complete(&wcnss->ack); |
143 | break; | 159 | break; |
160 | case WCNSS_CBC_COMPLETE_IND: | ||
161 | dev_dbg(wcnss->dev, "cold boot complete\n"); | ||
162 | complete(&wcnss->cbc); | ||
163 | break; | ||
144 | default: | 164 | default: |
145 | dev_info(wcnss->dev, "unknown message type %d\n", hdr->type); | 165 | dev_info(wcnss->dev, "unknown message type %d\n", hdr->type); |
146 | break; | 166 | break; |
@@ -156,20 +176,32 @@ static int wcnss_ctrl_smd_callback(struct qcom_smd_channel *channel, | |||
156 | static int wcnss_request_version(struct wcnss_ctrl *wcnss) | 176 | static int wcnss_request_version(struct wcnss_ctrl *wcnss) |
157 | { | 177 | { |
158 | struct wcnss_msg_hdr msg; | 178 | struct wcnss_msg_hdr msg; |
179 | int ret; | ||
159 | 180 | ||
160 | msg.type = WCNSS_VERSION_REQ; | 181 | msg.type = WCNSS_VERSION_REQ; |
161 | msg.len = sizeof(msg); | 182 | msg.len = sizeof(msg); |
183 | ret = qcom_smd_send(wcnss->channel, &msg, sizeof(msg)); | ||
184 | if (ret < 0) | ||
185 | return ret; | ||
186 | |||
187 | ret = wait_for_completion_timeout(&wcnss->ack, WCNSS_CBC_TIMEOUT); | ||
188 | if (!ret) { | ||
189 | dev_err(wcnss->dev, "timeout waiting for version response\n"); | ||
190 | return -ETIMEDOUT; | ||
191 | } | ||
162 | 192 | ||
163 | return qcom_smd_send(wcnss->channel, &msg, sizeof(msg)); | 193 | return 0; |
164 | } | 194 | } |
165 | 195 | ||
166 | /** | 196 | /** |
167 | * wcnss_download_nv() - send nv binary to WCNSS | 197 | * wcnss_download_nv() - send nv binary to WCNSS |
168 | * @work: work struct to acquire wcnss context | 198 | * @wcnss: wcnss_ctrl state handle |
199 | * @expect_cbc: indicator to caller that an cbc event is expected | ||
200 | * | ||
201 | * Returns 0 on success. Negative errno on failure. | ||
169 | */ | 202 | */ |
170 | static void wcnss_download_nv(struct work_struct *work) | 203 | static int wcnss_download_nv(struct wcnss_ctrl *wcnss, bool *expect_cbc) |
171 | { | 204 | { |
172 | struct wcnss_ctrl *wcnss = container_of(work, struct wcnss_ctrl, download_nv_work); | ||
173 | struct wcnss_download_nv_req *req; | 205 | struct wcnss_download_nv_req *req; |
174 | const struct firmware *fw; | 206 | const struct firmware *fw; |
175 | const void *data; | 207 | const void *data; |
@@ -178,10 +210,10 @@ static void wcnss_download_nv(struct work_struct *work) | |||
178 | 210 | ||
179 | req = kzalloc(sizeof(*req) + NV_FRAGMENT_SIZE, GFP_KERNEL); | 211 | req = kzalloc(sizeof(*req) + NV_FRAGMENT_SIZE, GFP_KERNEL); |
180 | if (!req) | 212 | if (!req) |
181 | return; | 213 | return -ENOMEM; |
182 | 214 | ||
183 | ret = request_firmware(&fw, NVBIN_FILE, wcnss->dev); | 215 | ret = request_firmware(&fw, NVBIN_FILE, wcnss->dev); |
184 | if (ret) { | 216 | if (ret < 0) { |
185 | dev_err(wcnss->dev, "Failed to load nv file %s: %d\n", | 217 | dev_err(wcnss->dev, "Failed to load nv file %s: %d\n", |
186 | NVBIN_FILE, ret); | 218 | NVBIN_FILE, ret); |
187 | goto free_req; | 219 | goto free_req; |
@@ -207,7 +239,7 @@ static void wcnss_download_nv(struct work_struct *work) | |||
207 | memcpy(req->fragment, data, req->frag_size); | 239 | memcpy(req->fragment, data, req->frag_size); |
208 | 240 | ||
209 | ret = qcom_smd_send(wcnss->channel, req, req->hdr.len); | 241 | ret = qcom_smd_send(wcnss->channel, req, req->hdr.len); |
210 | if (ret) { | 242 | if (ret < 0) { |
211 | dev_err(wcnss->dev, "failed to send smd packet\n"); | 243 | dev_err(wcnss->dev, "failed to send smd packet\n"); |
212 | goto release_fw; | 244 | goto release_fw; |
213 | } | 245 | } |
@@ -220,16 +252,58 @@ static void wcnss_download_nv(struct work_struct *work) | |||
220 | } while (left > 0); | 252 | } while (left > 0); |
221 | 253 | ||
222 | ret = wait_for_completion_timeout(&wcnss->ack, WCNSS_REQUEST_TIMEOUT); | 254 | ret = wait_for_completion_timeout(&wcnss->ack, WCNSS_REQUEST_TIMEOUT); |
223 | if (!ret) | 255 | if (!ret) { |
224 | dev_err(wcnss->dev, "timeout waiting for nv upload ack\n"); | 256 | dev_err(wcnss->dev, "timeout waiting for nv upload ack\n"); |
225 | else if (wcnss->ack_status != 1) | 257 | ret = -ETIMEDOUT; |
226 | dev_err(wcnss->dev, "nv upload response failed err: %d\n", | 258 | } else { |
227 | wcnss->ack_status); | 259 | *expect_cbc = wcnss->ack_status == WCNSS_ACK_COLD_BOOTING; |
260 | ret = 0; | ||
261 | } | ||
228 | 262 | ||
229 | release_fw: | 263 | release_fw: |
230 | release_firmware(fw); | 264 | release_firmware(fw); |
231 | free_req: | 265 | free_req: |
232 | kfree(req); | 266 | kfree(req); |
267 | |||
268 | return ret; | ||
269 | } | ||
270 | |||
271 | /** | ||
272 | * qcom_wcnss_open_channel() - open additional SMD channel to WCNSS | ||
273 | * @wcnss: wcnss handle, retrieved from drvdata | ||
274 | * @name: SMD channel name | ||
275 | * @cb: callback to handle incoming data on the channel | ||
276 | */ | ||
277 | struct qcom_smd_channel *qcom_wcnss_open_channel(void *wcnss, const char *name, qcom_smd_cb_t cb) | ||
278 | { | ||
279 | struct wcnss_ctrl *_wcnss = wcnss; | ||
280 | |||
281 | return qcom_smd_open_channel(_wcnss->channel, name, cb); | ||
282 | } | ||
283 | EXPORT_SYMBOL(qcom_wcnss_open_channel); | ||
284 | |||
285 | static void wcnss_async_probe(struct work_struct *work) | ||
286 | { | ||
287 | struct wcnss_ctrl *wcnss = container_of(work, struct wcnss_ctrl, probe_work); | ||
288 | bool expect_cbc; | ||
289 | int ret; | ||
290 | |||
291 | ret = wcnss_request_version(wcnss); | ||
292 | if (ret < 0) | ||
293 | return; | ||
294 | |||
295 | ret = wcnss_download_nv(wcnss, &expect_cbc); | ||
296 | if (ret < 0) | ||
297 | return; | ||
298 | |||
299 | /* Wait for pending cold boot completion if indicated by the nv downloader */ | ||
300 | if (expect_cbc) { | ||
301 | ret = wait_for_completion_timeout(&wcnss->cbc, WCNSS_REQUEST_TIMEOUT); | ||
302 | if (!ret) | ||
303 | dev_err(wcnss->dev, "expected cold boot completion\n"); | ||
304 | } | ||
305 | |||
306 | of_platform_populate(wcnss->dev->of_node, NULL, NULL, wcnss->dev); | ||
233 | } | 307 | } |
234 | 308 | ||
235 | static int wcnss_ctrl_probe(struct qcom_smd_device *sdev) | 309 | static int wcnss_ctrl_probe(struct qcom_smd_device *sdev) |
@@ -244,25 +318,38 @@ static int wcnss_ctrl_probe(struct qcom_smd_device *sdev) | |||
244 | wcnss->channel = sdev->channel; | 318 | wcnss->channel = sdev->channel; |
245 | 319 | ||
246 | init_completion(&wcnss->ack); | 320 | init_completion(&wcnss->ack); |
247 | INIT_WORK(&wcnss->download_nv_work, wcnss_download_nv); | 321 | init_completion(&wcnss->cbc); |
322 | INIT_WORK(&wcnss->probe_work, wcnss_async_probe); | ||
248 | 323 | ||
249 | qcom_smd_set_drvdata(sdev->channel, wcnss); | 324 | qcom_smd_set_drvdata(sdev->channel, wcnss); |
325 | dev_set_drvdata(&sdev->dev, wcnss); | ||
326 | |||
327 | schedule_work(&wcnss->probe_work); | ||
328 | |||
329 | return 0; | ||
330 | } | ||
331 | |||
332 | static void wcnss_ctrl_remove(struct qcom_smd_device *sdev) | ||
333 | { | ||
334 | struct wcnss_ctrl *wcnss = qcom_smd_get_drvdata(sdev->channel); | ||
250 | 335 | ||
251 | return wcnss_request_version(wcnss); | 336 | cancel_work_sync(&wcnss->probe_work); |
337 | of_platform_depopulate(&sdev->dev); | ||
252 | } | 338 | } |
253 | 339 | ||
254 | static const struct qcom_smd_id wcnss_ctrl_smd_match[] = { | 340 | static const struct of_device_id wcnss_ctrl_of_match[] = { |
255 | { .name = "WCNSS_CTRL" }, | 341 | { .compatible = "qcom,wcnss", }, |
256 | {} | 342 | {} |
257 | }; | 343 | }; |
258 | 344 | ||
259 | static struct qcom_smd_driver wcnss_ctrl_driver = { | 345 | static struct qcom_smd_driver wcnss_ctrl_driver = { |
260 | .probe = wcnss_ctrl_probe, | 346 | .probe = wcnss_ctrl_probe, |
347 | .remove = wcnss_ctrl_remove, | ||
261 | .callback = wcnss_ctrl_smd_callback, | 348 | .callback = wcnss_ctrl_smd_callback, |
262 | .smd_match_table = wcnss_ctrl_smd_match, | ||
263 | .driver = { | 349 | .driver = { |
264 | .name = "qcom_wcnss_ctrl", | 350 | .name = "qcom_wcnss_ctrl", |
265 | .owner = THIS_MODULE, | 351 | .owner = THIS_MODULE, |
352 | .of_match_table = wcnss_ctrl_of_match, | ||
266 | }, | 353 | }, |
267 | }; | 354 | }; |
268 | 355 | ||
diff --git a/include/linux/soc/qcom/wcnss_ctrl.h b/include/linux/soc/qcom/wcnss_ctrl.h new file mode 100644 index 000000000000..a37bc5538f19 --- /dev/null +++ b/include/linux/soc/qcom/wcnss_ctrl.h | |||
@@ -0,0 +1,8 @@ | |||
1 | #ifndef __WCNSS_CTRL_H__ | ||
2 | #define __WCNSS_CTRL_H__ | ||
3 | |||
4 | #include <linux/soc/qcom/smd.h> | ||
5 | |||
6 | struct qcom_smd_channel *qcom_wcnss_open_channel(void *wcnss, const char *name, qcom_smd_cb_t cb); | ||
7 | |||
8 | #endif | ||