Commit | Line | Data |
---|---|---|
ea7a1f27 BA |
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"); |