aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/soc/qcom
diff options
context:
space:
mode:
authorBjorn Andersson <bjorn.andersson@sonymobile.com>2015-09-21 13:52:55 -0400
committerAndy Gross <agross@codeaurora.org>2015-12-08 14:19:50 -0500
commitea7a1f275cf0b5aff4cc171606a6a3f27f1c6a95 (patch)
tree5b06d192d99127663bd54f3f63e4848a0b76f559 /drivers/soc/qcom
parentd9d6888feb990422a1ad0b9c18dd54cdfcbf2a41 (diff)
soc: qcom: Introduce WCNSS_CTRL SMD client
The WCNSS_CTRL SMD client is used for among other things upload nv firmware to a newly booted WCNSS chip. Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com> Signed-off-by: Andy Gross <agross@codeaurora.org>
Diffstat (limited to 'drivers/soc/qcom')
-rw-r--r--drivers/soc/qcom/Kconfig7
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/wcnss_ctrl.c272
3 files changed, 280 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 4c98ffe968c7..461b387d03cc 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -69,3 +69,10 @@ config QCOM_SMSM
69 help 69 help
70 Say yes here to support the Qualcomm Shared Memory State Machine. 70 Say yes here to support the Qualcomm Shared Memory State Machine.
71 The state machine is represented by bits in shared memory. 71 The state machine is represented by bits in shared memory.
72
73config QCOM_WCNSS_CTRL
74 tristate "Qualcomm WCNSS control driver"
75 depends on QCOM_SMD
76 help
77 Client driver for the WCNSS_CTRL SMD channel, used to download nv
78 firmware to a newly booted WCNSS chip.
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 69886e0f7ef8..fdd664edf0bd 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_QCOM_SMEM) += smem.o
6obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o 6obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
7obj-$(CONFIG_QCOM_SMP2P) += smp2p.o 7obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
8obj-$(CONFIG_QCOM_SMSM) += smsm.o 8obj-$(CONFIG_QCOM_SMSM) += smsm.o
9obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
diff --git a/drivers/soc/qcom/wcnss_ctrl.c b/drivers/soc/qcom/wcnss_ctrl.c
new file mode 100644
index 000000000000..7a986f881d5c
--- /dev/null
+++ b/drivers/soc/qcom/wcnss_ctrl.c
@@ -0,0 +1,272 @@
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 */
31struct 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 */
42enum {
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 */
58struct 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 */
67struct 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 */
83struct 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 */
96struct 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 */
109static 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 */
156static 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 */
170static 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
229release_fw:
230 release_firmware(fw);
231free_req:
232 kfree(req);
233}
234
235static 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
254static const struct qcom_smd_id wcnss_ctrl_smd_match[] = {
255 { .name = "WCNSS_CTRL" },
256 {}
257};
258
259static 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
269module_qcom_smd_driver(wcnss_ctrl_driver);
270
271MODULE_DESCRIPTION("Qualcomm WCNSS control driver");
272MODULE_LICENSE("GPL v2");