blob: aa29748e4186fee9863b3ab8779adda594f209b1 [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>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070037
Sameer Thalappileeb8c412012-08-10 17:17:09 -070038#include <mach/msm_smd.h>
Sameer Thalappil19e02352012-09-24 15:26:12 -070039#include <mach/msm_iomap.h>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070040#include <mach/subsystem_restart.h>
41
Sameer Thalappil24db5282012-09-10 11:58:33 -070042#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
43#include "wcnss_prealloc.h"
44#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070045
46#define DEVICE "wcnss_wlan"
47#define VERSION "1.01"
48#define WCNSS_PIL_DEVICE "wcnss"
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070049
Ankur Nandwanib0039b02011-08-09 14:00:45 -070050/* module params */
51#define WCNSS_CONFIG_UNSPECIFIED (-1)
Sameer Thalappileeb8c412012-08-10 17:17:09 -070052
Ankur Nandwania4510492011-08-29 15:56:23 -070053static int has_48mhz_xo = WCNSS_CONFIG_UNSPECIFIED;
Jeff Johnsonb3377e32011-11-18 23:32:27 -080054module_param(has_48mhz_xo, int, S_IWUSR | S_IRUGO);
Ankur Nandwanib0039b02011-08-09 14:00:45 -070055MODULE_PARM_DESC(has_48mhz_xo, "Is an external 48 MHz XO present");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070056
Sameer Thalappil1878d762013-04-24 14:54:39 -070057static int has_calibrated_data = WCNSS_CONFIG_UNSPECIFIED;
58module_param(has_calibrated_data, int, S_IWUSR | S_IRUGO);
59MODULE_PARM_DESC(has_calibrated_data, "whether calibrated data file available");
60
Sameer Thalappil58cec312013-05-13 15:52:08 -070061static int has_autodetect_xo = WCNSS_CONFIG_UNSPECIFIED;
62module_param(has_autodetect_xo, int, S_IWUSR | S_IRUGO);
63MODULE_PARM_DESC(has_autodetect_xo, "Perform auto detect to configure IRIS XO");
64
Sheng Fangbb421672013-03-19 08:27:28 +080065static int do_not_cancel_vote = WCNSS_CONFIG_UNSPECIFIED;
66module_param(do_not_cancel_vote, int, S_IWUSR | S_IRUGO);
67MODULE_PARM_DESC(do_not_cancel_vote, "Do not cancel votes for wcnss");
68
Sameer Thalappiled3f7da2012-11-01 12:54:01 -070069static DEFINE_SPINLOCK(reg_spinlock);
70
71#define MSM_RIVA_PHYS 0x03204000
72#define MSM_PRONTO_PHYS 0xfb21b000
73
74#define RIVA_SPARE_OFFSET 0x0b4
75#define RIVA_SUSPEND_BIT BIT(24)
76
Sameer Thalappil77716e52012-11-08 13:41:04 -080077#define MSM_RIVA_CCU_BASE 0x03200800
78
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080079#define CCU_RIVA_INVALID_ADDR_OFFSET 0x100
80#define CCU_RIVA_LAST_ADDR0_OFFSET 0x104
81#define CCU_RIVA_LAST_ADDR1_OFFSET 0x108
82#define CCU_RIVA_LAST_ADDR2_OFFSET 0x10c
Sameer Thalappil77716e52012-11-08 13:41:04 -080083
Sameer Thalappilbcc06182013-05-30 16:18:57 -070084#define PRONTO_PMU_SPARE_OFFSET 0x1088
85
Sameer Thalappil670197c2013-07-19 16:31:14 -070086#define PRONTO_PMU_COM_GDSCR_OFFSET 0x0024
87#define PRONTO_PMU_COM_GDSCR_SW_COLLAPSE BIT(0)
88#define PRONTO_PMU_COM_GDSCR_HW_CTRL BIT(1)
89
90#define PRONTO_PMU_WLAN_BCR_OFFSET 0x0050
91#define PRONTO_PMU_WLAN_BCR_BLK_ARES BIT(0)
92
93#define PRONTO_PMU_WLAN_GDSCR_OFFSET 0x0054
94#define PRONTO_PMU_WLAN_GDSCR_SW_COLLAPSE BIT(0)
95
Sameer Thalappilbcc06182013-05-30 16:18:57 -070096
97#define PRONTO_PMU_CBCR_OFFSET 0x0008
98#define PRONTO_PMU_CBCR_CLK_EN BIT(0)
99
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800100#define MSM_PRONTO_A2XB_BASE 0xfb100400
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800101#define A2XB_CFG_OFFSET 0x00
102#define A2XB_INT_SRC_OFFSET 0x0c
103#define A2XB_TSTBUS_CTRL_OFFSET 0x14
104#define A2XB_TSTBUS_OFFSET 0x18
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800105#define A2XB_ERR_INFO_OFFSET 0x1c
106
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800107#define WCNSS_TSTBUS_CTRL_EN BIT(0)
108#define WCNSS_TSTBUS_CTRL_AXIM (0x02 << 1)
109#define WCNSS_TSTBUS_CTRL_CMDFIFO (0x03 << 1)
110#define WCNSS_TSTBUS_CTRL_WRFIFO (0x04 << 1)
111#define WCNSS_TSTBUS_CTRL_RDFIFO (0x05 << 1)
112#define WCNSS_TSTBUS_CTRL_CTRL (0x07 << 1)
113#define WCNSS_TSTBUS_CTRL_AXIM_CFG0 (0x00 << 6)
114#define WCNSS_TSTBUS_CTRL_AXIM_CFG1 (0x01 << 6)
115#define WCNSS_TSTBUS_CTRL_CTRL_CFG0 (0x00 << 12)
116#define WCNSS_TSTBUS_CTRL_CTRL_CFG1 (0x01 << 12)
117
118#define MSM_PRONTO_CCPU_BASE 0xfb205050
119#define CCU_PRONTO_INVALID_ADDR_OFFSET 0x08
120#define CCU_PRONTO_LAST_ADDR0_OFFSET 0x0c
121#define CCU_PRONTO_LAST_ADDR1_OFFSET 0x10
122#define CCU_PRONTO_LAST_ADDR2_OFFSET 0x14
123
Sameer Thalappil767ff492013-06-19 15:25:21 -0700124#define MSM_PRONTO_SAW2_BASE 0xfb219000
125#define PRONTO_SAW2_SPM_STS_OFFSET 0x0c
126
Sameer Thalappil66d2b392013-07-02 11:32:55 -0700127#define MSM_PRONTO_PLL_BASE 0xfb21b1c0
128#define PRONTO_PLL_STATUS_OFFSET 0x1c
129
Sameer Thalappil670197c2013-07-19 16:31:14 -0700130#define MSM_PRONTO_TXP_PHY_ABORT 0xfb080488
131#define MSM_PRONTO_BRDG_ERR_SRC 0xfb080fb0
132
Sameer Thalappild0952f62013-05-03 10:18:29 -0700133#define WCNSS_DEF_WLAN_RX_BUFF_COUNT 1024
134
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700135#define WCNSS_CTRL_CHANNEL "WCNSS_CTRL"
Sameer Thalappil1878d762013-04-24 14:54:39 -0700136#define WCNSS_MAX_FRAME_SIZE (4*1024)
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700137#define WCNSS_VERSION_LEN 30
138
139/* message types */
140#define WCNSS_CTRL_MSG_START 0x01000000
Sameer Thalappil1878d762013-04-24 14:54:39 -0700141#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0)
142#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1)
143#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2)
144#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3)
145#define WCNSS_CALDATA_UPLD_REQ (WCNSS_CTRL_MSG_START + 4)
146#define WCNSS_CALDATA_UPLD_RSP (WCNSS_CTRL_MSG_START + 5)
147#define WCNSS_CALDATA_DNLD_REQ (WCNSS_CTRL_MSG_START + 6)
148#define WCNSS_CALDATA_DNLD_RSP (WCNSS_CTRL_MSG_START + 7)
Sameer Thalappilf106a682013-02-16 20:41:11 -0800149
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700150
151#define VALID_VERSION(version) \
152 ((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0)
153
Sameer Thalappil1878d762013-04-24 14:54:39 -0700154#define FW_CALDATA_CAPABLE() \
155 ((penv->fw_major >= 1) && (penv->fw_minor >= 5) ? 1 : 0)
156
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700157struct smd_msg_hdr {
Sameer Thalappilf106a682013-02-16 20:41:11 -0800158 unsigned int msg_type;
159 unsigned int msg_len;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700160};
161
162struct wcnss_version {
163 struct smd_msg_hdr hdr;
164 unsigned char major;
165 unsigned char minor;
166 unsigned char version;
167 unsigned char revision;
168};
169
Naresh Jayaram08cee442013-04-26 19:50:00 +0530170struct wcnss_pmic_dump {
171 char reg_name[10];
172 u16 reg_addr;
173};
174
175static struct wcnss_pmic_dump wcnss_pmic_reg_dump[] = {
176 {"S2", 0x1D8},
177 {"L4", 0xB4},
178 {"L10", 0xC0},
179 {"LVS2", 0x62},
180 {"S4", 0x1E8},
181 {"LVS7", 0x06C},
182 {"LVS1", 0x060},
183};
184
Sameer Thalappilf106a682013-02-16 20:41:11 -0800185#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
186
187/*
188 * On SMD channel 4K of maximum data can be transferred, including message
189 * header, so NV fragment size as next multiple of 1Kb is 3Kb.
190 */
191#define NV_FRAGMENT_SIZE 3072
Sameer Thalappil1878d762013-04-24 14:54:39 -0700192#define MAX_CALIBRATED_DATA_SIZE (64*1024)
193#define LAST_FRAGMENT (1 << 0)
194#define MESSAGE_TO_FOLLOW (1 << 1)
195#define CAN_RECEIVE_CALDATA (1 << 15)
196#define WCNSS_RESP_SUCCESS 1
197#define WCNSS_RESP_FAIL 0
198
Sameer Thalappilf106a682013-02-16 20:41:11 -0800199
200/* Macro to find the total number fragments of the NV bin Image */
201#define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \
202 (x / NV_FRAGMENT_SIZE) : ((x / NV_FRAGMENT_SIZE) + 1))
203
204struct nvbin_dnld_req_params {
205 /*
206 * Fragment sequence number of the NV bin Image. NV Bin Image
207 * might not fit into one message due to size limitation of
208 * the SMD channel FIFO so entire NV blob is chopped into
209 * multiple fragments starting with seqeunce number 0. The
210 * last fragment is indicated by marking is_last_fragment field
211 * to 1. At receiving side, NV blobs would be concatenated
212 * together without any padding bytes in between.
213 */
214 unsigned short frag_number;
215
216 /*
Sameer Thalappil1878d762013-04-24 14:54:39 -0700217 * bit 0: When set to 1 it indicates that no more fragments will
218 * be sent.
219 * bit 1: When set, a new message will be followed by this message
220 * bit 2- bit 14: Reserved
221 * bit 15: when set, it indicates that the sender is capable of
222 * receiving Calibrated data.
Sameer Thalappilf106a682013-02-16 20:41:11 -0800223 */
Sameer Thalappil1878d762013-04-24 14:54:39 -0700224 unsigned short msg_flags;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800225
226 /* NV Image size (number of bytes) */
227 unsigned int nvbin_buffer_size;
228
229 /*
230 * Following the 'nvbin_buffer_size', there should be
231 * nvbin_buffer_size bytes of NV bin Image i.e.
232 * uint8[nvbin_buffer_size].
233 */
234};
235
Sameer Thalappil1878d762013-04-24 14:54:39 -0700236
Sameer Thalappilf106a682013-02-16 20:41:11 -0800237struct nvbin_dnld_req_msg {
238 /*
239 * Note: The length specified in nvbin_dnld_req_msg messages
240 * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) +
241 * nvbin_buffer_size.
242 */
243 struct smd_msg_hdr hdr;
244 struct nvbin_dnld_req_params dnld_req_params;
245};
246
Sameer Thalappil1878d762013-04-24 14:54:39 -0700247struct cal_data_params {
248
249 /* The total size of the calibrated data, including all the
250 * fragments.
251 */
252 unsigned int total_size;
253 unsigned short frag_number;
254 /*
255 * bit 0: When set to 1 it indicates that no more fragments will
256 * be sent.
257 * bit 1: When set, a new message will be followed by this message
258 * bit 2- bit 15: Reserved
259 */
260 unsigned short msg_flags;
261 /*
262 * fragment size
263 */
264 unsigned int frag_size;
265 /*
266 * Following the frag_size, frag_size of fragmented
267 * data will be followed.
268 */
269};
270
271struct cal_data_msg {
272 /*
273 * The length specified in cal_data_msg should be
274 * hdr.msg_len = sizeof(cal_data_msg) + frag_size
275 */
276 struct smd_msg_hdr hdr;
277 struct cal_data_params cal_params;
278};
279
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700280static struct {
281 struct platform_device *pdev;
282 void *pil;
283 struct resource *mmio_res;
284 struct resource *tx_irq_res;
285 struct resource *rx_irq_res;
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700286 struct resource *gpios_5wire;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700287 const struct dev_pm_ops *pm_ops;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800288 int triggered;
289 int smd_channel_ready;
Sameer Thalappild0952f62013-05-03 10:18:29 -0700290 u32 wlan_rx_buff_count;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700291 smd_channel_t *smd_ch;
292 unsigned char wcnss_version[WCNSS_VERSION_LEN];
Sameer Thalappil1878d762013-04-24 14:54:39 -0700293 unsigned char fw_major;
294 unsigned char fw_minor;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800295 unsigned int serial_number;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800296 int thermal_mitigation;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700297 enum wcnss_hw_type wcnss_hw_type;
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700298 void (*tm_notify)(struct device *, int);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700299 struct wcnss_wlan_config wlan_config;
David Collinsb727f7d2011-09-12 11:54:49 -0700300 struct delayed_work wcnss_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700301 struct work_struct wcnssctrl_version_work;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800302 struct work_struct wcnssctrl_nvbin_dnld_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700303 struct work_struct wcnssctrl_rx_work;
Sameer Thalappilf138da52012-07-30 12:56:37 -0700304 struct wake_lock wcnss_wake_lock;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700305 void __iomem *msm_wcnss_base;
Sameer Thalappild3aad702013-01-22 18:52:24 -0800306 void __iomem *riva_ccu_base;
307 void __iomem *pronto_a2xb_base;
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800308 void __iomem *pronto_ccpu_base;
Sameer Thalappil767ff492013-06-19 15:25:21 -0700309 void __iomem *pronto_saw2_base;
Sameer Thalappil66d2b392013-07-02 11:32:55 -0700310 void __iomem *pronto_pll_base;
Sameer Thalappil670197c2013-07-19 16:31:14 -0700311 void __iomem *wlan_tx_phy_aborts;
312 void __iomem *wlan_brdg_err_source;
Sameer Thalappil58281ca2013-04-10 18:50:18 -0700313 void __iomem *fiq_reg;
Sameer Thalappil1878d762013-04-24 14:54:39 -0700314 int ssr_boot;
315 int nv_downloaded;
316 unsigned char *fw_cal_data;
317 unsigned char *user_cal_data;
318 int fw_cal_rcvd;
319 int fw_cal_exp_frag;
320 int fw_cal_available;
321 int user_cal_read;
322 int user_cal_available;
323 int user_cal_rcvd;
324 int user_cal_exp_size;
325 int device_opened;
Sameer Thalappil1d69b8022013-06-10 19:10:07 -0700326 int iris_xo_mode_set;
Sameer Thalappil1878d762013-04-24 14:54:39 -0700327 struct mutex dev_lock;
328 wait_queue_head_t read_wait;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700329} *penv = NULL;
330
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800331static ssize_t wcnss_serial_number_show(struct device *dev,
332 struct device_attribute *attr, char *buf)
333{
334 if (!penv)
335 return -ENODEV;
336
337 return scnprintf(buf, PAGE_SIZE, "%08X\n", penv->serial_number);
338}
339
340static ssize_t wcnss_serial_number_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700341 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800342{
343 unsigned int value;
344
345 if (!penv)
346 return -ENODEV;
347
348 if (sscanf(buf, "%08X", &value) != 1)
349 return -EINVAL;
350
351 penv->serial_number = value;
352 return count;
353}
354
355static DEVICE_ATTR(serial_number, S_IRUSR | S_IWUSR,
356 wcnss_serial_number_show, wcnss_serial_number_store);
357
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800358
359static ssize_t wcnss_thermal_mitigation_show(struct device *dev,
360 struct device_attribute *attr, char *buf)
361{
362 if (!penv)
363 return -ENODEV;
364
365 return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation);
366}
367
368static ssize_t wcnss_thermal_mitigation_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700369 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800370{
371 int value;
372
373 if (!penv)
374 return -ENODEV;
375
376 if (sscanf(buf, "%d", &value) != 1)
377 return -EINVAL;
378 penv->thermal_mitigation = value;
Jeff Johnson61374652012-04-16 20:51:48 -0700379 if (penv->tm_notify)
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700380 (penv->tm_notify)(dev, value);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800381 return count;
382}
383
384static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR,
385 wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store);
386
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700387
388static ssize_t wcnss_version_show(struct device *dev,
389 struct device_attribute *attr, char *buf)
390{
391 if (!penv)
392 return -ENODEV;
393
394 return scnprintf(buf, PAGE_SIZE, "%s", penv->wcnss_version);
395}
396
397static DEVICE_ATTR(wcnss_version, S_IRUSR,
398 wcnss_version_show, NULL);
399
Naresh Jayaram08cee442013-04-26 19:50:00 +0530400void wcnss_riva_dump_pmic_regs(void)
401{
402 int i, rc;
403 u8 val;
404
405 for (i = 0; i < ARRAY_SIZE(wcnss_pmic_reg_dump); i++) {
406 val = 0;
407 rc = pm8xxx_read_register(wcnss_pmic_reg_dump[i].reg_addr,
408 &val);
409 if (rc)
410 pr_err("PMIC READ: Failed to read addr = %d\n",
411 wcnss_pmic_reg_dump[i].reg_addr);
412 else
413 pr_info_ratelimited("PMIC READ: %s addr = %x, value = %x\n",
414 wcnss_pmic_reg_dump[i].reg_name,
415 wcnss_pmic_reg_dump[i].reg_addr, val);
416 }
417}
418
Sameer Thalappil77716e52012-11-08 13:41:04 -0800419/* wcnss_reset_intr() is invoked when host drivers fails to
420 * communicate with WCNSS over SMD; so logging these registers
Sameer Thalappild3aad702013-01-22 18:52:24 -0800421 * helps to know WCNSS failure reason
422 */
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800423void wcnss_riva_log_debug_regs(void)
Sameer Thalappil77716e52012-11-08 13:41:04 -0800424{
Sameer Thalappil77716e52012-11-08 13:41:04 -0800425 void __iomem *ccu_reg;
426 u32 reg = 0;
427
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800428 ccu_reg = penv->riva_ccu_base + CCU_RIVA_INVALID_ADDR_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800429 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800430 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800431
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800432 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR0_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800433 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800434 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800435
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800436 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR1_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800437 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800438 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800439
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800440 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR2_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800441 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800442 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
Naresh Jayaram08cee442013-04-26 19:50:00 +0530443 wcnss_riva_dump_pmic_regs();
Sameer Thalappil77716e52012-11-08 13:41:04 -0800444
Sameer Thalappil77716e52012-11-08 13:41:04 -0800445}
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800446EXPORT_SYMBOL(wcnss_riva_log_debug_regs);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800447
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800448/* Log pronto debug registers before sending reset interrupt */
449void wcnss_pronto_log_debug_regs(void)
450{
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800451 void __iomem *reg_addr, *tst_addr, *tst_ctrl_addr;
Sameer Thalappil670197c2013-07-19 16:31:14 -0700452 u32 reg = 0, reg2 = 0;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800453
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700454
455 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_SPARE_OFFSET;
456 reg = readl_relaxed(reg_addr);
457 pr_info_ratelimited("%s: PRONTO_PMU_SPARE %08x\n", __func__, reg);
458
Sameer Thalappil670197c2013-07-19 16:31:14 -0700459 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_GDSCR_OFFSET;
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700460 reg = readl_relaxed(reg_addr);
461 reg >>= 31;
462
463 if (!reg) {
464 pr_info_ratelimited("%s: Cannot log, Pronto common SS is power collapsed\n",
465 __func__);
466 return;
467 }
Sameer Thalappil670197c2013-07-19 16:31:14 -0700468 reg &= ~(PRONTO_PMU_COM_GDSCR_SW_COLLAPSE
469 | PRONTO_PMU_COM_GDSCR_HW_CTRL);
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700470 writel_relaxed(reg, reg_addr);
471
472 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CBCR_OFFSET;
473 reg = readl_relaxed(reg_addr);
474 reg |= PRONTO_PMU_CBCR_CLK_EN;
475 writel_relaxed(reg, reg_addr);
476
Sameer Thalappild3aad702013-01-22 18:52:24 -0800477 reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800478 reg = readl_relaxed(reg_addr);
479 pr_info_ratelimited("%s: A2XB_CFG_OFFSET %08x\n", __func__, reg);
480
Sameer Thalappild3aad702013-01-22 18:52:24 -0800481 reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800482 reg = readl_relaxed(reg_addr);
483 pr_info_ratelimited("%s: A2XB_INT_SRC_OFFSET %08x\n", __func__, reg);
484
Sameer Thalappild3aad702013-01-22 18:52:24 -0800485 reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800486 reg = readl_relaxed(reg_addr);
487 pr_info_ratelimited("%s: A2XB_ERR_INFO_OFFSET %08x\n", __func__, reg);
488
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800489 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_INVALID_ADDR_OFFSET;
490 reg = readl_relaxed(reg_addr);
491 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
492
493 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR0_OFFSET;
494 reg = readl_relaxed(reg_addr);
495 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
496
497 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR1_OFFSET;
498 reg = readl_relaxed(reg_addr);
499 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
500
501 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR2_OFFSET;
502 reg = readl_relaxed(reg_addr);
503 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
504
Sameer Thalappil767ff492013-06-19 15:25:21 -0700505 reg_addr = penv->pronto_saw2_base + PRONTO_SAW2_SPM_STS_OFFSET;
506 reg = readl_relaxed(reg_addr);
507 pr_info_ratelimited("%s: PRONTO_SAW2_SPM_STS %08x\n", __func__, reg);
508
Sameer Thalappil66d2b392013-07-02 11:32:55 -0700509 reg_addr = penv->pronto_pll_base + PRONTO_PLL_STATUS_OFFSET;
510 reg = readl_relaxed(reg_addr);
511 pr_info_ratelimited("%s: PRONTO_PLL_STATUS %08x\n", __func__, reg);
512
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800513 tst_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_OFFSET;
514 tst_ctrl_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_CTRL_OFFSET;
515
516 /* read data FIFO */
517 reg = 0;
518 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_RDFIFO;
519 writel_relaxed(reg, tst_ctrl_addr);
520 reg = readl_relaxed(tst_addr);
521 pr_info_ratelimited("%s: Read data FIFO testbus %08x\n",
522 __func__, reg);
523
524 /* command FIFO */
525 reg = 0;
526 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CMDFIFO;
527 writel_relaxed(reg, tst_ctrl_addr);
528 reg = readl_relaxed(tst_addr);
529 pr_info_ratelimited("%s: Command FIFO testbus %08x\n",
530 __func__, reg);
531
532 /* write data FIFO */
533 reg = 0;
534 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_WRFIFO;
535 writel_relaxed(reg, tst_ctrl_addr);
536 reg = readl_relaxed(tst_addr);
537 pr_info_ratelimited("%s: Rrite data FIFO testbus %08x\n",
538 __func__, reg);
539
540 /* AXIM SEL CFG0 */
541 reg = 0;
542 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
543 WCNSS_TSTBUS_CTRL_AXIM_CFG0;
544 writel_relaxed(reg, tst_ctrl_addr);
545 reg = readl_relaxed(tst_addr);
546 pr_info_ratelimited("%s: AXIM SEL CFG0 testbus %08x\n",
547 __func__, reg);
548
549 /* AXIM SEL CFG1 */
550 reg = 0;
551 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
552 WCNSS_TSTBUS_CTRL_AXIM_CFG1;
553 writel_relaxed(reg, tst_ctrl_addr);
554 reg = readl_relaxed(tst_addr);
555 pr_info_ratelimited("%s: AXIM SEL CFG1 testbus %08x\n",
556 __func__, reg);
557
558 /* CTRL SEL CFG0 */
559 reg = 0;
560 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
561 WCNSS_TSTBUS_CTRL_CTRL_CFG0;
562 writel_relaxed(reg, tst_ctrl_addr);
563 reg = readl_relaxed(tst_addr);
564 pr_info_ratelimited("%s: CTRL SEL CFG0 testbus %08x\n",
565 __func__, reg);
566
567 /* CTRL SEL CFG1 */
568 reg = 0;
569 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
570 WCNSS_TSTBUS_CTRL_CTRL_CFG1;
571 writel_relaxed(reg, tst_ctrl_addr);
572 reg = readl_relaxed(tst_addr);
573 pr_info_ratelimited("%s: CTRL SEL CFG1 testbus %08x\n", __func__, reg);
574
Sameer Thalappil670197c2013-07-19 16:31:14 -0700575
576 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_BCR_OFFSET;
577 reg = readl_relaxed(reg_addr);
578
579 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_GDSCR_OFFSET;
580 reg2 = readl_relaxed(reg_addr);
581
582 if ((reg & PRONTO_PMU_WLAN_BCR_BLK_ARES) ||
583 (reg2 & PRONTO_PMU_WLAN_GDSCR_SW_COLLAPSE)) {
584 pr_info_ratelimited("%s: Cannot log, wlan domain is power collapsed\n",
585 __func__);
586 return;
587 }
588
589 reg = readl_relaxed(penv->wlan_tx_phy_aborts);
590 pr_info_ratelimited("%s: WLAN_TX_PHY_ABORTS %08x\n", __func__, reg);
591
592 reg = readl_relaxed(penv->wlan_brdg_err_source);
593 pr_info_ratelimited("%s: WLAN_BRDG_ERR_SOURCE %08x\n", __func__, reg);
594
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800595}
596EXPORT_SYMBOL(wcnss_pronto_log_debug_regs);
597
598/* interface to reset wcnss by sending the reset interrupt */
Sameer Thalappil19e02352012-09-24 15:26:12 -0700599void wcnss_reset_intr(void)
600{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800601 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
602 wcnss_pronto_log_debug_regs();
Sameer Thalappil34920762013-03-21 15:43:28 -0700603 wmb();
Sameer Thalappil58281ca2013-04-10 18:50:18 -0700604 __raw_writel(1 << 16, penv->fiq_reg);
Sameer Thalappil34920762013-03-21 15:43:28 -0700605 } else {
606 wcnss_riva_log_debug_regs();
607 wmb();
608 __raw_writel(1 << 24, MSM_APCS_GCC_BASE + 0x8);
Sameer Thalappil19e02352012-09-24 15:26:12 -0700609 }
610}
611EXPORT_SYMBOL(wcnss_reset_intr);
612
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800613static int wcnss_create_sysfs(struct device *dev)
614{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800615 int ret;
616
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800617 if (!dev)
618 return -ENODEV;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800619
620 ret = device_create_file(dev, &dev_attr_serial_number);
621 if (ret)
622 return ret;
623
624 ret = device_create_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700625 if (ret)
626 goto remove_serial;
627
628 ret = device_create_file(dev, &dev_attr_wcnss_version);
629 if (ret)
630 goto remove_thermal;
631
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800632 return 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700633
634remove_thermal:
635 device_remove_file(dev, &dev_attr_thermal_mitigation);
636remove_serial:
637 device_remove_file(dev, &dev_attr_serial_number);
638
639 return ret;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800640}
641
642static void wcnss_remove_sysfs(struct device *dev)
643{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800644 if (dev) {
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800645 device_remove_file(dev, &dev_attr_serial_number);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800646 device_remove_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700647 device_remove_file(dev, &dev_attr_wcnss_version);
648 }
649}
650static void wcnss_smd_notify_event(void *data, unsigned int event)
651{
652 int len = 0;
653
654 if (penv != data) {
655 pr_err("wcnss: invalid env pointer in smd callback\n");
656 return;
657 }
658 switch (event) {
659 case SMD_EVENT_DATA:
660 len = smd_read_avail(penv->smd_ch);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700661 if (len < 0) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700662 pr_err("wcnss: failed to read from smd %d\n", len);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700663 return;
664 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700665 schedule_work(&penv->wcnssctrl_rx_work);
666 break;
667
668 case SMD_EVENT_OPEN:
669 pr_debug("wcnss: opening WCNSS SMD channel :%s",
670 WCNSS_CTRL_CHANNEL);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700671 schedule_work(&penv->wcnssctrl_version_work);
672
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700673 break;
674
675 case SMD_EVENT_CLOSE:
676 pr_debug("wcnss: closing WCNSS SMD channel :%s",
677 WCNSS_CTRL_CHANNEL);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700678 /* This SMD is closed only during SSR */
679 penv->ssr_boot = true;
680 penv->nv_downloaded = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700681 break;
682
683 default:
684 break;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800685 }
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800686}
687
David Collinsb727f7d2011-09-12 11:54:49 -0700688static void wcnss_post_bootup(struct work_struct *work)
689{
Sheng Fangbb421672013-03-19 08:27:28 +0800690 if (do_not_cancel_vote == 1) {
691 pr_info("%s: Keeping APPS vote for Iris & WCNSS\n", __func__);
692 return;
693 }
694
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700695 pr_info("%s: Cancel APPS vote for Iris & WCNSS\n", __func__);
David Collinsb727f7d2011-09-12 11:54:49 -0700696
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700697 /* Since WCNSS is up, cancel any APPS vote for Iris & WCNSS VREGs */
David Collinsb727f7d2011-09-12 11:54:49 -0700698 wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config,
Sameer Thalappil1d69b8022013-06-10 19:10:07 -0700699 WCNSS_WLAN_SWITCH_OFF, NULL);
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700700
701}
702
703static int
704wcnss_pronto_gpios_config(struct device *dev, bool enable)
705{
706 int rc = 0;
707 int i, j;
708 int WCNSS_WLAN_NUM_GPIOS = 5;
709
710 for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) {
711 int gpio = of_get_gpio(dev->of_node, i);
712 if (enable) {
713 rc = gpio_request(gpio, "wcnss_wlan");
714 if (rc) {
715 pr_err("WCNSS gpio_request %d err %d\n",
716 gpio, rc);
717 goto fail;
718 }
719 } else
720 gpio_free(gpio);
721 }
722
723 return rc;
724
725fail:
726 for (j = WCNSS_WLAN_NUM_GPIOS-1; j >= 0; j--) {
727 int gpio = of_get_gpio(dev->of_node, i);
728 gpio_free(gpio);
729 }
730 return rc;
David Collinsb727f7d2011-09-12 11:54:49 -0700731}
732
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700733static int
734wcnss_gpios_config(struct resource *gpios_5wire, bool enable)
735{
736 int i, j;
737 int rc = 0;
738
739 for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) {
740 if (enable) {
741 rc = gpio_request(i, gpios_5wire->name);
742 if (rc) {
743 pr_err("WCNSS gpio_request %d err %d\n", i, rc);
744 goto fail;
745 }
746 } else
747 gpio_free(i);
748 }
749
750 return rc;
751
752fail:
753 for (j = i-1; j >= gpios_5wire->start; j--)
754 gpio_free(j);
755 return rc;
756}
757
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700758static int __devinit
759wcnss_wlan_ctrl_probe(struct platform_device *pdev)
760{
Sameer Thalappil0ab82132013-06-10 10:10:05 -0700761 if (!penv || !penv->triggered)
Sameer Thalappil667f43f2011-10-10 11:12:54 -0700762 return -ENODEV;
763
764 penv->smd_channel_ready = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700765
766 pr_info("%s: SMD ctrl channel up\n", __func__);
767
David Collinsb727f7d2011-09-12 11:54:49 -0700768 /* Schedule a work to do any post boot up activity */
769 INIT_DELAYED_WORK(&penv->wcnss_work, wcnss_post_bootup);
770 schedule_delayed_work(&penv->wcnss_work, msecs_to_jiffies(10000));
771
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700772 return 0;
773}
774
Sameer Thalappil13f45182012-07-23 18:02:45 -0700775void wcnss_flush_delayed_boot_votes()
776{
Sameer Thalappilf106a682013-02-16 20:41:11 -0800777 flush_delayed_work(&penv->wcnss_work);
Sameer Thalappil13f45182012-07-23 18:02:45 -0700778}
779EXPORT_SYMBOL(wcnss_flush_delayed_boot_votes);
780
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700781static int __devexit
782wcnss_wlan_ctrl_remove(struct platform_device *pdev)
783{
784 if (penv)
785 penv->smd_channel_ready = 0;
786
787 pr_info("%s: SMD ctrl channel down\n", __func__);
788
789 return 0;
790}
791
792
793static struct platform_driver wcnss_wlan_ctrl_driver = {
794 .driver = {
795 .name = "WLAN_CTRL",
796 .owner = THIS_MODULE,
797 },
798 .probe = wcnss_wlan_ctrl_probe,
799 .remove = __devexit_p(wcnss_wlan_ctrl_remove),
800};
801
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700802static int __devexit
803wcnss_ctrl_remove(struct platform_device *pdev)
804{
805 if (penv && penv->smd_ch)
806 smd_close(penv->smd_ch);
807
808 return 0;
809}
810
811static int __devinit
812wcnss_ctrl_probe(struct platform_device *pdev)
813{
814 int ret = 0;
815
Sameer Thalappil0ab82132013-06-10 10:10:05 -0700816 if (!penv || !penv->triggered)
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700817 return -ENODEV;
818
819 ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS,
820 &penv->smd_ch, penv, wcnss_smd_notify_event);
821 if (ret < 0) {
822 pr_err("wcnss: cannot open the smd command channel %s: %d\n",
823 WCNSS_CTRL_CHANNEL, ret);
824 return -ENODEV;
825 }
826 smd_disable_read_intr(penv->smd_ch);
827
828 return 0;
829}
830
831/* platform device for WCNSS_CTRL SMD channel */
832static struct platform_driver wcnss_ctrl_driver = {
833 .driver = {
834 .name = "WCNSS_CTRL",
835 .owner = THIS_MODULE,
836 },
837 .probe = wcnss_ctrl_probe,
838 .remove = __devexit_p(wcnss_ctrl_remove),
839};
840
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700841struct device *wcnss_wlan_get_device(void)
842{
843 if (penv && penv->pdev && penv->smd_channel_ready)
844 return &penv->pdev->dev;
845 return NULL;
846}
847EXPORT_SYMBOL(wcnss_wlan_get_device);
848
Sameer Thalappil409ed352011-12-07 10:53:31 -0800849struct platform_device *wcnss_get_platform_device(void)
850{
851 if (penv && penv->pdev)
852 return penv->pdev;
853 return NULL;
854}
855EXPORT_SYMBOL(wcnss_get_platform_device);
856
857struct wcnss_wlan_config *wcnss_get_wlan_config(void)
858{
859 if (penv && penv->pdev)
860 return &penv->wlan_config;
861 return NULL;
862}
863EXPORT_SYMBOL(wcnss_get_wlan_config);
864
Sameer Thalappil1878d762013-04-24 14:54:39 -0700865int wcnss_device_ready(void)
866{
867 if (penv && penv->pdev && penv->nv_downloaded)
868 return 1;
869 return 0;
870}
871EXPORT_SYMBOL(wcnss_device_ready);
872
873
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700874struct resource *wcnss_wlan_get_memory_map(struct device *dev)
875{
876 if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready)
877 return penv->mmio_res;
878 return NULL;
879}
880EXPORT_SYMBOL(wcnss_wlan_get_memory_map);
881
882int wcnss_wlan_get_dxe_tx_irq(struct device *dev)
883{
884 if (penv && dev && (dev == &penv->pdev->dev) &&
885 penv->tx_irq_res && penv->smd_channel_ready)
886 return penv->tx_irq_res->start;
887 return WCNSS_WLAN_IRQ_INVALID;
888}
889EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq);
890
891int wcnss_wlan_get_dxe_rx_irq(struct device *dev)
892{
893 if (penv && dev && (dev == &penv->pdev->dev) &&
894 penv->rx_irq_res && penv->smd_channel_ready)
895 return penv->rx_irq_res->start;
896 return WCNSS_WLAN_IRQ_INVALID;
897}
898EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq);
899
900void wcnss_wlan_register_pm_ops(struct device *dev,
901 const struct dev_pm_ops *pm_ops)
902{
903 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops)
904 penv->pm_ops = pm_ops;
905}
906EXPORT_SYMBOL(wcnss_wlan_register_pm_ops);
907
Sameer Thalappilecdd1002011-09-09 10:52:27 -0700908void wcnss_wlan_unregister_pm_ops(struct device *dev,
909 const struct dev_pm_ops *pm_ops)
910{
911 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) {
912 if (pm_ops->suspend != penv->pm_ops->suspend ||
913 pm_ops->resume != penv->pm_ops->resume)
914 pr_err("PM APIs dont match with registered APIs\n");
915 penv->pm_ops = NULL;
916 }
917}
918EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops);
919
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700920void wcnss_register_thermal_mitigation(struct device *dev,
921 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700922{
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700923 if (penv && dev && tm_notify)
Jeff Johnson61374652012-04-16 20:51:48 -0700924 penv->tm_notify = tm_notify;
925}
926EXPORT_SYMBOL(wcnss_register_thermal_mitigation);
927
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700928void wcnss_unregister_thermal_mitigation(
929 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700930{
931 if (penv && tm_notify) {
932 if (tm_notify != penv->tm_notify)
933 pr_err("tm_notify doesn't match registered\n");
934 penv->tm_notify = NULL;
935 }
936}
937EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation);
938
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800939unsigned int wcnss_get_serial_number(void)
940{
941 if (penv)
942 return penv->serial_number;
943 return 0;
944}
945EXPORT_SYMBOL(wcnss_get_serial_number);
946
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700947static int enable_wcnss_suspend_notify;
948
949static int enable_wcnss_suspend_notify_set(const char *val,
950 struct kernel_param *kp)
951{
952 int ret;
953
954 ret = param_set_int(val, kp);
955 if (ret)
956 return ret;
957
958 if (enable_wcnss_suspend_notify)
959 pr_debug("Suspend notification activated for wcnss\n");
960
961 return 0;
962}
963module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set,
964 param_get_int, &enable_wcnss_suspend_notify, S_IRUGO | S_IWUSR);
965
Sameer Thalappil58cec312013-05-13 15:52:08 -0700966int wcnss_xo_auto_detect_enabled(void)
967{
968 return (has_autodetect_xo == 1 ? 1 : 0);
969}
970
Sameer Thalappil1d69b8022013-06-10 19:10:07 -0700971int wcnss_wlan_iris_xo_mode(void)
972{
973 if (penv && penv->pdev && penv->smd_channel_ready)
974 return penv->iris_xo_mode_set;
975 return -ENODEV;
976}
977EXPORT_SYMBOL(wcnss_wlan_iris_xo_mode);
978
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700979
980void wcnss_suspend_notify(void)
981{
982 void __iomem *pmu_spare_reg;
983 u32 reg = 0;
984 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700985
986 if (!enable_wcnss_suspend_notify)
987 return;
988
989 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
990 return;
991
992 /* For Riva */
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700993 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
994 spin_lock_irqsave(&reg_spinlock, flags);
995 reg = readl_relaxed(pmu_spare_reg);
996 reg |= RIVA_SUSPEND_BIT;
997 writel_relaxed(reg, pmu_spare_reg);
998 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700999}
1000EXPORT_SYMBOL(wcnss_suspend_notify);
1001
1002void wcnss_resume_notify(void)
1003{
1004 void __iomem *pmu_spare_reg;
1005 u32 reg = 0;
1006 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001007
1008 if (!enable_wcnss_suspend_notify)
1009 return;
1010
1011 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
1012 return;
1013
1014 /* For Riva */
1015 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
1016
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001017 spin_lock_irqsave(&reg_spinlock, flags);
1018 reg = readl_relaxed(pmu_spare_reg);
1019 reg &= ~RIVA_SUSPEND_BIT;
1020 writel_relaxed(reg, pmu_spare_reg);
1021 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001022}
1023EXPORT_SYMBOL(wcnss_resume_notify);
1024
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001025static int wcnss_wlan_suspend(struct device *dev)
1026{
1027 if (penv && dev && (dev == &penv->pdev->dev) &&
1028 penv->smd_channel_ready &&
1029 penv->pm_ops && penv->pm_ops->suspend)
1030 return penv->pm_ops->suspend(dev);
1031 return 0;
1032}
1033
1034static int wcnss_wlan_resume(struct device *dev)
1035{
1036 if (penv && dev && (dev == &penv->pdev->dev) &&
1037 penv->smd_channel_ready &&
1038 penv->pm_ops && penv->pm_ops->resume)
1039 return penv->pm_ops->resume(dev);
1040 return 0;
1041}
1042
Sameer Thalappilf138da52012-07-30 12:56:37 -07001043void wcnss_prevent_suspend()
1044{
1045 if (penv)
1046 wake_lock(&penv->wcnss_wake_lock);
1047}
1048EXPORT_SYMBOL(wcnss_prevent_suspend);
1049
1050void wcnss_allow_suspend()
1051{
1052 if (penv)
1053 wake_unlock(&penv->wcnss_wake_lock);
1054}
1055EXPORT_SYMBOL(wcnss_allow_suspend);
1056
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001057int wcnss_hardware_type(void)
1058{
1059 if (penv)
1060 return penv->wcnss_hw_type;
1061 else
1062 return -ENODEV;
1063}
1064EXPORT_SYMBOL(wcnss_hardware_type);
1065
Sameer Thalappil1878d762013-04-24 14:54:39 -07001066int fw_cal_data_available(void)
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001067{
1068 if (penv)
Sameer Thalappil1878d762013-04-24 14:54:39 -07001069 return penv->fw_cal_available;
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001070 else
1071 return -ENODEV;
1072}
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001073
Sameer Thalappild0952f62013-05-03 10:18:29 -07001074u32 wcnss_get_wlan_rx_buff_count(void)
1075{
1076 if (penv)
1077 return penv->wlan_rx_buff_count;
1078 else
1079 return WCNSS_DEF_WLAN_RX_BUFF_COUNT;
1080
1081}
1082EXPORT_SYMBOL(wcnss_get_wlan_rx_buff_count);
1083
1084
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001085static int wcnss_smd_tx(void *data, int len)
1086{
1087 int ret = 0;
1088
1089 ret = smd_write_avail(penv->smd_ch);
1090 if (ret < len) {
1091 pr_err("wcnss: no space available for smd frame\n");
Sameer Thalappilf106a682013-02-16 20:41:11 -08001092 return -ENOSPC;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001093 }
1094 ret = smd_write(penv->smd_ch, data, len);
1095 if (ret < len) {
1096 pr_err("wcnss: failed to write Command %d", len);
1097 ret = -ENODEV;
1098 }
1099 return ret;
1100}
1101
Sameer Thalappil1878d762013-04-24 14:54:39 -07001102static unsigned char wcnss_fw_status(void)
1103{
1104 int len = 0;
1105 int rc = 0;
1106
1107 unsigned char fw_status = 0xFF;
1108
1109 len = smd_read_avail(penv->smd_ch);
1110 if (len < 1) {
1111 pr_err("%s: invalid firmware status", __func__);
1112 return fw_status;
1113 }
1114
1115 rc = smd_read(penv->smd_ch, &fw_status, 1);
1116 if (rc < 0) {
1117 pr_err("%s: incomplete data read from smd\n", __func__);
1118 return fw_status;
1119 }
1120 return fw_status;
1121}
1122
1123static void wcnss_send_cal_rsp(unsigned char fw_status)
1124{
1125 struct smd_msg_hdr *rsphdr;
1126 unsigned char *msg = NULL;
1127 int rc;
1128
1129 msg = kmalloc((sizeof(struct smd_msg_hdr) + 1), GFP_KERNEL);
1130 if (NULL == msg) {
1131 pr_err("wcnss: %s: failed to get memory\n", __func__);
1132 return;
1133 }
1134
1135 rsphdr = (struct smd_msg_hdr *)msg;
1136 rsphdr->msg_type = WCNSS_CALDATA_UPLD_RSP;
1137 rsphdr->msg_len = sizeof(struct smd_msg_hdr) + 1;
1138 memcpy(msg+sizeof(struct smd_msg_hdr), &fw_status, 1);
1139
1140 rc = wcnss_smd_tx(msg, rsphdr->msg_len);
1141 if (rc < 0)
1142 pr_err("wcnss: smd tx failed\n");
Sameer Thalappil844d3602013-05-24 13:54:24 -07001143
1144 kfree(msg);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001145}
1146
1147/* Collect calibrated data from WCNSS */
1148void extract_cal_data(int len)
1149{
1150 int rc;
1151 struct cal_data_params calhdr;
1152 unsigned char fw_status = WCNSS_RESP_FAIL;
1153
1154 if (len < sizeof(struct cal_data_params)) {
1155 pr_err("wcnss: incomplete cal header length\n");
1156 return;
1157 }
1158
1159 rc = smd_read(penv->smd_ch, (unsigned char *)&calhdr,
1160 sizeof(struct cal_data_params));
1161 if (rc < sizeof(struct cal_data_params)) {
1162 pr_err("wcnss: incomplete cal header read from smd\n");
1163 return;
1164 }
1165
1166 if (penv->fw_cal_exp_frag != calhdr.frag_number) {
1167 pr_err("wcnss: Invalid frgament");
1168 goto exit;
1169 }
1170
1171 if (calhdr.frag_size > WCNSS_MAX_FRAME_SIZE) {
1172 pr_err("wcnss: Invalid fragment size");
1173 goto exit;
1174 }
1175
1176 if (0 == calhdr.frag_number) {
1177 if (calhdr.total_size > MAX_CALIBRATED_DATA_SIZE) {
1178 pr_err("wcnss: Invalid cal data size %d",
1179 calhdr.total_size);
1180 goto exit;
1181 }
1182 kfree(penv->fw_cal_data);
1183 penv->fw_cal_rcvd = 0;
1184 penv->fw_cal_data = kmalloc(calhdr.total_size,
1185 GFP_KERNEL);
1186 if (penv->fw_cal_data == NULL) {
1187 smd_read(penv->smd_ch, NULL, calhdr.frag_size);
1188 goto exit;
1189 }
1190 }
1191
1192 mutex_lock(&penv->dev_lock);
1193 if (penv->fw_cal_rcvd + calhdr.frag_size >
1194 MAX_CALIBRATED_DATA_SIZE) {
1195 pr_err("calibrated data size is more than expected %d",
1196 penv->fw_cal_rcvd + calhdr.frag_size);
1197 penv->fw_cal_exp_frag = 0;
1198 penv->fw_cal_rcvd = 0;
1199 smd_read(penv->smd_ch, NULL, calhdr.frag_size);
1200 goto unlock_exit;
1201 }
1202
1203 rc = smd_read(penv->smd_ch, penv->fw_cal_data + penv->fw_cal_rcvd,
1204 calhdr.frag_size);
1205 if (rc < calhdr.frag_size)
1206 goto unlock_exit;
1207
1208 penv->fw_cal_exp_frag++;
1209 penv->fw_cal_rcvd += calhdr.frag_size;
1210
1211 if (calhdr.msg_flags & LAST_FRAGMENT) {
1212 penv->fw_cal_exp_frag = 0;
1213 penv->fw_cal_available = true;
1214 pr_info("wcnss: cal data collection completed\n");
1215 }
1216 mutex_unlock(&penv->dev_lock);
1217 wake_up(&penv->read_wait);
1218
1219 if (penv->fw_cal_available) {
1220 fw_status = WCNSS_RESP_SUCCESS;
1221 wcnss_send_cal_rsp(fw_status);
1222 }
1223 return;
1224
1225unlock_exit:
1226 mutex_unlock(&penv->dev_lock);
1227
1228exit:
1229 wcnss_send_cal_rsp(fw_status);
1230 return;
1231}
1232
1233
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001234static void wcnssctrl_rx_handler(struct work_struct *worker)
1235{
1236 int len = 0;
1237 int rc = 0;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001238 unsigned char buf[sizeof(struct wcnss_version)];
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001239 struct smd_msg_hdr *phdr;
1240 struct wcnss_version *pversion;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001241 int hw_type;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001242 unsigned char fw_status = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001243
1244 len = smd_read_avail(penv->smd_ch);
1245 if (len > WCNSS_MAX_FRAME_SIZE) {
1246 pr_err("wcnss: frame larger than the allowed size\n");
1247 smd_read(penv->smd_ch, NULL, len);
1248 return;
1249 }
1250 if (len <= 0)
1251 return;
1252
Sameer Thalappil1878d762013-04-24 14:54:39 -07001253 rc = smd_read(penv->smd_ch, buf, sizeof(struct smd_msg_hdr));
1254 if (rc < sizeof(struct smd_msg_hdr)) {
1255 pr_err("wcnss: incomplete header read from smd\n");
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001256 return;
1257 }
Sameer Thalappil1878d762013-04-24 14:54:39 -07001258 len -= sizeof(struct smd_msg_hdr);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001259
1260 phdr = (struct smd_msg_hdr *)buf;
1261
Sameer Thalappilf106a682013-02-16 20:41:11 -08001262 switch (phdr->msg_type) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001263
1264 case WCNSS_VERSION_RSP:
Sameer Thalappil1878d762013-04-24 14:54:39 -07001265 if (len != sizeof(struct wcnss_version)
1266 - sizeof(struct smd_msg_hdr)) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001267 pr_err("wcnss: invalid version data from wcnss %d\n",
Sameer Thalappil1878d762013-04-24 14:54:39 -07001268 len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001269 return;
1270 }
Sameer Thalappil1878d762013-04-24 14:54:39 -07001271 rc = smd_read(penv->smd_ch, buf+sizeof(struct smd_msg_hdr),
1272 len);
1273 if (rc < len) {
1274 pr_err("wcnss: incomplete data read from smd\n");
1275 return;
1276 }
1277 pversion = (struct wcnss_version *)buf;
1278 penv->fw_major = pversion->major;
1279 penv->fw_minor = pversion->minor;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001280 snprintf(penv->wcnss_version, WCNSS_VERSION_LEN,
1281 "%02x%02x%02x%02x", pversion->major, pversion->minor,
1282 pversion->version, pversion->revision);
1283 pr_info("wcnss: version %s\n", penv->wcnss_version);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001284 /* schedule work to download nvbin to ccpu */
1285 hw_type = wcnss_hardware_type();
1286 switch (hw_type) {
1287 case WCNSS_RIVA_HW:
1288 /* supported only if riva major >= 1 and minor >= 4 */
1289 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
1290 pr_info("wcnss: schedule dnld work for riva\n");
1291 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
1292 }
1293 break;
1294
1295 case WCNSS_PRONTO_HW:
1296 /* supported only if pronto major >= 1 and minor >= 4 */
1297 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
1298 pr_info("wcnss: schedule dnld work for pronto\n");
1299 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
1300 }
1301 break;
1302
1303 default:
1304 pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n",
1305 hw_type);
1306 break;
1307 }
1308 break;
1309
1310 case WCNSS_NVBIN_DNLD_RSP:
Sameer Thalappil1878d762013-04-24 14:54:39 -07001311 penv->nv_downloaded = true;
1312 fw_status = wcnss_fw_status();
1313 pr_debug("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu %u\n",
1314 fw_status);
1315 break;
1316
1317 case WCNSS_CALDATA_DNLD_RSP:
1318 penv->nv_downloaded = true;
1319 fw_status = wcnss_fw_status();
1320 pr_debug("wcnss: received WCNSS_CALDATA_DNLD_RSP from ccpu %u\n",
1321 fw_status);
1322 break;
1323
1324 case WCNSS_CALDATA_UPLD_REQ:
1325 penv->fw_cal_available = 0;
1326 extract_cal_data(len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001327 break;
1328
1329 default:
Sameer Thalappilf106a682013-02-16 20:41:11 -08001330 pr_err("wcnss: invalid message type %d\n", phdr->msg_type);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001331 }
1332 return;
1333}
1334
1335static void wcnss_send_version_req(struct work_struct *worker)
1336{
1337 struct smd_msg_hdr smd_msg;
1338 int ret = 0;
1339
Sameer Thalappilf106a682013-02-16 20:41:11 -08001340 smd_msg.msg_type = WCNSS_VERSION_REQ;
1341 smd_msg.msg_len = sizeof(smd_msg);
1342 ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001343 if (ret < 0)
1344 pr_err("wcnss: smd tx failed\n");
1345
1346 return;
1347}
1348
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001349static DECLARE_RWSEM(wcnss_pm_sem);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001350
1351static void wcnss_nvbin_dnld(void)
Sameer Thalappilf106a682013-02-16 20:41:11 -08001352{
1353 int ret = 0;
1354 struct nvbin_dnld_req_msg *dnld_req_msg;
1355 unsigned short total_fragments = 0;
1356 unsigned short count = 0;
1357 unsigned short retry_count = 0;
1358 unsigned short cur_frag_size = 0;
1359 unsigned char *outbuffer = NULL;
1360 const void *nv_blob_addr = NULL;
1361 unsigned int nv_blob_size = 0;
1362 const struct firmware *nv = NULL;
Sameer Thalappilf0d89ba2013-03-11 17:33:20 -07001363 struct device *dev = &penv->pdev->dev;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001364
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001365 down_read(&wcnss_pm_sem);
1366
Sameer Thalappilf106a682013-02-16 20:41:11 -08001367 ret = request_firmware(&nv, NVBIN_FILE, dev);
1368
1369 if (ret || !nv || !nv->data || !nv->size) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001370 pr_err("wcnss: %s: request_firmware failed for %s\n",
1371 __func__, NVBIN_FILE);
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001372 goto out;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001373 }
1374
1375 /*
1376 * First 4 bytes in nv blob is validity bitmap.
1377 * We cannot validate nv, so skip those 4 bytes.
1378 */
1379 nv_blob_addr = nv->data + 4;
1380 nv_blob_size = nv->size - 4;
1381
1382 total_fragments = TOTALFRAGMENTS(nv_blob_size);
1383
1384 pr_info("wcnss: NV bin size: %d, total_fragments: %d\n",
1385 nv_blob_size, total_fragments);
1386
1387 /* get buffer for nv bin dnld req message */
1388 outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) +
1389 NV_FRAGMENT_SIZE), GFP_KERNEL);
1390
1391 if (NULL == outbuffer) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001392 pr_err("wcnss: %s: failed to get buffer\n", __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001393 goto err_free_nv;
1394 }
1395
1396 dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
1397
1398 dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001399 dnld_req_msg->dnld_req_params.msg_flags = 0;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001400
1401 for (count = 0; count < total_fragments; count++) {
1402 dnld_req_msg->dnld_req_params.frag_number = count;
1403
1404 if (count == (total_fragments - 1)) {
1405 /* last fragment, take care of boundry condition */
1406 cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE;
1407 if (!cur_frag_size)
1408 cur_frag_size = NV_FRAGMENT_SIZE;
1409
Sameer Thalappil1878d762013-04-24 14:54:39 -07001410 dnld_req_msg->dnld_req_params.msg_flags |=
1411 LAST_FRAGMENT;
1412 dnld_req_msg->dnld_req_params.msg_flags |=
1413 CAN_RECEIVE_CALDATA;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001414 } else {
1415 cur_frag_size = NV_FRAGMENT_SIZE;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001416 dnld_req_msg->dnld_req_params.msg_flags &=
1417 ~LAST_FRAGMENT;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001418 }
1419
1420 dnld_req_msg->dnld_req_params.nvbin_buffer_size =
1421 cur_frag_size;
1422
1423 dnld_req_msg->hdr.msg_len =
1424 sizeof(struct nvbin_dnld_req_msg) + cur_frag_size;
1425
1426 /* copy NV fragment */
1427 memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)),
1428 (nv_blob_addr + count * NV_FRAGMENT_SIZE),
1429 cur_frag_size);
1430
1431 ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len);
1432
1433 retry_count = 0;
1434 while ((ret == -ENOSPC) && (retry_count <= 3)) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001435 pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
1436 __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001437 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1438 count, dnld_req_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 dnld_req_msg->hdr.msg_len);
1446 }
1447
1448 if (ret < 0) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001449 pr_err("wcnss: %s: smd tx failed\n", __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001450 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1451 count, dnld_req_msg->hdr.msg_len,
1452 total_fragments, retry_count);
1453 goto err_dnld;
1454 }
1455 }
1456
1457err_dnld:
1458 /* free buffer */
1459 kfree(outbuffer);
1460
1461err_free_nv:
1462 /* release firmware */
1463 release_firmware(nv);
1464
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001465out:
1466 up_read(&wcnss_pm_sem);
1467
Sameer Thalappilf106a682013-02-16 20:41:11 -08001468 return;
1469}
1470
Sameer Thalappil1878d762013-04-24 14:54:39 -07001471
1472static void wcnss_caldata_dnld(const void *cal_data,
1473 unsigned int cal_data_size, bool msg_to_follow)
1474{
1475 int ret = 0;
1476 struct cal_data_msg *cal_msg;
1477 unsigned short total_fragments = 0;
1478 unsigned short count = 0;
1479 unsigned short retry_count = 0;
1480 unsigned short cur_frag_size = 0;
1481 unsigned char *outbuffer = NULL;
1482
1483 total_fragments = TOTALFRAGMENTS(cal_data_size);
1484
1485 outbuffer = kmalloc((sizeof(struct cal_data_msg) +
1486 NV_FRAGMENT_SIZE), GFP_KERNEL);
1487
1488 if (NULL == outbuffer) {
1489 pr_err("wcnss: %s: failed to get buffer\n", __func__);
1490 return;
1491 }
1492
1493 cal_msg = (struct cal_data_msg *)outbuffer;
1494
1495 cal_msg->hdr.msg_type = WCNSS_CALDATA_DNLD_REQ;
1496 cal_msg->cal_params.msg_flags = 0;
1497
1498 for (count = 0; count < total_fragments; count++) {
1499 cal_msg->cal_params.frag_number = count;
1500
1501 if (count == (total_fragments - 1)) {
1502 cur_frag_size = cal_data_size % NV_FRAGMENT_SIZE;
1503 if (!cur_frag_size)
1504 cur_frag_size = NV_FRAGMENT_SIZE;
1505
1506 cal_msg->cal_params.msg_flags
1507 |= LAST_FRAGMENT;
1508 if (msg_to_follow)
1509 cal_msg->cal_params.msg_flags |=
1510 MESSAGE_TO_FOLLOW;
1511 } else {
1512 cur_frag_size = NV_FRAGMENT_SIZE;
1513 cal_msg->cal_params.msg_flags &=
1514 ~LAST_FRAGMENT;
1515 }
1516
1517 cal_msg->cal_params.total_size = cal_data_size;
1518 cal_msg->cal_params.frag_size =
1519 cur_frag_size;
1520
1521 cal_msg->hdr.msg_len =
1522 sizeof(struct cal_data_msg) + cur_frag_size;
1523
1524 memcpy((outbuffer + sizeof(struct cal_data_msg)),
1525 (cal_data + count * NV_FRAGMENT_SIZE),
1526 cur_frag_size);
1527
1528 ret = wcnss_smd_tx(outbuffer, cal_msg->hdr.msg_len);
1529
1530 retry_count = 0;
1531 while ((ret == -ENOSPC) && (retry_count <= 3)) {
1532 pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
1533 __func__);
1534 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1535 count, cal_msg->hdr.msg_len,
1536 total_fragments, retry_count);
1537
1538 /* wait and try again */
1539 msleep(20);
1540 retry_count++;
1541 ret = wcnss_smd_tx(outbuffer,
1542 cal_msg->hdr.msg_len);
1543 }
1544
1545 if (ret < 0) {
1546 pr_err("wcnss: %s: smd tx failed\n", __func__);
1547 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1548 count, cal_msg->hdr.msg_len,
1549 total_fragments, retry_count);
1550 goto err_dnld;
1551 }
1552 }
1553
1554
1555err_dnld:
1556 /* free buffer */
1557 kfree(outbuffer);
1558
1559 return;
1560}
1561
1562
1563static void wcnss_nvbin_dnld_main(struct work_struct *worker)
1564{
1565 int retry = 0;
1566
1567 if (!FW_CALDATA_CAPABLE())
1568 goto nv_download;
1569
1570 if (!penv->fw_cal_available && WCNSS_CONFIG_UNSPECIFIED
1571 != has_calibrated_data && !penv->user_cal_available) {
1572 while (!penv->user_cal_available && retry++ < 5)
1573 msleep(500);
1574 }
1575
1576 /* only cal data is sent during ssr (if available) */
1577 if (penv->fw_cal_available && penv->ssr_boot) {
1578 pr_info_ratelimited("wcnss: cal download during SSR, using fw cal");
1579 wcnss_caldata_dnld(penv->fw_cal_data, penv->fw_cal_rcvd, false);
1580 return;
1581
1582 } else if (penv->user_cal_available && penv->ssr_boot) {
1583 pr_info_ratelimited("wcnss: cal download during SSR, using user cal");
1584 wcnss_caldata_dnld(penv->user_cal_data,
1585 penv->user_cal_rcvd, false);
1586 return;
1587
1588 } else if (penv->user_cal_available) {
1589 pr_info_ratelimited("wcnss: cal download during cold boot, using user cal");
1590 wcnss_caldata_dnld(penv->user_cal_data,
1591 penv->user_cal_rcvd, true);
1592 }
1593
1594nv_download:
1595 pr_info_ratelimited("wcnss: NV download");
1596 wcnss_nvbin_dnld();
1597
1598 return;
1599}
1600
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001601static int wcnss_pm_notify(struct notifier_block *b,
1602 unsigned long event, void *p)
1603{
1604 switch (event) {
1605 case PM_SUSPEND_PREPARE:
1606 down_write(&wcnss_pm_sem);
1607 break;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001608
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001609 case PM_POST_SUSPEND:
1610 up_write(&wcnss_pm_sem);
1611 break;
1612 }
1613
1614 return NOTIFY_DONE;
1615}
1616
1617static struct notifier_block wcnss_pm_notifier = {
1618 .notifier_call = wcnss_pm_notify,
1619};
Sameer Thalappil1878d762013-04-24 14:54:39 -07001620
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001621static int
1622wcnss_trigger_config(struct platform_device *pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001623{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001624 int ret;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001625 struct qcom_wcnss_opts *pdata;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001626 unsigned long wcnss_phys_addr;
1627 int size = 0;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001628 struct resource *res;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001629 int has_pronto_hw = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001630 "qcom,has-pronto-hw");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001631
Sameer Thalappild0952f62013-05-03 10:18:29 -07001632 if (of_property_read_u32(pdev->dev.of_node,
1633 "qcom,wlan-rx-buff-count", &penv->wlan_rx_buff_count)) {
1634 penv->wlan_rx_buff_count = WCNSS_DEF_WLAN_RX_BUFF_COUNT;
1635 }
1636
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001637 /* make sure we are only triggered once */
1638 if (penv->triggered)
1639 return 0;
1640 penv->triggered = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001641
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001642 /* initialize the WCNSS device configuration */
1643 pdata = pdev->dev.platform_data;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001644 if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) {
1645 if (has_pronto_hw) {
1646 has_48mhz_xo = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001647 "qcom,has-48mhz-xo");
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001648 } else {
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001649 has_48mhz_xo = pdata->has_48mhz_xo;
1650 }
1651 }
Sameer Thalappilf00dbeb2013-03-01 18:33:30 -08001652 penv->wcnss_hw_type = (has_pronto_hw) ? WCNSS_PRONTO_HW : WCNSS_RIVA_HW;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001653 penv->wlan_config.use_48mhz_xo = has_48mhz_xo;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001654
Sameer Thalappil58cec312013-05-13 15:52:08 -07001655 if (WCNSS_CONFIG_UNSPECIFIED == has_autodetect_xo && has_pronto_hw) {
1656 has_autodetect_xo = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001657 "qcom,has-autodetect-xo");
Sameer Thalappil58cec312013-05-13 15:52:08 -07001658 }
1659
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001660 penv->thermal_mitigation = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001661 strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN);
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001662
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001663 /* Configure 5 wire GPIOs */
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001664 if (!has_pronto_hw) {
1665 penv->gpios_5wire = platform_get_resource_byname(pdev,
1666 IORESOURCE_IO, "wcnss_gpios_5wire");
1667
1668 /* allocate 5-wire GPIO resources */
1669 if (!penv->gpios_5wire) {
1670 dev_err(&pdev->dev, "insufficient IO resources\n");
1671 ret = -ENOENT;
1672 goto fail_gpio_res;
1673 }
1674 ret = wcnss_gpios_config(penv->gpios_5wire, true);
1675 } else
1676 ret = wcnss_pronto_gpios_config(&pdev->dev, true);
1677
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001678 if (ret) {
1679 dev_err(&pdev->dev, "WCNSS gpios config failed.\n");
1680 goto fail_gpio_res;
1681 }
1682
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001683 /* power up the WCNSS */
1684 ret = wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
Sameer Thalappil1d69b8022013-06-10 19:10:07 -07001685 WCNSS_WLAN_SWITCH_ON,
1686 &penv->iris_xo_mode_set);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001687 if (ret) {
1688 dev_err(&pdev->dev, "WCNSS Power-up failed.\n");
1689 goto fail_power;
1690 }
1691
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001692 /* allocate resources */
1693 penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1694 "wcnss_mmio");
1695 penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1696 "wcnss_wlantx_irq");
1697 penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1698 "wcnss_wlanrx_irq");
1699
1700 if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) {
1701 dev_err(&pdev->dev, "insufficient resources\n");
1702 ret = -ENOENT;
1703 goto fail_res;
1704 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001705 INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
1706 INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001707 INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_main);
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001708
Sameer Thalappilf138da52012-07-30 12:56:37 -07001709 wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");
1710
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001711 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
1712 size = 0x3000;
1713 wcnss_phys_addr = MSM_PRONTO_PHYS;
1714 } else {
1715 wcnss_phys_addr = MSM_RIVA_PHYS;
1716 size = SZ_256;
1717 }
1718
1719 penv->msm_wcnss_base = ioremap(wcnss_phys_addr, size);
1720 if (!penv->msm_wcnss_base) {
1721 ret = -ENOMEM;
1722 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001723 goto fail_ioremap;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001724 }
1725
Sameer Thalappild3aad702013-01-22 18:52:24 -08001726 if (wcnss_hardware_type() == WCNSS_RIVA_HW) {
1727 penv->riva_ccu_base = ioremap(MSM_RIVA_CCU_BASE, SZ_512);
1728 if (!penv->riva_ccu_base) {
1729 ret = -ENOMEM;
1730 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001731 goto fail_ioremap2;
Sameer Thalappild3aad702013-01-22 18:52:24 -08001732 }
1733 } else {
1734 penv->pronto_a2xb_base = ioremap(MSM_PRONTO_A2XB_BASE, SZ_512);
1735 if (!penv->pronto_a2xb_base) {
1736 ret = -ENOMEM;
1737 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001738 goto fail_ioremap2;
Sameer Thalappild3aad702013-01-22 18:52:24 -08001739 }
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001740 penv->pronto_ccpu_base = ioremap(MSM_PRONTO_CCPU_BASE, SZ_512);
1741 if (!penv->pronto_ccpu_base) {
1742 ret = -ENOMEM;
1743 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001744 goto fail_ioremap3;
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001745 }
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001746 /* for reset FIQ */
1747 res = platform_get_resource_byname(penv->pdev,
1748 IORESOURCE_MEM, "wcnss_fiq");
1749 if (!res) {
1750 dev_err(&pdev->dev, "insufficient irq mem resources\n");
1751 ret = -ENOENT;
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001752 goto fail_ioremap4;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001753 }
1754 penv->fiq_reg = ioremap_nocache(res->start, resource_size(res));
1755 if (!penv->fiq_reg) {
1756 pr_err("wcnss: %s: ioremap_nocache() failed fiq_reg addr:%pr\n",
1757 __func__, &res->start);
1758 ret = -ENOMEM;
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001759 goto fail_ioremap4;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001760 }
Sameer Thalappil767ff492013-06-19 15:25:21 -07001761 penv->pronto_saw2_base = ioremap_nocache(MSM_PRONTO_SAW2_BASE,
1762 SZ_32);
1763 if (!penv->pronto_saw2_base) {
1764 pr_err("%s: ioremap wcnss physical(saw2) failed\n",
1765 __func__);
1766 ret = -ENOMEM;
1767 goto fail_ioremap5;
1768 }
Sameer Thalappil66d2b392013-07-02 11:32:55 -07001769 penv->pronto_pll_base = ioremap_nocache(MSM_PRONTO_PLL_BASE,
1770 SZ_64);
1771 if (!penv->pronto_pll_base) {
1772 pr_err("%s: ioremap wcnss physical(pll) failed\n",
1773 __func__);
1774 ret = -ENOMEM;
1775 goto fail_ioremap6;
1776 }
1777
Sameer Thalappil670197c2013-07-19 16:31:14 -07001778 penv->wlan_tx_phy_aborts = ioremap(MSM_PRONTO_TXP_PHY_ABORT,
1779 SZ_8);
1780 if (!penv->wlan_tx_phy_aborts) {
1781 ret = -ENOMEM;
1782 pr_err("%s: ioremap wlan TX PHY failed\n", __func__);
1783 goto fail_ioremap7;
1784 }
1785 penv->wlan_brdg_err_source = ioremap(MSM_PRONTO_BRDG_ERR_SRC,
1786 SZ_8);
1787 if (!penv->wlan_brdg_err_source) {
1788 ret = -ENOMEM;
1789 pr_err("%s: ioremap wlan BRDG ERR failed\n", __func__);
1790 goto fail_ioremap8;
1791 }
1792
Sameer Thalappild3aad702013-01-22 18:52:24 -08001793 }
1794
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001795 /* trigger initialization of the WCNSS */
1796 penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
1797 if (IS_ERR(penv->pil)) {
1798 dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n");
1799 ret = PTR_ERR(penv->pil);
1800 penv->pil = NULL;
1801 goto fail_pil;
1802 }
1803
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001804 return 0;
1805
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001806fail_pil:
1807 if (penv->riva_ccu_base)
1808 iounmap(penv->riva_ccu_base);
Sameer Thalappil670197c2013-07-19 16:31:14 -07001809 if (penv->wlan_brdg_err_source)
1810 iounmap(penv->wlan_brdg_err_source);
1811fail_ioremap8:
1812 if (penv->wlan_tx_phy_aborts)
1813 iounmap(penv->wlan_tx_phy_aborts);
1814fail_ioremap7:
Sameer Thalappil66d2b392013-07-02 11:32:55 -07001815 if (penv->pronto_pll_base)
1816 iounmap(penv->pronto_pll_base);
1817fail_ioremap6:
Sameer Thalappil767ff492013-06-19 15:25:21 -07001818 if (penv->pronto_saw2_base)
1819 iounmap(penv->pronto_saw2_base);
1820fail_ioremap5:
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001821 if (penv->fiq_reg)
1822 iounmap(penv->fiq_reg);
1823fail_ioremap4:
1824 if (penv->pronto_ccpu_base)
1825 iounmap(penv->pronto_ccpu_base);
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001826fail_ioremap3:
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001827 if (penv->pronto_a2xb_base)
1828 iounmap(penv->pronto_a2xb_base);
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001829fail_ioremap2:
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001830 if (penv->msm_wcnss_base)
1831 iounmap(penv->msm_wcnss_base);
Sameer Thalappild3aad702013-01-22 18:52:24 -08001832fail_ioremap:
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001833 wake_lock_destroy(&penv->wcnss_wake_lock);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001834fail_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001835 wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
Sameer Thalappil1d69b8022013-06-10 19:10:07 -07001836 WCNSS_WLAN_SWITCH_OFF, NULL);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001837fail_power:
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001838 if (has_pronto_hw)
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001839 wcnss_pronto_gpios_config(&pdev->dev, false);
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001840 else
1841 wcnss_gpios_config(penv->gpios_5wire, false);
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001842fail_gpio_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001843 penv = NULL;
1844 return ret;
1845}
1846
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001847static int wcnss_node_open(struct inode *inode, struct file *file)
1848{
1849 struct platform_device *pdev;
1850
Sameer Thalappil37c20682013-05-31 14:01:25 -07001851 if (!penv)
1852 return -EFAULT;
1853
Sameer Thalappil1878d762013-04-24 14:54:39 -07001854 /* first open is only to trigger WCNSS platform driver */
1855 if (!penv->triggered) {
1856 pr_info(DEVICE " triggered by userspace\n");
1857 pdev = penv->pdev;
1858 return wcnss_trigger_config(pdev);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001859
Sameer Thalappil1878d762013-04-24 14:54:39 -07001860 } else if (penv->device_opened) {
1861 pr_info(DEVICE " already opened\n");
1862 return -EBUSY;
1863 }
1864
1865 mutex_lock(&penv->dev_lock);
1866 penv->user_cal_rcvd = 0;
1867 penv->user_cal_read = 0;
1868 penv->user_cal_available = false;
1869 penv->user_cal_data = NULL;
1870 penv->device_opened = 1;
1871 mutex_unlock(&penv->dev_lock);
1872
1873 return 0;
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001874}
1875
Sameer Thalappil1878d762013-04-24 14:54:39 -07001876static ssize_t wcnss_wlan_read(struct file *fp, char __user
1877 *buffer, size_t count, loff_t *position)
1878{
1879 int rc = 0;
1880
Sameer Thalappil37c20682013-05-31 14:01:25 -07001881 if (!penv || !penv->device_opened)
Sameer Thalappil1878d762013-04-24 14:54:39 -07001882 return -EFAULT;
1883
1884 rc = wait_event_interruptible(penv->read_wait, penv->fw_cal_rcvd
1885 > penv->user_cal_read || penv->fw_cal_available);
1886
1887 if (rc < 0)
1888 return rc;
1889
1890 mutex_lock(&penv->dev_lock);
1891
1892 if (penv->fw_cal_available && penv->fw_cal_rcvd
1893 == penv->user_cal_read) {
1894 rc = 0;
1895 goto exit;
1896 }
1897
1898 if (count > penv->fw_cal_rcvd - penv->user_cal_read)
1899 count = penv->fw_cal_rcvd - penv->user_cal_read;
1900
1901 rc = copy_to_user(buffer, penv->fw_cal_data +
1902 penv->user_cal_read, count);
1903 if (rc == 0) {
1904 penv->user_cal_read += count;
1905 rc = count;
1906 }
1907
1908exit:
1909 mutex_unlock(&penv->dev_lock);
1910 return rc;
1911}
1912
1913/* first (valid) write to this device should be 4 bytes cal file size */
1914static ssize_t wcnss_wlan_write(struct file *fp, const char __user
1915 *user_buffer, size_t count, loff_t *position)
1916{
1917 int rc = 0;
1918 int size = 0;
1919
Sameer Thalappil37c20682013-05-31 14:01:25 -07001920 if (!penv || !penv->device_opened || penv->user_cal_available)
Sameer Thalappil1878d762013-04-24 14:54:39 -07001921 return -EFAULT;
1922
1923 if (penv->user_cal_rcvd == 0 && count >= 4
1924 && !penv->user_cal_data) {
1925 rc = copy_from_user((void *)&size, user_buffer, 4);
1926 if (size > MAX_CALIBRATED_DATA_SIZE) {
1927 pr_err(DEVICE " invalid size to write %d\n", size);
1928 return -EFAULT;
1929 }
1930
1931 rc += count;
1932 count -= 4;
1933 penv->user_cal_exp_size = size;
1934 penv->user_cal_data = kmalloc(size, GFP_KERNEL);
1935 if (penv->user_cal_data == NULL) {
1936 pr_err(DEVICE " no memory to write\n");
1937 return -ENOMEM;
1938 }
1939 if (0 == count)
1940 goto exit;
1941
1942 } else if (penv->user_cal_rcvd == 0 && count < 4)
1943 return -EFAULT;
1944
1945 if (MAX_CALIBRATED_DATA_SIZE < count + penv->user_cal_rcvd) {
1946 pr_err(DEVICE " invalid size to write %d\n", count +
1947 penv->user_cal_rcvd);
1948 rc = -ENOMEM;
1949 goto exit;
1950 }
1951 rc = copy_from_user((void *)penv->user_cal_data +
1952 penv->user_cal_rcvd, user_buffer, count);
1953 if (0 == rc) {
1954 penv->user_cal_rcvd += count;
1955 rc += count;
1956 }
1957 if (penv->user_cal_rcvd == penv->user_cal_exp_size) {
1958 penv->user_cal_available = true;
1959 pr_info_ratelimited("wcnss: user cal written");
1960 }
1961
1962exit:
1963 return rc;
1964}
1965
1966
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001967static const struct file_operations wcnss_node_fops = {
1968 .owner = THIS_MODULE,
1969 .open = wcnss_node_open,
Sameer Thalappil1878d762013-04-24 14:54:39 -07001970 .read = wcnss_wlan_read,
1971 .write = wcnss_wlan_write,
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001972};
1973
1974static struct miscdevice wcnss_misc = {
1975 .minor = MISC_DYNAMIC_MINOR,
1976 .name = DEVICE,
1977 .fops = &wcnss_node_fops,
1978};
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001979
1980static int __devinit
1981wcnss_wlan_probe(struct platform_device *pdev)
1982{
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001983 int ret = 0;
1984
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001985 /* verify we haven't been called more than once */
1986 if (penv) {
1987 dev_err(&pdev->dev, "cannot handle multiple devices.\n");
1988 return -ENODEV;
1989 }
1990
1991 /* create an environment to track the device */
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001992 penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001993 if (!penv) {
1994 dev_err(&pdev->dev, "cannot allocate device memory.\n");
1995 return -ENOMEM;
1996 }
1997 penv->pdev = pdev;
1998
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001999 /* register sysfs entries */
2000 ret = wcnss_create_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002001 if (ret) {
2002 penv = NULL;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002003 return -ENOENT;
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002004 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002005
Sameer Thalappil1878d762013-04-24 14:54:39 -07002006 mutex_init(&penv->dev_lock);
2007 init_waitqueue_head(&penv->read_wait);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002008
Sameer Thalappild3aad702013-01-22 18:52:24 -08002009 /* Since we were built into the kernel we'll be called as part
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002010 * of kernel initialization. We don't know if userspace
2011 * applications are available to service PIL at this time
2012 * (they probably are not), so we simply create a device node
2013 * here. When userspace is available it should touch the
2014 * device so that we know that WCNSS configuration can take
2015 * place
2016 */
2017 pr_info(DEVICE " probed in built-in mode\n");
2018 return misc_register(&wcnss_misc);
2019
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002020}
2021
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002022static int __devexit
2023wcnss_wlan_remove(struct platform_device *pdev)
2024{
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08002025 wcnss_remove_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002026 penv = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002027 return 0;
2028}
2029
2030
2031static const struct dev_pm_ops wcnss_wlan_pm_ops = {
2032 .suspend = wcnss_wlan_suspend,
2033 .resume = wcnss_wlan_resume,
2034};
2035
Sameer Thalappil8d686d42012-08-24 10:07:31 -07002036#ifdef CONFIG_WCNSS_CORE_PRONTO
2037static struct of_device_id msm_wcnss_pronto_match[] = {
2038 {.compatible = "qcom,wcnss_wlan"},
2039 {}
2040};
2041#endif
2042
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002043static struct platform_driver wcnss_wlan_driver = {
2044 .driver = {
2045 .name = DEVICE,
2046 .owner = THIS_MODULE,
2047 .pm = &wcnss_wlan_pm_ops,
Sameer Thalappil8d686d42012-08-24 10:07:31 -07002048#ifdef CONFIG_WCNSS_CORE_PRONTO
2049 .of_match_table = msm_wcnss_pronto_match,
2050#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002051 },
2052 .probe = wcnss_wlan_probe,
2053 .remove = __devexit_p(wcnss_wlan_remove),
2054};
2055
2056static int __init wcnss_wlan_init(void)
2057{
Sameer Thalappil24db5282012-09-10 11:58:33 -07002058 int ret = 0;
2059
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002060 platform_driver_register(&wcnss_wlan_driver);
2061 platform_driver_register(&wcnss_wlan_ctrl_driver);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002062 platform_driver_register(&wcnss_ctrl_driver);
Sameer Thalappil944c72d2013-08-14 17:56:17 -07002063 register_pm_notifier(&wcnss_pm_notifier);
Sameer Thalappil24db5282012-09-10 11:58:33 -07002064#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
2065 ret = wcnss_prealloc_init();
2066 if (ret < 0)
2067 pr_err("wcnss: pre-allocation failed\n");
2068#endif
2069
2070 return ret;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002071}
2072
2073static void __exit wcnss_wlan_exit(void)
2074{
2075 if (penv) {
2076 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07002077 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002078 penv = NULL;
2079 }
2080
Sameer Thalappil24db5282012-09-10 11:58:33 -07002081#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
2082 wcnss_prealloc_deinit();
2083#endif
Sameer Thalappil944c72d2013-08-14 17:56:17 -07002084 unregister_pm_notifier(&wcnss_pm_notifier);
2085 platform_driver_unregister(&wcnss_ctrl_driver);
2086 platform_driver_unregister(&wcnss_wlan_ctrl_driver);
2087 platform_driver_unregister(&wcnss_wlan_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002088}
2089
2090module_init(wcnss_wlan_init);
2091module_exit(wcnss_wlan_exit);
2092
2093MODULE_LICENSE("GPL v2");
2094MODULE_VERSION(VERSION);
2095MODULE_DESCRIPTION(DEVICE "Driver");