blob: 8dc30ee046a74b91dace454510f1a5e09984859d [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>
Sameer Thalappil944c72d2013-08-14 17:56:17 -070034#include <linux/suspend.h>
35#include <linux/rwsem.h>
Naresh Jayaram08cee442013-04-26 19:50:00 +053036#include <linux/mfd/pm8xxx/misc.h>
Sameer Thalappilf6fb1892013-09-06 17:33:30 -070037#include <linux/qpnp/qpnp-adc.h>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070038
Sameer Thalappileeb8c412012-08-10 17:17:09 -070039#include <mach/msm_smd.h>
Sameer Thalappil19e02352012-09-24 15:26:12 -070040#include <mach/msm_iomap.h>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070041#include <mach/subsystem_restart.h>
42
Sameer Thalappil24db5282012-09-10 11:58:33 -070043#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
44#include "wcnss_prealloc.h"
45#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070046
47#define DEVICE "wcnss_wlan"
48#define VERSION "1.01"
49#define WCNSS_PIL_DEVICE "wcnss"
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070050
Ankur Nandwanib0039b02011-08-09 14:00:45 -070051/* module params */
52#define WCNSS_CONFIG_UNSPECIFIED (-1)
Sameer Thalappileeb8c412012-08-10 17:17:09 -070053
Ankur Nandwania4510492011-08-29 15:56:23 -070054static int has_48mhz_xo = WCNSS_CONFIG_UNSPECIFIED;
Jeff Johnsonb3377e32011-11-18 23:32:27 -080055module_param(has_48mhz_xo, int, S_IWUSR | S_IRUGO);
Ankur Nandwanib0039b02011-08-09 14:00:45 -070056MODULE_PARM_DESC(has_48mhz_xo, "Is an external 48 MHz XO present");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070057
Sameer Thalappil1878d762013-04-24 14:54:39 -070058static int has_calibrated_data = WCNSS_CONFIG_UNSPECIFIED;
59module_param(has_calibrated_data, int, S_IWUSR | S_IRUGO);
60MODULE_PARM_DESC(has_calibrated_data, "whether calibrated data file available");
61
Sameer Thalappil58cec312013-05-13 15:52:08 -070062static int has_autodetect_xo = WCNSS_CONFIG_UNSPECIFIED;
63module_param(has_autodetect_xo, int, S_IWUSR | S_IRUGO);
64MODULE_PARM_DESC(has_autodetect_xo, "Perform auto detect to configure IRIS XO");
65
Sheng Fangbb421672013-03-19 08:27:28 +080066static int do_not_cancel_vote = WCNSS_CONFIG_UNSPECIFIED;
67module_param(do_not_cancel_vote, int, S_IWUSR | S_IRUGO);
68MODULE_PARM_DESC(do_not_cancel_vote, "Do not cancel votes for wcnss");
69
Sameer Thalappiled3f7da2012-11-01 12:54:01 -070070static DEFINE_SPINLOCK(reg_spinlock);
71
72#define MSM_RIVA_PHYS 0x03204000
73#define MSM_PRONTO_PHYS 0xfb21b000
74
75#define RIVA_SPARE_OFFSET 0x0b4
76#define RIVA_SUSPEND_BIT BIT(24)
77
Sameer Thalappil77716e52012-11-08 13:41:04 -080078#define MSM_RIVA_CCU_BASE 0x03200800
79
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080080#define CCU_RIVA_INVALID_ADDR_OFFSET 0x100
81#define CCU_RIVA_LAST_ADDR0_OFFSET 0x104
82#define CCU_RIVA_LAST_ADDR1_OFFSET 0x108
83#define CCU_RIVA_LAST_ADDR2_OFFSET 0x10c
Sameer Thalappil77716e52012-11-08 13:41:04 -080084
Sameer Thalappilbcc06182013-05-30 16:18:57 -070085#define PRONTO_PMU_SPARE_OFFSET 0x1088
86
Sameer Thalappil670197c2013-07-19 16:31:14 -070087#define PRONTO_PMU_COM_GDSCR_OFFSET 0x0024
88#define PRONTO_PMU_COM_GDSCR_SW_COLLAPSE BIT(0)
89#define PRONTO_PMU_COM_GDSCR_HW_CTRL BIT(1)
90
91#define PRONTO_PMU_WLAN_BCR_OFFSET 0x0050
92#define PRONTO_PMU_WLAN_BCR_BLK_ARES BIT(0)
93
94#define PRONTO_PMU_WLAN_GDSCR_OFFSET 0x0054
95#define PRONTO_PMU_WLAN_GDSCR_SW_COLLAPSE BIT(0)
96
Sameer Thalappilbcc06182013-05-30 16:18:57 -070097
98#define PRONTO_PMU_CBCR_OFFSET 0x0008
99#define PRONTO_PMU_CBCR_CLK_EN BIT(0)
100
Mihir Shete2775e4652013-09-20 16:21:49 -0700101#define PRONTO_PMU_COM_CPU_CBCR_OFFSET 0x0030
102#define PRONTO_PMU_COM_AHB_CBCR_OFFSET 0x0034
Mihir Shete31396f42013-10-18 13:34:03 -0700103
104#define PRONTO_PMU_WLAN_AHB_CBCR_OFFSET 0x0074
105#define PRONTO_PMU_WLAN_AHB_CBCR_CLK_EN BIT(0)
106#define PRONTO_PMU_WLAN_AHB_CBCR_CLK_OFF BIT(31)
107
108#define PRONTO_PMU_CPU_AHB_CMD_RCGR_OFFSET 0x0120
109#define PRONTO_PMU_CPU_AHB_CMD_RCGR_ROOT_EN BIT(1)
110
Mihir Shete2775e4652013-09-20 16:21:49 -0700111#define PRONTO_PMU_CFG_OFFSET 0x1004
112#define PRONTO_PMU_COM_CSR_OFFSET 0x1040
113#define PRONTO_PMU_SOFT_RESET_OFFSET 0x104C
114
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800115#define MSM_PRONTO_A2XB_BASE 0xfb100400
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800116#define A2XB_CFG_OFFSET 0x00
117#define A2XB_INT_SRC_OFFSET 0x0c
118#define A2XB_TSTBUS_CTRL_OFFSET 0x14
119#define A2XB_TSTBUS_OFFSET 0x18
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800120#define A2XB_ERR_INFO_OFFSET 0x1c
121
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800122#define WCNSS_TSTBUS_CTRL_EN BIT(0)
123#define WCNSS_TSTBUS_CTRL_AXIM (0x02 << 1)
124#define WCNSS_TSTBUS_CTRL_CMDFIFO (0x03 << 1)
125#define WCNSS_TSTBUS_CTRL_WRFIFO (0x04 << 1)
126#define WCNSS_TSTBUS_CTRL_RDFIFO (0x05 << 1)
127#define WCNSS_TSTBUS_CTRL_CTRL (0x07 << 1)
128#define WCNSS_TSTBUS_CTRL_AXIM_CFG0 (0x00 << 6)
129#define WCNSS_TSTBUS_CTRL_AXIM_CFG1 (0x01 << 6)
130#define WCNSS_TSTBUS_CTRL_CTRL_CFG0 (0x00 << 12)
131#define WCNSS_TSTBUS_CTRL_CTRL_CFG1 (0x01 << 12)
132
133#define MSM_PRONTO_CCPU_BASE 0xfb205050
134#define CCU_PRONTO_INVALID_ADDR_OFFSET 0x08
135#define CCU_PRONTO_LAST_ADDR0_OFFSET 0x0c
136#define CCU_PRONTO_LAST_ADDR1_OFFSET 0x10
137#define CCU_PRONTO_LAST_ADDR2_OFFSET 0x14
138
Sameer Thalappil767ff492013-06-19 15:25:21 -0700139#define MSM_PRONTO_SAW2_BASE 0xfb219000
140#define PRONTO_SAW2_SPM_STS_OFFSET 0x0c
141
Sameer Thalappil66d2b392013-07-02 11:32:55 -0700142#define MSM_PRONTO_PLL_BASE 0xfb21b1c0
143#define PRONTO_PLL_STATUS_OFFSET 0x1c
144
Sameer Thalappil670197c2013-07-19 16:31:14 -0700145#define MSM_PRONTO_TXP_PHY_ABORT 0xfb080488
146#define MSM_PRONTO_BRDG_ERR_SRC 0xfb080fb0
147
Sameer Thalappild0952f62013-05-03 10:18:29 -0700148#define WCNSS_DEF_WLAN_RX_BUFF_COUNT 1024
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700149#define WCNSS_VBATT_THRESHOLD 3500000
150#define WCNSS_VBATT_GUARD 200
151#define WCNSS_VBATT_HIGH 3700000
152#define WCNSS_VBATT_LOW 3300000
Sameer Thalappild0952f62013-05-03 10:18:29 -0700153
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700154#define WCNSS_CTRL_CHANNEL "WCNSS_CTRL"
Sameer Thalappil1878d762013-04-24 14:54:39 -0700155#define WCNSS_MAX_FRAME_SIZE (4*1024)
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700156#define WCNSS_VERSION_LEN 30
157
158/* message types */
159#define WCNSS_CTRL_MSG_START 0x01000000
Sameer Thalappil1878d762013-04-24 14:54:39 -0700160#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0)
161#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1)
162#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2)
163#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3)
164#define WCNSS_CALDATA_UPLD_REQ (WCNSS_CTRL_MSG_START + 4)
165#define WCNSS_CALDATA_UPLD_RSP (WCNSS_CTRL_MSG_START + 5)
166#define WCNSS_CALDATA_DNLD_REQ (WCNSS_CTRL_MSG_START + 6)
167#define WCNSS_CALDATA_DNLD_RSP (WCNSS_CTRL_MSG_START + 7)
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700168#define WCNSS_VBATT_LEVEL_IND (WCNSS_CTRL_MSG_START + 8)
Sameer Thalappilf106a682013-02-16 20:41:11 -0800169
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700170
171#define VALID_VERSION(version) \
172 ((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0)
173
Sameer Thalappil1878d762013-04-24 14:54:39 -0700174#define FW_CALDATA_CAPABLE() \
175 ((penv->fw_major >= 1) && (penv->fw_minor >= 5) ? 1 : 0)
176
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700177struct smd_msg_hdr {
Sameer Thalappilf106a682013-02-16 20:41:11 -0800178 unsigned int msg_type;
179 unsigned int msg_len;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700180};
181
182struct wcnss_version {
183 struct smd_msg_hdr hdr;
184 unsigned char major;
185 unsigned char minor;
186 unsigned char version;
187 unsigned char revision;
188};
189
Naresh Jayaram08cee442013-04-26 19:50:00 +0530190struct wcnss_pmic_dump {
191 char reg_name[10];
192 u16 reg_addr;
193};
194
195static struct wcnss_pmic_dump wcnss_pmic_reg_dump[] = {
196 {"S2", 0x1D8},
197 {"L4", 0xB4},
198 {"L10", 0xC0},
199 {"LVS2", 0x62},
200 {"S4", 0x1E8},
201 {"LVS7", 0x06C},
202 {"LVS1", 0x060},
203};
204
Sameer Thalappilf106a682013-02-16 20:41:11 -0800205#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
206
207/*
208 * On SMD channel 4K of maximum data can be transferred, including message
209 * header, so NV fragment size as next multiple of 1Kb is 3Kb.
210 */
211#define NV_FRAGMENT_SIZE 3072
Sameer Thalappil1878d762013-04-24 14:54:39 -0700212#define MAX_CALIBRATED_DATA_SIZE (64*1024)
213#define LAST_FRAGMENT (1 << 0)
214#define MESSAGE_TO_FOLLOW (1 << 1)
215#define CAN_RECEIVE_CALDATA (1 << 15)
216#define WCNSS_RESP_SUCCESS 1
217#define WCNSS_RESP_FAIL 0
218
Sameer Thalappilf106a682013-02-16 20:41:11 -0800219
220/* Macro to find the total number fragments of the NV bin Image */
221#define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \
222 (x / NV_FRAGMENT_SIZE) : ((x / NV_FRAGMENT_SIZE) + 1))
223
224struct nvbin_dnld_req_params {
225 /*
226 * Fragment sequence number of the NV bin Image. NV Bin Image
227 * might not fit into one message due to size limitation of
228 * the SMD channel FIFO so entire NV blob is chopped into
229 * multiple fragments starting with seqeunce number 0. The
230 * last fragment is indicated by marking is_last_fragment field
231 * to 1. At receiving side, NV blobs would be concatenated
232 * together without any padding bytes in between.
233 */
234 unsigned short frag_number;
235
236 /*
Sameer Thalappil1878d762013-04-24 14:54:39 -0700237 * bit 0: When set to 1 it indicates that no more fragments will
238 * be sent.
239 * bit 1: When set, a new message will be followed by this message
240 * bit 2- bit 14: Reserved
241 * bit 15: when set, it indicates that the sender is capable of
242 * receiving Calibrated data.
Sameer Thalappilf106a682013-02-16 20:41:11 -0800243 */
Sameer Thalappil1878d762013-04-24 14:54:39 -0700244 unsigned short msg_flags;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800245
246 /* NV Image size (number of bytes) */
247 unsigned int nvbin_buffer_size;
248
249 /*
250 * Following the 'nvbin_buffer_size', there should be
251 * nvbin_buffer_size bytes of NV bin Image i.e.
252 * uint8[nvbin_buffer_size].
253 */
254};
255
Sameer Thalappil1878d762013-04-24 14:54:39 -0700256
Sameer Thalappilf106a682013-02-16 20:41:11 -0800257struct nvbin_dnld_req_msg {
258 /*
259 * Note: The length specified in nvbin_dnld_req_msg messages
260 * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) +
261 * nvbin_buffer_size.
262 */
263 struct smd_msg_hdr hdr;
264 struct nvbin_dnld_req_params dnld_req_params;
265};
266
Sameer Thalappil1878d762013-04-24 14:54:39 -0700267struct cal_data_params {
268
269 /* The total size of the calibrated data, including all the
270 * fragments.
271 */
272 unsigned int total_size;
273 unsigned short frag_number;
274 /*
275 * bit 0: When set to 1 it indicates that no more fragments will
276 * be sent.
277 * bit 1: When set, a new message will be followed by this message
278 * bit 2- bit 15: Reserved
279 */
280 unsigned short msg_flags;
281 /*
282 * fragment size
283 */
284 unsigned int frag_size;
285 /*
286 * Following the frag_size, frag_size of fragmented
287 * data will be followed.
288 */
289};
290
291struct cal_data_msg {
292 /*
293 * The length specified in cal_data_msg should be
294 * hdr.msg_len = sizeof(cal_data_msg) + frag_size
295 */
296 struct smd_msg_hdr hdr;
297 struct cal_data_params cal_params;
298};
299
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700300struct vbatt_level {
301 u32 curr_volt;
302 u32 threshold;
303};
304
305struct vbatt_message {
306 struct smd_msg_hdr hdr;
307 struct vbatt_level vbatt;
308};
309
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700310static struct {
311 struct platform_device *pdev;
312 void *pil;
313 struct resource *mmio_res;
314 struct resource *tx_irq_res;
315 struct resource *rx_irq_res;
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700316 struct resource *gpios_5wire;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700317 const struct dev_pm_ops *pm_ops;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800318 int triggered;
319 int smd_channel_ready;
Sameer Thalappild0952f62013-05-03 10:18:29 -0700320 u32 wlan_rx_buff_count;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700321 smd_channel_t *smd_ch;
322 unsigned char wcnss_version[WCNSS_VERSION_LEN];
Sameer Thalappil1878d762013-04-24 14:54:39 -0700323 unsigned char fw_major;
324 unsigned char fw_minor;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800325 unsigned int serial_number;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800326 int thermal_mitigation;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700327 enum wcnss_hw_type wcnss_hw_type;
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700328 void (*tm_notify)(struct device *, int);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700329 struct wcnss_wlan_config wlan_config;
David Collinsb727f7d2011-09-12 11:54:49 -0700330 struct delayed_work wcnss_work;
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700331 struct delayed_work vbatt_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700332 struct work_struct wcnssctrl_version_work;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800333 struct work_struct wcnssctrl_nvbin_dnld_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700334 struct work_struct wcnssctrl_rx_work;
Sameer Thalappilf138da52012-07-30 12:56:37 -0700335 struct wake_lock wcnss_wake_lock;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700336 void __iomem *msm_wcnss_base;
Sameer Thalappild3aad702013-01-22 18:52:24 -0800337 void __iomem *riva_ccu_base;
338 void __iomem *pronto_a2xb_base;
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800339 void __iomem *pronto_ccpu_base;
Sameer Thalappil767ff492013-06-19 15:25:21 -0700340 void __iomem *pronto_saw2_base;
Sameer Thalappil66d2b392013-07-02 11:32:55 -0700341 void __iomem *pronto_pll_base;
Sameer Thalappil670197c2013-07-19 16:31:14 -0700342 void __iomem *wlan_tx_phy_aborts;
343 void __iomem *wlan_brdg_err_source;
Sameer Thalappil58281ca2013-04-10 18:50:18 -0700344 void __iomem *fiq_reg;
Sameer Thalappil1878d762013-04-24 14:54:39 -0700345 int ssr_boot;
346 int nv_downloaded;
347 unsigned char *fw_cal_data;
348 unsigned char *user_cal_data;
349 int fw_cal_rcvd;
350 int fw_cal_exp_frag;
351 int fw_cal_available;
352 int user_cal_read;
353 int user_cal_available;
354 int user_cal_rcvd;
355 int user_cal_exp_size;
356 int device_opened;
Sameer Thalappil1d69b8022013-06-10 19:10:07 -0700357 int iris_xo_mode_set;
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700358 int fw_vbatt_state;
Sameer Thalappil1878d762013-04-24 14:54:39 -0700359 struct mutex dev_lock;
360 wait_queue_head_t read_wait;
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700361 struct qpnp_adc_tm_btm_param vbat_monitor_params;
362 struct qpnp_adc_tm_chip *adc_tm_dev;
363 struct mutex vbat_monitor_mutex;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700364} *penv = NULL;
365
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800366static ssize_t wcnss_serial_number_show(struct device *dev,
367 struct device_attribute *attr, char *buf)
368{
369 if (!penv)
370 return -ENODEV;
371
372 return scnprintf(buf, PAGE_SIZE, "%08X\n", penv->serial_number);
373}
374
375static ssize_t wcnss_serial_number_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700376 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800377{
378 unsigned int value;
379
380 if (!penv)
381 return -ENODEV;
382
383 if (sscanf(buf, "%08X", &value) != 1)
384 return -EINVAL;
385
386 penv->serial_number = value;
387 return count;
388}
389
390static DEVICE_ATTR(serial_number, S_IRUSR | S_IWUSR,
391 wcnss_serial_number_show, wcnss_serial_number_store);
392
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800393
394static ssize_t wcnss_thermal_mitigation_show(struct device *dev,
395 struct device_attribute *attr, char *buf)
396{
397 if (!penv)
398 return -ENODEV;
399
400 return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation);
401}
402
403static ssize_t wcnss_thermal_mitigation_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700404 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800405{
406 int value;
407
408 if (!penv)
409 return -ENODEV;
410
411 if (sscanf(buf, "%d", &value) != 1)
412 return -EINVAL;
413 penv->thermal_mitigation = value;
Jeff Johnson61374652012-04-16 20:51:48 -0700414 if (penv->tm_notify)
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700415 (penv->tm_notify)(dev, value);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800416 return count;
417}
418
419static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR,
420 wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store);
421
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700422
423static ssize_t wcnss_version_show(struct device *dev,
424 struct device_attribute *attr, char *buf)
425{
426 if (!penv)
427 return -ENODEV;
428
429 return scnprintf(buf, PAGE_SIZE, "%s", penv->wcnss_version);
430}
431
432static DEVICE_ATTR(wcnss_version, S_IRUSR,
433 wcnss_version_show, NULL);
434
Naresh Jayaram08cee442013-04-26 19:50:00 +0530435void wcnss_riva_dump_pmic_regs(void)
436{
437 int i, rc;
438 u8 val;
439
440 for (i = 0; i < ARRAY_SIZE(wcnss_pmic_reg_dump); i++) {
441 val = 0;
442 rc = pm8xxx_read_register(wcnss_pmic_reg_dump[i].reg_addr,
443 &val);
444 if (rc)
445 pr_err("PMIC READ: Failed to read addr = %d\n",
446 wcnss_pmic_reg_dump[i].reg_addr);
447 else
448 pr_info_ratelimited("PMIC READ: %s addr = %x, value = %x\n",
449 wcnss_pmic_reg_dump[i].reg_name,
450 wcnss_pmic_reg_dump[i].reg_addr, val);
451 }
452}
453
Sameer Thalappil77716e52012-11-08 13:41:04 -0800454/* wcnss_reset_intr() is invoked when host drivers fails to
455 * communicate with WCNSS over SMD; so logging these registers
Sameer Thalappild3aad702013-01-22 18:52:24 -0800456 * helps to know WCNSS failure reason
457 */
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800458void wcnss_riva_log_debug_regs(void)
Sameer Thalappil77716e52012-11-08 13:41:04 -0800459{
Sameer Thalappil77716e52012-11-08 13:41:04 -0800460 void __iomem *ccu_reg;
461 u32 reg = 0;
462
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800463 ccu_reg = penv->riva_ccu_base + CCU_RIVA_INVALID_ADDR_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800464 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800465 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800466
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800467 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR0_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800468 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800469 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800470
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800471 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR1_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800472 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800473 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800474
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800475 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR2_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800476 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800477 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
Naresh Jayaram08cee442013-04-26 19:50:00 +0530478 wcnss_riva_dump_pmic_regs();
Sameer Thalappil77716e52012-11-08 13:41:04 -0800479
Sameer Thalappil77716e52012-11-08 13:41:04 -0800480}
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800481EXPORT_SYMBOL(wcnss_riva_log_debug_regs);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800482
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800483/* Log pronto debug registers before sending reset interrupt */
484void wcnss_pronto_log_debug_regs(void)
485{
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800486 void __iomem *reg_addr, *tst_addr, *tst_ctrl_addr;
Mihir Shete31396f42013-10-18 13:34:03 -0700487 u32 reg = 0, reg2 = 0, reg3 = 0, reg4 = 0;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800488
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700489
490 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_SPARE_OFFSET;
491 reg = readl_relaxed(reg_addr);
492 pr_info_ratelimited("%s: PRONTO_PMU_SPARE %08x\n", __func__, reg);
493
Mihir Shete2775e4652013-09-20 16:21:49 -0700494 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_CPU_CBCR_OFFSET;
495 reg = readl_relaxed(reg_addr);
496 pr_info_ratelimited("%s: PRONTO_PMU_COM_CPU_CBCR %08x\n",
497 __func__, reg);
498
499 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_AHB_CBCR_OFFSET;
500 reg = readl_relaxed(reg_addr);
501 pr_info_ratelimited("%s: PRONTO_PMU_COM_AHB_CBCR %08x\n",
502 __func__, reg);
503
504 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CFG_OFFSET;
505 reg = readl_relaxed(reg_addr);
506 pr_info_ratelimited("%s: PRONTO_PMU_CFG %08x\n", __func__, reg);
507
508 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_CSR_OFFSET;
509 reg = readl_relaxed(reg_addr);
510 pr_info_ratelimited("%s: PRONTO_PMU_COM_CSR %08x\n",
511 __func__, reg);
512
513 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_SOFT_RESET_OFFSET;
514 reg = readl_relaxed(reg_addr);
515 pr_info_ratelimited("%s: PRONTO_PMU_SOFT_RESET %08x\n",
516 __func__, reg);
517
Sameer Thalappil670197c2013-07-19 16:31:14 -0700518 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_GDSCR_OFFSET;
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700519 reg = readl_relaxed(reg_addr);
Mihir Shete2775e4652013-09-20 16:21:49 -0700520 pr_info_ratelimited("%s: PRONTO_PMU_COM_GDSCR %08x\n",
521 __func__, reg);
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700522 reg >>= 31;
523
524 if (!reg) {
525 pr_info_ratelimited("%s: Cannot log, Pronto common SS is power collapsed\n",
526 __func__);
527 return;
528 }
Sameer Thalappil670197c2013-07-19 16:31:14 -0700529 reg &= ~(PRONTO_PMU_COM_GDSCR_SW_COLLAPSE
530 | PRONTO_PMU_COM_GDSCR_HW_CTRL);
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700531 writel_relaxed(reg, reg_addr);
532
533 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CBCR_OFFSET;
534 reg = readl_relaxed(reg_addr);
535 reg |= PRONTO_PMU_CBCR_CLK_EN;
536 writel_relaxed(reg, reg_addr);
537
Sameer Thalappild3aad702013-01-22 18:52:24 -0800538 reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800539 reg = readl_relaxed(reg_addr);
540 pr_info_ratelimited("%s: A2XB_CFG_OFFSET %08x\n", __func__, reg);
541
Sameer Thalappild3aad702013-01-22 18:52:24 -0800542 reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800543 reg = readl_relaxed(reg_addr);
544 pr_info_ratelimited("%s: A2XB_INT_SRC_OFFSET %08x\n", __func__, reg);
545
Sameer Thalappild3aad702013-01-22 18:52:24 -0800546 reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800547 reg = readl_relaxed(reg_addr);
548 pr_info_ratelimited("%s: A2XB_ERR_INFO_OFFSET %08x\n", __func__, reg);
549
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800550 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_INVALID_ADDR_OFFSET;
551 reg = readl_relaxed(reg_addr);
552 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
553
554 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR0_OFFSET;
555 reg = readl_relaxed(reg_addr);
556 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
557
558 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR1_OFFSET;
559 reg = readl_relaxed(reg_addr);
560 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
561
562 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR2_OFFSET;
563 reg = readl_relaxed(reg_addr);
564 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
565
Sameer Thalappil767ff492013-06-19 15:25:21 -0700566 reg_addr = penv->pronto_saw2_base + PRONTO_SAW2_SPM_STS_OFFSET;
567 reg = readl_relaxed(reg_addr);
568 pr_info_ratelimited("%s: PRONTO_SAW2_SPM_STS %08x\n", __func__, reg);
569
Sameer Thalappil66d2b392013-07-02 11:32:55 -0700570 reg_addr = penv->pronto_pll_base + PRONTO_PLL_STATUS_OFFSET;
571 reg = readl_relaxed(reg_addr);
572 pr_info_ratelimited("%s: PRONTO_PLL_STATUS %08x\n", __func__, reg);
573
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800574 tst_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_OFFSET;
575 tst_ctrl_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_CTRL_OFFSET;
576
577 /* read data FIFO */
578 reg = 0;
579 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_RDFIFO;
580 writel_relaxed(reg, tst_ctrl_addr);
581 reg = readl_relaxed(tst_addr);
582 pr_info_ratelimited("%s: Read data FIFO testbus %08x\n",
583 __func__, reg);
584
585 /* command FIFO */
586 reg = 0;
587 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CMDFIFO;
588 writel_relaxed(reg, tst_ctrl_addr);
589 reg = readl_relaxed(tst_addr);
590 pr_info_ratelimited("%s: Command FIFO testbus %08x\n",
591 __func__, reg);
592
593 /* write data FIFO */
594 reg = 0;
595 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_WRFIFO;
596 writel_relaxed(reg, tst_ctrl_addr);
597 reg = readl_relaxed(tst_addr);
598 pr_info_ratelimited("%s: Rrite data FIFO testbus %08x\n",
599 __func__, reg);
600
601 /* AXIM SEL CFG0 */
602 reg = 0;
603 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
604 WCNSS_TSTBUS_CTRL_AXIM_CFG0;
605 writel_relaxed(reg, tst_ctrl_addr);
606 reg = readl_relaxed(tst_addr);
607 pr_info_ratelimited("%s: AXIM SEL CFG0 testbus %08x\n",
608 __func__, reg);
609
610 /* AXIM SEL CFG1 */
611 reg = 0;
612 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
613 WCNSS_TSTBUS_CTRL_AXIM_CFG1;
614 writel_relaxed(reg, tst_ctrl_addr);
615 reg = readl_relaxed(tst_addr);
616 pr_info_ratelimited("%s: AXIM SEL CFG1 testbus %08x\n",
617 __func__, reg);
618
619 /* CTRL SEL CFG0 */
620 reg = 0;
621 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
622 WCNSS_TSTBUS_CTRL_CTRL_CFG0;
623 writel_relaxed(reg, tst_ctrl_addr);
624 reg = readl_relaxed(tst_addr);
625 pr_info_ratelimited("%s: CTRL SEL CFG0 testbus %08x\n",
626 __func__, reg);
627
628 /* CTRL SEL CFG1 */
629 reg = 0;
630 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
631 WCNSS_TSTBUS_CTRL_CTRL_CFG1;
632 writel_relaxed(reg, tst_ctrl_addr);
633 reg = readl_relaxed(tst_addr);
634 pr_info_ratelimited("%s: CTRL SEL CFG1 testbus %08x\n", __func__, reg);
635
Sameer Thalappil670197c2013-07-19 16:31:14 -0700636
637 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_BCR_OFFSET;
638 reg = readl_relaxed(reg_addr);
639
640 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_GDSCR_OFFSET;
641 reg2 = readl_relaxed(reg_addr);
642
Mihir Shete31396f42013-10-18 13:34:03 -0700643 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_AHB_CBCR_OFFSET;
644 reg3 = readl_relaxed(reg_addr);
645 pr_info_ratelimited("%s: PMU_WLAN_AHB_CBCR %08x\n", __func__, reg3);
646
647 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CPU_AHB_CMD_RCGR_OFFSET;
648 reg4 = readl_relaxed(reg_addr);
649 pr_info_ratelimited("%s: PMU_CPU_CMD_RCGR %08x\n", __func__, reg4);
650
Sameer Thalappil670197c2013-07-19 16:31:14 -0700651 if ((reg & PRONTO_PMU_WLAN_BCR_BLK_ARES) ||
Mihir Shete31396f42013-10-18 13:34:03 -0700652 (reg2 & PRONTO_PMU_WLAN_GDSCR_SW_COLLAPSE) ||
653 (!(reg4 & PRONTO_PMU_CPU_AHB_CMD_RCGR_ROOT_EN)) ||
654 (reg3 & PRONTO_PMU_WLAN_AHB_CBCR_CLK_OFF) ||
655 (!(reg3 & PRONTO_PMU_WLAN_AHB_CBCR_CLK_EN))) {
Sameer Thalappil670197c2013-07-19 16:31:14 -0700656 pr_info_ratelimited("%s: Cannot log, wlan domain is power collapsed\n",
657 __func__);
658 return;
659 }
660
661 reg = readl_relaxed(penv->wlan_tx_phy_aborts);
662 pr_info_ratelimited("%s: WLAN_TX_PHY_ABORTS %08x\n", __func__, reg);
663
664 reg = readl_relaxed(penv->wlan_brdg_err_source);
665 pr_info_ratelimited("%s: WLAN_BRDG_ERR_SOURCE %08x\n", __func__, reg);
666
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800667}
668EXPORT_SYMBOL(wcnss_pronto_log_debug_regs);
669
Sameer Thalappil36f7a1a2013-10-09 18:32:38 -0700670#ifdef CONFIG_WCNSS_REGISTER_DUMP_ON_BITE
671void wcnss_log_debug_regs_on_bite(void)
672{
673 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
674 wcnss_pronto_log_debug_regs();
675}
676#endif
677
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800678/* interface to reset wcnss by sending the reset interrupt */
Sameer Thalappil19e02352012-09-24 15:26:12 -0700679void wcnss_reset_intr(void)
680{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800681 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
682 wcnss_pronto_log_debug_regs();
Sameer Thalappil34920762013-03-21 15:43:28 -0700683 wmb();
Sameer Thalappil58281ca2013-04-10 18:50:18 -0700684 __raw_writel(1 << 16, penv->fiq_reg);
Sameer Thalappil34920762013-03-21 15:43:28 -0700685 } else {
686 wcnss_riva_log_debug_regs();
687 wmb();
688 __raw_writel(1 << 24, MSM_APCS_GCC_BASE + 0x8);
Sameer Thalappil19e02352012-09-24 15:26:12 -0700689 }
690}
691EXPORT_SYMBOL(wcnss_reset_intr);
692
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800693static int wcnss_create_sysfs(struct device *dev)
694{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800695 int ret;
696
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800697 if (!dev)
698 return -ENODEV;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800699
700 ret = device_create_file(dev, &dev_attr_serial_number);
701 if (ret)
702 return ret;
703
704 ret = device_create_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700705 if (ret)
706 goto remove_serial;
707
708 ret = device_create_file(dev, &dev_attr_wcnss_version);
709 if (ret)
710 goto remove_thermal;
711
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800712 return 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700713
714remove_thermal:
715 device_remove_file(dev, &dev_attr_thermal_mitigation);
716remove_serial:
717 device_remove_file(dev, &dev_attr_serial_number);
718
719 return ret;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800720}
721
722static void wcnss_remove_sysfs(struct device *dev)
723{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800724 if (dev) {
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800725 device_remove_file(dev, &dev_attr_serial_number);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800726 device_remove_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700727 device_remove_file(dev, &dev_attr_wcnss_version);
728 }
729}
730static void wcnss_smd_notify_event(void *data, unsigned int event)
731{
732 int len = 0;
733
734 if (penv != data) {
735 pr_err("wcnss: invalid env pointer in smd callback\n");
736 return;
737 }
738 switch (event) {
739 case SMD_EVENT_DATA:
740 len = smd_read_avail(penv->smd_ch);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700741 if (len < 0) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700742 pr_err("wcnss: failed to read from smd %d\n", len);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700743 return;
744 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700745 schedule_work(&penv->wcnssctrl_rx_work);
746 break;
747
748 case SMD_EVENT_OPEN:
749 pr_debug("wcnss: opening WCNSS SMD channel :%s",
750 WCNSS_CTRL_CHANNEL);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700751 schedule_work(&penv->wcnssctrl_version_work);
752
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700753 break;
754
755 case SMD_EVENT_CLOSE:
756 pr_debug("wcnss: closing WCNSS SMD channel :%s",
757 WCNSS_CTRL_CHANNEL);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700758 /* This SMD is closed only during SSR */
759 penv->ssr_boot = true;
760 penv->nv_downloaded = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700761 break;
762
763 default:
764 break;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800765 }
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800766}
767
David Collinsb727f7d2011-09-12 11:54:49 -0700768static void wcnss_post_bootup(struct work_struct *work)
769{
Sheng Fangbb421672013-03-19 08:27:28 +0800770 if (do_not_cancel_vote == 1) {
771 pr_info("%s: Keeping APPS vote for Iris & WCNSS\n", __func__);
772 return;
773 }
774
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700775 pr_info("%s: Cancel APPS vote for Iris & WCNSS\n", __func__);
David Collinsb727f7d2011-09-12 11:54:49 -0700776
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700777 /* Since WCNSS is up, cancel any APPS vote for Iris & WCNSS VREGs */
David Collinsb727f7d2011-09-12 11:54:49 -0700778 wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config,
Sameer Thalappil1d69b8022013-06-10 19:10:07 -0700779 WCNSS_WLAN_SWITCH_OFF, NULL);
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700780
781}
782
783static int
784wcnss_pronto_gpios_config(struct device *dev, bool enable)
785{
786 int rc = 0;
787 int i, j;
788 int WCNSS_WLAN_NUM_GPIOS = 5;
789
790 for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) {
791 int gpio = of_get_gpio(dev->of_node, i);
792 if (enable) {
793 rc = gpio_request(gpio, "wcnss_wlan");
794 if (rc) {
795 pr_err("WCNSS gpio_request %d err %d\n",
796 gpio, rc);
797 goto fail;
798 }
799 } else
800 gpio_free(gpio);
801 }
802
803 return rc;
804
805fail:
806 for (j = WCNSS_WLAN_NUM_GPIOS-1; j >= 0; j--) {
807 int gpio = of_get_gpio(dev->of_node, i);
808 gpio_free(gpio);
809 }
810 return rc;
David Collinsb727f7d2011-09-12 11:54:49 -0700811}
812
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700813static int
814wcnss_gpios_config(struct resource *gpios_5wire, bool enable)
815{
816 int i, j;
817 int rc = 0;
818
819 for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) {
820 if (enable) {
821 rc = gpio_request(i, gpios_5wire->name);
822 if (rc) {
823 pr_err("WCNSS gpio_request %d err %d\n", i, rc);
824 goto fail;
825 }
826 } else
827 gpio_free(i);
828 }
829
830 return rc;
831
832fail:
833 for (j = i-1; j >= gpios_5wire->start; j--)
834 gpio_free(j);
835 return rc;
836}
837
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700838static int __devinit
839wcnss_wlan_ctrl_probe(struct platform_device *pdev)
840{
Sameer Thalappil0ab82132013-06-10 10:10:05 -0700841 if (!penv || !penv->triggered)
Sameer Thalappil667f43f2011-10-10 11:12:54 -0700842 return -ENODEV;
843
844 penv->smd_channel_ready = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700845
846 pr_info("%s: SMD ctrl channel up\n", __func__);
847
David Collinsb727f7d2011-09-12 11:54:49 -0700848 /* Schedule a work to do any post boot up activity */
849 INIT_DELAYED_WORK(&penv->wcnss_work, wcnss_post_bootup);
850 schedule_delayed_work(&penv->wcnss_work, msecs_to_jiffies(10000));
851
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700852 return 0;
853}
854
Sameer Thalappil13f45182012-07-23 18:02:45 -0700855void wcnss_flush_delayed_boot_votes()
856{
Sameer Thalappilf106a682013-02-16 20:41:11 -0800857 flush_delayed_work(&penv->wcnss_work);
Sameer Thalappil13f45182012-07-23 18:02:45 -0700858}
859EXPORT_SYMBOL(wcnss_flush_delayed_boot_votes);
860
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700861static int __devexit
862wcnss_wlan_ctrl_remove(struct platform_device *pdev)
863{
864 if (penv)
865 penv->smd_channel_ready = 0;
866
867 pr_info("%s: SMD ctrl channel down\n", __func__);
868
869 return 0;
870}
871
872
873static struct platform_driver wcnss_wlan_ctrl_driver = {
874 .driver = {
875 .name = "WLAN_CTRL",
876 .owner = THIS_MODULE,
877 },
878 .probe = wcnss_wlan_ctrl_probe,
879 .remove = __devexit_p(wcnss_wlan_ctrl_remove),
880};
881
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700882static int __devexit
883wcnss_ctrl_remove(struct platform_device *pdev)
884{
885 if (penv && penv->smd_ch)
886 smd_close(penv->smd_ch);
887
888 return 0;
889}
890
891static int __devinit
892wcnss_ctrl_probe(struct platform_device *pdev)
893{
894 int ret = 0;
895
Sameer Thalappil0ab82132013-06-10 10:10:05 -0700896 if (!penv || !penv->triggered)
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700897 return -ENODEV;
898
899 ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS,
900 &penv->smd_ch, penv, wcnss_smd_notify_event);
901 if (ret < 0) {
902 pr_err("wcnss: cannot open the smd command channel %s: %d\n",
903 WCNSS_CTRL_CHANNEL, ret);
904 return -ENODEV;
905 }
906 smd_disable_read_intr(penv->smd_ch);
907
908 return 0;
909}
910
911/* platform device for WCNSS_CTRL SMD channel */
912static struct platform_driver wcnss_ctrl_driver = {
913 .driver = {
914 .name = "WCNSS_CTRL",
915 .owner = THIS_MODULE,
916 },
917 .probe = wcnss_ctrl_probe,
918 .remove = __devexit_p(wcnss_ctrl_remove),
919};
920
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700921struct device *wcnss_wlan_get_device(void)
922{
923 if (penv && penv->pdev && penv->smd_channel_ready)
924 return &penv->pdev->dev;
925 return NULL;
926}
927EXPORT_SYMBOL(wcnss_wlan_get_device);
928
Sameer Thalappil409ed352011-12-07 10:53:31 -0800929struct platform_device *wcnss_get_platform_device(void)
930{
931 if (penv && penv->pdev)
932 return penv->pdev;
933 return NULL;
934}
935EXPORT_SYMBOL(wcnss_get_platform_device);
936
937struct wcnss_wlan_config *wcnss_get_wlan_config(void)
938{
939 if (penv && penv->pdev)
940 return &penv->wlan_config;
941 return NULL;
942}
943EXPORT_SYMBOL(wcnss_get_wlan_config);
944
Sameer Thalappil1878d762013-04-24 14:54:39 -0700945int wcnss_device_ready(void)
946{
947 if (penv && penv->pdev && penv->nv_downloaded)
948 return 1;
949 return 0;
950}
951EXPORT_SYMBOL(wcnss_device_ready);
952
953
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700954struct resource *wcnss_wlan_get_memory_map(struct device *dev)
955{
956 if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready)
957 return penv->mmio_res;
958 return NULL;
959}
960EXPORT_SYMBOL(wcnss_wlan_get_memory_map);
961
962int wcnss_wlan_get_dxe_tx_irq(struct device *dev)
963{
964 if (penv && dev && (dev == &penv->pdev->dev) &&
965 penv->tx_irq_res && penv->smd_channel_ready)
966 return penv->tx_irq_res->start;
967 return WCNSS_WLAN_IRQ_INVALID;
968}
969EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq);
970
971int wcnss_wlan_get_dxe_rx_irq(struct device *dev)
972{
973 if (penv && dev && (dev == &penv->pdev->dev) &&
974 penv->rx_irq_res && penv->smd_channel_ready)
975 return penv->rx_irq_res->start;
976 return WCNSS_WLAN_IRQ_INVALID;
977}
978EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq);
979
980void wcnss_wlan_register_pm_ops(struct device *dev,
981 const struct dev_pm_ops *pm_ops)
982{
983 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops)
984 penv->pm_ops = pm_ops;
985}
986EXPORT_SYMBOL(wcnss_wlan_register_pm_ops);
987
Sameer Thalappilecdd1002011-09-09 10:52:27 -0700988void wcnss_wlan_unregister_pm_ops(struct device *dev,
989 const struct dev_pm_ops *pm_ops)
990{
991 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) {
992 if (pm_ops->suspend != penv->pm_ops->suspend ||
993 pm_ops->resume != penv->pm_ops->resume)
994 pr_err("PM APIs dont match with registered APIs\n");
995 penv->pm_ops = NULL;
996 }
997}
998EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops);
999
Sameer Thalappila112bbc2012-05-23 12:20:32 -07001000void wcnss_register_thermal_mitigation(struct device *dev,
1001 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -07001002{
Sameer Thalappila112bbc2012-05-23 12:20:32 -07001003 if (penv && dev && tm_notify)
Jeff Johnson61374652012-04-16 20:51:48 -07001004 penv->tm_notify = tm_notify;
1005}
1006EXPORT_SYMBOL(wcnss_register_thermal_mitigation);
1007
Sameer Thalappila112bbc2012-05-23 12:20:32 -07001008void wcnss_unregister_thermal_mitigation(
1009 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -07001010{
1011 if (penv && tm_notify) {
1012 if (tm_notify != penv->tm_notify)
1013 pr_err("tm_notify doesn't match registered\n");
1014 penv->tm_notify = NULL;
1015 }
1016}
1017EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation);
1018
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001019unsigned int wcnss_get_serial_number(void)
1020{
1021 if (penv)
1022 return penv->serial_number;
1023 return 0;
1024}
1025EXPORT_SYMBOL(wcnss_get_serial_number);
1026
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001027static int enable_wcnss_suspend_notify;
1028
1029static int enable_wcnss_suspend_notify_set(const char *val,
1030 struct kernel_param *kp)
1031{
1032 int ret;
1033
1034 ret = param_set_int(val, kp);
1035 if (ret)
1036 return ret;
1037
1038 if (enable_wcnss_suspend_notify)
1039 pr_debug("Suspend notification activated for wcnss\n");
1040
1041 return 0;
1042}
1043module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set,
1044 param_get_int, &enable_wcnss_suspend_notify, S_IRUGO | S_IWUSR);
1045
Sameer Thalappil58cec312013-05-13 15:52:08 -07001046int wcnss_xo_auto_detect_enabled(void)
1047{
1048 return (has_autodetect_xo == 1 ? 1 : 0);
1049}
1050
Sameer Thalappil1d69b8022013-06-10 19:10:07 -07001051int wcnss_wlan_iris_xo_mode(void)
1052{
1053 if (penv && penv->pdev && penv->smd_channel_ready)
1054 return penv->iris_xo_mode_set;
1055 return -ENODEV;
1056}
1057EXPORT_SYMBOL(wcnss_wlan_iris_xo_mode);
1058
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001059
1060void wcnss_suspend_notify(void)
1061{
1062 void __iomem *pmu_spare_reg;
1063 u32 reg = 0;
1064 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001065
1066 if (!enable_wcnss_suspend_notify)
1067 return;
1068
1069 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
1070 return;
1071
1072 /* For Riva */
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001073 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
1074 spin_lock_irqsave(&reg_spinlock, flags);
1075 reg = readl_relaxed(pmu_spare_reg);
1076 reg |= RIVA_SUSPEND_BIT;
1077 writel_relaxed(reg, pmu_spare_reg);
1078 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001079}
1080EXPORT_SYMBOL(wcnss_suspend_notify);
1081
1082void wcnss_resume_notify(void)
1083{
1084 void __iomem *pmu_spare_reg;
1085 u32 reg = 0;
1086 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001087
1088 if (!enable_wcnss_suspend_notify)
1089 return;
1090
1091 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
1092 return;
1093
1094 /* For Riva */
1095 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
1096
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001097 spin_lock_irqsave(&reg_spinlock, flags);
1098 reg = readl_relaxed(pmu_spare_reg);
1099 reg &= ~RIVA_SUSPEND_BIT;
1100 writel_relaxed(reg, pmu_spare_reg);
1101 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001102}
1103EXPORT_SYMBOL(wcnss_resume_notify);
1104
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001105static int wcnss_wlan_suspend(struct device *dev)
1106{
1107 if (penv && dev && (dev == &penv->pdev->dev) &&
1108 penv->smd_channel_ready &&
1109 penv->pm_ops && penv->pm_ops->suspend)
1110 return penv->pm_ops->suspend(dev);
1111 return 0;
1112}
1113
1114static int wcnss_wlan_resume(struct device *dev)
1115{
1116 if (penv && dev && (dev == &penv->pdev->dev) &&
1117 penv->smd_channel_ready &&
1118 penv->pm_ops && penv->pm_ops->resume)
1119 return penv->pm_ops->resume(dev);
1120 return 0;
1121}
1122
Sameer Thalappilf138da52012-07-30 12:56:37 -07001123void wcnss_prevent_suspend()
1124{
1125 if (penv)
1126 wake_lock(&penv->wcnss_wake_lock);
1127}
1128EXPORT_SYMBOL(wcnss_prevent_suspend);
1129
1130void wcnss_allow_suspend()
1131{
1132 if (penv)
1133 wake_unlock(&penv->wcnss_wake_lock);
1134}
1135EXPORT_SYMBOL(wcnss_allow_suspend);
1136
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001137int wcnss_hardware_type(void)
1138{
1139 if (penv)
1140 return penv->wcnss_hw_type;
1141 else
1142 return -ENODEV;
1143}
1144EXPORT_SYMBOL(wcnss_hardware_type);
1145
Sameer Thalappil1878d762013-04-24 14:54:39 -07001146int fw_cal_data_available(void)
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001147{
1148 if (penv)
Sameer Thalappil1878d762013-04-24 14:54:39 -07001149 return penv->fw_cal_available;
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001150 else
1151 return -ENODEV;
1152}
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001153
Sameer Thalappild0952f62013-05-03 10:18:29 -07001154u32 wcnss_get_wlan_rx_buff_count(void)
1155{
1156 if (penv)
1157 return penv->wlan_rx_buff_count;
1158 else
1159 return WCNSS_DEF_WLAN_RX_BUFF_COUNT;
1160
1161}
1162EXPORT_SYMBOL(wcnss_get_wlan_rx_buff_count);
1163
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001164static int wcnss_smd_tx(void *data, int len)
1165{
1166 int ret = 0;
1167
1168 ret = smd_write_avail(penv->smd_ch);
1169 if (ret < len) {
1170 pr_err("wcnss: no space available for smd frame\n");
Sameer Thalappilf106a682013-02-16 20:41:11 -08001171 return -ENOSPC;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001172 }
1173 ret = smd_write(penv->smd_ch, data, len);
1174 if (ret < len) {
1175 pr_err("wcnss: failed to write Command %d", len);
1176 ret = -ENODEV;
1177 }
1178 return ret;
1179}
1180
Sameer Thalappilf6fb1892013-09-06 17:33:30 -07001181static void wcnss_notify_vbat(enum qpnp_tm_state state, void *ctx)
1182{
1183 mutex_lock(&penv->vbat_monitor_mutex);
1184 cancel_delayed_work_sync(&penv->vbatt_work);
1185
1186 if (state == ADC_TM_LOW_STATE) {
1187 pr_debug("wcnss: low voltage notification triggered\n");
1188 penv->vbat_monitor_params.state_request =
1189 ADC_TM_HIGH_THR_ENABLE;
1190 penv->vbat_monitor_params.high_thr = WCNSS_VBATT_THRESHOLD +
1191 WCNSS_VBATT_GUARD;
1192 penv->vbat_monitor_params.low_thr = 0;
1193 } else if (state == ADC_TM_HIGH_STATE) {
1194 penv->vbat_monitor_params.state_request =
1195 ADC_TM_LOW_THR_ENABLE;
1196 penv->vbat_monitor_params.low_thr = WCNSS_VBATT_THRESHOLD -
1197 WCNSS_VBATT_GUARD;
1198 penv->vbat_monitor_params.high_thr = 0;
1199 pr_debug("wcnss: high voltage notification triggered\n");
1200 } else {
1201 pr_debug("wcnss: unknown voltage notification state: %d\n",
1202 state);
1203 mutex_unlock(&penv->vbat_monitor_mutex);
1204 return;
1205 }
1206 pr_debug("wcnss: set low thr to %d and high to %d\n",
1207 penv->vbat_monitor_params.low_thr,
1208 penv->vbat_monitor_params.high_thr);
1209
1210 qpnp_adc_tm_channel_measure(penv->adc_tm_dev,
1211 &penv->vbat_monitor_params);
1212 schedule_delayed_work(&penv->vbatt_work, msecs_to_jiffies(2000));
1213 mutex_unlock(&penv->vbat_monitor_mutex);
1214}
1215
1216static int wcnss_setup_vbat_monitoring(void)
1217{
1218 int rc = -1;
1219
1220 if (!penv->adc_tm_dev) {
1221 pr_err("wcnss: not setting up vbatt\n");
1222 return rc;
1223 }
1224 penv->vbat_monitor_params.low_thr = WCNSS_VBATT_THRESHOLD;
1225 penv->vbat_monitor_params.high_thr = WCNSS_VBATT_THRESHOLD;
1226 penv->vbat_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_ENABLE;
1227 penv->vbat_monitor_params.channel = VBAT_SNS;
1228 penv->vbat_monitor_params.btm_ctx = (void *)penv;
1229 penv->vbat_monitor_params.timer_interval = ADC_MEAS1_INTERVAL_1S;
1230 penv->vbat_monitor_params.threshold_notification = &wcnss_notify_vbat;
1231 pr_debug("wcnss: set low thr to %d and high to %d\n",
1232 penv->vbat_monitor_params.low_thr,
1233 penv->vbat_monitor_params.high_thr);
1234
1235 rc = qpnp_adc_tm_channel_measure(penv->adc_tm_dev,
1236 &penv->vbat_monitor_params);
1237 if (rc)
1238 pr_err("wcnss: tm setup failed: %d\n", rc);
1239
1240 return rc;
1241}
1242
1243static void wcnss_update_vbatt(struct work_struct *work)
1244{
1245 struct vbatt_message vbatt_msg;
1246 int ret = 0;
1247
1248 vbatt_msg.hdr.msg_type = WCNSS_VBATT_LEVEL_IND;
1249 vbatt_msg.hdr.msg_len = sizeof(struct vbatt_message);
1250 vbatt_msg.vbatt.threshold = WCNSS_VBATT_THRESHOLD;
1251
1252 mutex_lock(&penv->vbat_monitor_mutex);
1253 if (penv->vbat_monitor_params.low_thr &&
1254 (penv->fw_vbatt_state == WCNSS_VBATT_LOW ||
1255 penv->fw_vbatt_state == WCNSS_CONFIG_UNSPECIFIED)) {
1256 vbatt_msg.vbatt.curr_volt = WCNSS_VBATT_HIGH;
1257 penv->fw_vbatt_state = WCNSS_VBATT_HIGH;
1258 pr_debug("wcnss: send HIGH BATT to FW\n");
1259 } else if (!penv->vbat_monitor_params.low_thr &&
1260 (penv->fw_vbatt_state == WCNSS_VBATT_HIGH ||
1261 penv->fw_vbatt_state == WCNSS_CONFIG_UNSPECIFIED)){
1262 vbatt_msg.vbatt.curr_volt = WCNSS_VBATT_LOW;
1263 penv->fw_vbatt_state = WCNSS_VBATT_LOW;
1264 pr_debug("wcnss: send LOW BATT to FW\n");
1265 } else {
1266 mutex_unlock(&penv->vbat_monitor_mutex);
1267 return;
1268 }
1269 mutex_unlock(&penv->vbat_monitor_mutex);
1270 ret = wcnss_smd_tx(&vbatt_msg, vbatt_msg.hdr.msg_len);
1271 if (ret < 0)
1272 pr_err("wcnss: smd tx failed\n");
1273 return;
1274}
1275
1276
Sameer Thalappil1878d762013-04-24 14:54:39 -07001277static unsigned char wcnss_fw_status(void)
1278{
1279 int len = 0;
1280 int rc = 0;
1281
1282 unsigned char fw_status = 0xFF;
1283
1284 len = smd_read_avail(penv->smd_ch);
1285 if (len < 1) {
1286 pr_err("%s: invalid firmware status", __func__);
1287 return fw_status;
1288 }
1289
1290 rc = smd_read(penv->smd_ch, &fw_status, 1);
1291 if (rc < 0) {
1292 pr_err("%s: incomplete data read from smd\n", __func__);
1293 return fw_status;
1294 }
1295 return fw_status;
1296}
1297
1298static void wcnss_send_cal_rsp(unsigned char fw_status)
1299{
1300 struct smd_msg_hdr *rsphdr;
1301 unsigned char *msg = NULL;
1302 int rc;
1303
1304 msg = kmalloc((sizeof(struct smd_msg_hdr) + 1), GFP_KERNEL);
1305 if (NULL == msg) {
1306 pr_err("wcnss: %s: failed to get memory\n", __func__);
1307 return;
1308 }
1309
1310 rsphdr = (struct smd_msg_hdr *)msg;
1311 rsphdr->msg_type = WCNSS_CALDATA_UPLD_RSP;
1312 rsphdr->msg_len = sizeof(struct smd_msg_hdr) + 1;
1313 memcpy(msg+sizeof(struct smd_msg_hdr), &fw_status, 1);
1314
1315 rc = wcnss_smd_tx(msg, rsphdr->msg_len);
1316 if (rc < 0)
1317 pr_err("wcnss: smd tx failed\n");
Sameer Thalappil844d3602013-05-24 13:54:24 -07001318
1319 kfree(msg);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001320}
1321
1322/* Collect calibrated data from WCNSS */
1323void extract_cal_data(int len)
1324{
1325 int rc;
1326 struct cal_data_params calhdr;
1327 unsigned char fw_status = WCNSS_RESP_FAIL;
1328
1329 if (len < sizeof(struct cal_data_params)) {
1330 pr_err("wcnss: incomplete cal header length\n");
1331 return;
1332 }
1333
1334 rc = smd_read(penv->smd_ch, (unsigned char *)&calhdr,
1335 sizeof(struct cal_data_params));
1336 if (rc < sizeof(struct cal_data_params)) {
1337 pr_err("wcnss: incomplete cal header read from smd\n");
1338 return;
1339 }
1340
1341 if (penv->fw_cal_exp_frag != calhdr.frag_number) {
1342 pr_err("wcnss: Invalid frgament");
1343 goto exit;
1344 }
1345
1346 if (calhdr.frag_size > WCNSS_MAX_FRAME_SIZE) {
1347 pr_err("wcnss: Invalid fragment size");
1348 goto exit;
1349 }
1350
1351 if (0 == calhdr.frag_number) {
1352 if (calhdr.total_size > MAX_CALIBRATED_DATA_SIZE) {
1353 pr_err("wcnss: Invalid cal data size %d",
1354 calhdr.total_size);
1355 goto exit;
1356 }
1357 kfree(penv->fw_cal_data);
1358 penv->fw_cal_rcvd = 0;
1359 penv->fw_cal_data = kmalloc(calhdr.total_size,
1360 GFP_KERNEL);
1361 if (penv->fw_cal_data == NULL) {
1362 smd_read(penv->smd_ch, NULL, calhdr.frag_size);
1363 goto exit;
1364 }
1365 }
1366
1367 mutex_lock(&penv->dev_lock);
1368 if (penv->fw_cal_rcvd + calhdr.frag_size >
1369 MAX_CALIBRATED_DATA_SIZE) {
1370 pr_err("calibrated data size is more than expected %d",
1371 penv->fw_cal_rcvd + calhdr.frag_size);
1372 penv->fw_cal_exp_frag = 0;
1373 penv->fw_cal_rcvd = 0;
1374 smd_read(penv->smd_ch, NULL, calhdr.frag_size);
1375 goto unlock_exit;
1376 }
1377
1378 rc = smd_read(penv->smd_ch, penv->fw_cal_data + penv->fw_cal_rcvd,
1379 calhdr.frag_size);
1380 if (rc < calhdr.frag_size)
1381 goto unlock_exit;
1382
1383 penv->fw_cal_exp_frag++;
1384 penv->fw_cal_rcvd += calhdr.frag_size;
1385
1386 if (calhdr.msg_flags & LAST_FRAGMENT) {
1387 penv->fw_cal_exp_frag = 0;
1388 penv->fw_cal_available = true;
1389 pr_info("wcnss: cal data collection completed\n");
1390 }
1391 mutex_unlock(&penv->dev_lock);
1392 wake_up(&penv->read_wait);
1393
1394 if (penv->fw_cal_available) {
1395 fw_status = WCNSS_RESP_SUCCESS;
1396 wcnss_send_cal_rsp(fw_status);
1397 }
1398 return;
1399
1400unlock_exit:
1401 mutex_unlock(&penv->dev_lock);
1402
1403exit:
1404 wcnss_send_cal_rsp(fw_status);
1405 return;
1406}
1407
1408
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001409static void wcnssctrl_rx_handler(struct work_struct *worker)
1410{
1411 int len = 0;
1412 int rc = 0;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001413 unsigned char buf[sizeof(struct wcnss_version)];
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001414 struct smd_msg_hdr *phdr;
1415 struct wcnss_version *pversion;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001416 int hw_type;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001417 unsigned char fw_status = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001418
1419 len = smd_read_avail(penv->smd_ch);
1420 if (len > WCNSS_MAX_FRAME_SIZE) {
1421 pr_err("wcnss: frame larger than the allowed size\n");
1422 smd_read(penv->smd_ch, NULL, len);
1423 return;
1424 }
1425 if (len <= 0)
1426 return;
1427
Sameer Thalappil1878d762013-04-24 14:54:39 -07001428 rc = smd_read(penv->smd_ch, buf, sizeof(struct smd_msg_hdr));
1429 if (rc < sizeof(struct smd_msg_hdr)) {
1430 pr_err("wcnss: incomplete header read from smd\n");
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001431 return;
1432 }
Sameer Thalappil1878d762013-04-24 14:54:39 -07001433 len -= sizeof(struct smd_msg_hdr);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001434
1435 phdr = (struct smd_msg_hdr *)buf;
1436
Sameer Thalappilf106a682013-02-16 20:41:11 -08001437 switch (phdr->msg_type) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001438
1439 case WCNSS_VERSION_RSP:
Sameer Thalappil1878d762013-04-24 14:54:39 -07001440 if (len != sizeof(struct wcnss_version)
1441 - sizeof(struct smd_msg_hdr)) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001442 pr_err("wcnss: invalid version data from wcnss %d\n",
Sameer Thalappil1878d762013-04-24 14:54:39 -07001443 len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001444 return;
1445 }
Sameer Thalappil1878d762013-04-24 14:54:39 -07001446 rc = smd_read(penv->smd_ch, buf+sizeof(struct smd_msg_hdr),
1447 len);
1448 if (rc < len) {
1449 pr_err("wcnss: incomplete data read from smd\n");
1450 return;
1451 }
1452 pversion = (struct wcnss_version *)buf;
1453 penv->fw_major = pversion->major;
1454 penv->fw_minor = pversion->minor;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001455 snprintf(penv->wcnss_version, WCNSS_VERSION_LEN,
1456 "%02x%02x%02x%02x", pversion->major, pversion->minor,
1457 pversion->version, pversion->revision);
1458 pr_info("wcnss: version %s\n", penv->wcnss_version);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001459 /* schedule work to download nvbin to ccpu */
1460 hw_type = wcnss_hardware_type();
1461 switch (hw_type) {
1462 case WCNSS_RIVA_HW:
1463 /* supported only if riva major >= 1 and minor >= 4 */
1464 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
1465 pr_info("wcnss: schedule dnld work for riva\n");
1466 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
1467 }
1468 break;
1469
1470 case WCNSS_PRONTO_HW:
1471 /* supported only if pronto major >= 1 and minor >= 4 */
1472 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
1473 pr_info("wcnss: schedule dnld work for pronto\n");
1474 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
1475 }
1476 break;
1477
1478 default:
1479 pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n",
1480 hw_type);
1481 break;
1482 }
1483 break;
1484
1485 case WCNSS_NVBIN_DNLD_RSP:
Sameer Thalappil1878d762013-04-24 14:54:39 -07001486 penv->nv_downloaded = true;
1487 fw_status = wcnss_fw_status();
1488 pr_debug("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu %u\n",
1489 fw_status);
Sameer Thalappilf6fb1892013-09-06 17:33:30 -07001490 wcnss_setup_vbat_monitoring();
Sameer Thalappil1878d762013-04-24 14:54:39 -07001491 break;
1492
1493 case WCNSS_CALDATA_DNLD_RSP:
1494 penv->nv_downloaded = true;
1495 fw_status = wcnss_fw_status();
1496 pr_debug("wcnss: received WCNSS_CALDATA_DNLD_RSP from ccpu %u\n",
1497 fw_status);
1498 break;
1499
1500 case WCNSS_CALDATA_UPLD_REQ:
1501 penv->fw_cal_available = 0;
1502 extract_cal_data(len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001503 break;
1504
1505 default:
Sameer Thalappilf106a682013-02-16 20:41:11 -08001506 pr_err("wcnss: invalid message type %d\n", phdr->msg_type);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001507 }
1508 return;
1509}
1510
1511static void wcnss_send_version_req(struct work_struct *worker)
1512{
1513 struct smd_msg_hdr smd_msg;
1514 int ret = 0;
1515
Sameer Thalappilf106a682013-02-16 20:41:11 -08001516 smd_msg.msg_type = WCNSS_VERSION_REQ;
1517 smd_msg.msg_len = sizeof(smd_msg);
1518 ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001519 if (ret < 0)
1520 pr_err("wcnss: smd tx failed\n");
1521
1522 return;
1523}
1524
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001525static DECLARE_RWSEM(wcnss_pm_sem);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001526
1527static void wcnss_nvbin_dnld(void)
Sameer Thalappilf106a682013-02-16 20:41:11 -08001528{
1529 int ret = 0;
1530 struct nvbin_dnld_req_msg *dnld_req_msg;
1531 unsigned short total_fragments = 0;
1532 unsigned short count = 0;
1533 unsigned short retry_count = 0;
1534 unsigned short cur_frag_size = 0;
1535 unsigned char *outbuffer = NULL;
1536 const void *nv_blob_addr = NULL;
1537 unsigned int nv_blob_size = 0;
1538 const struct firmware *nv = NULL;
Sameer Thalappilf0d89ba2013-03-11 17:33:20 -07001539 struct device *dev = &penv->pdev->dev;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001540
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001541 down_read(&wcnss_pm_sem);
1542
Sameer Thalappilf106a682013-02-16 20:41:11 -08001543 ret = request_firmware(&nv, NVBIN_FILE, dev);
1544
1545 if (ret || !nv || !nv->data || !nv->size) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001546 pr_err("wcnss: %s: request_firmware failed for %s\n",
1547 __func__, NVBIN_FILE);
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001548 goto out;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001549 }
1550
1551 /*
1552 * First 4 bytes in nv blob is validity bitmap.
1553 * We cannot validate nv, so skip those 4 bytes.
1554 */
1555 nv_blob_addr = nv->data + 4;
1556 nv_blob_size = nv->size - 4;
1557
1558 total_fragments = TOTALFRAGMENTS(nv_blob_size);
1559
1560 pr_info("wcnss: NV bin size: %d, total_fragments: %d\n",
1561 nv_blob_size, total_fragments);
1562
1563 /* get buffer for nv bin dnld req message */
1564 outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) +
1565 NV_FRAGMENT_SIZE), GFP_KERNEL);
1566
1567 if (NULL == outbuffer) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001568 pr_err("wcnss: %s: failed to get buffer\n", __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001569 goto err_free_nv;
1570 }
1571
1572 dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
1573
1574 dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001575 dnld_req_msg->dnld_req_params.msg_flags = 0;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001576
1577 for (count = 0; count < total_fragments; count++) {
1578 dnld_req_msg->dnld_req_params.frag_number = count;
1579
1580 if (count == (total_fragments - 1)) {
1581 /* last fragment, take care of boundry condition */
1582 cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE;
1583 if (!cur_frag_size)
1584 cur_frag_size = NV_FRAGMENT_SIZE;
1585
Sameer Thalappil1878d762013-04-24 14:54:39 -07001586 dnld_req_msg->dnld_req_params.msg_flags |=
1587 LAST_FRAGMENT;
1588 dnld_req_msg->dnld_req_params.msg_flags |=
1589 CAN_RECEIVE_CALDATA;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001590 } else {
1591 cur_frag_size = NV_FRAGMENT_SIZE;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001592 dnld_req_msg->dnld_req_params.msg_flags &=
1593 ~LAST_FRAGMENT;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001594 }
1595
1596 dnld_req_msg->dnld_req_params.nvbin_buffer_size =
1597 cur_frag_size;
1598
1599 dnld_req_msg->hdr.msg_len =
1600 sizeof(struct nvbin_dnld_req_msg) + cur_frag_size;
1601
1602 /* copy NV fragment */
1603 memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)),
1604 (nv_blob_addr + count * NV_FRAGMENT_SIZE),
1605 cur_frag_size);
1606
1607 ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len);
1608
1609 retry_count = 0;
1610 while ((ret == -ENOSPC) && (retry_count <= 3)) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001611 pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
1612 __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001613 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1614 count, dnld_req_msg->hdr.msg_len,
1615 total_fragments, retry_count);
1616
1617 /* wait and try again */
1618 msleep(20);
1619 retry_count++;
1620 ret = wcnss_smd_tx(outbuffer,
1621 dnld_req_msg->hdr.msg_len);
1622 }
1623
1624 if (ret < 0) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001625 pr_err("wcnss: %s: smd tx failed\n", __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001626 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1627 count, dnld_req_msg->hdr.msg_len,
1628 total_fragments, retry_count);
1629 goto err_dnld;
1630 }
1631 }
1632
1633err_dnld:
1634 /* free buffer */
1635 kfree(outbuffer);
1636
1637err_free_nv:
1638 /* release firmware */
1639 release_firmware(nv);
1640
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001641out:
1642 up_read(&wcnss_pm_sem);
1643
Sameer Thalappilf106a682013-02-16 20:41:11 -08001644 return;
1645}
1646
Sameer Thalappil1878d762013-04-24 14:54:39 -07001647
1648static void wcnss_caldata_dnld(const void *cal_data,
1649 unsigned int cal_data_size, bool msg_to_follow)
1650{
1651 int ret = 0;
1652 struct cal_data_msg *cal_msg;
1653 unsigned short total_fragments = 0;
1654 unsigned short count = 0;
1655 unsigned short retry_count = 0;
1656 unsigned short cur_frag_size = 0;
1657 unsigned char *outbuffer = NULL;
1658
1659 total_fragments = TOTALFRAGMENTS(cal_data_size);
1660
1661 outbuffer = kmalloc((sizeof(struct cal_data_msg) +
1662 NV_FRAGMENT_SIZE), GFP_KERNEL);
1663
1664 if (NULL == outbuffer) {
1665 pr_err("wcnss: %s: failed to get buffer\n", __func__);
1666 return;
1667 }
1668
1669 cal_msg = (struct cal_data_msg *)outbuffer;
1670
1671 cal_msg->hdr.msg_type = WCNSS_CALDATA_DNLD_REQ;
1672 cal_msg->cal_params.msg_flags = 0;
1673
1674 for (count = 0; count < total_fragments; count++) {
1675 cal_msg->cal_params.frag_number = count;
1676
1677 if (count == (total_fragments - 1)) {
1678 cur_frag_size = cal_data_size % NV_FRAGMENT_SIZE;
1679 if (!cur_frag_size)
1680 cur_frag_size = NV_FRAGMENT_SIZE;
1681
1682 cal_msg->cal_params.msg_flags
1683 |= LAST_FRAGMENT;
1684 if (msg_to_follow)
1685 cal_msg->cal_params.msg_flags |=
1686 MESSAGE_TO_FOLLOW;
1687 } else {
1688 cur_frag_size = NV_FRAGMENT_SIZE;
1689 cal_msg->cal_params.msg_flags &=
1690 ~LAST_FRAGMENT;
1691 }
1692
1693 cal_msg->cal_params.total_size = cal_data_size;
1694 cal_msg->cal_params.frag_size =
1695 cur_frag_size;
1696
1697 cal_msg->hdr.msg_len =
1698 sizeof(struct cal_data_msg) + cur_frag_size;
1699
1700 memcpy((outbuffer + sizeof(struct cal_data_msg)),
1701 (cal_data + count * NV_FRAGMENT_SIZE),
1702 cur_frag_size);
1703
1704 ret = wcnss_smd_tx(outbuffer, cal_msg->hdr.msg_len);
1705
1706 retry_count = 0;
1707 while ((ret == -ENOSPC) && (retry_count <= 3)) {
1708 pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
1709 __func__);
1710 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1711 count, cal_msg->hdr.msg_len,
1712 total_fragments, retry_count);
1713
1714 /* wait and try again */
1715 msleep(20);
1716 retry_count++;
1717 ret = wcnss_smd_tx(outbuffer,
1718 cal_msg->hdr.msg_len);
1719 }
1720
1721 if (ret < 0) {
1722 pr_err("wcnss: %s: smd tx failed\n", __func__);
1723 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1724 count, cal_msg->hdr.msg_len,
1725 total_fragments, retry_count);
1726 goto err_dnld;
1727 }
1728 }
1729
1730
1731err_dnld:
1732 /* free buffer */
1733 kfree(outbuffer);
1734
1735 return;
1736}
1737
1738
1739static void wcnss_nvbin_dnld_main(struct work_struct *worker)
1740{
1741 int retry = 0;
1742
1743 if (!FW_CALDATA_CAPABLE())
1744 goto nv_download;
1745
1746 if (!penv->fw_cal_available && WCNSS_CONFIG_UNSPECIFIED
1747 != has_calibrated_data && !penv->user_cal_available) {
1748 while (!penv->user_cal_available && retry++ < 5)
1749 msleep(500);
1750 }
1751
1752 /* only cal data is sent during ssr (if available) */
1753 if (penv->fw_cal_available && penv->ssr_boot) {
1754 pr_info_ratelimited("wcnss: cal download during SSR, using fw cal");
1755 wcnss_caldata_dnld(penv->fw_cal_data, penv->fw_cal_rcvd, false);
1756 return;
1757
1758 } else if (penv->user_cal_available && penv->ssr_boot) {
1759 pr_info_ratelimited("wcnss: cal download during SSR, using user cal");
1760 wcnss_caldata_dnld(penv->user_cal_data,
1761 penv->user_cal_rcvd, false);
1762 return;
1763
1764 } else if (penv->user_cal_available) {
1765 pr_info_ratelimited("wcnss: cal download during cold boot, using user cal");
1766 wcnss_caldata_dnld(penv->user_cal_data,
1767 penv->user_cal_rcvd, true);
1768 }
1769
1770nv_download:
1771 pr_info_ratelimited("wcnss: NV download");
1772 wcnss_nvbin_dnld();
1773
1774 return;
1775}
1776
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001777static int wcnss_pm_notify(struct notifier_block *b,
1778 unsigned long event, void *p)
1779{
1780 switch (event) {
1781 case PM_SUSPEND_PREPARE:
1782 down_write(&wcnss_pm_sem);
1783 break;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001784
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001785 case PM_POST_SUSPEND:
1786 up_write(&wcnss_pm_sem);
1787 break;
1788 }
1789
1790 return NOTIFY_DONE;
1791}
1792
1793static struct notifier_block wcnss_pm_notifier = {
1794 .notifier_call = wcnss_pm_notify,
1795};
Sameer Thalappil1878d762013-04-24 14:54:39 -07001796
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001797static int
1798wcnss_trigger_config(struct platform_device *pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001799{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001800 int ret;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001801 struct qcom_wcnss_opts *pdata;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001802 unsigned long wcnss_phys_addr;
1803 int size = 0;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001804 struct resource *res;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001805 int has_pronto_hw = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001806 "qcom,has-pronto-hw");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001807
Sameer Thalappild0952f62013-05-03 10:18:29 -07001808 if (of_property_read_u32(pdev->dev.of_node,
1809 "qcom,wlan-rx-buff-count", &penv->wlan_rx_buff_count)) {
1810 penv->wlan_rx_buff_count = WCNSS_DEF_WLAN_RX_BUFF_COUNT;
1811 }
1812
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001813 /* make sure we are only triggered once */
1814 if (penv->triggered)
1815 return 0;
1816 penv->triggered = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001817
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001818 /* initialize the WCNSS device configuration */
1819 pdata = pdev->dev.platform_data;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001820 if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) {
1821 if (has_pronto_hw) {
1822 has_48mhz_xo = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001823 "qcom,has-48mhz-xo");
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001824 } else {
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001825 has_48mhz_xo = pdata->has_48mhz_xo;
1826 }
1827 }
Sameer Thalappilf00dbeb2013-03-01 18:33:30 -08001828 penv->wcnss_hw_type = (has_pronto_hw) ? WCNSS_PRONTO_HW : WCNSS_RIVA_HW;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001829 penv->wlan_config.use_48mhz_xo = has_48mhz_xo;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001830
Sameer Thalappil58cec312013-05-13 15:52:08 -07001831 if (WCNSS_CONFIG_UNSPECIFIED == has_autodetect_xo && has_pronto_hw) {
1832 has_autodetect_xo = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001833 "qcom,has-autodetect-xo");
Sameer Thalappil58cec312013-05-13 15:52:08 -07001834 }
1835
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001836 penv->thermal_mitigation = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001837 strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN);
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001838
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001839 /* Configure 5 wire GPIOs */
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001840 if (!has_pronto_hw) {
1841 penv->gpios_5wire = platform_get_resource_byname(pdev,
1842 IORESOURCE_IO, "wcnss_gpios_5wire");
1843
1844 /* allocate 5-wire GPIO resources */
1845 if (!penv->gpios_5wire) {
1846 dev_err(&pdev->dev, "insufficient IO resources\n");
1847 ret = -ENOENT;
1848 goto fail_gpio_res;
1849 }
1850 ret = wcnss_gpios_config(penv->gpios_5wire, true);
1851 } else
1852 ret = wcnss_pronto_gpios_config(&pdev->dev, true);
1853
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001854 if (ret) {
1855 dev_err(&pdev->dev, "WCNSS gpios config failed.\n");
1856 goto fail_gpio_res;
1857 }
1858
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001859 /* power up the WCNSS */
1860 ret = wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
Sameer Thalappil1d69b8022013-06-10 19:10:07 -07001861 WCNSS_WLAN_SWITCH_ON,
1862 &penv->iris_xo_mode_set);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001863 if (ret) {
1864 dev_err(&pdev->dev, "WCNSS Power-up failed.\n");
1865 goto fail_power;
1866 }
1867
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001868 /* allocate resources */
1869 penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1870 "wcnss_mmio");
1871 penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1872 "wcnss_wlantx_irq");
1873 penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1874 "wcnss_wlanrx_irq");
1875
1876 if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) {
1877 dev_err(&pdev->dev, "insufficient resources\n");
1878 ret = -ENOENT;
1879 goto fail_res;
1880 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001881 INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
1882 INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001883 INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_main);
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001884
Sameer Thalappilf138da52012-07-30 12:56:37 -07001885 wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");
1886
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001887 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
1888 size = 0x3000;
1889 wcnss_phys_addr = MSM_PRONTO_PHYS;
1890 } else {
1891 wcnss_phys_addr = MSM_RIVA_PHYS;
1892 size = SZ_256;
1893 }
1894
1895 penv->msm_wcnss_base = ioremap(wcnss_phys_addr, size);
1896 if (!penv->msm_wcnss_base) {
1897 ret = -ENOMEM;
1898 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001899 goto fail_ioremap;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001900 }
1901
Sameer Thalappild3aad702013-01-22 18:52:24 -08001902 if (wcnss_hardware_type() == WCNSS_RIVA_HW) {
1903 penv->riva_ccu_base = ioremap(MSM_RIVA_CCU_BASE, SZ_512);
1904 if (!penv->riva_ccu_base) {
1905 ret = -ENOMEM;
1906 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001907 goto fail_ioremap2;
Sameer Thalappild3aad702013-01-22 18:52:24 -08001908 }
1909 } else {
1910 penv->pronto_a2xb_base = ioremap(MSM_PRONTO_A2XB_BASE, SZ_512);
1911 if (!penv->pronto_a2xb_base) {
1912 ret = -ENOMEM;
1913 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001914 goto fail_ioremap2;
Sameer Thalappild3aad702013-01-22 18:52:24 -08001915 }
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001916 penv->pronto_ccpu_base = ioremap(MSM_PRONTO_CCPU_BASE, SZ_512);
1917 if (!penv->pronto_ccpu_base) {
1918 ret = -ENOMEM;
1919 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001920 goto fail_ioremap3;
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001921 }
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001922 /* for reset FIQ */
1923 res = platform_get_resource_byname(penv->pdev,
1924 IORESOURCE_MEM, "wcnss_fiq");
1925 if (!res) {
1926 dev_err(&pdev->dev, "insufficient irq mem resources\n");
1927 ret = -ENOENT;
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001928 goto fail_ioremap4;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001929 }
1930 penv->fiq_reg = ioremap_nocache(res->start, resource_size(res));
1931 if (!penv->fiq_reg) {
1932 pr_err("wcnss: %s: ioremap_nocache() failed fiq_reg addr:%pr\n",
1933 __func__, &res->start);
1934 ret = -ENOMEM;
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001935 goto fail_ioremap4;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001936 }
Sameer Thalappil767ff492013-06-19 15:25:21 -07001937 penv->pronto_saw2_base = ioremap_nocache(MSM_PRONTO_SAW2_BASE,
1938 SZ_32);
1939 if (!penv->pronto_saw2_base) {
1940 pr_err("%s: ioremap wcnss physical(saw2) failed\n",
1941 __func__);
1942 ret = -ENOMEM;
1943 goto fail_ioremap5;
1944 }
Sameer Thalappil66d2b392013-07-02 11:32:55 -07001945 penv->pronto_pll_base = ioremap_nocache(MSM_PRONTO_PLL_BASE,
1946 SZ_64);
1947 if (!penv->pronto_pll_base) {
1948 pr_err("%s: ioremap wcnss physical(pll) failed\n",
1949 __func__);
1950 ret = -ENOMEM;
1951 goto fail_ioremap6;
1952 }
1953
Sameer Thalappil670197c2013-07-19 16:31:14 -07001954 penv->wlan_tx_phy_aborts = ioremap(MSM_PRONTO_TXP_PHY_ABORT,
1955 SZ_8);
1956 if (!penv->wlan_tx_phy_aborts) {
1957 ret = -ENOMEM;
1958 pr_err("%s: ioremap wlan TX PHY failed\n", __func__);
1959 goto fail_ioremap7;
1960 }
1961 penv->wlan_brdg_err_source = ioremap(MSM_PRONTO_BRDG_ERR_SRC,
1962 SZ_8);
1963 if (!penv->wlan_brdg_err_source) {
1964 ret = -ENOMEM;
1965 pr_err("%s: ioremap wlan BRDG ERR failed\n", __func__);
1966 goto fail_ioremap8;
1967 }
1968
Sameer Thalappild3aad702013-01-22 18:52:24 -08001969 }
Sameer Thalappilf6fb1892013-09-06 17:33:30 -07001970 penv->adc_tm_dev = qpnp_get_adc_tm(&penv->pdev->dev, "wcnss");
1971 if (IS_ERR(penv->adc_tm_dev)) {
1972 pr_err("%s: adc get failed\n", __func__);
1973 penv->adc_tm_dev = NULL;
1974 } else {
1975 INIT_DELAYED_WORK(&penv->vbatt_work, wcnss_update_vbatt);
1976 penv->fw_vbatt_state = WCNSS_CONFIG_UNSPECIFIED;
1977 }
Sameer Thalappild3aad702013-01-22 18:52:24 -08001978
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001979 /* trigger initialization of the WCNSS */
1980 penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
1981 if (IS_ERR(penv->pil)) {
1982 dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n");
1983 ret = PTR_ERR(penv->pil);
Sameer Thalappilc78facf2013-07-30 12:07:31 -07001984 wcnss_pronto_log_debug_regs();
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001985 penv->pil = NULL;
1986 goto fail_pil;
1987 }
1988
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001989 return 0;
1990
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001991fail_pil:
1992 if (penv->riva_ccu_base)
1993 iounmap(penv->riva_ccu_base);
Sameer Thalappil670197c2013-07-19 16:31:14 -07001994 if (penv->wlan_brdg_err_source)
1995 iounmap(penv->wlan_brdg_err_source);
1996fail_ioremap8:
1997 if (penv->wlan_tx_phy_aborts)
1998 iounmap(penv->wlan_tx_phy_aborts);
1999fail_ioremap7:
Sameer Thalappil66d2b392013-07-02 11:32:55 -07002000 if (penv->pronto_pll_base)
2001 iounmap(penv->pronto_pll_base);
2002fail_ioremap6:
Sameer Thalappil767ff492013-06-19 15:25:21 -07002003 if (penv->pronto_saw2_base)
2004 iounmap(penv->pronto_saw2_base);
2005fail_ioremap5:
Sameer Thalappil0ab82132013-06-10 10:10:05 -07002006 if (penv->fiq_reg)
2007 iounmap(penv->fiq_reg);
2008fail_ioremap4:
2009 if (penv->pronto_ccpu_base)
2010 iounmap(penv->pronto_ccpu_base);
Sameer Thalappil58281ca2013-04-10 18:50:18 -07002011fail_ioremap3:
Sameer Thalappil0ab82132013-06-10 10:10:05 -07002012 if (penv->pronto_a2xb_base)
2013 iounmap(penv->pronto_a2xb_base);
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08002014fail_ioremap2:
Sameer Thalappil0ab82132013-06-10 10:10:05 -07002015 if (penv->msm_wcnss_base)
2016 iounmap(penv->msm_wcnss_base);
Sameer Thalappild3aad702013-01-22 18:52:24 -08002017fail_ioremap:
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07002018 wake_lock_destroy(&penv->wcnss_wake_lock);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002019fail_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002020 wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
Sameer Thalappil1d69b8022013-06-10 19:10:07 -07002021 WCNSS_WLAN_SWITCH_OFF, NULL);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002022fail_power:
Sameer Thalappil8d686d42012-08-24 10:07:31 -07002023 if (has_pronto_hw)
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07002024 wcnss_pronto_gpios_config(&pdev->dev, false);
Sameer Thalappil8d686d42012-08-24 10:07:31 -07002025 else
2026 wcnss_gpios_config(penv->gpios_5wire, false);
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07002027fail_gpio_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002028 penv = NULL;
2029 return ret;
2030}
2031
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002032static int wcnss_node_open(struct inode *inode, struct file *file)
2033{
2034 struct platform_device *pdev;
Sameer Thalappild0bb3bb2013-10-09 13:36:26 -07002035 int rc = 0;
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002036
Sameer Thalappil37c20682013-05-31 14:01:25 -07002037 if (!penv)
2038 return -EFAULT;
2039
Sameer Thalappil1878d762013-04-24 14:54:39 -07002040 if (!penv->triggered) {
2041 pr_info(DEVICE " triggered by userspace\n");
2042 pdev = penv->pdev;
Sameer Thalappild0bb3bb2013-10-09 13:36:26 -07002043 rc = wcnss_trigger_config(pdev);
2044 if (rc)
2045 return -EFAULT;
Sameer Thalappil1878d762013-04-24 14:54:39 -07002046 }
2047
2048 mutex_lock(&penv->dev_lock);
2049 penv->user_cal_rcvd = 0;
2050 penv->user_cal_read = 0;
2051 penv->user_cal_available = false;
2052 penv->user_cal_data = NULL;
2053 penv->device_opened = 1;
2054 mutex_unlock(&penv->dev_lock);
2055
Sameer Thalappild0bb3bb2013-10-09 13:36:26 -07002056 return rc;
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002057}
2058
Sameer Thalappil1878d762013-04-24 14:54:39 -07002059static ssize_t wcnss_wlan_read(struct file *fp, char __user
2060 *buffer, size_t count, loff_t *position)
2061{
2062 int rc = 0;
2063
Sameer Thalappil37c20682013-05-31 14:01:25 -07002064 if (!penv || !penv->device_opened)
Sameer Thalappil1878d762013-04-24 14:54:39 -07002065 return -EFAULT;
2066
2067 rc = wait_event_interruptible(penv->read_wait, penv->fw_cal_rcvd
2068 > penv->user_cal_read || penv->fw_cal_available);
2069
2070 if (rc < 0)
2071 return rc;
2072
2073 mutex_lock(&penv->dev_lock);
2074
2075 if (penv->fw_cal_available && penv->fw_cal_rcvd
2076 == penv->user_cal_read) {
2077 rc = 0;
2078 goto exit;
2079 }
2080
2081 if (count > penv->fw_cal_rcvd - penv->user_cal_read)
2082 count = penv->fw_cal_rcvd - penv->user_cal_read;
2083
2084 rc = copy_to_user(buffer, penv->fw_cal_data +
2085 penv->user_cal_read, count);
2086 if (rc == 0) {
2087 penv->user_cal_read += count;
2088 rc = count;
2089 }
2090
2091exit:
2092 mutex_unlock(&penv->dev_lock);
2093 return rc;
2094}
2095
2096/* first (valid) write to this device should be 4 bytes cal file size */
2097static ssize_t wcnss_wlan_write(struct file *fp, const char __user
2098 *user_buffer, size_t count, loff_t *position)
2099{
2100 int rc = 0;
2101 int size = 0;
2102
Sameer Thalappil37c20682013-05-31 14:01:25 -07002103 if (!penv || !penv->device_opened || penv->user_cal_available)
Sameer Thalappil1878d762013-04-24 14:54:39 -07002104 return -EFAULT;
2105
2106 if (penv->user_cal_rcvd == 0 && count >= 4
2107 && !penv->user_cal_data) {
2108 rc = copy_from_user((void *)&size, user_buffer, 4);
2109 if (size > MAX_CALIBRATED_DATA_SIZE) {
2110 pr_err(DEVICE " invalid size to write %d\n", size);
2111 return -EFAULT;
2112 }
2113
2114 rc += count;
2115 count -= 4;
2116 penv->user_cal_exp_size = size;
2117 penv->user_cal_data = kmalloc(size, GFP_KERNEL);
2118 if (penv->user_cal_data == NULL) {
2119 pr_err(DEVICE " no memory to write\n");
2120 return -ENOMEM;
2121 }
2122 if (0 == count)
2123 goto exit;
2124
2125 } else if (penv->user_cal_rcvd == 0 && count < 4)
2126 return -EFAULT;
2127
2128 if (MAX_CALIBRATED_DATA_SIZE < count + penv->user_cal_rcvd) {
2129 pr_err(DEVICE " invalid size to write %d\n", count +
2130 penv->user_cal_rcvd);
2131 rc = -ENOMEM;
2132 goto exit;
2133 }
2134 rc = copy_from_user((void *)penv->user_cal_data +
2135 penv->user_cal_rcvd, user_buffer, count);
2136 if (0 == rc) {
2137 penv->user_cal_rcvd += count;
2138 rc += count;
2139 }
2140 if (penv->user_cal_rcvd == penv->user_cal_exp_size) {
2141 penv->user_cal_available = true;
2142 pr_info_ratelimited("wcnss: user cal written");
2143 }
2144
2145exit:
2146 return rc;
2147}
2148
2149
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002150static const struct file_operations wcnss_node_fops = {
2151 .owner = THIS_MODULE,
2152 .open = wcnss_node_open,
Sameer Thalappil1878d762013-04-24 14:54:39 -07002153 .read = wcnss_wlan_read,
2154 .write = wcnss_wlan_write,
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002155};
2156
2157static struct miscdevice wcnss_misc = {
2158 .minor = MISC_DYNAMIC_MINOR,
2159 .name = DEVICE,
2160 .fops = &wcnss_node_fops,
2161};
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002162
2163static int __devinit
2164wcnss_wlan_probe(struct platform_device *pdev)
2165{
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002166 int ret = 0;
2167
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002168 /* verify we haven't been called more than once */
2169 if (penv) {
2170 dev_err(&pdev->dev, "cannot handle multiple devices.\n");
2171 return -ENODEV;
2172 }
2173
2174 /* create an environment to track the device */
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002175 penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002176 if (!penv) {
2177 dev_err(&pdev->dev, "cannot allocate device memory.\n");
2178 return -ENOMEM;
2179 }
2180 penv->pdev = pdev;
2181
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002182 /* register sysfs entries */
2183 ret = wcnss_create_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002184 if (ret) {
2185 penv = NULL;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002186 return -ENOENT;
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002187 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002188
Sameer Thalappil1878d762013-04-24 14:54:39 -07002189 mutex_init(&penv->dev_lock);
Sameer Thalappilf6fb1892013-09-06 17:33:30 -07002190 mutex_init(&penv->vbat_monitor_mutex);
Sameer Thalappil1878d762013-04-24 14:54:39 -07002191 init_waitqueue_head(&penv->read_wait);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002192
Sameer Thalappild3aad702013-01-22 18:52:24 -08002193 /* Since we were built into the kernel we'll be called as part
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002194 * of kernel initialization. We don't know if userspace
2195 * applications are available to service PIL at this time
2196 * (they probably are not), so we simply create a device node
2197 * here. When userspace is available it should touch the
2198 * device so that we know that WCNSS configuration can take
2199 * place
2200 */
2201 pr_info(DEVICE " probed in built-in mode\n");
2202 return misc_register(&wcnss_misc);
2203
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002204}
2205
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002206static int __devexit
2207wcnss_wlan_remove(struct platform_device *pdev)
2208{
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08002209 wcnss_remove_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002210 penv = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002211 return 0;
2212}
2213
2214
2215static const struct dev_pm_ops wcnss_wlan_pm_ops = {
2216 .suspend = wcnss_wlan_suspend,
2217 .resume = wcnss_wlan_resume,
2218};
2219
Sameer Thalappil8d686d42012-08-24 10:07:31 -07002220#ifdef CONFIG_WCNSS_CORE_PRONTO
2221static struct of_device_id msm_wcnss_pronto_match[] = {
2222 {.compatible = "qcom,wcnss_wlan"},
2223 {}
2224};
2225#endif
2226
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002227static struct platform_driver wcnss_wlan_driver = {
2228 .driver = {
2229 .name = DEVICE,
2230 .owner = THIS_MODULE,
2231 .pm = &wcnss_wlan_pm_ops,
Sameer Thalappil8d686d42012-08-24 10:07:31 -07002232#ifdef CONFIG_WCNSS_CORE_PRONTO
2233 .of_match_table = msm_wcnss_pronto_match,
2234#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002235 },
2236 .probe = wcnss_wlan_probe,
2237 .remove = __devexit_p(wcnss_wlan_remove),
2238};
2239
2240static int __init wcnss_wlan_init(void)
2241{
Sameer Thalappil24db5282012-09-10 11:58:33 -07002242 int ret = 0;
2243
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002244 platform_driver_register(&wcnss_wlan_driver);
2245 platform_driver_register(&wcnss_wlan_ctrl_driver);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002246 platform_driver_register(&wcnss_ctrl_driver);
Sameer Thalappil944c72d2013-08-14 17:56:17 -07002247 register_pm_notifier(&wcnss_pm_notifier);
Sameer Thalappil24db5282012-09-10 11:58:33 -07002248#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
2249 ret = wcnss_prealloc_init();
2250 if (ret < 0)
2251 pr_err("wcnss: pre-allocation failed\n");
2252#endif
2253
2254 return ret;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002255}
2256
2257static void __exit wcnss_wlan_exit(void)
2258{
2259 if (penv) {
2260 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07002261 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002262 penv = NULL;
2263 }
2264
Sameer Thalappil24db5282012-09-10 11:58:33 -07002265#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
2266 wcnss_prealloc_deinit();
2267#endif
Sameer Thalappil944c72d2013-08-14 17:56:17 -07002268 unregister_pm_notifier(&wcnss_pm_notifier);
2269 platform_driver_unregister(&wcnss_ctrl_driver);
2270 platform_driver_unregister(&wcnss_wlan_ctrl_driver);
2271 platform_driver_unregister(&wcnss_wlan_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002272}
2273
2274module_init(wcnss_wlan_init);
2275module_exit(wcnss_wlan_exit);
2276
2277MODULE_LICENSE("GPL v2");
2278MODULE_VERSION(VERSION);
2279MODULE_DESCRIPTION(DEVICE "Driver");