]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/soc/qcom/wcnss_ctrl.c
7a986f881d5c860df19614c4ca781bce69d2d296
[karo-tx-linux.git] / drivers / soc / qcom / wcnss_ctrl.c
1 /*
2  * Copyright (c) 2015, Sony Mobile Communications Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 and
6  * only version 2 as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  */
13 #include <linux/firmware.h>
14 #include <linux/module.h>
15 #include <linux/slab.h>
16 #include <linux/soc/qcom/smd.h>
17
18 #define WCNSS_REQUEST_TIMEOUT   (5 * HZ)
19
20 #define NV_FRAGMENT_SIZE        3072
21 #define NVBIN_FILE              "wlan/prima/WCNSS_qcom_wlan_nv.bin"
22
23 /**
24  * struct wcnss_ctrl - driver context
25  * @dev:        device handle
26  * @channel:    SMD channel handle
27  * @ack:        completion for outstanding requests
28  * @ack_status: status of the outstanding request
29  * @download_nv_work: worker for uploading nv binary
30  */
31 struct wcnss_ctrl {
32         struct device *dev;
33         struct qcom_smd_channel *channel;
34
35         struct completion ack;
36         int ack_status;
37
38         struct work_struct download_nv_work;
39 };
40
41 /* message types */
42 enum {
43         WCNSS_VERSION_REQ = 0x01000000,
44         WCNSS_VERSION_RESP,
45         WCNSS_DOWNLOAD_NV_REQ,
46         WCNSS_DOWNLOAD_NV_RESP,
47         WCNSS_UPLOAD_CAL_REQ,
48         WCNSS_UPLOAD_CAL_RESP,
49         WCNSS_DOWNLOAD_CAL_REQ,
50         WCNSS_DOWNLOAD_CAL_RESP,
51 };
52
53 /**
54  * struct wcnss_msg_hdr - common packet header for requests and responses
55  * @type:       packet message type
56  * @len:        total length of the packet, including this header
57  */
58 struct wcnss_msg_hdr {
59         u32 type;
60         u32 len;
61 } __packed;
62
63 /**
64  * struct wcnss_version_resp - version request response
65  * @hdr:        common packet wcnss_msg_hdr header
66  */
67 struct wcnss_version_resp {
68         struct wcnss_msg_hdr hdr;
69         u8 major;
70         u8 minor;
71         u8 version;
72         u8 revision;
73 } __packed;
74
75 /**
76  * struct wcnss_download_nv_req - firmware fragment request
77  * @hdr:        common packet wcnss_msg_hdr header
78  * @seq:        sequence number of this fragment
79  * @last:       boolean indicator of this being the last fragment of the binary
80  * @frag_size:  length of this fragment
81  * @fragment:   fragment data
82  */
83 struct wcnss_download_nv_req {
84         struct wcnss_msg_hdr hdr;
85         u16 seq;
86         u16 last;
87         u32 frag_size;
88         u8 fragment[];
89 } __packed;
90
91 /**
92  * struct wcnss_download_nv_resp - firmware download response
93  * @hdr:        common packet wcnss_msg_hdr header
94  * @status:     boolean to indicate success of the download
95  */
96 struct wcnss_download_nv_resp {
97         struct wcnss_msg_hdr hdr;
98         u8 status;
99 } __packed;
100
101 /**
102  * wcnss_ctrl_smd_callback() - handler from SMD responses
103  * @qsdev:      smd device handle
104  * @data:       pointer to the incoming data packet
105  * @count:      size of the incoming data packet
106  *
107  * Handles any incoming packets from the remote WCNSS_CTRL service.
108  */
109 static int wcnss_ctrl_smd_callback(struct qcom_smd_device *qsdev,
110                                    const void *data,
111                                    size_t count)
112 {
113         struct wcnss_ctrl *wcnss = dev_get_drvdata(&qsdev->dev);
114         const struct wcnss_download_nv_resp *nvresp;
115         const struct wcnss_version_resp *version;
116         const struct wcnss_msg_hdr *hdr = data;
117
118         switch (hdr->type) {
119         case WCNSS_VERSION_RESP:
120                 if (count != sizeof(*version)) {
121                         dev_err(wcnss->dev,
122                                 "invalid size of version response\n");
123                         break;
124                 }
125
126                 version = data;
127                 dev_info(wcnss->dev, "WCNSS Version %d.%d %d.%d\n",
128                          version->major, version->minor,
129                          version->version, version->revision);
130
131                 schedule_work(&wcnss->download_nv_work);
132                 break;
133         case WCNSS_DOWNLOAD_NV_RESP:
134                 if (count != sizeof(*nvresp)) {
135                         dev_err(wcnss->dev,
136                                 "invalid size of download response\n");
137                         break;
138                 }
139
140                 nvresp = data;
141                 wcnss->ack_status = nvresp->status;
142                 complete(&wcnss->ack);
143                 break;
144         default:
145                 dev_info(wcnss->dev, "unknown message type %d\n", hdr->type);
146                 break;
147         }
148
149         return 0;
150 }
151
152 /**
153  * wcnss_request_version() - send a version request to WCNSS
154  * @wcnss:      wcnss ctrl driver context
155  */
156 static int wcnss_request_version(struct wcnss_ctrl *wcnss)
157 {
158         struct wcnss_msg_hdr msg;
159
160         msg.type = WCNSS_VERSION_REQ;
161         msg.len = sizeof(msg);
162
163         return qcom_smd_send(wcnss->channel, &msg, sizeof(msg));
164 }
165
166 /**
167  * wcnss_download_nv() - send nv binary to WCNSS
168  * @work:       work struct to acquire wcnss context
169  */
170 static void wcnss_download_nv(struct work_struct *work)
171 {
172         struct wcnss_ctrl *wcnss = container_of(work, struct wcnss_ctrl, download_nv_work);
173         struct wcnss_download_nv_req *req;
174         const struct firmware *fw;
175         const void *data;
176         ssize_t left;
177         int ret;
178
179         req = kzalloc(sizeof(*req) + NV_FRAGMENT_SIZE, GFP_KERNEL);
180         if (!req)
181                 return;
182
183         ret = request_firmware(&fw, NVBIN_FILE, wcnss->dev);
184         if (ret) {
185                 dev_err(wcnss->dev, "Failed to load nv file %s: %d\n",
186                         NVBIN_FILE, ret);
187                 goto free_req;
188         }
189
190         data = fw->data;
191         left = fw->size;
192
193         req->hdr.type = WCNSS_DOWNLOAD_NV_REQ;
194         req->hdr.len = sizeof(*req) + NV_FRAGMENT_SIZE;
195
196         req->last = 0;
197         req->frag_size = NV_FRAGMENT_SIZE;
198
199         req->seq = 0;
200         do {
201                 if (left <= NV_FRAGMENT_SIZE) {
202                         req->last = 1;
203                         req->frag_size = left;
204                         req->hdr.len = sizeof(*req) + left;
205                 }
206
207                 memcpy(req->fragment, data, req->frag_size);
208
209                 ret = qcom_smd_send(wcnss->channel, req, req->hdr.len);
210                 if (ret) {
211                         dev_err(wcnss->dev, "failed to send smd packet\n");
212                         goto release_fw;
213                 }
214
215                 /* Increment for next fragment */
216                 req->seq++;
217
218                 data += req->hdr.len;
219                 left -= NV_FRAGMENT_SIZE;
220         } while (left > 0);
221
222         ret = wait_for_completion_timeout(&wcnss->ack, WCNSS_REQUEST_TIMEOUT);
223         if (!ret)
224                 dev_err(wcnss->dev, "timeout waiting for nv upload ack\n");
225         else if (wcnss->ack_status != 1)
226                 dev_err(wcnss->dev, "nv upload response failed err: %d\n",
227                         wcnss->ack_status);
228
229 release_fw:
230         release_firmware(fw);
231 free_req:
232         kfree(req);
233 }
234
235 static int wcnss_ctrl_probe(struct qcom_smd_device *sdev)
236 {
237         struct wcnss_ctrl *wcnss;
238
239         wcnss = devm_kzalloc(&sdev->dev, sizeof(*wcnss), GFP_KERNEL);
240         if (!wcnss)
241                 return -ENOMEM;
242
243         wcnss->dev = &sdev->dev;
244         wcnss->channel = sdev->channel;
245
246         init_completion(&wcnss->ack);
247         INIT_WORK(&wcnss->download_nv_work, wcnss_download_nv);
248
249         dev_set_drvdata(&sdev->dev, wcnss);
250
251         return wcnss_request_version(wcnss);
252 }
253
254 static const struct qcom_smd_id wcnss_ctrl_smd_match[] = {
255         { .name = "WCNSS_CTRL" },
256         {}
257 };
258
259 static struct qcom_smd_driver wcnss_ctrl_driver = {
260         .probe = wcnss_ctrl_probe,
261         .callback = wcnss_ctrl_smd_callback,
262         .smd_match_table = wcnss_ctrl_smd_match,
263         .driver  = {
264                 .name  = "qcom_wcnss_ctrl",
265                 .owner = THIS_MODULE,
266         },
267 };
268
269 module_qcom_smd_driver(wcnss_ctrl_driver);
270
271 MODULE_DESCRIPTION("Qualcomm WCNSS control driver");
272 MODULE_LICENSE("GPL v2");