]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/soc/qcom/wcnss_ctrl.c
soc: qcom: wcnss_ctrl: Make wcnss_ctrl parent the other components
[karo-tx-linux.git] / drivers / soc / qcom / wcnss_ctrl.c
1 /*
2  * Copyright (c) 2016, Linaro Ltd.
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>
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>
22
23 #define WCNSS_REQUEST_TIMEOUT   (5 * HZ)
24 #define WCNSS_CBC_TIMEOUT       (10 * HZ)
25
26 #define NV_FRAGMENT_SIZE        3072
27 #define NVBIN_FILE              "wlan/prima/WCNSS_qcom_wlan_nv.bin"
28
29 /**
30  * struct wcnss_ctrl - driver context
31  * @dev:        device handle
32  * @channel:    SMD channel handle
33  * @ack:        completion for outstanding requests
34  * @cbc:        completion for cbc complete indication
35  * @ack_status: status of the outstanding request
36  * @probe_work: worker for uploading nv binary
37  */
38 struct wcnss_ctrl {
39         struct device *dev;
40         struct qcom_smd_channel *channel;
41
42         struct completion ack;
43         struct completion cbc;
44         int ack_status;
45
46         struct work_struct probe_work;
47 };
48
49 /* message types */
50 enum {
51         WCNSS_VERSION_REQ = 0x01000000,
52         WCNSS_VERSION_RESP,
53         WCNSS_DOWNLOAD_NV_REQ,
54         WCNSS_DOWNLOAD_NV_RESP,
55         WCNSS_UPLOAD_CAL_REQ,
56         WCNSS_UPLOAD_CAL_RESP,
57         WCNSS_DOWNLOAD_CAL_REQ,
58         WCNSS_DOWNLOAD_CAL_RESP,
59         WCNSS_VBAT_LEVEL_IND,
60         WCNSS_BUILD_VERSION_REQ,
61         WCNSS_BUILD_VERSION_RESP,
62         WCNSS_PM_CONFIG_REQ,
63         WCNSS_CBC_COMPLETE_IND,
64 };
65
66 /**
67  * struct wcnss_msg_hdr - common packet header for requests and responses
68  * @type:       packet message type
69  * @len:        total length of the packet, including this header
70  */
71 struct wcnss_msg_hdr {
72         u32 type;
73         u32 len;
74 } __packed;
75
76 /**
77  * struct wcnss_version_resp - version request response
78  * @hdr:        common packet wcnss_msg_hdr header
79  */
80 struct wcnss_version_resp {
81         struct wcnss_msg_hdr hdr;
82         u8 major;
83         u8 minor;
84         u8 version;
85         u8 revision;
86 } __packed;
87
88 /**
89  * struct wcnss_download_nv_req - firmware fragment request
90  * @hdr:        common packet wcnss_msg_hdr header
91  * @seq:        sequence number of this fragment
92  * @last:       boolean indicator of this being the last fragment of the binary
93  * @frag_size:  length of this fragment
94  * @fragment:   fragment data
95  */
96 struct wcnss_download_nv_req {
97         struct wcnss_msg_hdr hdr;
98         u16 seq;
99         u16 last;
100         u32 frag_size;
101         u8 fragment[];
102 } __packed;
103
104 /**
105  * struct wcnss_download_nv_resp - firmware download response
106  * @hdr:        common packet wcnss_msg_hdr header
107  * @status:     boolean to indicate success of the download
108  */
109 struct wcnss_download_nv_resp {
110         struct wcnss_msg_hdr hdr;
111         u8 status;
112 } __packed;
113
114 /**
115  * wcnss_ctrl_smd_callback() - handler from SMD responses
116  * @qsdev:      smd device handle
117  * @data:       pointer to the incoming data packet
118  * @count:      size of the incoming data packet
119  *
120  * Handles any incoming packets from the remote WCNSS_CTRL service.
121  */
122 static int wcnss_ctrl_smd_callback(struct qcom_smd_device *qsdev,
123                                    const void *data,
124                                    size_t count)
125 {
126         struct wcnss_ctrl *wcnss = dev_get_drvdata(&qsdev->dev);
127         const struct wcnss_download_nv_resp *nvresp;
128         const struct wcnss_version_resp *version;
129         const struct wcnss_msg_hdr *hdr = data;
130
131         switch (hdr->type) {
132         case WCNSS_VERSION_RESP:
133                 if (count != sizeof(*version)) {
134                         dev_err(wcnss->dev,
135                                 "invalid size of version response\n");
136                         break;
137                 }
138
139                 version = data;
140                 dev_info(wcnss->dev, "WCNSS Version %d.%d %d.%d\n",
141                          version->major, version->minor,
142                          version->version, version->revision);
143
144                 complete(&wcnss->ack);
145                 break;
146         case WCNSS_DOWNLOAD_NV_RESP:
147                 if (count != sizeof(*nvresp)) {
148                         dev_err(wcnss->dev,
149                                 "invalid size of download response\n");
150                         break;
151                 }
152
153                 nvresp = data;
154                 wcnss->ack_status = nvresp->status;
155                 complete(&wcnss->ack);
156                 break;
157         case WCNSS_CBC_COMPLETE_IND:
158                 dev_dbg(wcnss->dev, "WCNSS booted\n");
159                 complete(&wcnss->cbc);
160                 break;
161         default:
162                 dev_info(wcnss->dev, "unknown message type %d\n", hdr->type);
163                 break;
164         }
165
166         return 0;
167 }
168
169 /**
170  * wcnss_request_version() - send a version request to WCNSS
171  * @wcnss:      wcnss ctrl driver context
172  */
173 static int wcnss_request_version(struct wcnss_ctrl *wcnss)
174 {
175         struct wcnss_msg_hdr msg;
176         int ret;
177
178         msg.type = WCNSS_VERSION_REQ;
179         msg.len = sizeof(msg);
180         ret = qcom_smd_send(wcnss->channel, &msg, sizeof(msg));
181         if (ret < 0)
182                 return ret;
183
184         ret = wait_for_completion_timeout(&wcnss->ack, WCNSS_CBC_TIMEOUT);
185         if (!ret) {
186                 dev_err(wcnss->dev, "timeout waiting for version response\n");
187                 return -ETIMEDOUT;
188         }
189
190         return 0;
191 }
192
193 /**
194  * wcnss_download_nv() - send nv binary to WCNSS
195  * @work:       work struct to acquire wcnss context
196  */
197 static int wcnss_download_nv(struct wcnss_ctrl *wcnss)
198 {
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)
207                 return -ENOMEM;
208
209         ret = request_firmware(&fw, NVBIN_FILE, wcnss->dev);
210         if (ret) {
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
235                 ret = qcom_smd_send(wcnss->channel, req, req->hdr.len);
236                 if (ret) {
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
244                 data += req->hdr.len;
245                 left -= NV_FRAGMENT_SIZE;
246         } while (left > 0);
247
248         ret = wait_for_completion_timeout(&wcnss->ack, WCNSS_REQUEST_TIMEOUT);
249         if (!ret) {
250                 dev_err(wcnss->dev, "timeout waiting for nv upload ack\n");
251                 ret = -ETIMEDOUT;
252         } else {
253                 ret = wcnss->ack_status;
254         }
255
256 release_fw:
257         release_firmware(fw);
258 free_req:
259         kfree(req);
260
261         return ret;
262 }
263
264 static void wcnss_async_probe(struct work_struct *work)
265 {
266         struct wcnss_ctrl *wcnss = container_of(work, struct wcnss_ctrl, probe_work);
267         int ret;
268
269         ret = wcnss_request_version(wcnss);
270         if (ret < 0)
271                 return;
272
273         ret = wcnss_download_nv(wcnss);
274         if (ret < 0)
275                 return;
276
277         if (ret == 2) {
278                 ret = wait_for_completion_timeout(&wcnss->cbc, WCNSS_REQUEST_TIMEOUT);
279                 if (!ret)
280                         dev_err(wcnss->dev, "expected cbc completion\n");
281         }
282
283         of_platform_populate(wcnss->dev->of_node, NULL, NULL, wcnss->dev);
284 }
285
286 static int wcnss_ctrl_probe(struct qcom_smd_device *sdev)
287 {
288         struct wcnss_ctrl *wcnss;
289
290         wcnss = devm_kzalloc(&sdev->dev, sizeof(*wcnss), GFP_KERNEL);
291         if (!wcnss)
292                 return -ENOMEM;
293
294         wcnss->dev = &sdev->dev;
295         wcnss->channel = sdev->channel;
296
297         init_completion(&wcnss->ack);
298         init_completion(&wcnss->cbc);
299         INIT_WORK(&wcnss->probe_work, wcnss_async_probe);
300
301         dev_set_drvdata(&sdev->dev, wcnss);
302
303         schedule_work(&wcnss->probe_work);
304
305         return 0;
306 }
307
308 static void wcnss_ctrl_remove(struct qcom_smd_device *sdev)
309 {
310         struct wcnss_ctrl *wcnss = dev_get_drvdata(&sdev->dev);
311
312         cancel_work_sync(&wcnss->probe_work);
313         of_platform_depopulate(&sdev->dev);
314 }
315
316 static const struct of_device_id wcnss_ctrl_of_match[] = {
317         { .compatible = "qcom,wcnss", },
318         {}
319 };
320
321 static struct qcom_smd_driver wcnss_ctrl_driver = {
322         .probe = wcnss_ctrl_probe,
323         .remove = wcnss_ctrl_remove,
324         .callback = wcnss_ctrl_smd_callback,
325         .driver  = {
326                 .name  = "qcom_wcnss_ctrl",
327                 .owner = THIS_MODULE,
328                 .of_match_table = wcnss_ctrl_of_match,
329         },
330 };
331
332 module_qcom_smd_driver(wcnss_ctrl_driver);
333
334 MODULE_DESCRIPTION("Qualcomm WCNSS control driver");
335 MODULE_LICENSE("GPL v2");