aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjorn Andersson <bjorn.andersson@linaro.org>2016-06-06 19:58:20 -0400
committerAndy Gross <andy.gross@linaro.org>2016-06-24 14:34:00 -0400
commit6be2b3d0848d1ed3e78e416cc4ae9007e85c7533 (patch)
tree3b1d507ec15248fd3b41449531072999f4682734
parent6b1751a86ce2eb6ebbffa426a703a12f15bcea28 (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.c125
-rw-r--r--include/linux/soc/qcom/wcnss_ctrl.h8
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 */
31struct wcnss_ctrl { 41struct 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,
156static int wcnss_request_version(struct wcnss_ctrl *wcnss) 176static 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 */
170static void wcnss_download_nv(struct work_struct *work) 203static 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
229release_fw: 263release_fw:
230 release_firmware(fw); 264 release_firmware(fw);
231free_req: 265free_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 */
277struct 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}
283EXPORT_SYMBOL(qcom_wcnss_open_channel);
284
285static 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
235static int wcnss_ctrl_probe(struct qcom_smd_device *sdev) 309static 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
332static 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
254static const struct qcom_smd_id wcnss_ctrl_smd_match[] = { 340static const struct of_device_id wcnss_ctrl_of_match[] = {
255 { .name = "WCNSS_CTRL" }, 341 { .compatible = "qcom,wcnss", },
256 {} 342 {}
257}; 343};
258 344
259static struct qcom_smd_driver wcnss_ctrl_driver = { 345static 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
6struct qcom_smd_channel *qcom_wcnss_open_channel(void *wcnss, const char *name, qcom_smd_cb_t cb);
7
8#endif