blob: e8b8fc22a2908a721c80acd95d932034bd3b1d37 [file] [log] [blame]
Sameer Thalappilf106a682013-02-16 20:41:11 -08001/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#include <linux/module.h>
Sameer Thalappilf106a682013-02-16 20:41:11 -080014#include <linux/firmware.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070015#include <linux/slab.h>
16#include <linux/err.h>
17#include <linux/platform_device.h>
Jeff Johnsonb3377e32011-11-18 23:32:27 -080018#include <linux/miscdevice.h>
19#include <linux/fs.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070020#include <linux/wcnss_wlan.h>
Ankur Nandwanib0039b02011-08-09 14:00:45 -070021#include <linux/platform_data/qcom_wcnss_device.h>
David Collinsb727f7d2011-09-12 11:54:49 -070022#include <linux/workqueue.h>
23#include <linux/jiffies.h>
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -070024#include <linux/gpio.h>
Sameer Thalappilf138da52012-07-30 12:56:37 -070025#include <linux/wakelock.h>
Sameer Thalappileeb8c412012-08-10 17:17:09 -070026#include <linux/delay.h>
Sameer Thalappil8d686d42012-08-24 10:07:31 -070027#include <linux/of.h>
28#include <linux/of_gpio.h>
Sameer Thalappiled3f7da2012-11-01 12:54:01 -070029#include <linux/clk.h>
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080030#include <linux/ratelimit.h>
Sameer Thalappil1878d762013-04-24 14:54:39 -070031#include <linux/kthread.h>
32#include <linux/wait.h>
33#include <linux/uaccess.h>
Naresh Jayaram08cee442013-04-26 19:50:00 +053034#include <linux/mfd/pm8xxx/misc.h>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070035
Sameer Thalappileeb8c412012-08-10 17:17:09 -070036#include <mach/msm_smd.h>
Sameer Thalappil19e02352012-09-24 15:26:12 -070037#include <mach/msm_iomap.h>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070038#include <mach/subsystem_restart.h>
39
Sameer Thalappil24db5282012-09-10 11:58:33 -070040#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
41#include "wcnss_prealloc.h"
42#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070043
44#define DEVICE "wcnss_wlan"
45#define VERSION "1.01"
46#define WCNSS_PIL_DEVICE "wcnss"
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070047
Ankur Nandwanib0039b02011-08-09 14:00:45 -070048/* module params */
49#define WCNSS_CONFIG_UNSPECIFIED (-1)
Sameer Thalappileeb8c412012-08-10 17:17:09 -070050
Ankur Nandwania4510492011-08-29 15:56:23 -070051static int has_48mhz_xo = WCNSS_CONFIG_UNSPECIFIED;
Jeff Johnsonb3377e32011-11-18 23:32:27 -080052module_param(has_48mhz_xo, int, S_IWUSR | S_IRUGO);
Ankur Nandwanib0039b02011-08-09 14:00:45 -070053MODULE_PARM_DESC(has_48mhz_xo, "Is an external 48 MHz XO present");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070054
Sameer Thalappil1878d762013-04-24 14:54:39 -070055static int has_calibrated_data = WCNSS_CONFIG_UNSPECIFIED;
56module_param(has_calibrated_data, int, S_IWUSR | S_IRUGO);
57MODULE_PARM_DESC(has_calibrated_data, "whether calibrated data file available");
58
Sameer Thalappil58cec312013-05-13 15:52:08 -070059static int has_autodetect_xo = WCNSS_CONFIG_UNSPECIFIED;
60module_param(has_autodetect_xo, int, S_IWUSR | S_IRUGO);
61MODULE_PARM_DESC(has_autodetect_xo, "Perform auto detect to configure IRIS XO");
62
Sheng Fangbb421672013-03-19 08:27:28 +080063static int do_not_cancel_vote = WCNSS_CONFIG_UNSPECIFIED;
64module_param(do_not_cancel_vote, int, S_IWUSR | S_IRUGO);
65MODULE_PARM_DESC(do_not_cancel_vote, "Do not cancel votes for wcnss");
66
Sameer Thalappiled3f7da2012-11-01 12:54:01 -070067static DEFINE_SPINLOCK(reg_spinlock);
68
69#define MSM_RIVA_PHYS 0x03204000
70#define MSM_PRONTO_PHYS 0xfb21b000
71
72#define RIVA_SPARE_OFFSET 0x0b4
73#define RIVA_SUSPEND_BIT BIT(24)
74
Sameer Thalappil77716e52012-11-08 13:41:04 -080075#define MSM_RIVA_CCU_BASE 0x03200800
76
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080077#define CCU_RIVA_INVALID_ADDR_OFFSET 0x100
78#define CCU_RIVA_LAST_ADDR0_OFFSET 0x104
79#define CCU_RIVA_LAST_ADDR1_OFFSET 0x108
80#define CCU_RIVA_LAST_ADDR2_OFFSET 0x10c
Sameer Thalappil77716e52012-11-08 13:41:04 -080081
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080082#define MSM_PRONTO_A2XB_BASE 0xfb100400
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080083#define A2XB_CFG_OFFSET 0x00
84#define A2XB_INT_SRC_OFFSET 0x0c
85#define A2XB_TSTBUS_CTRL_OFFSET 0x14
86#define A2XB_TSTBUS_OFFSET 0x18
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080087#define A2XB_ERR_INFO_OFFSET 0x1c
88
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080089#define WCNSS_TSTBUS_CTRL_EN BIT(0)
90#define WCNSS_TSTBUS_CTRL_AXIM (0x02 << 1)
91#define WCNSS_TSTBUS_CTRL_CMDFIFO (0x03 << 1)
92#define WCNSS_TSTBUS_CTRL_WRFIFO (0x04 << 1)
93#define WCNSS_TSTBUS_CTRL_RDFIFO (0x05 << 1)
94#define WCNSS_TSTBUS_CTRL_CTRL (0x07 << 1)
95#define WCNSS_TSTBUS_CTRL_AXIM_CFG0 (0x00 << 6)
96#define WCNSS_TSTBUS_CTRL_AXIM_CFG1 (0x01 << 6)
97#define WCNSS_TSTBUS_CTRL_CTRL_CFG0 (0x00 << 12)
98#define WCNSS_TSTBUS_CTRL_CTRL_CFG1 (0x01 << 12)
99
100#define MSM_PRONTO_CCPU_BASE 0xfb205050
101#define CCU_PRONTO_INVALID_ADDR_OFFSET 0x08
102#define CCU_PRONTO_LAST_ADDR0_OFFSET 0x0c
103#define CCU_PRONTO_LAST_ADDR1_OFFSET 0x10
104#define CCU_PRONTO_LAST_ADDR2_OFFSET 0x14
105
Sameer Thalappild0952f62013-05-03 10:18:29 -0700106#define WCNSS_DEF_WLAN_RX_BUFF_COUNT 1024
107
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700108#define WCNSS_CTRL_CHANNEL "WCNSS_CTRL"
Sameer Thalappil1878d762013-04-24 14:54:39 -0700109#define WCNSS_MAX_FRAME_SIZE (4*1024)
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700110#define WCNSS_VERSION_LEN 30
111
112/* message types */
113#define WCNSS_CTRL_MSG_START 0x01000000
Sameer Thalappil1878d762013-04-24 14:54:39 -0700114#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0)
115#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1)
116#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2)
117#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3)
118#define WCNSS_CALDATA_UPLD_REQ (WCNSS_CTRL_MSG_START + 4)
119#define WCNSS_CALDATA_UPLD_RSP (WCNSS_CTRL_MSG_START + 5)
120#define WCNSS_CALDATA_DNLD_REQ (WCNSS_CTRL_MSG_START + 6)
121#define WCNSS_CALDATA_DNLD_RSP (WCNSS_CTRL_MSG_START + 7)
Sameer Thalappilf106a682013-02-16 20:41:11 -0800122
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700123
124#define VALID_VERSION(version) \
125 ((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0)
126
Sameer Thalappil1878d762013-04-24 14:54:39 -0700127#define FW_CALDATA_CAPABLE() \
128 ((penv->fw_major >= 1) && (penv->fw_minor >= 5) ? 1 : 0)
129
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700130struct smd_msg_hdr {
Sameer Thalappilf106a682013-02-16 20:41:11 -0800131 unsigned int msg_type;
132 unsigned int msg_len;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700133};
134
135struct wcnss_version {
136 struct smd_msg_hdr hdr;
137 unsigned char major;
138 unsigned char minor;
139 unsigned char version;
140 unsigned char revision;
141};
142
Naresh Jayaram08cee442013-04-26 19:50:00 +0530143struct wcnss_pmic_dump {
144 char reg_name[10];
145 u16 reg_addr;
146};
147
148static struct wcnss_pmic_dump wcnss_pmic_reg_dump[] = {
149 {"S2", 0x1D8},
150 {"L4", 0xB4},
151 {"L10", 0xC0},
152 {"LVS2", 0x62},
153 {"S4", 0x1E8},
154 {"LVS7", 0x06C},
155 {"LVS1", 0x060},
156};
157
Sameer Thalappilf106a682013-02-16 20:41:11 -0800158#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
159
160/*
161 * On SMD channel 4K of maximum data can be transferred, including message
162 * header, so NV fragment size as next multiple of 1Kb is 3Kb.
163 */
164#define NV_FRAGMENT_SIZE 3072
Sameer Thalappil1878d762013-04-24 14:54:39 -0700165#define MAX_CALIBRATED_DATA_SIZE (64*1024)
166#define LAST_FRAGMENT (1 << 0)
167#define MESSAGE_TO_FOLLOW (1 << 1)
168#define CAN_RECEIVE_CALDATA (1 << 15)
169#define WCNSS_RESP_SUCCESS 1
170#define WCNSS_RESP_FAIL 0
171
Sameer Thalappilf106a682013-02-16 20:41:11 -0800172
173/* Macro to find the total number fragments of the NV bin Image */
174#define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \
175 (x / NV_FRAGMENT_SIZE) : ((x / NV_FRAGMENT_SIZE) + 1))
176
177struct nvbin_dnld_req_params {
178 /*
179 * Fragment sequence number of the NV bin Image. NV Bin Image
180 * might not fit into one message due to size limitation of
181 * the SMD channel FIFO so entire NV blob is chopped into
182 * multiple fragments starting with seqeunce number 0. The
183 * last fragment is indicated by marking is_last_fragment field
184 * to 1. At receiving side, NV blobs would be concatenated
185 * together without any padding bytes in between.
186 */
187 unsigned short frag_number;
188
189 /*
Sameer Thalappil1878d762013-04-24 14:54:39 -0700190 * bit 0: When set to 1 it indicates that no more fragments will
191 * be sent.
192 * bit 1: When set, a new message will be followed by this message
193 * bit 2- bit 14: Reserved
194 * bit 15: when set, it indicates that the sender is capable of
195 * receiving Calibrated data.
Sameer Thalappilf106a682013-02-16 20:41:11 -0800196 */
Sameer Thalappil1878d762013-04-24 14:54:39 -0700197 unsigned short msg_flags;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800198
199 /* NV Image size (number of bytes) */
200 unsigned int nvbin_buffer_size;
201
202 /*
203 * Following the 'nvbin_buffer_size', there should be
204 * nvbin_buffer_size bytes of NV bin Image i.e.
205 * uint8[nvbin_buffer_size].
206 */
207};
208
Sameer Thalappil1878d762013-04-24 14:54:39 -0700209
Sameer Thalappilf106a682013-02-16 20:41:11 -0800210struct nvbin_dnld_req_msg {
211 /*
212 * Note: The length specified in nvbin_dnld_req_msg messages
213 * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) +
214 * nvbin_buffer_size.
215 */
216 struct smd_msg_hdr hdr;
217 struct nvbin_dnld_req_params dnld_req_params;
218};
219
Sameer Thalappil1878d762013-04-24 14:54:39 -0700220struct cal_data_params {
221
222 /* The total size of the calibrated data, including all the
223 * fragments.
224 */
225 unsigned int total_size;
226 unsigned short frag_number;
227 /*
228 * bit 0: When set to 1 it indicates that no more fragments will
229 * be sent.
230 * bit 1: When set, a new message will be followed by this message
231 * bit 2- bit 15: Reserved
232 */
233 unsigned short msg_flags;
234 /*
235 * fragment size
236 */
237 unsigned int frag_size;
238 /*
239 * Following the frag_size, frag_size of fragmented
240 * data will be followed.
241 */
242};
243
244struct cal_data_msg {
245 /*
246 * The length specified in cal_data_msg should be
247 * hdr.msg_len = sizeof(cal_data_msg) + frag_size
248 */
249 struct smd_msg_hdr hdr;
250 struct cal_data_params cal_params;
251};
252
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700253static struct {
254 struct platform_device *pdev;
255 void *pil;
256 struct resource *mmio_res;
257 struct resource *tx_irq_res;
258 struct resource *rx_irq_res;
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700259 struct resource *gpios_5wire;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700260 const struct dev_pm_ops *pm_ops;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800261 int triggered;
262 int smd_channel_ready;
Sameer Thalappild0952f62013-05-03 10:18:29 -0700263 u32 wlan_rx_buff_count;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700264 smd_channel_t *smd_ch;
265 unsigned char wcnss_version[WCNSS_VERSION_LEN];
Sameer Thalappil1878d762013-04-24 14:54:39 -0700266 unsigned char fw_major;
267 unsigned char fw_minor;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800268 unsigned int serial_number;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800269 int thermal_mitigation;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700270 enum wcnss_hw_type wcnss_hw_type;
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700271 void (*tm_notify)(struct device *, int);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700272 struct wcnss_wlan_config wlan_config;
David Collinsb727f7d2011-09-12 11:54:49 -0700273 struct delayed_work wcnss_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700274 struct work_struct wcnssctrl_version_work;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800275 struct work_struct wcnssctrl_nvbin_dnld_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700276 struct work_struct wcnssctrl_rx_work;
Sameer Thalappilf138da52012-07-30 12:56:37 -0700277 struct wake_lock wcnss_wake_lock;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700278 void __iomem *msm_wcnss_base;
Sameer Thalappild3aad702013-01-22 18:52:24 -0800279 void __iomem *riva_ccu_base;
280 void __iomem *pronto_a2xb_base;
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800281 void __iomem *pronto_ccpu_base;
Sameer Thalappil58281ca2013-04-10 18:50:18 -0700282 void __iomem *fiq_reg;
Sameer Thalappil1878d762013-04-24 14:54:39 -0700283 int ssr_boot;
284 int nv_downloaded;
285 unsigned char *fw_cal_data;
286 unsigned char *user_cal_data;
287 int fw_cal_rcvd;
288 int fw_cal_exp_frag;
289 int fw_cal_available;
290 int user_cal_read;
291 int user_cal_available;
292 int user_cal_rcvd;
293 int user_cal_exp_size;
294 int device_opened;
295 struct mutex dev_lock;
296 wait_queue_head_t read_wait;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700297} *penv = NULL;
298
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800299static ssize_t wcnss_serial_number_show(struct device *dev,
300 struct device_attribute *attr, char *buf)
301{
302 if (!penv)
303 return -ENODEV;
304
305 return scnprintf(buf, PAGE_SIZE, "%08X\n", penv->serial_number);
306}
307
308static ssize_t wcnss_serial_number_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700309 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800310{
311 unsigned int value;
312
313 if (!penv)
314 return -ENODEV;
315
316 if (sscanf(buf, "%08X", &value) != 1)
317 return -EINVAL;
318
319 penv->serial_number = value;
320 return count;
321}
322
323static DEVICE_ATTR(serial_number, S_IRUSR | S_IWUSR,
324 wcnss_serial_number_show, wcnss_serial_number_store);
325
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800326
327static ssize_t wcnss_thermal_mitigation_show(struct device *dev,
328 struct device_attribute *attr, char *buf)
329{
330 if (!penv)
331 return -ENODEV;
332
333 return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation);
334}
335
336static ssize_t wcnss_thermal_mitigation_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700337 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800338{
339 int value;
340
341 if (!penv)
342 return -ENODEV;
343
344 if (sscanf(buf, "%d", &value) != 1)
345 return -EINVAL;
346 penv->thermal_mitigation = value;
Jeff Johnson61374652012-04-16 20:51:48 -0700347 if (penv->tm_notify)
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700348 (penv->tm_notify)(dev, value);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800349 return count;
350}
351
352static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR,
353 wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store);
354
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700355
356static ssize_t wcnss_version_show(struct device *dev,
357 struct device_attribute *attr, char *buf)
358{
359 if (!penv)
360 return -ENODEV;
361
362 return scnprintf(buf, PAGE_SIZE, "%s", penv->wcnss_version);
363}
364
365static DEVICE_ATTR(wcnss_version, S_IRUSR,
366 wcnss_version_show, NULL);
367
Naresh Jayaram08cee442013-04-26 19:50:00 +0530368void wcnss_riva_dump_pmic_regs(void)
369{
370 int i, rc;
371 u8 val;
372
373 for (i = 0; i < ARRAY_SIZE(wcnss_pmic_reg_dump); i++) {
374 val = 0;
375 rc = pm8xxx_read_register(wcnss_pmic_reg_dump[i].reg_addr,
376 &val);
377 if (rc)
378 pr_err("PMIC READ: Failed to read addr = %d\n",
379 wcnss_pmic_reg_dump[i].reg_addr);
380 else
381 pr_info_ratelimited("PMIC READ: %s addr = %x, value = %x\n",
382 wcnss_pmic_reg_dump[i].reg_name,
383 wcnss_pmic_reg_dump[i].reg_addr, val);
384 }
385}
386
Sameer Thalappil77716e52012-11-08 13:41:04 -0800387/* wcnss_reset_intr() is invoked when host drivers fails to
388 * communicate with WCNSS over SMD; so logging these registers
Sameer Thalappild3aad702013-01-22 18:52:24 -0800389 * helps to know WCNSS failure reason
390 */
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800391void wcnss_riva_log_debug_regs(void)
Sameer Thalappil77716e52012-11-08 13:41:04 -0800392{
Sameer Thalappil77716e52012-11-08 13:41:04 -0800393 void __iomem *ccu_reg;
394 u32 reg = 0;
395
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800396 ccu_reg = penv->riva_ccu_base + CCU_RIVA_INVALID_ADDR_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800397 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800398 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800399
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800400 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR0_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800401 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800402 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800403
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800404 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR1_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800405 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800406 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800407
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800408 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR2_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800409 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800410 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
Naresh Jayaram08cee442013-04-26 19:50:00 +0530411 wcnss_riva_dump_pmic_regs();
Sameer Thalappil77716e52012-11-08 13:41:04 -0800412
Sameer Thalappil77716e52012-11-08 13:41:04 -0800413}
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800414EXPORT_SYMBOL(wcnss_riva_log_debug_regs);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800415
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800416/* Log pronto debug registers before sending reset interrupt */
417void wcnss_pronto_log_debug_regs(void)
418{
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800419 void __iomem *reg_addr, *tst_addr, *tst_ctrl_addr;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800420 u32 reg = 0;
421
Sameer Thalappild3aad702013-01-22 18:52:24 -0800422 reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800423 reg = readl_relaxed(reg_addr);
424 pr_info_ratelimited("%s: A2XB_CFG_OFFSET %08x\n", __func__, reg);
425
Sameer Thalappild3aad702013-01-22 18:52:24 -0800426 reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800427 reg = readl_relaxed(reg_addr);
428 pr_info_ratelimited("%s: A2XB_INT_SRC_OFFSET %08x\n", __func__, reg);
429
Sameer Thalappild3aad702013-01-22 18:52:24 -0800430 reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800431 reg = readl_relaxed(reg_addr);
432 pr_info_ratelimited("%s: A2XB_ERR_INFO_OFFSET %08x\n", __func__, reg);
433
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800434 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_INVALID_ADDR_OFFSET;
435 reg = readl_relaxed(reg_addr);
436 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
437
438 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR0_OFFSET;
439 reg = readl_relaxed(reg_addr);
440 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
441
442 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR1_OFFSET;
443 reg = readl_relaxed(reg_addr);
444 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
445
446 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR2_OFFSET;
447 reg = readl_relaxed(reg_addr);
448 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
449
450 tst_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_OFFSET;
451 tst_ctrl_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_CTRL_OFFSET;
452
453 /* read data FIFO */
454 reg = 0;
455 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_RDFIFO;
456 writel_relaxed(reg, tst_ctrl_addr);
457 reg = readl_relaxed(tst_addr);
458 pr_info_ratelimited("%s: Read data FIFO testbus %08x\n",
459 __func__, reg);
460
461 /* command FIFO */
462 reg = 0;
463 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CMDFIFO;
464 writel_relaxed(reg, tst_ctrl_addr);
465 reg = readl_relaxed(tst_addr);
466 pr_info_ratelimited("%s: Command FIFO testbus %08x\n",
467 __func__, reg);
468
469 /* write data FIFO */
470 reg = 0;
471 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_WRFIFO;
472 writel_relaxed(reg, tst_ctrl_addr);
473 reg = readl_relaxed(tst_addr);
474 pr_info_ratelimited("%s: Rrite data FIFO testbus %08x\n",
475 __func__, reg);
476
477 /* AXIM SEL CFG0 */
478 reg = 0;
479 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
480 WCNSS_TSTBUS_CTRL_AXIM_CFG0;
481 writel_relaxed(reg, tst_ctrl_addr);
482 reg = readl_relaxed(tst_addr);
483 pr_info_ratelimited("%s: AXIM SEL CFG0 testbus %08x\n",
484 __func__, reg);
485
486 /* AXIM SEL CFG1 */
487 reg = 0;
488 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
489 WCNSS_TSTBUS_CTRL_AXIM_CFG1;
490 writel_relaxed(reg, tst_ctrl_addr);
491 reg = readl_relaxed(tst_addr);
492 pr_info_ratelimited("%s: AXIM SEL CFG1 testbus %08x\n",
493 __func__, reg);
494
495 /* CTRL SEL CFG0 */
496 reg = 0;
497 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
498 WCNSS_TSTBUS_CTRL_CTRL_CFG0;
499 writel_relaxed(reg, tst_ctrl_addr);
500 reg = readl_relaxed(tst_addr);
501 pr_info_ratelimited("%s: CTRL SEL CFG0 testbus %08x\n",
502 __func__, reg);
503
504 /* CTRL SEL CFG1 */
505 reg = 0;
506 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
507 WCNSS_TSTBUS_CTRL_CTRL_CFG1;
508 writel_relaxed(reg, tst_ctrl_addr);
509 reg = readl_relaxed(tst_addr);
510 pr_info_ratelimited("%s: CTRL SEL CFG1 testbus %08x\n", __func__, reg);
511
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800512}
513EXPORT_SYMBOL(wcnss_pronto_log_debug_regs);
514
515/* interface to reset wcnss by sending the reset interrupt */
Sameer Thalappil19e02352012-09-24 15:26:12 -0700516void wcnss_reset_intr(void)
517{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800518 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
519 wcnss_pronto_log_debug_regs();
Sameer Thalappil34920762013-03-21 15:43:28 -0700520 wmb();
Sameer Thalappil58281ca2013-04-10 18:50:18 -0700521 __raw_writel(1 << 16, penv->fiq_reg);
Sameer Thalappil34920762013-03-21 15:43:28 -0700522 } else {
523 wcnss_riva_log_debug_regs();
524 wmb();
525 __raw_writel(1 << 24, MSM_APCS_GCC_BASE + 0x8);
Sameer Thalappil19e02352012-09-24 15:26:12 -0700526 }
527}
528EXPORT_SYMBOL(wcnss_reset_intr);
529
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800530static int wcnss_create_sysfs(struct device *dev)
531{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800532 int ret;
533
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800534 if (!dev)
535 return -ENODEV;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800536
537 ret = device_create_file(dev, &dev_attr_serial_number);
538 if (ret)
539 return ret;
540
541 ret = device_create_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700542 if (ret)
543 goto remove_serial;
544
545 ret = device_create_file(dev, &dev_attr_wcnss_version);
546 if (ret)
547 goto remove_thermal;
548
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800549 return 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700550
551remove_thermal:
552 device_remove_file(dev, &dev_attr_thermal_mitigation);
553remove_serial:
554 device_remove_file(dev, &dev_attr_serial_number);
555
556 return ret;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800557}
558
559static void wcnss_remove_sysfs(struct device *dev)
560{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800561 if (dev) {
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800562 device_remove_file(dev, &dev_attr_serial_number);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800563 device_remove_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700564 device_remove_file(dev, &dev_attr_wcnss_version);
565 }
566}
567static void wcnss_smd_notify_event(void *data, unsigned int event)
568{
569 int len = 0;
570
571 if (penv != data) {
572 pr_err("wcnss: invalid env pointer in smd callback\n");
573 return;
574 }
575 switch (event) {
576 case SMD_EVENT_DATA:
577 len = smd_read_avail(penv->smd_ch);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700578 if (len < 0) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700579 pr_err("wcnss: failed to read from smd %d\n", len);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700580 return;
581 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700582 schedule_work(&penv->wcnssctrl_rx_work);
583 break;
584
585 case SMD_EVENT_OPEN:
586 pr_debug("wcnss: opening WCNSS SMD channel :%s",
587 WCNSS_CTRL_CHANNEL);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700588 schedule_work(&penv->wcnssctrl_version_work);
589
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700590 break;
591
592 case SMD_EVENT_CLOSE:
593 pr_debug("wcnss: closing WCNSS SMD channel :%s",
594 WCNSS_CTRL_CHANNEL);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700595 /* This SMD is closed only during SSR */
596 penv->ssr_boot = true;
597 penv->nv_downloaded = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700598 break;
599
600 default:
601 break;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800602 }
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800603}
604
David Collinsb727f7d2011-09-12 11:54:49 -0700605static void wcnss_post_bootup(struct work_struct *work)
606{
Sheng Fangbb421672013-03-19 08:27:28 +0800607 if (do_not_cancel_vote == 1) {
608 pr_info("%s: Keeping APPS vote for Iris & WCNSS\n", __func__);
609 return;
610 }
611
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700612 pr_info("%s: Cancel APPS vote for Iris & WCNSS\n", __func__);
David Collinsb727f7d2011-09-12 11:54:49 -0700613
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700614 /* Since WCNSS is up, cancel any APPS vote for Iris & WCNSS VREGs */
David Collinsb727f7d2011-09-12 11:54:49 -0700615 wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config,
616 WCNSS_WLAN_SWITCH_OFF);
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700617
618}
619
620static int
621wcnss_pronto_gpios_config(struct device *dev, bool enable)
622{
623 int rc = 0;
624 int i, j;
625 int WCNSS_WLAN_NUM_GPIOS = 5;
626
627 for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) {
628 int gpio = of_get_gpio(dev->of_node, i);
629 if (enable) {
630 rc = gpio_request(gpio, "wcnss_wlan");
631 if (rc) {
632 pr_err("WCNSS gpio_request %d err %d\n",
633 gpio, rc);
634 goto fail;
635 }
636 } else
637 gpio_free(gpio);
638 }
639
640 return rc;
641
642fail:
643 for (j = WCNSS_WLAN_NUM_GPIOS-1; j >= 0; j--) {
644 int gpio = of_get_gpio(dev->of_node, i);
645 gpio_free(gpio);
646 }
647 return rc;
David Collinsb727f7d2011-09-12 11:54:49 -0700648}
649
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700650static int
651wcnss_gpios_config(struct resource *gpios_5wire, bool enable)
652{
653 int i, j;
654 int rc = 0;
655
656 for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) {
657 if (enable) {
658 rc = gpio_request(i, gpios_5wire->name);
659 if (rc) {
660 pr_err("WCNSS gpio_request %d err %d\n", i, rc);
661 goto fail;
662 }
663 } else
664 gpio_free(i);
665 }
666
667 return rc;
668
669fail:
670 for (j = i-1; j >= gpios_5wire->start; j--)
671 gpio_free(j);
672 return rc;
673}
674
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700675static int __devinit
676wcnss_wlan_ctrl_probe(struct platform_device *pdev)
677{
Sameer Thalappil667f43f2011-10-10 11:12:54 -0700678 if (!penv)
679 return -ENODEV;
680
681 penv->smd_channel_ready = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700682
683 pr_info("%s: SMD ctrl channel up\n", __func__);
684
David Collinsb727f7d2011-09-12 11:54:49 -0700685 /* Schedule a work to do any post boot up activity */
686 INIT_DELAYED_WORK(&penv->wcnss_work, wcnss_post_bootup);
687 schedule_delayed_work(&penv->wcnss_work, msecs_to_jiffies(10000));
688
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700689 return 0;
690}
691
Sameer Thalappil13f45182012-07-23 18:02:45 -0700692void wcnss_flush_delayed_boot_votes()
693{
Sameer Thalappilf106a682013-02-16 20:41:11 -0800694 flush_delayed_work(&penv->wcnss_work);
Sameer Thalappil13f45182012-07-23 18:02:45 -0700695}
696EXPORT_SYMBOL(wcnss_flush_delayed_boot_votes);
697
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700698static int __devexit
699wcnss_wlan_ctrl_remove(struct platform_device *pdev)
700{
701 if (penv)
702 penv->smd_channel_ready = 0;
703
704 pr_info("%s: SMD ctrl channel down\n", __func__);
705
706 return 0;
707}
708
709
710static struct platform_driver wcnss_wlan_ctrl_driver = {
711 .driver = {
712 .name = "WLAN_CTRL",
713 .owner = THIS_MODULE,
714 },
715 .probe = wcnss_wlan_ctrl_probe,
716 .remove = __devexit_p(wcnss_wlan_ctrl_remove),
717};
718
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700719static int __devexit
720wcnss_ctrl_remove(struct platform_device *pdev)
721{
722 if (penv && penv->smd_ch)
723 smd_close(penv->smd_ch);
724
725 return 0;
726}
727
728static int __devinit
729wcnss_ctrl_probe(struct platform_device *pdev)
730{
731 int ret = 0;
732
733 if (!penv)
734 return -ENODEV;
735
736 ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS,
737 &penv->smd_ch, penv, wcnss_smd_notify_event);
738 if (ret < 0) {
739 pr_err("wcnss: cannot open the smd command channel %s: %d\n",
740 WCNSS_CTRL_CHANNEL, ret);
741 return -ENODEV;
742 }
743 smd_disable_read_intr(penv->smd_ch);
744
745 return 0;
746}
747
748/* platform device for WCNSS_CTRL SMD channel */
749static struct platform_driver wcnss_ctrl_driver = {
750 .driver = {
751 .name = "WCNSS_CTRL",
752 .owner = THIS_MODULE,
753 },
754 .probe = wcnss_ctrl_probe,
755 .remove = __devexit_p(wcnss_ctrl_remove),
756};
757
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700758struct device *wcnss_wlan_get_device(void)
759{
760 if (penv && penv->pdev && penv->smd_channel_ready)
761 return &penv->pdev->dev;
762 return NULL;
763}
764EXPORT_SYMBOL(wcnss_wlan_get_device);
765
Sameer Thalappil409ed352011-12-07 10:53:31 -0800766struct platform_device *wcnss_get_platform_device(void)
767{
768 if (penv && penv->pdev)
769 return penv->pdev;
770 return NULL;
771}
772EXPORT_SYMBOL(wcnss_get_platform_device);
773
774struct wcnss_wlan_config *wcnss_get_wlan_config(void)
775{
776 if (penv && penv->pdev)
777 return &penv->wlan_config;
778 return NULL;
779}
780EXPORT_SYMBOL(wcnss_get_wlan_config);
781
Sameer Thalappil1878d762013-04-24 14:54:39 -0700782int wcnss_device_ready(void)
783{
784 if (penv && penv->pdev && penv->nv_downloaded)
785 return 1;
786 return 0;
787}
788EXPORT_SYMBOL(wcnss_device_ready);
789
790
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700791struct resource *wcnss_wlan_get_memory_map(struct device *dev)
792{
793 if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready)
794 return penv->mmio_res;
795 return NULL;
796}
797EXPORT_SYMBOL(wcnss_wlan_get_memory_map);
798
799int wcnss_wlan_get_dxe_tx_irq(struct device *dev)
800{
801 if (penv && dev && (dev == &penv->pdev->dev) &&
802 penv->tx_irq_res && penv->smd_channel_ready)
803 return penv->tx_irq_res->start;
804 return WCNSS_WLAN_IRQ_INVALID;
805}
806EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq);
807
808int wcnss_wlan_get_dxe_rx_irq(struct device *dev)
809{
810 if (penv && dev && (dev == &penv->pdev->dev) &&
811 penv->rx_irq_res && penv->smd_channel_ready)
812 return penv->rx_irq_res->start;
813 return WCNSS_WLAN_IRQ_INVALID;
814}
815EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq);
816
817void wcnss_wlan_register_pm_ops(struct device *dev,
818 const struct dev_pm_ops *pm_ops)
819{
820 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops)
821 penv->pm_ops = pm_ops;
822}
823EXPORT_SYMBOL(wcnss_wlan_register_pm_ops);
824
Sameer Thalappilecdd1002011-09-09 10:52:27 -0700825void wcnss_wlan_unregister_pm_ops(struct device *dev,
826 const struct dev_pm_ops *pm_ops)
827{
828 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) {
829 if (pm_ops->suspend != penv->pm_ops->suspend ||
830 pm_ops->resume != penv->pm_ops->resume)
831 pr_err("PM APIs dont match with registered APIs\n");
832 penv->pm_ops = NULL;
833 }
834}
835EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops);
836
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700837void wcnss_register_thermal_mitigation(struct device *dev,
838 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700839{
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700840 if (penv && dev && tm_notify)
Jeff Johnson61374652012-04-16 20:51:48 -0700841 penv->tm_notify = tm_notify;
842}
843EXPORT_SYMBOL(wcnss_register_thermal_mitigation);
844
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700845void wcnss_unregister_thermal_mitigation(
846 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700847{
848 if (penv && tm_notify) {
849 if (tm_notify != penv->tm_notify)
850 pr_err("tm_notify doesn't match registered\n");
851 penv->tm_notify = NULL;
852 }
853}
854EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation);
855
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800856unsigned int wcnss_get_serial_number(void)
857{
858 if (penv)
859 return penv->serial_number;
860 return 0;
861}
862EXPORT_SYMBOL(wcnss_get_serial_number);
863
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700864static int enable_wcnss_suspend_notify;
865
866static int enable_wcnss_suspend_notify_set(const char *val,
867 struct kernel_param *kp)
868{
869 int ret;
870
871 ret = param_set_int(val, kp);
872 if (ret)
873 return ret;
874
875 if (enable_wcnss_suspend_notify)
876 pr_debug("Suspend notification activated for wcnss\n");
877
878 return 0;
879}
880module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set,
881 param_get_int, &enable_wcnss_suspend_notify, S_IRUGO | S_IWUSR);
882
Sameer Thalappil58cec312013-05-13 15:52:08 -0700883int wcnss_xo_auto_detect_enabled(void)
884{
885 return (has_autodetect_xo == 1 ? 1 : 0);
886}
887
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700888
889void wcnss_suspend_notify(void)
890{
891 void __iomem *pmu_spare_reg;
892 u32 reg = 0;
893 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700894
895 if (!enable_wcnss_suspend_notify)
896 return;
897
898 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
899 return;
900
901 /* For Riva */
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700902 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
903 spin_lock_irqsave(&reg_spinlock, flags);
904 reg = readl_relaxed(pmu_spare_reg);
905 reg |= RIVA_SUSPEND_BIT;
906 writel_relaxed(reg, pmu_spare_reg);
907 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700908}
909EXPORT_SYMBOL(wcnss_suspend_notify);
910
911void wcnss_resume_notify(void)
912{
913 void __iomem *pmu_spare_reg;
914 u32 reg = 0;
915 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700916
917 if (!enable_wcnss_suspend_notify)
918 return;
919
920 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
921 return;
922
923 /* For Riva */
924 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
925
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700926 spin_lock_irqsave(&reg_spinlock, flags);
927 reg = readl_relaxed(pmu_spare_reg);
928 reg &= ~RIVA_SUSPEND_BIT;
929 writel_relaxed(reg, pmu_spare_reg);
930 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700931}
932EXPORT_SYMBOL(wcnss_resume_notify);
933
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700934static int wcnss_wlan_suspend(struct device *dev)
935{
936 if (penv && dev && (dev == &penv->pdev->dev) &&
937 penv->smd_channel_ready &&
938 penv->pm_ops && penv->pm_ops->suspend)
939 return penv->pm_ops->suspend(dev);
940 return 0;
941}
942
943static int wcnss_wlan_resume(struct device *dev)
944{
945 if (penv && dev && (dev == &penv->pdev->dev) &&
946 penv->smd_channel_ready &&
947 penv->pm_ops && penv->pm_ops->resume)
948 return penv->pm_ops->resume(dev);
949 return 0;
950}
951
Sameer Thalappilf138da52012-07-30 12:56:37 -0700952void wcnss_prevent_suspend()
953{
954 if (penv)
955 wake_lock(&penv->wcnss_wake_lock);
956}
957EXPORT_SYMBOL(wcnss_prevent_suspend);
958
959void wcnss_allow_suspend()
960{
961 if (penv)
962 wake_unlock(&penv->wcnss_wake_lock);
963}
964EXPORT_SYMBOL(wcnss_allow_suspend);
965
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700966int wcnss_hardware_type(void)
967{
968 if (penv)
969 return penv->wcnss_hw_type;
970 else
971 return -ENODEV;
972}
973EXPORT_SYMBOL(wcnss_hardware_type);
974
Sameer Thalappil1878d762013-04-24 14:54:39 -0700975int fw_cal_data_available(void)
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800976{
977 if (penv)
Sameer Thalappil1878d762013-04-24 14:54:39 -0700978 return penv->fw_cal_available;
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800979 else
980 return -ENODEV;
981}
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800982
Sameer Thalappild0952f62013-05-03 10:18:29 -0700983u32 wcnss_get_wlan_rx_buff_count(void)
984{
985 if (penv)
986 return penv->wlan_rx_buff_count;
987 else
988 return WCNSS_DEF_WLAN_RX_BUFF_COUNT;
989
990}
991EXPORT_SYMBOL(wcnss_get_wlan_rx_buff_count);
992
993
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700994static int wcnss_smd_tx(void *data, int len)
995{
996 int ret = 0;
997
998 ret = smd_write_avail(penv->smd_ch);
999 if (ret < len) {
1000 pr_err("wcnss: no space available for smd frame\n");
Sameer Thalappilf106a682013-02-16 20:41:11 -08001001 return -ENOSPC;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001002 }
1003 ret = smd_write(penv->smd_ch, data, len);
1004 if (ret < len) {
1005 pr_err("wcnss: failed to write Command %d", len);
1006 ret = -ENODEV;
1007 }
1008 return ret;
1009}
1010
Sameer Thalappil1878d762013-04-24 14:54:39 -07001011static unsigned char wcnss_fw_status(void)
1012{
1013 int len = 0;
1014 int rc = 0;
1015
1016 unsigned char fw_status = 0xFF;
1017
1018 len = smd_read_avail(penv->smd_ch);
1019 if (len < 1) {
1020 pr_err("%s: invalid firmware status", __func__);
1021 return fw_status;
1022 }
1023
1024 rc = smd_read(penv->smd_ch, &fw_status, 1);
1025 if (rc < 0) {
1026 pr_err("%s: incomplete data read from smd\n", __func__);
1027 return fw_status;
1028 }
1029 return fw_status;
1030}
1031
1032static void wcnss_send_cal_rsp(unsigned char fw_status)
1033{
1034 struct smd_msg_hdr *rsphdr;
1035 unsigned char *msg = NULL;
1036 int rc;
1037
1038 msg = kmalloc((sizeof(struct smd_msg_hdr) + 1), GFP_KERNEL);
1039 if (NULL == msg) {
1040 pr_err("wcnss: %s: failed to get memory\n", __func__);
1041 return;
1042 }
1043
1044 rsphdr = (struct smd_msg_hdr *)msg;
1045 rsphdr->msg_type = WCNSS_CALDATA_UPLD_RSP;
1046 rsphdr->msg_len = sizeof(struct smd_msg_hdr) + 1;
1047 memcpy(msg+sizeof(struct smd_msg_hdr), &fw_status, 1);
1048
1049 rc = wcnss_smd_tx(msg, rsphdr->msg_len);
1050 if (rc < 0)
1051 pr_err("wcnss: smd tx failed\n");
Sameer Thalappil844d3602013-05-24 13:54:24 -07001052
1053 kfree(msg);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001054}
1055
1056/* Collect calibrated data from WCNSS */
1057void extract_cal_data(int len)
1058{
1059 int rc;
1060 struct cal_data_params calhdr;
1061 unsigned char fw_status = WCNSS_RESP_FAIL;
1062
1063 if (len < sizeof(struct cal_data_params)) {
1064 pr_err("wcnss: incomplete cal header length\n");
1065 return;
1066 }
1067
1068 rc = smd_read(penv->smd_ch, (unsigned char *)&calhdr,
1069 sizeof(struct cal_data_params));
1070 if (rc < sizeof(struct cal_data_params)) {
1071 pr_err("wcnss: incomplete cal header read from smd\n");
1072 return;
1073 }
1074
1075 if (penv->fw_cal_exp_frag != calhdr.frag_number) {
1076 pr_err("wcnss: Invalid frgament");
1077 goto exit;
1078 }
1079
1080 if (calhdr.frag_size > WCNSS_MAX_FRAME_SIZE) {
1081 pr_err("wcnss: Invalid fragment size");
1082 goto exit;
1083 }
1084
1085 if (0 == calhdr.frag_number) {
1086 if (calhdr.total_size > MAX_CALIBRATED_DATA_SIZE) {
1087 pr_err("wcnss: Invalid cal data size %d",
1088 calhdr.total_size);
1089 goto exit;
1090 }
1091 kfree(penv->fw_cal_data);
1092 penv->fw_cal_rcvd = 0;
1093 penv->fw_cal_data = kmalloc(calhdr.total_size,
1094 GFP_KERNEL);
1095 if (penv->fw_cal_data == NULL) {
1096 smd_read(penv->smd_ch, NULL, calhdr.frag_size);
1097 goto exit;
1098 }
1099 }
1100
1101 mutex_lock(&penv->dev_lock);
1102 if (penv->fw_cal_rcvd + calhdr.frag_size >
1103 MAX_CALIBRATED_DATA_SIZE) {
1104 pr_err("calibrated data size is more than expected %d",
1105 penv->fw_cal_rcvd + calhdr.frag_size);
1106 penv->fw_cal_exp_frag = 0;
1107 penv->fw_cal_rcvd = 0;
1108 smd_read(penv->smd_ch, NULL, calhdr.frag_size);
1109 goto unlock_exit;
1110 }
1111
1112 rc = smd_read(penv->smd_ch, penv->fw_cal_data + penv->fw_cal_rcvd,
1113 calhdr.frag_size);
1114 if (rc < calhdr.frag_size)
1115 goto unlock_exit;
1116
1117 penv->fw_cal_exp_frag++;
1118 penv->fw_cal_rcvd += calhdr.frag_size;
1119
1120 if (calhdr.msg_flags & LAST_FRAGMENT) {
1121 penv->fw_cal_exp_frag = 0;
1122 penv->fw_cal_available = true;
1123 pr_info("wcnss: cal data collection completed\n");
1124 }
1125 mutex_unlock(&penv->dev_lock);
1126 wake_up(&penv->read_wait);
1127
1128 if (penv->fw_cal_available) {
1129 fw_status = WCNSS_RESP_SUCCESS;
1130 wcnss_send_cal_rsp(fw_status);
1131 }
1132 return;
1133
1134unlock_exit:
1135 mutex_unlock(&penv->dev_lock);
1136
1137exit:
1138 wcnss_send_cal_rsp(fw_status);
1139 return;
1140}
1141
1142
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001143static void wcnssctrl_rx_handler(struct work_struct *worker)
1144{
1145 int len = 0;
1146 int rc = 0;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001147 unsigned char buf[sizeof(struct wcnss_version)];
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001148 struct smd_msg_hdr *phdr;
1149 struct wcnss_version *pversion;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001150 int hw_type;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001151 unsigned char fw_status = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001152
1153 len = smd_read_avail(penv->smd_ch);
1154 if (len > WCNSS_MAX_FRAME_SIZE) {
1155 pr_err("wcnss: frame larger than the allowed size\n");
1156 smd_read(penv->smd_ch, NULL, len);
1157 return;
1158 }
1159 if (len <= 0)
1160 return;
1161
Sameer Thalappil1878d762013-04-24 14:54:39 -07001162 rc = smd_read(penv->smd_ch, buf, sizeof(struct smd_msg_hdr));
1163 if (rc < sizeof(struct smd_msg_hdr)) {
1164 pr_err("wcnss: incomplete header read from smd\n");
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001165 return;
1166 }
Sameer Thalappil1878d762013-04-24 14:54:39 -07001167 len -= sizeof(struct smd_msg_hdr);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001168
1169 phdr = (struct smd_msg_hdr *)buf;
1170
Sameer Thalappilf106a682013-02-16 20:41:11 -08001171 switch (phdr->msg_type) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001172
1173 case WCNSS_VERSION_RSP:
Sameer Thalappil1878d762013-04-24 14:54:39 -07001174 if (len != sizeof(struct wcnss_version)
1175 - sizeof(struct smd_msg_hdr)) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001176 pr_err("wcnss: invalid version data from wcnss %d\n",
Sameer Thalappil1878d762013-04-24 14:54:39 -07001177 len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001178 return;
1179 }
Sameer Thalappil1878d762013-04-24 14:54:39 -07001180 rc = smd_read(penv->smd_ch, buf+sizeof(struct smd_msg_hdr),
1181 len);
1182 if (rc < len) {
1183 pr_err("wcnss: incomplete data read from smd\n");
1184 return;
1185 }
1186 pversion = (struct wcnss_version *)buf;
1187 penv->fw_major = pversion->major;
1188 penv->fw_minor = pversion->minor;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001189 snprintf(penv->wcnss_version, WCNSS_VERSION_LEN,
1190 "%02x%02x%02x%02x", pversion->major, pversion->minor,
1191 pversion->version, pversion->revision);
1192 pr_info("wcnss: version %s\n", penv->wcnss_version);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001193 /* schedule work to download nvbin to ccpu */
1194 hw_type = wcnss_hardware_type();
1195 switch (hw_type) {
1196 case WCNSS_RIVA_HW:
1197 /* supported only if riva major >= 1 and minor >= 4 */
1198 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
1199 pr_info("wcnss: schedule dnld work for riva\n");
1200 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
1201 }
1202 break;
1203
1204 case WCNSS_PRONTO_HW:
1205 /* supported only if pronto major >= 1 and minor >= 4 */
1206 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
1207 pr_info("wcnss: schedule dnld work for pronto\n");
1208 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
1209 }
1210 break;
1211
1212 default:
1213 pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n",
1214 hw_type);
1215 break;
1216 }
1217 break;
1218
1219 case WCNSS_NVBIN_DNLD_RSP:
Sameer Thalappil1878d762013-04-24 14:54:39 -07001220 penv->nv_downloaded = true;
1221 fw_status = wcnss_fw_status();
1222 pr_debug("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu %u\n",
1223 fw_status);
1224 break;
1225
1226 case WCNSS_CALDATA_DNLD_RSP:
1227 penv->nv_downloaded = true;
1228 fw_status = wcnss_fw_status();
1229 pr_debug("wcnss: received WCNSS_CALDATA_DNLD_RSP from ccpu %u\n",
1230 fw_status);
1231 break;
1232
1233 case WCNSS_CALDATA_UPLD_REQ:
1234 penv->fw_cal_available = 0;
1235 extract_cal_data(len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001236 break;
1237
1238 default:
Sameer Thalappilf106a682013-02-16 20:41:11 -08001239 pr_err("wcnss: invalid message type %d\n", phdr->msg_type);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001240 }
1241 return;
1242}
1243
1244static void wcnss_send_version_req(struct work_struct *worker)
1245{
1246 struct smd_msg_hdr smd_msg;
1247 int ret = 0;
1248
Sameer Thalappilf106a682013-02-16 20:41:11 -08001249 smd_msg.msg_type = WCNSS_VERSION_REQ;
1250 smd_msg.msg_len = sizeof(smd_msg);
1251 ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001252 if (ret < 0)
1253 pr_err("wcnss: smd tx failed\n");
1254
1255 return;
1256}
1257
Sameer Thalappil1878d762013-04-24 14:54:39 -07001258
1259static void wcnss_nvbin_dnld(void)
Sameer Thalappilf106a682013-02-16 20:41:11 -08001260{
1261 int ret = 0;
1262 struct nvbin_dnld_req_msg *dnld_req_msg;
1263 unsigned short total_fragments = 0;
1264 unsigned short count = 0;
1265 unsigned short retry_count = 0;
1266 unsigned short cur_frag_size = 0;
1267 unsigned char *outbuffer = NULL;
1268 const void *nv_blob_addr = NULL;
1269 unsigned int nv_blob_size = 0;
1270 const struct firmware *nv = NULL;
Sameer Thalappilf0d89ba2013-03-11 17:33:20 -07001271 struct device *dev = &penv->pdev->dev;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001272
1273 ret = request_firmware(&nv, NVBIN_FILE, dev);
1274
1275 if (ret || !nv || !nv->data || !nv->size) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001276 pr_err("wcnss: %s: request_firmware failed for %s\n",
1277 __func__, NVBIN_FILE);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001278 return;
1279 }
1280
1281 /*
1282 * First 4 bytes in nv blob is validity bitmap.
1283 * We cannot validate nv, so skip those 4 bytes.
1284 */
1285 nv_blob_addr = nv->data + 4;
1286 nv_blob_size = nv->size - 4;
1287
1288 total_fragments = TOTALFRAGMENTS(nv_blob_size);
1289
1290 pr_info("wcnss: NV bin size: %d, total_fragments: %d\n",
1291 nv_blob_size, total_fragments);
1292
1293 /* get buffer for nv bin dnld req message */
1294 outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) +
1295 NV_FRAGMENT_SIZE), GFP_KERNEL);
1296
1297 if (NULL == outbuffer) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001298 pr_err("wcnss: %s: failed to get buffer\n", __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001299 goto err_free_nv;
1300 }
1301
1302 dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
1303
1304 dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001305 dnld_req_msg->dnld_req_params.msg_flags = 0;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001306
1307 for (count = 0; count < total_fragments; count++) {
1308 dnld_req_msg->dnld_req_params.frag_number = count;
1309
1310 if (count == (total_fragments - 1)) {
1311 /* last fragment, take care of boundry condition */
1312 cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE;
1313 if (!cur_frag_size)
1314 cur_frag_size = NV_FRAGMENT_SIZE;
1315
Sameer Thalappil1878d762013-04-24 14:54:39 -07001316 dnld_req_msg->dnld_req_params.msg_flags |=
1317 LAST_FRAGMENT;
1318 dnld_req_msg->dnld_req_params.msg_flags |=
1319 CAN_RECEIVE_CALDATA;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001320 } else {
1321 cur_frag_size = NV_FRAGMENT_SIZE;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001322 dnld_req_msg->dnld_req_params.msg_flags &=
1323 ~LAST_FRAGMENT;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001324 }
1325
1326 dnld_req_msg->dnld_req_params.nvbin_buffer_size =
1327 cur_frag_size;
1328
1329 dnld_req_msg->hdr.msg_len =
1330 sizeof(struct nvbin_dnld_req_msg) + cur_frag_size;
1331
1332 /* copy NV fragment */
1333 memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)),
1334 (nv_blob_addr + count * NV_FRAGMENT_SIZE),
1335 cur_frag_size);
1336
1337 ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len);
1338
1339 retry_count = 0;
1340 while ((ret == -ENOSPC) && (retry_count <= 3)) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001341 pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
1342 __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001343 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1344 count, dnld_req_msg->hdr.msg_len,
1345 total_fragments, retry_count);
1346
1347 /* wait and try again */
1348 msleep(20);
1349 retry_count++;
1350 ret = wcnss_smd_tx(outbuffer,
1351 dnld_req_msg->hdr.msg_len);
1352 }
1353
1354 if (ret < 0) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001355 pr_err("wcnss: %s: smd tx failed\n", __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001356 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1357 count, dnld_req_msg->hdr.msg_len,
1358 total_fragments, retry_count);
1359 goto err_dnld;
1360 }
1361 }
1362
1363err_dnld:
1364 /* free buffer */
1365 kfree(outbuffer);
1366
1367err_free_nv:
1368 /* release firmware */
1369 release_firmware(nv);
1370
1371 return;
1372}
1373
Sameer Thalappil1878d762013-04-24 14:54:39 -07001374
1375static void wcnss_caldata_dnld(const void *cal_data,
1376 unsigned int cal_data_size, bool msg_to_follow)
1377{
1378 int ret = 0;
1379 struct cal_data_msg *cal_msg;
1380 unsigned short total_fragments = 0;
1381 unsigned short count = 0;
1382 unsigned short retry_count = 0;
1383 unsigned short cur_frag_size = 0;
1384 unsigned char *outbuffer = NULL;
1385
1386 total_fragments = TOTALFRAGMENTS(cal_data_size);
1387
1388 outbuffer = kmalloc((sizeof(struct cal_data_msg) +
1389 NV_FRAGMENT_SIZE), GFP_KERNEL);
1390
1391 if (NULL == outbuffer) {
1392 pr_err("wcnss: %s: failed to get buffer\n", __func__);
1393 return;
1394 }
1395
1396 cal_msg = (struct cal_data_msg *)outbuffer;
1397
1398 cal_msg->hdr.msg_type = WCNSS_CALDATA_DNLD_REQ;
1399 cal_msg->cal_params.msg_flags = 0;
1400
1401 for (count = 0; count < total_fragments; count++) {
1402 cal_msg->cal_params.frag_number = count;
1403
1404 if (count == (total_fragments - 1)) {
1405 cur_frag_size = cal_data_size % NV_FRAGMENT_SIZE;
1406 if (!cur_frag_size)
1407 cur_frag_size = NV_FRAGMENT_SIZE;
1408
1409 cal_msg->cal_params.msg_flags
1410 |= LAST_FRAGMENT;
1411 if (msg_to_follow)
1412 cal_msg->cal_params.msg_flags |=
1413 MESSAGE_TO_FOLLOW;
1414 } else {
1415 cur_frag_size = NV_FRAGMENT_SIZE;
1416 cal_msg->cal_params.msg_flags &=
1417 ~LAST_FRAGMENT;
1418 }
1419
1420 cal_msg->cal_params.total_size = cal_data_size;
1421 cal_msg->cal_params.frag_size =
1422 cur_frag_size;
1423
1424 cal_msg->hdr.msg_len =
1425 sizeof(struct cal_data_msg) + cur_frag_size;
1426
1427 memcpy((outbuffer + sizeof(struct cal_data_msg)),
1428 (cal_data + count * NV_FRAGMENT_SIZE),
1429 cur_frag_size);
1430
1431 ret = wcnss_smd_tx(outbuffer, cal_msg->hdr.msg_len);
1432
1433 retry_count = 0;
1434 while ((ret == -ENOSPC) && (retry_count <= 3)) {
1435 pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
1436 __func__);
1437 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1438 count, cal_msg->hdr.msg_len,
1439 total_fragments, retry_count);
1440
1441 /* wait and try again */
1442 msleep(20);
1443 retry_count++;
1444 ret = wcnss_smd_tx(outbuffer,
1445 cal_msg->hdr.msg_len);
1446 }
1447
1448 if (ret < 0) {
1449 pr_err("wcnss: %s: smd tx failed\n", __func__);
1450 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1451 count, cal_msg->hdr.msg_len,
1452 total_fragments, retry_count);
1453 goto err_dnld;
1454 }
1455 }
1456
1457
1458err_dnld:
1459 /* free buffer */
1460 kfree(outbuffer);
1461
1462 return;
1463}
1464
1465
1466static void wcnss_nvbin_dnld_main(struct work_struct *worker)
1467{
1468 int retry = 0;
1469
1470 if (!FW_CALDATA_CAPABLE())
1471 goto nv_download;
1472
1473 if (!penv->fw_cal_available && WCNSS_CONFIG_UNSPECIFIED
1474 != has_calibrated_data && !penv->user_cal_available) {
1475 while (!penv->user_cal_available && retry++ < 5)
1476 msleep(500);
1477 }
1478
1479 /* only cal data is sent during ssr (if available) */
1480 if (penv->fw_cal_available && penv->ssr_boot) {
1481 pr_info_ratelimited("wcnss: cal download during SSR, using fw cal");
1482 wcnss_caldata_dnld(penv->fw_cal_data, penv->fw_cal_rcvd, false);
1483 return;
1484
1485 } else if (penv->user_cal_available && penv->ssr_boot) {
1486 pr_info_ratelimited("wcnss: cal download during SSR, using user cal");
1487 wcnss_caldata_dnld(penv->user_cal_data,
1488 penv->user_cal_rcvd, false);
1489 return;
1490
1491 } else if (penv->user_cal_available) {
1492 pr_info_ratelimited("wcnss: cal download during cold boot, using user cal");
1493 wcnss_caldata_dnld(penv->user_cal_data,
1494 penv->user_cal_rcvd, true);
1495 }
1496
1497nv_download:
1498 pr_info_ratelimited("wcnss: NV download");
1499 wcnss_nvbin_dnld();
1500
1501 return;
1502}
1503
1504
1505
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001506static int
1507wcnss_trigger_config(struct platform_device *pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001508{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001509 int ret;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001510 struct qcom_wcnss_opts *pdata;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001511 unsigned long wcnss_phys_addr;
1512 int size = 0;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001513 struct resource *res;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001514 int has_pronto_hw = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001515 "qcom,has-pronto-hw");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001516
Sameer Thalappild0952f62013-05-03 10:18:29 -07001517 if (of_property_read_u32(pdev->dev.of_node,
1518 "qcom,wlan-rx-buff-count", &penv->wlan_rx_buff_count)) {
1519 penv->wlan_rx_buff_count = WCNSS_DEF_WLAN_RX_BUFF_COUNT;
1520 }
1521
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001522 /* make sure we are only triggered once */
1523 if (penv->triggered)
1524 return 0;
1525 penv->triggered = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001526
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001527 /* initialize the WCNSS device configuration */
1528 pdata = pdev->dev.platform_data;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001529 if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) {
1530 if (has_pronto_hw) {
1531 has_48mhz_xo = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001532 "qcom,has-48mhz-xo");
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001533 } else {
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001534 has_48mhz_xo = pdata->has_48mhz_xo;
1535 }
1536 }
Sameer Thalappilf00dbeb2013-03-01 18:33:30 -08001537 penv->wcnss_hw_type = (has_pronto_hw) ? WCNSS_PRONTO_HW : WCNSS_RIVA_HW;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001538 penv->wlan_config.use_48mhz_xo = has_48mhz_xo;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001539
Sameer Thalappil58cec312013-05-13 15:52:08 -07001540 if (WCNSS_CONFIG_UNSPECIFIED == has_autodetect_xo && has_pronto_hw) {
1541 has_autodetect_xo = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001542 "qcom,has-autodetect-xo");
Sameer Thalappil58cec312013-05-13 15:52:08 -07001543 }
1544
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001545 penv->thermal_mitigation = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001546 strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN);
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001547
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001548 /* Configure 5 wire GPIOs */
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001549 if (!has_pronto_hw) {
1550 penv->gpios_5wire = platform_get_resource_byname(pdev,
1551 IORESOURCE_IO, "wcnss_gpios_5wire");
1552
1553 /* allocate 5-wire GPIO resources */
1554 if (!penv->gpios_5wire) {
1555 dev_err(&pdev->dev, "insufficient IO resources\n");
1556 ret = -ENOENT;
1557 goto fail_gpio_res;
1558 }
1559 ret = wcnss_gpios_config(penv->gpios_5wire, true);
1560 } else
1561 ret = wcnss_pronto_gpios_config(&pdev->dev, true);
1562
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001563 if (ret) {
1564 dev_err(&pdev->dev, "WCNSS gpios config failed.\n");
1565 goto fail_gpio_res;
1566 }
1567
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001568 /* power up the WCNSS */
1569 ret = wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1570 WCNSS_WLAN_SWITCH_ON);
1571 if (ret) {
1572 dev_err(&pdev->dev, "WCNSS Power-up failed.\n");
1573 goto fail_power;
1574 }
1575
1576 /* trigger initialization of the WCNSS */
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001577 penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001578 if (IS_ERR(penv->pil)) {
1579 dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n");
1580 ret = PTR_ERR(penv->pil);
1581 penv->pil = NULL;
1582 goto fail_pil;
1583 }
1584
1585 /* allocate resources */
1586 penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1587 "wcnss_mmio");
1588 penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1589 "wcnss_wlantx_irq");
1590 penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1591 "wcnss_wlanrx_irq");
1592
1593 if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) {
1594 dev_err(&pdev->dev, "insufficient resources\n");
1595 ret = -ENOENT;
1596 goto fail_res;
1597 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001598 INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
1599 INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001600 INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_main);
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001601
Sameer Thalappilf138da52012-07-30 12:56:37 -07001602 wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");
1603
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001604 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
1605 size = 0x3000;
1606 wcnss_phys_addr = MSM_PRONTO_PHYS;
1607 } else {
1608 wcnss_phys_addr = MSM_RIVA_PHYS;
1609 size = SZ_256;
1610 }
1611
1612 penv->msm_wcnss_base = ioremap(wcnss_phys_addr, size);
1613 if (!penv->msm_wcnss_base) {
1614 ret = -ENOMEM;
1615 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1616 goto fail_wake;
1617 }
1618
Sameer Thalappild3aad702013-01-22 18:52:24 -08001619 if (wcnss_hardware_type() == WCNSS_RIVA_HW) {
1620 penv->riva_ccu_base = ioremap(MSM_RIVA_CCU_BASE, SZ_512);
1621 if (!penv->riva_ccu_base) {
1622 ret = -ENOMEM;
1623 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1624 goto fail_ioremap;
1625 }
1626 } else {
1627 penv->pronto_a2xb_base = ioremap(MSM_PRONTO_A2XB_BASE, SZ_512);
1628 if (!penv->pronto_a2xb_base) {
1629 ret = -ENOMEM;
1630 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1631 goto fail_ioremap;
1632 }
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001633 penv->pronto_ccpu_base = ioremap(MSM_PRONTO_CCPU_BASE, SZ_512);
1634 if (!penv->pronto_ccpu_base) {
1635 ret = -ENOMEM;
1636 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1637 goto fail_ioremap2;
1638 }
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001639 /* for reset FIQ */
1640 res = platform_get_resource_byname(penv->pdev,
1641 IORESOURCE_MEM, "wcnss_fiq");
1642 if (!res) {
1643 dev_err(&pdev->dev, "insufficient irq mem resources\n");
1644 ret = -ENOENT;
1645 goto fail_ioremap3;
1646 }
1647 penv->fiq_reg = ioremap_nocache(res->start, resource_size(res));
1648 if (!penv->fiq_reg) {
1649 pr_err("wcnss: %s: ioremap_nocache() failed fiq_reg addr:%pr\n",
1650 __func__, &res->start);
1651 ret = -ENOMEM;
1652 goto fail_ioremap3;
1653 }
Sameer Thalappild3aad702013-01-22 18:52:24 -08001654 }
1655
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001656 return 0;
1657
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001658fail_ioremap3:
1659 iounmap(penv->pronto_ccpu_base);
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001660fail_ioremap2:
1661 iounmap(penv->pronto_a2xb_base);
Sameer Thalappild3aad702013-01-22 18:52:24 -08001662fail_ioremap:
1663 iounmap(penv->msm_wcnss_base);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001664fail_wake:
1665 wake_lock_destroy(&penv->wcnss_wake_lock);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001666fail_res:
1667 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001668 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001669fail_pil:
1670 wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1671 WCNSS_WLAN_SWITCH_OFF);
1672fail_power:
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001673 if (has_pronto_hw)
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001674 wcnss_pronto_gpios_config(&pdev->dev, false);
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001675 else
1676 wcnss_gpios_config(penv->gpios_5wire, false);
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001677fail_gpio_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001678 penv = NULL;
1679 return ret;
1680}
1681
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001682static int wcnss_node_open(struct inode *inode, struct file *file)
1683{
1684 struct platform_device *pdev;
1685
Sameer Thalappil37c20682013-05-31 14:01:25 -07001686 if (!penv)
1687 return -EFAULT;
1688
Sameer Thalappil1878d762013-04-24 14:54:39 -07001689 /* first open is only to trigger WCNSS platform driver */
1690 if (!penv->triggered) {
1691 pr_info(DEVICE " triggered by userspace\n");
1692 pdev = penv->pdev;
1693 return wcnss_trigger_config(pdev);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001694
Sameer Thalappil1878d762013-04-24 14:54:39 -07001695 } else if (penv->device_opened) {
1696 pr_info(DEVICE " already opened\n");
1697 return -EBUSY;
1698 }
1699
1700 mutex_lock(&penv->dev_lock);
1701 penv->user_cal_rcvd = 0;
1702 penv->user_cal_read = 0;
1703 penv->user_cal_available = false;
1704 penv->user_cal_data = NULL;
1705 penv->device_opened = 1;
1706 mutex_unlock(&penv->dev_lock);
1707
1708 return 0;
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001709}
1710
Sameer Thalappil1878d762013-04-24 14:54:39 -07001711static ssize_t wcnss_wlan_read(struct file *fp, char __user
1712 *buffer, size_t count, loff_t *position)
1713{
1714 int rc = 0;
1715
Sameer Thalappil37c20682013-05-31 14:01:25 -07001716 if (!penv || !penv->device_opened)
Sameer Thalappil1878d762013-04-24 14:54:39 -07001717 return -EFAULT;
1718
1719 rc = wait_event_interruptible(penv->read_wait, penv->fw_cal_rcvd
1720 > penv->user_cal_read || penv->fw_cal_available);
1721
1722 if (rc < 0)
1723 return rc;
1724
1725 mutex_lock(&penv->dev_lock);
1726
1727 if (penv->fw_cal_available && penv->fw_cal_rcvd
1728 == penv->user_cal_read) {
1729 rc = 0;
1730 goto exit;
1731 }
1732
1733 if (count > penv->fw_cal_rcvd - penv->user_cal_read)
1734 count = penv->fw_cal_rcvd - penv->user_cal_read;
1735
1736 rc = copy_to_user(buffer, penv->fw_cal_data +
1737 penv->user_cal_read, count);
1738 if (rc == 0) {
1739 penv->user_cal_read += count;
1740 rc = count;
1741 }
1742
1743exit:
1744 mutex_unlock(&penv->dev_lock);
1745 return rc;
1746}
1747
1748/* first (valid) write to this device should be 4 bytes cal file size */
1749static ssize_t wcnss_wlan_write(struct file *fp, const char __user
1750 *user_buffer, size_t count, loff_t *position)
1751{
1752 int rc = 0;
1753 int size = 0;
1754
Sameer Thalappil37c20682013-05-31 14:01:25 -07001755 if (!penv || !penv->device_opened || penv->user_cal_available)
Sameer Thalappil1878d762013-04-24 14:54:39 -07001756 return -EFAULT;
1757
1758 if (penv->user_cal_rcvd == 0 && count >= 4
1759 && !penv->user_cal_data) {
1760 rc = copy_from_user((void *)&size, user_buffer, 4);
1761 if (size > MAX_CALIBRATED_DATA_SIZE) {
1762 pr_err(DEVICE " invalid size to write %d\n", size);
1763 return -EFAULT;
1764 }
1765
1766 rc += count;
1767 count -= 4;
1768 penv->user_cal_exp_size = size;
1769 penv->user_cal_data = kmalloc(size, GFP_KERNEL);
1770 if (penv->user_cal_data == NULL) {
1771 pr_err(DEVICE " no memory to write\n");
1772 return -ENOMEM;
1773 }
1774 if (0 == count)
1775 goto exit;
1776
1777 } else if (penv->user_cal_rcvd == 0 && count < 4)
1778 return -EFAULT;
1779
1780 if (MAX_CALIBRATED_DATA_SIZE < count + penv->user_cal_rcvd) {
1781 pr_err(DEVICE " invalid size to write %d\n", count +
1782 penv->user_cal_rcvd);
1783 rc = -ENOMEM;
1784 goto exit;
1785 }
1786 rc = copy_from_user((void *)penv->user_cal_data +
1787 penv->user_cal_rcvd, user_buffer, count);
1788 if (0 == rc) {
1789 penv->user_cal_rcvd += count;
1790 rc += count;
1791 }
1792 if (penv->user_cal_rcvd == penv->user_cal_exp_size) {
1793 penv->user_cal_available = true;
1794 pr_info_ratelimited("wcnss: user cal written");
1795 }
1796
1797exit:
1798 return rc;
1799}
1800
1801
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001802static const struct file_operations wcnss_node_fops = {
1803 .owner = THIS_MODULE,
1804 .open = wcnss_node_open,
Sameer Thalappil1878d762013-04-24 14:54:39 -07001805 .read = wcnss_wlan_read,
1806 .write = wcnss_wlan_write,
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001807};
1808
1809static struct miscdevice wcnss_misc = {
1810 .minor = MISC_DYNAMIC_MINOR,
1811 .name = DEVICE,
1812 .fops = &wcnss_node_fops,
1813};
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001814
1815static int __devinit
1816wcnss_wlan_probe(struct platform_device *pdev)
1817{
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001818 int ret = 0;
1819
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001820 /* verify we haven't been called more than once */
1821 if (penv) {
1822 dev_err(&pdev->dev, "cannot handle multiple devices.\n");
1823 return -ENODEV;
1824 }
1825
1826 /* create an environment to track the device */
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001827 penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001828 if (!penv) {
1829 dev_err(&pdev->dev, "cannot allocate device memory.\n");
1830 return -ENOMEM;
1831 }
1832 penv->pdev = pdev;
1833
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001834 /* register sysfs entries */
1835 ret = wcnss_create_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001836 if (ret) {
1837 penv = NULL;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001838 return -ENOENT;
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001839 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001840
Sameer Thalappil1878d762013-04-24 14:54:39 -07001841 mutex_init(&penv->dev_lock);
1842 init_waitqueue_head(&penv->read_wait);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001843
Sameer Thalappild3aad702013-01-22 18:52:24 -08001844 /* Since we were built into the kernel we'll be called as part
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001845 * of kernel initialization. We don't know if userspace
1846 * applications are available to service PIL at this time
1847 * (they probably are not), so we simply create a device node
1848 * here. When userspace is available it should touch the
1849 * device so that we know that WCNSS configuration can take
1850 * place
1851 */
1852 pr_info(DEVICE " probed in built-in mode\n");
1853 return misc_register(&wcnss_misc);
1854
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001855}
1856
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001857static int __devexit
1858wcnss_wlan_remove(struct platform_device *pdev)
1859{
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001860 wcnss_remove_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001861 penv = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001862 return 0;
1863}
1864
1865
1866static const struct dev_pm_ops wcnss_wlan_pm_ops = {
1867 .suspend = wcnss_wlan_suspend,
1868 .resume = wcnss_wlan_resume,
1869};
1870
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001871#ifdef CONFIG_WCNSS_CORE_PRONTO
1872static struct of_device_id msm_wcnss_pronto_match[] = {
1873 {.compatible = "qcom,wcnss_wlan"},
1874 {}
1875};
1876#endif
1877
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001878static struct platform_driver wcnss_wlan_driver = {
1879 .driver = {
1880 .name = DEVICE,
1881 .owner = THIS_MODULE,
1882 .pm = &wcnss_wlan_pm_ops,
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001883#ifdef CONFIG_WCNSS_CORE_PRONTO
1884 .of_match_table = msm_wcnss_pronto_match,
1885#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001886 },
1887 .probe = wcnss_wlan_probe,
1888 .remove = __devexit_p(wcnss_wlan_remove),
1889};
1890
1891static int __init wcnss_wlan_init(void)
1892{
Sameer Thalappil24db5282012-09-10 11:58:33 -07001893 int ret = 0;
1894
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001895 platform_driver_register(&wcnss_wlan_driver);
1896 platform_driver_register(&wcnss_wlan_ctrl_driver);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001897 platform_driver_register(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001898
Sameer Thalappil24db5282012-09-10 11:58:33 -07001899#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1900 ret = wcnss_prealloc_init();
1901 if (ret < 0)
1902 pr_err("wcnss: pre-allocation failed\n");
1903#endif
1904
1905 return ret;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001906}
1907
1908static void __exit wcnss_wlan_exit(void)
1909{
1910 if (penv) {
1911 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001912 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001913
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001914
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001915 penv = NULL;
1916 }
1917
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001918 platform_driver_unregister(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001919 platform_driver_unregister(&wcnss_wlan_ctrl_driver);
1920 platform_driver_unregister(&wcnss_wlan_driver);
Sameer Thalappil24db5282012-09-10 11:58:33 -07001921#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1922 wcnss_prealloc_deinit();
1923#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001924}
1925
1926module_init(wcnss_wlan_init);
1927module_exit(wcnss_wlan_exit);
1928
1929MODULE_LICENSE("GPL v2");
1930MODULE_VERSION(VERSION);
1931MODULE_DESCRIPTION(DEVICE "Driver");