blob: 64d1478b8150a7beac1d7669b5fc0e6e5a390dc5 [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
103#define PRONTO_PMU_CFG_OFFSET 0x1004
104#define PRONTO_PMU_COM_CSR_OFFSET 0x1040
105#define PRONTO_PMU_SOFT_RESET_OFFSET 0x104C
106
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800107#define MSM_PRONTO_A2XB_BASE 0xfb100400
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800108#define A2XB_CFG_OFFSET 0x00
109#define A2XB_INT_SRC_OFFSET 0x0c
110#define A2XB_TSTBUS_CTRL_OFFSET 0x14
111#define A2XB_TSTBUS_OFFSET 0x18
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800112#define A2XB_ERR_INFO_OFFSET 0x1c
113
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800114#define WCNSS_TSTBUS_CTRL_EN BIT(0)
115#define WCNSS_TSTBUS_CTRL_AXIM (0x02 << 1)
116#define WCNSS_TSTBUS_CTRL_CMDFIFO (0x03 << 1)
117#define WCNSS_TSTBUS_CTRL_WRFIFO (0x04 << 1)
118#define WCNSS_TSTBUS_CTRL_RDFIFO (0x05 << 1)
119#define WCNSS_TSTBUS_CTRL_CTRL (0x07 << 1)
120#define WCNSS_TSTBUS_CTRL_AXIM_CFG0 (0x00 << 6)
121#define WCNSS_TSTBUS_CTRL_AXIM_CFG1 (0x01 << 6)
122#define WCNSS_TSTBUS_CTRL_CTRL_CFG0 (0x00 << 12)
123#define WCNSS_TSTBUS_CTRL_CTRL_CFG1 (0x01 << 12)
124
125#define MSM_PRONTO_CCPU_BASE 0xfb205050
126#define CCU_PRONTO_INVALID_ADDR_OFFSET 0x08
127#define CCU_PRONTO_LAST_ADDR0_OFFSET 0x0c
128#define CCU_PRONTO_LAST_ADDR1_OFFSET 0x10
129#define CCU_PRONTO_LAST_ADDR2_OFFSET 0x14
130
Sameer Thalappil767ff492013-06-19 15:25:21 -0700131#define MSM_PRONTO_SAW2_BASE 0xfb219000
132#define PRONTO_SAW2_SPM_STS_OFFSET 0x0c
133
Sameer Thalappil66d2b392013-07-02 11:32:55 -0700134#define MSM_PRONTO_PLL_BASE 0xfb21b1c0
135#define PRONTO_PLL_STATUS_OFFSET 0x1c
136
Sameer Thalappil670197c2013-07-19 16:31:14 -0700137#define MSM_PRONTO_TXP_PHY_ABORT 0xfb080488
138#define MSM_PRONTO_BRDG_ERR_SRC 0xfb080fb0
139
Sameer Thalappild0952f62013-05-03 10:18:29 -0700140#define WCNSS_DEF_WLAN_RX_BUFF_COUNT 1024
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700141#define WCNSS_VBATT_THRESHOLD 3500000
142#define WCNSS_VBATT_GUARD 200
143#define WCNSS_VBATT_HIGH 3700000
144#define WCNSS_VBATT_LOW 3300000
Sameer Thalappild0952f62013-05-03 10:18:29 -0700145
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700146#define WCNSS_CTRL_CHANNEL "WCNSS_CTRL"
Sameer Thalappil1878d762013-04-24 14:54:39 -0700147#define WCNSS_MAX_FRAME_SIZE (4*1024)
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700148#define WCNSS_VERSION_LEN 30
149
150/* message types */
151#define WCNSS_CTRL_MSG_START 0x01000000
Sameer Thalappil1878d762013-04-24 14:54:39 -0700152#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0)
153#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1)
154#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2)
155#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3)
156#define WCNSS_CALDATA_UPLD_REQ (WCNSS_CTRL_MSG_START + 4)
157#define WCNSS_CALDATA_UPLD_RSP (WCNSS_CTRL_MSG_START + 5)
158#define WCNSS_CALDATA_DNLD_REQ (WCNSS_CTRL_MSG_START + 6)
159#define WCNSS_CALDATA_DNLD_RSP (WCNSS_CTRL_MSG_START + 7)
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700160#define WCNSS_VBATT_LEVEL_IND (WCNSS_CTRL_MSG_START + 8)
Sameer Thalappilf106a682013-02-16 20:41:11 -0800161
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700162
163#define VALID_VERSION(version) \
164 ((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0)
165
Sameer Thalappil1878d762013-04-24 14:54:39 -0700166#define FW_CALDATA_CAPABLE() \
167 ((penv->fw_major >= 1) && (penv->fw_minor >= 5) ? 1 : 0)
168
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700169struct smd_msg_hdr {
Sameer Thalappilf106a682013-02-16 20:41:11 -0800170 unsigned int msg_type;
171 unsigned int msg_len;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700172};
173
174struct wcnss_version {
175 struct smd_msg_hdr hdr;
176 unsigned char major;
177 unsigned char minor;
178 unsigned char version;
179 unsigned char revision;
180};
181
Naresh Jayaram08cee442013-04-26 19:50:00 +0530182struct wcnss_pmic_dump {
183 char reg_name[10];
184 u16 reg_addr;
185};
186
187static struct wcnss_pmic_dump wcnss_pmic_reg_dump[] = {
188 {"S2", 0x1D8},
189 {"L4", 0xB4},
190 {"L10", 0xC0},
191 {"LVS2", 0x62},
192 {"S4", 0x1E8},
193 {"LVS7", 0x06C},
194 {"LVS1", 0x060},
195};
196
Sameer Thalappilf106a682013-02-16 20:41:11 -0800197#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
198
199/*
200 * On SMD channel 4K of maximum data can be transferred, including message
201 * header, so NV fragment size as next multiple of 1Kb is 3Kb.
202 */
203#define NV_FRAGMENT_SIZE 3072
Sameer Thalappil1878d762013-04-24 14:54:39 -0700204#define MAX_CALIBRATED_DATA_SIZE (64*1024)
205#define LAST_FRAGMENT (1 << 0)
206#define MESSAGE_TO_FOLLOW (1 << 1)
207#define CAN_RECEIVE_CALDATA (1 << 15)
208#define WCNSS_RESP_SUCCESS 1
209#define WCNSS_RESP_FAIL 0
210
Sameer Thalappilf106a682013-02-16 20:41:11 -0800211
212/* Macro to find the total number fragments of the NV bin Image */
213#define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \
214 (x / NV_FRAGMENT_SIZE) : ((x / NV_FRAGMENT_SIZE) + 1))
215
216struct nvbin_dnld_req_params {
217 /*
218 * Fragment sequence number of the NV bin Image. NV Bin Image
219 * might not fit into one message due to size limitation of
220 * the SMD channel FIFO so entire NV blob is chopped into
221 * multiple fragments starting with seqeunce number 0. The
222 * last fragment is indicated by marking is_last_fragment field
223 * to 1. At receiving side, NV blobs would be concatenated
224 * together without any padding bytes in between.
225 */
226 unsigned short frag_number;
227
228 /*
Sameer Thalappil1878d762013-04-24 14:54:39 -0700229 * bit 0: When set to 1 it indicates that no more fragments will
230 * be sent.
231 * bit 1: When set, a new message will be followed by this message
232 * bit 2- bit 14: Reserved
233 * bit 15: when set, it indicates that the sender is capable of
234 * receiving Calibrated data.
Sameer Thalappilf106a682013-02-16 20:41:11 -0800235 */
Sameer Thalappil1878d762013-04-24 14:54:39 -0700236 unsigned short msg_flags;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800237
238 /* NV Image size (number of bytes) */
239 unsigned int nvbin_buffer_size;
240
241 /*
242 * Following the 'nvbin_buffer_size', there should be
243 * nvbin_buffer_size bytes of NV bin Image i.e.
244 * uint8[nvbin_buffer_size].
245 */
246};
247
Sameer Thalappil1878d762013-04-24 14:54:39 -0700248
Sameer Thalappilf106a682013-02-16 20:41:11 -0800249struct nvbin_dnld_req_msg {
250 /*
251 * Note: The length specified in nvbin_dnld_req_msg messages
252 * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) +
253 * nvbin_buffer_size.
254 */
255 struct smd_msg_hdr hdr;
256 struct nvbin_dnld_req_params dnld_req_params;
257};
258
Sameer Thalappil1878d762013-04-24 14:54:39 -0700259struct cal_data_params {
260
261 /* The total size of the calibrated data, including all the
262 * fragments.
263 */
264 unsigned int total_size;
265 unsigned short frag_number;
266 /*
267 * bit 0: When set to 1 it indicates that no more fragments will
268 * be sent.
269 * bit 1: When set, a new message will be followed by this message
270 * bit 2- bit 15: Reserved
271 */
272 unsigned short msg_flags;
273 /*
274 * fragment size
275 */
276 unsigned int frag_size;
277 /*
278 * Following the frag_size, frag_size of fragmented
279 * data will be followed.
280 */
281};
282
283struct cal_data_msg {
284 /*
285 * The length specified in cal_data_msg should be
286 * hdr.msg_len = sizeof(cal_data_msg) + frag_size
287 */
288 struct smd_msg_hdr hdr;
289 struct cal_data_params cal_params;
290};
291
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700292struct vbatt_level {
293 u32 curr_volt;
294 u32 threshold;
295};
296
297struct vbatt_message {
298 struct smd_msg_hdr hdr;
299 struct vbatt_level vbatt;
300};
301
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700302static struct {
303 struct platform_device *pdev;
304 void *pil;
305 struct resource *mmio_res;
306 struct resource *tx_irq_res;
307 struct resource *rx_irq_res;
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700308 struct resource *gpios_5wire;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700309 const struct dev_pm_ops *pm_ops;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800310 int triggered;
311 int smd_channel_ready;
Sameer Thalappild0952f62013-05-03 10:18:29 -0700312 u32 wlan_rx_buff_count;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700313 smd_channel_t *smd_ch;
314 unsigned char wcnss_version[WCNSS_VERSION_LEN];
Sameer Thalappil1878d762013-04-24 14:54:39 -0700315 unsigned char fw_major;
316 unsigned char fw_minor;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800317 unsigned int serial_number;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800318 int thermal_mitigation;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700319 enum wcnss_hw_type wcnss_hw_type;
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700320 void (*tm_notify)(struct device *, int);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700321 struct wcnss_wlan_config wlan_config;
David Collinsb727f7d2011-09-12 11:54:49 -0700322 struct delayed_work wcnss_work;
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700323 struct delayed_work vbatt_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700324 struct work_struct wcnssctrl_version_work;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800325 struct work_struct wcnssctrl_nvbin_dnld_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700326 struct work_struct wcnssctrl_rx_work;
Sameer Thalappilf138da52012-07-30 12:56:37 -0700327 struct wake_lock wcnss_wake_lock;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700328 void __iomem *msm_wcnss_base;
Sameer Thalappild3aad702013-01-22 18:52:24 -0800329 void __iomem *riva_ccu_base;
330 void __iomem *pronto_a2xb_base;
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800331 void __iomem *pronto_ccpu_base;
Sameer Thalappil767ff492013-06-19 15:25:21 -0700332 void __iomem *pronto_saw2_base;
Sameer Thalappil66d2b392013-07-02 11:32:55 -0700333 void __iomem *pronto_pll_base;
Sameer Thalappil670197c2013-07-19 16:31:14 -0700334 void __iomem *wlan_tx_phy_aborts;
335 void __iomem *wlan_brdg_err_source;
Sameer Thalappil58281ca2013-04-10 18:50:18 -0700336 void __iomem *fiq_reg;
Sameer Thalappil1878d762013-04-24 14:54:39 -0700337 int ssr_boot;
338 int nv_downloaded;
339 unsigned char *fw_cal_data;
340 unsigned char *user_cal_data;
341 int fw_cal_rcvd;
342 int fw_cal_exp_frag;
343 int fw_cal_available;
344 int user_cal_read;
345 int user_cal_available;
346 int user_cal_rcvd;
347 int user_cal_exp_size;
348 int device_opened;
Sameer Thalappil1d69b8022013-06-10 19:10:07 -0700349 int iris_xo_mode_set;
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700350 int fw_vbatt_state;
Sameer Thalappil1878d762013-04-24 14:54:39 -0700351 struct mutex dev_lock;
352 wait_queue_head_t read_wait;
Sameer Thalappilf6fb1892013-09-06 17:33:30 -0700353 struct qpnp_adc_tm_btm_param vbat_monitor_params;
354 struct qpnp_adc_tm_chip *adc_tm_dev;
355 struct mutex vbat_monitor_mutex;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700356} *penv = NULL;
357
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800358static ssize_t wcnss_serial_number_show(struct device *dev,
359 struct device_attribute *attr, char *buf)
360{
361 if (!penv)
362 return -ENODEV;
363
364 return scnprintf(buf, PAGE_SIZE, "%08X\n", penv->serial_number);
365}
366
367static ssize_t wcnss_serial_number_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700368 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800369{
370 unsigned int value;
371
372 if (!penv)
373 return -ENODEV;
374
375 if (sscanf(buf, "%08X", &value) != 1)
376 return -EINVAL;
377
378 penv->serial_number = value;
379 return count;
380}
381
382static DEVICE_ATTR(serial_number, S_IRUSR | S_IWUSR,
383 wcnss_serial_number_show, wcnss_serial_number_store);
384
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800385
386static ssize_t wcnss_thermal_mitigation_show(struct device *dev,
387 struct device_attribute *attr, char *buf)
388{
389 if (!penv)
390 return -ENODEV;
391
392 return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation);
393}
394
395static ssize_t wcnss_thermal_mitigation_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700396 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800397{
398 int value;
399
400 if (!penv)
401 return -ENODEV;
402
403 if (sscanf(buf, "%d", &value) != 1)
404 return -EINVAL;
405 penv->thermal_mitigation = value;
Jeff Johnson61374652012-04-16 20:51:48 -0700406 if (penv->tm_notify)
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700407 (penv->tm_notify)(dev, value);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800408 return count;
409}
410
411static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR,
412 wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store);
413
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700414
415static ssize_t wcnss_version_show(struct device *dev,
416 struct device_attribute *attr, char *buf)
417{
418 if (!penv)
419 return -ENODEV;
420
421 return scnprintf(buf, PAGE_SIZE, "%s", penv->wcnss_version);
422}
423
424static DEVICE_ATTR(wcnss_version, S_IRUSR,
425 wcnss_version_show, NULL);
426
Naresh Jayaram08cee442013-04-26 19:50:00 +0530427void wcnss_riva_dump_pmic_regs(void)
428{
429 int i, rc;
430 u8 val;
431
432 for (i = 0; i < ARRAY_SIZE(wcnss_pmic_reg_dump); i++) {
433 val = 0;
434 rc = pm8xxx_read_register(wcnss_pmic_reg_dump[i].reg_addr,
435 &val);
436 if (rc)
437 pr_err("PMIC READ: Failed to read addr = %d\n",
438 wcnss_pmic_reg_dump[i].reg_addr);
439 else
440 pr_info_ratelimited("PMIC READ: %s addr = %x, value = %x\n",
441 wcnss_pmic_reg_dump[i].reg_name,
442 wcnss_pmic_reg_dump[i].reg_addr, val);
443 }
444}
445
Sameer Thalappil77716e52012-11-08 13:41:04 -0800446/* wcnss_reset_intr() is invoked when host drivers fails to
447 * communicate with WCNSS over SMD; so logging these registers
Sameer Thalappild3aad702013-01-22 18:52:24 -0800448 * helps to know WCNSS failure reason
449 */
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800450void wcnss_riva_log_debug_regs(void)
Sameer Thalappil77716e52012-11-08 13:41:04 -0800451{
Sameer Thalappil77716e52012-11-08 13:41:04 -0800452 void __iomem *ccu_reg;
453 u32 reg = 0;
454
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800455 ccu_reg = penv->riva_ccu_base + CCU_RIVA_INVALID_ADDR_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800456 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800457 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800458
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800459 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR0_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800460 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800461 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800462
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800463 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR1_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_LAST_ADDR1 %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_ADDR2_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_ADDR2 %08x\n", __func__, reg);
Naresh Jayaram08cee442013-04-26 19:50:00 +0530470 wcnss_riva_dump_pmic_regs();
Sameer Thalappil77716e52012-11-08 13:41:04 -0800471
Sameer Thalappil77716e52012-11-08 13:41:04 -0800472}
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800473EXPORT_SYMBOL(wcnss_riva_log_debug_regs);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800474
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800475/* Log pronto debug registers before sending reset interrupt */
476void wcnss_pronto_log_debug_regs(void)
477{
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800478 void __iomem *reg_addr, *tst_addr, *tst_ctrl_addr;
Sameer Thalappil670197c2013-07-19 16:31:14 -0700479 u32 reg = 0, reg2 = 0;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800480
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700481
482 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_SPARE_OFFSET;
483 reg = readl_relaxed(reg_addr);
484 pr_info_ratelimited("%s: PRONTO_PMU_SPARE %08x\n", __func__, reg);
485
Mihir Shete2775e4652013-09-20 16:21:49 -0700486 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_CPU_CBCR_OFFSET;
487 reg = readl_relaxed(reg_addr);
488 pr_info_ratelimited("%s: PRONTO_PMU_COM_CPU_CBCR %08x\n",
489 __func__, reg);
490
491 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_AHB_CBCR_OFFSET;
492 reg = readl_relaxed(reg_addr);
493 pr_info_ratelimited("%s: PRONTO_PMU_COM_AHB_CBCR %08x\n",
494 __func__, reg);
495
496 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CFG_OFFSET;
497 reg = readl_relaxed(reg_addr);
498 pr_info_ratelimited("%s: PRONTO_PMU_CFG %08x\n", __func__, reg);
499
500 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_CSR_OFFSET;
501 reg = readl_relaxed(reg_addr);
502 pr_info_ratelimited("%s: PRONTO_PMU_COM_CSR %08x\n",
503 __func__, reg);
504
505 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_SOFT_RESET_OFFSET;
506 reg = readl_relaxed(reg_addr);
507 pr_info_ratelimited("%s: PRONTO_PMU_SOFT_RESET %08x\n",
508 __func__, reg);
509
Sameer Thalappil670197c2013-07-19 16:31:14 -0700510 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_COM_GDSCR_OFFSET;
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700511 reg = readl_relaxed(reg_addr);
Mihir Shete2775e4652013-09-20 16:21:49 -0700512 pr_info_ratelimited("%s: PRONTO_PMU_COM_GDSCR %08x\n",
513 __func__, reg);
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700514 reg >>= 31;
515
516 if (!reg) {
517 pr_info_ratelimited("%s: Cannot log, Pronto common SS is power collapsed\n",
518 __func__);
519 return;
520 }
Sameer Thalappil670197c2013-07-19 16:31:14 -0700521 reg &= ~(PRONTO_PMU_COM_GDSCR_SW_COLLAPSE
522 | PRONTO_PMU_COM_GDSCR_HW_CTRL);
Sameer Thalappilbcc06182013-05-30 16:18:57 -0700523 writel_relaxed(reg, reg_addr);
524
525 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_CBCR_OFFSET;
526 reg = readl_relaxed(reg_addr);
527 reg |= PRONTO_PMU_CBCR_CLK_EN;
528 writel_relaxed(reg, reg_addr);
529
Sameer Thalappild3aad702013-01-22 18:52:24 -0800530 reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800531 reg = readl_relaxed(reg_addr);
532 pr_info_ratelimited("%s: A2XB_CFG_OFFSET %08x\n", __func__, reg);
533
Sameer Thalappild3aad702013-01-22 18:52:24 -0800534 reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800535 reg = readl_relaxed(reg_addr);
536 pr_info_ratelimited("%s: A2XB_INT_SRC_OFFSET %08x\n", __func__, reg);
537
Sameer Thalappild3aad702013-01-22 18:52:24 -0800538 reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800539 reg = readl_relaxed(reg_addr);
540 pr_info_ratelimited("%s: A2XB_ERR_INFO_OFFSET %08x\n", __func__, reg);
541
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800542 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_INVALID_ADDR_OFFSET;
543 reg = readl_relaxed(reg_addr);
544 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
545
546 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR0_OFFSET;
547 reg = readl_relaxed(reg_addr);
548 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
549
550 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR1_OFFSET;
551 reg = readl_relaxed(reg_addr);
552 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
553
554 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR2_OFFSET;
555 reg = readl_relaxed(reg_addr);
556 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
557
Sameer Thalappil767ff492013-06-19 15:25:21 -0700558 reg_addr = penv->pronto_saw2_base + PRONTO_SAW2_SPM_STS_OFFSET;
559 reg = readl_relaxed(reg_addr);
560 pr_info_ratelimited("%s: PRONTO_SAW2_SPM_STS %08x\n", __func__, reg);
561
Sameer Thalappil66d2b392013-07-02 11:32:55 -0700562 reg_addr = penv->pronto_pll_base + PRONTO_PLL_STATUS_OFFSET;
563 reg = readl_relaxed(reg_addr);
564 pr_info_ratelimited("%s: PRONTO_PLL_STATUS %08x\n", __func__, reg);
565
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800566 tst_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_OFFSET;
567 tst_ctrl_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_CTRL_OFFSET;
568
569 /* read data FIFO */
570 reg = 0;
571 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_RDFIFO;
572 writel_relaxed(reg, tst_ctrl_addr);
573 reg = readl_relaxed(tst_addr);
574 pr_info_ratelimited("%s: Read data FIFO testbus %08x\n",
575 __func__, reg);
576
577 /* command FIFO */
578 reg = 0;
579 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CMDFIFO;
580 writel_relaxed(reg, tst_ctrl_addr);
581 reg = readl_relaxed(tst_addr);
582 pr_info_ratelimited("%s: Command FIFO testbus %08x\n",
583 __func__, reg);
584
585 /* write data FIFO */
586 reg = 0;
587 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_WRFIFO;
588 writel_relaxed(reg, tst_ctrl_addr);
589 reg = readl_relaxed(tst_addr);
590 pr_info_ratelimited("%s: Rrite data FIFO testbus %08x\n",
591 __func__, reg);
592
593 /* AXIM SEL CFG0 */
594 reg = 0;
595 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
596 WCNSS_TSTBUS_CTRL_AXIM_CFG0;
597 writel_relaxed(reg, tst_ctrl_addr);
598 reg = readl_relaxed(tst_addr);
599 pr_info_ratelimited("%s: AXIM SEL CFG0 testbus %08x\n",
600 __func__, reg);
601
602 /* AXIM SEL CFG1 */
603 reg = 0;
604 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
605 WCNSS_TSTBUS_CTRL_AXIM_CFG1;
606 writel_relaxed(reg, tst_ctrl_addr);
607 reg = readl_relaxed(tst_addr);
608 pr_info_ratelimited("%s: AXIM SEL CFG1 testbus %08x\n",
609 __func__, reg);
610
611 /* CTRL SEL CFG0 */
612 reg = 0;
613 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
614 WCNSS_TSTBUS_CTRL_CTRL_CFG0;
615 writel_relaxed(reg, tst_ctrl_addr);
616 reg = readl_relaxed(tst_addr);
617 pr_info_ratelimited("%s: CTRL SEL CFG0 testbus %08x\n",
618 __func__, reg);
619
620 /* CTRL SEL CFG1 */
621 reg = 0;
622 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
623 WCNSS_TSTBUS_CTRL_CTRL_CFG1;
624 writel_relaxed(reg, tst_ctrl_addr);
625 reg = readl_relaxed(tst_addr);
626 pr_info_ratelimited("%s: CTRL SEL CFG1 testbus %08x\n", __func__, reg);
627
Sameer Thalappil670197c2013-07-19 16:31:14 -0700628
629 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_BCR_OFFSET;
630 reg = readl_relaxed(reg_addr);
631
632 reg_addr = penv->msm_wcnss_base + PRONTO_PMU_WLAN_GDSCR_OFFSET;
633 reg2 = readl_relaxed(reg_addr);
634
635 if ((reg & PRONTO_PMU_WLAN_BCR_BLK_ARES) ||
636 (reg2 & PRONTO_PMU_WLAN_GDSCR_SW_COLLAPSE)) {
637 pr_info_ratelimited("%s: Cannot log, wlan domain is power collapsed\n",
638 __func__);
639 return;
640 }
641
642 reg = readl_relaxed(penv->wlan_tx_phy_aborts);
643 pr_info_ratelimited("%s: WLAN_TX_PHY_ABORTS %08x\n", __func__, reg);
644
645 reg = readl_relaxed(penv->wlan_brdg_err_source);
646 pr_info_ratelimited("%s: WLAN_BRDG_ERR_SOURCE %08x\n", __func__, reg);
647
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800648}
649EXPORT_SYMBOL(wcnss_pronto_log_debug_regs);
650
651/* interface to reset wcnss by sending the reset interrupt */
Sameer Thalappil19e02352012-09-24 15:26:12 -0700652void wcnss_reset_intr(void)
653{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800654 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
655 wcnss_pronto_log_debug_regs();
Sameer Thalappil34920762013-03-21 15:43:28 -0700656 wmb();
Sameer Thalappil58281ca2013-04-10 18:50:18 -0700657 __raw_writel(1 << 16, penv->fiq_reg);
Sameer Thalappil34920762013-03-21 15:43:28 -0700658 } else {
659 wcnss_riva_log_debug_regs();
660 wmb();
661 __raw_writel(1 << 24, MSM_APCS_GCC_BASE + 0x8);
Sameer Thalappil19e02352012-09-24 15:26:12 -0700662 }
663}
664EXPORT_SYMBOL(wcnss_reset_intr);
665
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800666static int wcnss_create_sysfs(struct device *dev)
667{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800668 int ret;
669
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800670 if (!dev)
671 return -ENODEV;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800672
673 ret = device_create_file(dev, &dev_attr_serial_number);
674 if (ret)
675 return ret;
676
677 ret = device_create_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700678 if (ret)
679 goto remove_serial;
680
681 ret = device_create_file(dev, &dev_attr_wcnss_version);
682 if (ret)
683 goto remove_thermal;
684
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800685 return 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700686
687remove_thermal:
688 device_remove_file(dev, &dev_attr_thermal_mitigation);
689remove_serial:
690 device_remove_file(dev, &dev_attr_serial_number);
691
692 return ret;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800693}
694
695static void wcnss_remove_sysfs(struct device *dev)
696{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800697 if (dev) {
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800698 device_remove_file(dev, &dev_attr_serial_number);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800699 device_remove_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700700 device_remove_file(dev, &dev_attr_wcnss_version);
701 }
702}
703static void wcnss_smd_notify_event(void *data, unsigned int event)
704{
705 int len = 0;
706
707 if (penv != data) {
708 pr_err("wcnss: invalid env pointer in smd callback\n");
709 return;
710 }
711 switch (event) {
712 case SMD_EVENT_DATA:
713 len = smd_read_avail(penv->smd_ch);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700714 if (len < 0) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700715 pr_err("wcnss: failed to read from smd %d\n", len);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700716 return;
717 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700718 schedule_work(&penv->wcnssctrl_rx_work);
719 break;
720
721 case SMD_EVENT_OPEN:
722 pr_debug("wcnss: opening WCNSS SMD channel :%s",
723 WCNSS_CTRL_CHANNEL);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700724 schedule_work(&penv->wcnssctrl_version_work);
725
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700726 break;
727
728 case SMD_EVENT_CLOSE:
729 pr_debug("wcnss: closing WCNSS SMD channel :%s",
730 WCNSS_CTRL_CHANNEL);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700731 /* This SMD is closed only during SSR */
732 penv->ssr_boot = true;
733 penv->nv_downloaded = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700734 break;
735
736 default:
737 break;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800738 }
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800739}
740
David Collinsb727f7d2011-09-12 11:54:49 -0700741static void wcnss_post_bootup(struct work_struct *work)
742{
Sheng Fangbb421672013-03-19 08:27:28 +0800743 if (do_not_cancel_vote == 1) {
744 pr_info("%s: Keeping APPS vote for Iris & WCNSS\n", __func__);
745 return;
746 }
747
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700748 pr_info("%s: Cancel APPS vote for Iris & WCNSS\n", __func__);
David Collinsb727f7d2011-09-12 11:54:49 -0700749
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700750 /* Since WCNSS is up, cancel any APPS vote for Iris & WCNSS VREGs */
David Collinsb727f7d2011-09-12 11:54:49 -0700751 wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config,
Sameer Thalappil1d69b8022013-06-10 19:10:07 -0700752 WCNSS_WLAN_SWITCH_OFF, NULL);
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700753
754}
755
756static int
757wcnss_pronto_gpios_config(struct device *dev, bool enable)
758{
759 int rc = 0;
760 int i, j;
761 int WCNSS_WLAN_NUM_GPIOS = 5;
762
763 for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) {
764 int gpio = of_get_gpio(dev->of_node, i);
765 if (enable) {
766 rc = gpio_request(gpio, "wcnss_wlan");
767 if (rc) {
768 pr_err("WCNSS gpio_request %d err %d\n",
769 gpio, rc);
770 goto fail;
771 }
772 } else
773 gpio_free(gpio);
774 }
775
776 return rc;
777
778fail:
779 for (j = WCNSS_WLAN_NUM_GPIOS-1; j >= 0; j--) {
780 int gpio = of_get_gpio(dev->of_node, i);
781 gpio_free(gpio);
782 }
783 return rc;
David Collinsb727f7d2011-09-12 11:54:49 -0700784}
785
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700786static int
787wcnss_gpios_config(struct resource *gpios_5wire, bool enable)
788{
789 int i, j;
790 int rc = 0;
791
792 for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) {
793 if (enable) {
794 rc = gpio_request(i, gpios_5wire->name);
795 if (rc) {
796 pr_err("WCNSS gpio_request %d err %d\n", i, rc);
797 goto fail;
798 }
799 } else
800 gpio_free(i);
801 }
802
803 return rc;
804
805fail:
806 for (j = i-1; j >= gpios_5wire->start; j--)
807 gpio_free(j);
808 return rc;
809}
810
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700811static int __devinit
812wcnss_wlan_ctrl_probe(struct platform_device *pdev)
813{
Sameer Thalappil0ab82132013-06-10 10:10:05 -0700814 if (!penv || !penv->triggered)
Sameer Thalappil667f43f2011-10-10 11:12:54 -0700815 return -ENODEV;
816
817 penv->smd_channel_ready = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700818
819 pr_info("%s: SMD ctrl channel up\n", __func__);
820
David Collinsb727f7d2011-09-12 11:54:49 -0700821 /* Schedule a work to do any post boot up activity */
822 INIT_DELAYED_WORK(&penv->wcnss_work, wcnss_post_bootup);
823 schedule_delayed_work(&penv->wcnss_work, msecs_to_jiffies(10000));
824
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700825 return 0;
826}
827
Sameer Thalappil13f45182012-07-23 18:02:45 -0700828void wcnss_flush_delayed_boot_votes()
829{
Sameer Thalappilf106a682013-02-16 20:41:11 -0800830 flush_delayed_work(&penv->wcnss_work);
Sameer Thalappil13f45182012-07-23 18:02:45 -0700831}
832EXPORT_SYMBOL(wcnss_flush_delayed_boot_votes);
833
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700834static int __devexit
835wcnss_wlan_ctrl_remove(struct platform_device *pdev)
836{
837 if (penv)
838 penv->smd_channel_ready = 0;
839
840 pr_info("%s: SMD ctrl channel down\n", __func__);
841
842 return 0;
843}
844
845
846static struct platform_driver wcnss_wlan_ctrl_driver = {
847 .driver = {
848 .name = "WLAN_CTRL",
849 .owner = THIS_MODULE,
850 },
851 .probe = wcnss_wlan_ctrl_probe,
852 .remove = __devexit_p(wcnss_wlan_ctrl_remove),
853};
854
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700855static int __devexit
856wcnss_ctrl_remove(struct platform_device *pdev)
857{
858 if (penv && penv->smd_ch)
859 smd_close(penv->smd_ch);
860
861 return 0;
862}
863
864static int __devinit
865wcnss_ctrl_probe(struct platform_device *pdev)
866{
867 int ret = 0;
868
Sameer Thalappil0ab82132013-06-10 10:10:05 -0700869 if (!penv || !penv->triggered)
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700870 return -ENODEV;
871
872 ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS,
873 &penv->smd_ch, penv, wcnss_smd_notify_event);
874 if (ret < 0) {
875 pr_err("wcnss: cannot open the smd command channel %s: %d\n",
876 WCNSS_CTRL_CHANNEL, ret);
877 return -ENODEV;
878 }
879 smd_disable_read_intr(penv->smd_ch);
880
881 return 0;
882}
883
884/* platform device for WCNSS_CTRL SMD channel */
885static struct platform_driver wcnss_ctrl_driver = {
886 .driver = {
887 .name = "WCNSS_CTRL",
888 .owner = THIS_MODULE,
889 },
890 .probe = wcnss_ctrl_probe,
891 .remove = __devexit_p(wcnss_ctrl_remove),
892};
893
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700894struct device *wcnss_wlan_get_device(void)
895{
896 if (penv && penv->pdev && penv->smd_channel_ready)
897 return &penv->pdev->dev;
898 return NULL;
899}
900EXPORT_SYMBOL(wcnss_wlan_get_device);
901
Sameer Thalappil409ed352011-12-07 10:53:31 -0800902struct platform_device *wcnss_get_platform_device(void)
903{
904 if (penv && penv->pdev)
905 return penv->pdev;
906 return NULL;
907}
908EXPORT_SYMBOL(wcnss_get_platform_device);
909
910struct wcnss_wlan_config *wcnss_get_wlan_config(void)
911{
912 if (penv && penv->pdev)
913 return &penv->wlan_config;
914 return NULL;
915}
916EXPORT_SYMBOL(wcnss_get_wlan_config);
917
Sameer Thalappil1878d762013-04-24 14:54:39 -0700918int wcnss_device_ready(void)
919{
920 if (penv && penv->pdev && penv->nv_downloaded)
921 return 1;
922 return 0;
923}
924EXPORT_SYMBOL(wcnss_device_ready);
925
926
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700927struct resource *wcnss_wlan_get_memory_map(struct device *dev)
928{
929 if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready)
930 return penv->mmio_res;
931 return NULL;
932}
933EXPORT_SYMBOL(wcnss_wlan_get_memory_map);
934
935int wcnss_wlan_get_dxe_tx_irq(struct device *dev)
936{
937 if (penv && dev && (dev == &penv->pdev->dev) &&
938 penv->tx_irq_res && penv->smd_channel_ready)
939 return penv->tx_irq_res->start;
940 return WCNSS_WLAN_IRQ_INVALID;
941}
942EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq);
943
944int wcnss_wlan_get_dxe_rx_irq(struct device *dev)
945{
946 if (penv && dev && (dev == &penv->pdev->dev) &&
947 penv->rx_irq_res && penv->smd_channel_ready)
948 return penv->rx_irq_res->start;
949 return WCNSS_WLAN_IRQ_INVALID;
950}
951EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq);
952
953void wcnss_wlan_register_pm_ops(struct device *dev,
954 const struct dev_pm_ops *pm_ops)
955{
956 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops)
957 penv->pm_ops = pm_ops;
958}
959EXPORT_SYMBOL(wcnss_wlan_register_pm_ops);
960
Sameer Thalappilecdd1002011-09-09 10:52:27 -0700961void wcnss_wlan_unregister_pm_ops(struct device *dev,
962 const struct dev_pm_ops *pm_ops)
963{
964 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) {
965 if (pm_ops->suspend != penv->pm_ops->suspend ||
966 pm_ops->resume != penv->pm_ops->resume)
967 pr_err("PM APIs dont match with registered APIs\n");
968 penv->pm_ops = NULL;
969 }
970}
971EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops);
972
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700973void wcnss_register_thermal_mitigation(struct device *dev,
974 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700975{
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700976 if (penv && dev && tm_notify)
Jeff Johnson61374652012-04-16 20:51:48 -0700977 penv->tm_notify = tm_notify;
978}
979EXPORT_SYMBOL(wcnss_register_thermal_mitigation);
980
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700981void wcnss_unregister_thermal_mitigation(
982 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700983{
984 if (penv && tm_notify) {
985 if (tm_notify != penv->tm_notify)
986 pr_err("tm_notify doesn't match registered\n");
987 penv->tm_notify = NULL;
988 }
989}
990EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation);
991
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800992unsigned int wcnss_get_serial_number(void)
993{
994 if (penv)
995 return penv->serial_number;
996 return 0;
997}
998EXPORT_SYMBOL(wcnss_get_serial_number);
999
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001000static int enable_wcnss_suspend_notify;
1001
1002static int enable_wcnss_suspend_notify_set(const char *val,
1003 struct kernel_param *kp)
1004{
1005 int ret;
1006
1007 ret = param_set_int(val, kp);
1008 if (ret)
1009 return ret;
1010
1011 if (enable_wcnss_suspend_notify)
1012 pr_debug("Suspend notification activated for wcnss\n");
1013
1014 return 0;
1015}
1016module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set,
1017 param_get_int, &enable_wcnss_suspend_notify, S_IRUGO | S_IWUSR);
1018
Sameer Thalappil58cec312013-05-13 15:52:08 -07001019int wcnss_xo_auto_detect_enabled(void)
1020{
1021 return (has_autodetect_xo == 1 ? 1 : 0);
1022}
1023
Sameer Thalappil1d69b8022013-06-10 19:10:07 -07001024int wcnss_wlan_iris_xo_mode(void)
1025{
1026 if (penv && penv->pdev && penv->smd_channel_ready)
1027 return penv->iris_xo_mode_set;
1028 return -ENODEV;
1029}
1030EXPORT_SYMBOL(wcnss_wlan_iris_xo_mode);
1031
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001032
1033void wcnss_suspend_notify(void)
1034{
1035 void __iomem *pmu_spare_reg;
1036 u32 reg = 0;
1037 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001038
1039 if (!enable_wcnss_suspend_notify)
1040 return;
1041
1042 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
1043 return;
1044
1045 /* For Riva */
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001046 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
1047 spin_lock_irqsave(&reg_spinlock, flags);
1048 reg = readl_relaxed(pmu_spare_reg);
1049 reg |= RIVA_SUSPEND_BIT;
1050 writel_relaxed(reg, pmu_spare_reg);
1051 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001052}
1053EXPORT_SYMBOL(wcnss_suspend_notify);
1054
1055void wcnss_resume_notify(void)
1056{
1057 void __iomem *pmu_spare_reg;
1058 u32 reg = 0;
1059 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001060
1061 if (!enable_wcnss_suspend_notify)
1062 return;
1063
1064 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
1065 return;
1066
1067 /* For Riva */
1068 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
1069
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001070 spin_lock_irqsave(&reg_spinlock, flags);
1071 reg = readl_relaxed(pmu_spare_reg);
1072 reg &= ~RIVA_SUSPEND_BIT;
1073 writel_relaxed(reg, pmu_spare_reg);
1074 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001075}
1076EXPORT_SYMBOL(wcnss_resume_notify);
1077
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001078static int wcnss_wlan_suspend(struct device *dev)
1079{
1080 if (penv && dev && (dev == &penv->pdev->dev) &&
1081 penv->smd_channel_ready &&
1082 penv->pm_ops && penv->pm_ops->suspend)
1083 return penv->pm_ops->suspend(dev);
1084 return 0;
1085}
1086
1087static int wcnss_wlan_resume(struct device *dev)
1088{
1089 if (penv && dev && (dev == &penv->pdev->dev) &&
1090 penv->smd_channel_ready &&
1091 penv->pm_ops && penv->pm_ops->resume)
1092 return penv->pm_ops->resume(dev);
1093 return 0;
1094}
1095
Sameer Thalappilf138da52012-07-30 12:56:37 -07001096void wcnss_prevent_suspend()
1097{
1098 if (penv)
1099 wake_lock(&penv->wcnss_wake_lock);
1100}
1101EXPORT_SYMBOL(wcnss_prevent_suspend);
1102
1103void wcnss_allow_suspend()
1104{
1105 if (penv)
1106 wake_unlock(&penv->wcnss_wake_lock);
1107}
1108EXPORT_SYMBOL(wcnss_allow_suspend);
1109
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001110int wcnss_hardware_type(void)
1111{
1112 if (penv)
1113 return penv->wcnss_hw_type;
1114 else
1115 return -ENODEV;
1116}
1117EXPORT_SYMBOL(wcnss_hardware_type);
1118
Sameer Thalappil1878d762013-04-24 14:54:39 -07001119int fw_cal_data_available(void)
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001120{
1121 if (penv)
Sameer Thalappil1878d762013-04-24 14:54:39 -07001122 return penv->fw_cal_available;
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001123 else
1124 return -ENODEV;
1125}
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001126
Sameer Thalappild0952f62013-05-03 10:18:29 -07001127u32 wcnss_get_wlan_rx_buff_count(void)
1128{
1129 if (penv)
1130 return penv->wlan_rx_buff_count;
1131 else
1132 return WCNSS_DEF_WLAN_RX_BUFF_COUNT;
1133
1134}
1135EXPORT_SYMBOL(wcnss_get_wlan_rx_buff_count);
1136
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001137static int wcnss_smd_tx(void *data, int len)
1138{
1139 int ret = 0;
1140
1141 ret = smd_write_avail(penv->smd_ch);
1142 if (ret < len) {
1143 pr_err("wcnss: no space available for smd frame\n");
Sameer Thalappilf106a682013-02-16 20:41:11 -08001144 return -ENOSPC;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001145 }
1146 ret = smd_write(penv->smd_ch, data, len);
1147 if (ret < len) {
1148 pr_err("wcnss: failed to write Command %d", len);
1149 ret = -ENODEV;
1150 }
1151 return ret;
1152}
1153
Sameer Thalappilf6fb1892013-09-06 17:33:30 -07001154static void wcnss_notify_vbat(enum qpnp_tm_state state, void *ctx)
1155{
1156 mutex_lock(&penv->vbat_monitor_mutex);
1157 cancel_delayed_work_sync(&penv->vbatt_work);
1158
1159 if (state == ADC_TM_LOW_STATE) {
1160 pr_debug("wcnss: low voltage notification triggered\n");
1161 penv->vbat_monitor_params.state_request =
1162 ADC_TM_HIGH_THR_ENABLE;
1163 penv->vbat_monitor_params.high_thr = WCNSS_VBATT_THRESHOLD +
1164 WCNSS_VBATT_GUARD;
1165 penv->vbat_monitor_params.low_thr = 0;
1166 } else if (state == ADC_TM_HIGH_STATE) {
1167 penv->vbat_monitor_params.state_request =
1168 ADC_TM_LOW_THR_ENABLE;
1169 penv->vbat_monitor_params.low_thr = WCNSS_VBATT_THRESHOLD -
1170 WCNSS_VBATT_GUARD;
1171 penv->vbat_monitor_params.high_thr = 0;
1172 pr_debug("wcnss: high voltage notification triggered\n");
1173 } else {
1174 pr_debug("wcnss: unknown voltage notification state: %d\n",
1175 state);
1176 mutex_unlock(&penv->vbat_monitor_mutex);
1177 return;
1178 }
1179 pr_debug("wcnss: set low thr to %d and high to %d\n",
1180 penv->vbat_monitor_params.low_thr,
1181 penv->vbat_monitor_params.high_thr);
1182
1183 qpnp_adc_tm_channel_measure(penv->adc_tm_dev,
1184 &penv->vbat_monitor_params);
1185 schedule_delayed_work(&penv->vbatt_work, msecs_to_jiffies(2000));
1186 mutex_unlock(&penv->vbat_monitor_mutex);
1187}
1188
1189static int wcnss_setup_vbat_monitoring(void)
1190{
1191 int rc = -1;
1192
1193 if (!penv->adc_tm_dev) {
1194 pr_err("wcnss: not setting up vbatt\n");
1195 return rc;
1196 }
1197 penv->vbat_monitor_params.low_thr = WCNSS_VBATT_THRESHOLD;
1198 penv->vbat_monitor_params.high_thr = WCNSS_VBATT_THRESHOLD;
1199 penv->vbat_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_ENABLE;
1200 penv->vbat_monitor_params.channel = VBAT_SNS;
1201 penv->vbat_monitor_params.btm_ctx = (void *)penv;
1202 penv->vbat_monitor_params.timer_interval = ADC_MEAS1_INTERVAL_1S;
1203 penv->vbat_monitor_params.threshold_notification = &wcnss_notify_vbat;
1204 pr_debug("wcnss: set low thr to %d and high to %d\n",
1205 penv->vbat_monitor_params.low_thr,
1206 penv->vbat_monitor_params.high_thr);
1207
1208 rc = qpnp_adc_tm_channel_measure(penv->adc_tm_dev,
1209 &penv->vbat_monitor_params);
1210 if (rc)
1211 pr_err("wcnss: tm setup failed: %d\n", rc);
1212
1213 return rc;
1214}
1215
1216static void wcnss_update_vbatt(struct work_struct *work)
1217{
1218 struct vbatt_message vbatt_msg;
1219 int ret = 0;
1220
1221 vbatt_msg.hdr.msg_type = WCNSS_VBATT_LEVEL_IND;
1222 vbatt_msg.hdr.msg_len = sizeof(struct vbatt_message);
1223 vbatt_msg.vbatt.threshold = WCNSS_VBATT_THRESHOLD;
1224
1225 mutex_lock(&penv->vbat_monitor_mutex);
1226 if (penv->vbat_monitor_params.low_thr &&
1227 (penv->fw_vbatt_state == WCNSS_VBATT_LOW ||
1228 penv->fw_vbatt_state == WCNSS_CONFIG_UNSPECIFIED)) {
1229 vbatt_msg.vbatt.curr_volt = WCNSS_VBATT_HIGH;
1230 penv->fw_vbatt_state = WCNSS_VBATT_HIGH;
1231 pr_debug("wcnss: send HIGH BATT to FW\n");
1232 } else if (!penv->vbat_monitor_params.low_thr &&
1233 (penv->fw_vbatt_state == WCNSS_VBATT_HIGH ||
1234 penv->fw_vbatt_state == WCNSS_CONFIG_UNSPECIFIED)){
1235 vbatt_msg.vbatt.curr_volt = WCNSS_VBATT_LOW;
1236 penv->fw_vbatt_state = WCNSS_VBATT_LOW;
1237 pr_debug("wcnss: send LOW BATT to FW\n");
1238 } else {
1239 mutex_unlock(&penv->vbat_monitor_mutex);
1240 return;
1241 }
1242 mutex_unlock(&penv->vbat_monitor_mutex);
1243 ret = wcnss_smd_tx(&vbatt_msg, vbatt_msg.hdr.msg_len);
1244 if (ret < 0)
1245 pr_err("wcnss: smd tx failed\n");
1246 return;
1247}
1248
1249
Sameer Thalappil1878d762013-04-24 14:54:39 -07001250static unsigned char wcnss_fw_status(void)
1251{
1252 int len = 0;
1253 int rc = 0;
1254
1255 unsigned char fw_status = 0xFF;
1256
1257 len = smd_read_avail(penv->smd_ch);
1258 if (len < 1) {
1259 pr_err("%s: invalid firmware status", __func__);
1260 return fw_status;
1261 }
1262
1263 rc = smd_read(penv->smd_ch, &fw_status, 1);
1264 if (rc < 0) {
1265 pr_err("%s: incomplete data read from smd\n", __func__);
1266 return fw_status;
1267 }
1268 return fw_status;
1269}
1270
1271static void wcnss_send_cal_rsp(unsigned char fw_status)
1272{
1273 struct smd_msg_hdr *rsphdr;
1274 unsigned char *msg = NULL;
1275 int rc;
1276
1277 msg = kmalloc((sizeof(struct smd_msg_hdr) + 1), GFP_KERNEL);
1278 if (NULL == msg) {
1279 pr_err("wcnss: %s: failed to get memory\n", __func__);
1280 return;
1281 }
1282
1283 rsphdr = (struct smd_msg_hdr *)msg;
1284 rsphdr->msg_type = WCNSS_CALDATA_UPLD_RSP;
1285 rsphdr->msg_len = sizeof(struct smd_msg_hdr) + 1;
1286 memcpy(msg+sizeof(struct smd_msg_hdr), &fw_status, 1);
1287
1288 rc = wcnss_smd_tx(msg, rsphdr->msg_len);
1289 if (rc < 0)
1290 pr_err("wcnss: smd tx failed\n");
Sameer Thalappil844d3602013-05-24 13:54:24 -07001291
1292 kfree(msg);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001293}
1294
1295/* Collect calibrated data from WCNSS */
1296void extract_cal_data(int len)
1297{
1298 int rc;
1299 struct cal_data_params calhdr;
1300 unsigned char fw_status = WCNSS_RESP_FAIL;
1301
1302 if (len < sizeof(struct cal_data_params)) {
1303 pr_err("wcnss: incomplete cal header length\n");
1304 return;
1305 }
1306
1307 rc = smd_read(penv->smd_ch, (unsigned char *)&calhdr,
1308 sizeof(struct cal_data_params));
1309 if (rc < sizeof(struct cal_data_params)) {
1310 pr_err("wcnss: incomplete cal header read from smd\n");
1311 return;
1312 }
1313
1314 if (penv->fw_cal_exp_frag != calhdr.frag_number) {
1315 pr_err("wcnss: Invalid frgament");
1316 goto exit;
1317 }
1318
1319 if (calhdr.frag_size > WCNSS_MAX_FRAME_SIZE) {
1320 pr_err("wcnss: Invalid fragment size");
1321 goto exit;
1322 }
1323
1324 if (0 == calhdr.frag_number) {
1325 if (calhdr.total_size > MAX_CALIBRATED_DATA_SIZE) {
1326 pr_err("wcnss: Invalid cal data size %d",
1327 calhdr.total_size);
1328 goto exit;
1329 }
1330 kfree(penv->fw_cal_data);
1331 penv->fw_cal_rcvd = 0;
1332 penv->fw_cal_data = kmalloc(calhdr.total_size,
1333 GFP_KERNEL);
1334 if (penv->fw_cal_data == NULL) {
1335 smd_read(penv->smd_ch, NULL, calhdr.frag_size);
1336 goto exit;
1337 }
1338 }
1339
1340 mutex_lock(&penv->dev_lock);
1341 if (penv->fw_cal_rcvd + calhdr.frag_size >
1342 MAX_CALIBRATED_DATA_SIZE) {
1343 pr_err("calibrated data size is more than expected %d",
1344 penv->fw_cal_rcvd + calhdr.frag_size);
1345 penv->fw_cal_exp_frag = 0;
1346 penv->fw_cal_rcvd = 0;
1347 smd_read(penv->smd_ch, NULL, calhdr.frag_size);
1348 goto unlock_exit;
1349 }
1350
1351 rc = smd_read(penv->smd_ch, penv->fw_cal_data + penv->fw_cal_rcvd,
1352 calhdr.frag_size);
1353 if (rc < calhdr.frag_size)
1354 goto unlock_exit;
1355
1356 penv->fw_cal_exp_frag++;
1357 penv->fw_cal_rcvd += calhdr.frag_size;
1358
1359 if (calhdr.msg_flags & LAST_FRAGMENT) {
1360 penv->fw_cal_exp_frag = 0;
1361 penv->fw_cal_available = true;
1362 pr_info("wcnss: cal data collection completed\n");
1363 }
1364 mutex_unlock(&penv->dev_lock);
1365 wake_up(&penv->read_wait);
1366
1367 if (penv->fw_cal_available) {
1368 fw_status = WCNSS_RESP_SUCCESS;
1369 wcnss_send_cal_rsp(fw_status);
1370 }
1371 return;
1372
1373unlock_exit:
1374 mutex_unlock(&penv->dev_lock);
1375
1376exit:
1377 wcnss_send_cal_rsp(fw_status);
1378 return;
1379}
1380
1381
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001382static void wcnssctrl_rx_handler(struct work_struct *worker)
1383{
1384 int len = 0;
1385 int rc = 0;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001386 unsigned char buf[sizeof(struct wcnss_version)];
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001387 struct smd_msg_hdr *phdr;
1388 struct wcnss_version *pversion;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001389 int hw_type;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001390 unsigned char fw_status = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001391
1392 len = smd_read_avail(penv->smd_ch);
1393 if (len > WCNSS_MAX_FRAME_SIZE) {
1394 pr_err("wcnss: frame larger than the allowed size\n");
1395 smd_read(penv->smd_ch, NULL, len);
1396 return;
1397 }
1398 if (len <= 0)
1399 return;
1400
Sameer Thalappil1878d762013-04-24 14:54:39 -07001401 rc = smd_read(penv->smd_ch, buf, sizeof(struct smd_msg_hdr));
1402 if (rc < sizeof(struct smd_msg_hdr)) {
1403 pr_err("wcnss: incomplete header read from smd\n");
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001404 return;
1405 }
Sameer Thalappil1878d762013-04-24 14:54:39 -07001406 len -= sizeof(struct smd_msg_hdr);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001407
1408 phdr = (struct smd_msg_hdr *)buf;
1409
Sameer Thalappilf106a682013-02-16 20:41:11 -08001410 switch (phdr->msg_type) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001411
1412 case WCNSS_VERSION_RSP:
Sameer Thalappil1878d762013-04-24 14:54:39 -07001413 if (len != sizeof(struct wcnss_version)
1414 - sizeof(struct smd_msg_hdr)) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001415 pr_err("wcnss: invalid version data from wcnss %d\n",
Sameer Thalappil1878d762013-04-24 14:54:39 -07001416 len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001417 return;
1418 }
Sameer Thalappil1878d762013-04-24 14:54:39 -07001419 rc = smd_read(penv->smd_ch, buf+sizeof(struct smd_msg_hdr),
1420 len);
1421 if (rc < len) {
1422 pr_err("wcnss: incomplete data read from smd\n");
1423 return;
1424 }
1425 pversion = (struct wcnss_version *)buf;
1426 penv->fw_major = pversion->major;
1427 penv->fw_minor = pversion->minor;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001428 snprintf(penv->wcnss_version, WCNSS_VERSION_LEN,
1429 "%02x%02x%02x%02x", pversion->major, pversion->minor,
1430 pversion->version, pversion->revision);
1431 pr_info("wcnss: version %s\n", penv->wcnss_version);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001432 /* schedule work to download nvbin to ccpu */
1433 hw_type = wcnss_hardware_type();
1434 switch (hw_type) {
1435 case WCNSS_RIVA_HW:
1436 /* supported only if riva major >= 1 and minor >= 4 */
1437 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
1438 pr_info("wcnss: schedule dnld work for riva\n");
1439 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
1440 }
1441 break;
1442
1443 case WCNSS_PRONTO_HW:
1444 /* supported only if pronto major >= 1 and minor >= 4 */
1445 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
1446 pr_info("wcnss: schedule dnld work for pronto\n");
1447 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
1448 }
1449 break;
1450
1451 default:
1452 pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n",
1453 hw_type);
1454 break;
1455 }
1456 break;
1457
1458 case WCNSS_NVBIN_DNLD_RSP:
Sameer Thalappil1878d762013-04-24 14:54:39 -07001459 penv->nv_downloaded = true;
1460 fw_status = wcnss_fw_status();
1461 pr_debug("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu %u\n",
1462 fw_status);
Sameer Thalappilf6fb1892013-09-06 17:33:30 -07001463 wcnss_setup_vbat_monitoring();
Sameer Thalappil1878d762013-04-24 14:54:39 -07001464 break;
1465
1466 case WCNSS_CALDATA_DNLD_RSP:
1467 penv->nv_downloaded = true;
1468 fw_status = wcnss_fw_status();
1469 pr_debug("wcnss: received WCNSS_CALDATA_DNLD_RSP from ccpu %u\n",
1470 fw_status);
1471 break;
1472
1473 case WCNSS_CALDATA_UPLD_REQ:
1474 penv->fw_cal_available = 0;
1475 extract_cal_data(len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001476 break;
1477
1478 default:
Sameer Thalappilf106a682013-02-16 20:41:11 -08001479 pr_err("wcnss: invalid message type %d\n", phdr->msg_type);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001480 }
1481 return;
1482}
1483
1484static void wcnss_send_version_req(struct work_struct *worker)
1485{
1486 struct smd_msg_hdr smd_msg;
1487 int ret = 0;
1488
Sameer Thalappilf106a682013-02-16 20:41:11 -08001489 smd_msg.msg_type = WCNSS_VERSION_REQ;
1490 smd_msg.msg_len = sizeof(smd_msg);
1491 ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001492 if (ret < 0)
1493 pr_err("wcnss: smd tx failed\n");
1494
1495 return;
1496}
1497
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001498static DECLARE_RWSEM(wcnss_pm_sem);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001499
1500static void wcnss_nvbin_dnld(void)
Sameer Thalappilf106a682013-02-16 20:41:11 -08001501{
1502 int ret = 0;
1503 struct nvbin_dnld_req_msg *dnld_req_msg;
1504 unsigned short total_fragments = 0;
1505 unsigned short count = 0;
1506 unsigned short retry_count = 0;
1507 unsigned short cur_frag_size = 0;
1508 unsigned char *outbuffer = NULL;
1509 const void *nv_blob_addr = NULL;
1510 unsigned int nv_blob_size = 0;
1511 const struct firmware *nv = NULL;
Sameer Thalappilf0d89ba2013-03-11 17:33:20 -07001512 struct device *dev = &penv->pdev->dev;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001513
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001514 down_read(&wcnss_pm_sem);
1515
Sameer Thalappilf106a682013-02-16 20:41:11 -08001516 ret = request_firmware(&nv, NVBIN_FILE, dev);
1517
1518 if (ret || !nv || !nv->data || !nv->size) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001519 pr_err("wcnss: %s: request_firmware failed for %s\n",
1520 __func__, NVBIN_FILE);
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001521 goto out;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001522 }
1523
1524 /*
1525 * First 4 bytes in nv blob is validity bitmap.
1526 * We cannot validate nv, so skip those 4 bytes.
1527 */
1528 nv_blob_addr = nv->data + 4;
1529 nv_blob_size = nv->size - 4;
1530
1531 total_fragments = TOTALFRAGMENTS(nv_blob_size);
1532
1533 pr_info("wcnss: NV bin size: %d, total_fragments: %d\n",
1534 nv_blob_size, total_fragments);
1535
1536 /* get buffer for nv bin dnld req message */
1537 outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) +
1538 NV_FRAGMENT_SIZE), GFP_KERNEL);
1539
1540 if (NULL == outbuffer) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001541 pr_err("wcnss: %s: failed to get buffer\n", __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001542 goto err_free_nv;
1543 }
1544
1545 dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
1546
1547 dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001548 dnld_req_msg->dnld_req_params.msg_flags = 0;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001549
1550 for (count = 0; count < total_fragments; count++) {
1551 dnld_req_msg->dnld_req_params.frag_number = count;
1552
1553 if (count == (total_fragments - 1)) {
1554 /* last fragment, take care of boundry condition */
1555 cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE;
1556 if (!cur_frag_size)
1557 cur_frag_size = NV_FRAGMENT_SIZE;
1558
Sameer Thalappil1878d762013-04-24 14:54:39 -07001559 dnld_req_msg->dnld_req_params.msg_flags |=
1560 LAST_FRAGMENT;
1561 dnld_req_msg->dnld_req_params.msg_flags |=
1562 CAN_RECEIVE_CALDATA;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001563 } else {
1564 cur_frag_size = NV_FRAGMENT_SIZE;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001565 dnld_req_msg->dnld_req_params.msg_flags &=
1566 ~LAST_FRAGMENT;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001567 }
1568
1569 dnld_req_msg->dnld_req_params.nvbin_buffer_size =
1570 cur_frag_size;
1571
1572 dnld_req_msg->hdr.msg_len =
1573 sizeof(struct nvbin_dnld_req_msg) + cur_frag_size;
1574
1575 /* copy NV fragment */
1576 memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)),
1577 (nv_blob_addr + count * NV_FRAGMENT_SIZE),
1578 cur_frag_size);
1579
1580 ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len);
1581
1582 retry_count = 0;
1583 while ((ret == -ENOSPC) && (retry_count <= 3)) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001584 pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
1585 __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001586 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1587 count, dnld_req_msg->hdr.msg_len,
1588 total_fragments, retry_count);
1589
1590 /* wait and try again */
1591 msleep(20);
1592 retry_count++;
1593 ret = wcnss_smd_tx(outbuffer,
1594 dnld_req_msg->hdr.msg_len);
1595 }
1596
1597 if (ret < 0) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001598 pr_err("wcnss: %s: smd tx failed\n", __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001599 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1600 count, dnld_req_msg->hdr.msg_len,
1601 total_fragments, retry_count);
1602 goto err_dnld;
1603 }
1604 }
1605
1606err_dnld:
1607 /* free buffer */
1608 kfree(outbuffer);
1609
1610err_free_nv:
1611 /* release firmware */
1612 release_firmware(nv);
1613
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001614out:
1615 up_read(&wcnss_pm_sem);
1616
Sameer Thalappilf106a682013-02-16 20:41:11 -08001617 return;
1618}
1619
Sameer Thalappil1878d762013-04-24 14:54:39 -07001620
1621static void wcnss_caldata_dnld(const void *cal_data,
1622 unsigned int cal_data_size, bool msg_to_follow)
1623{
1624 int ret = 0;
1625 struct cal_data_msg *cal_msg;
1626 unsigned short total_fragments = 0;
1627 unsigned short count = 0;
1628 unsigned short retry_count = 0;
1629 unsigned short cur_frag_size = 0;
1630 unsigned char *outbuffer = NULL;
1631
1632 total_fragments = TOTALFRAGMENTS(cal_data_size);
1633
1634 outbuffer = kmalloc((sizeof(struct cal_data_msg) +
1635 NV_FRAGMENT_SIZE), GFP_KERNEL);
1636
1637 if (NULL == outbuffer) {
1638 pr_err("wcnss: %s: failed to get buffer\n", __func__);
1639 return;
1640 }
1641
1642 cal_msg = (struct cal_data_msg *)outbuffer;
1643
1644 cal_msg->hdr.msg_type = WCNSS_CALDATA_DNLD_REQ;
1645 cal_msg->cal_params.msg_flags = 0;
1646
1647 for (count = 0; count < total_fragments; count++) {
1648 cal_msg->cal_params.frag_number = count;
1649
1650 if (count == (total_fragments - 1)) {
1651 cur_frag_size = cal_data_size % NV_FRAGMENT_SIZE;
1652 if (!cur_frag_size)
1653 cur_frag_size = NV_FRAGMENT_SIZE;
1654
1655 cal_msg->cal_params.msg_flags
1656 |= LAST_FRAGMENT;
1657 if (msg_to_follow)
1658 cal_msg->cal_params.msg_flags |=
1659 MESSAGE_TO_FOLLOW;
1660 } else {
1661 cur_frag_size = NV_FRAGMENT_SIZE;
1662 cal_msg->cal_params.msg_flags &=
1663 ~LAST_FRAGMENT;
1664 }
1665
1666 cal_msg->cal_params.total_size = cal_data_size;
1667 cal_msg->cal_params.frag_size =
1668 cur_frag_size;
1669
1670 cal_msg->hdr.msg_len =
1671 sizeof(struct cal_data_msg) + cur_frag_size;
1672
1673 memcpy((outbuffer + sizeof(struct cal_data_msg)),
1674 (cal_data + count * NV_FRAGMENT_SIZE),
1675 cur_frag_size);
1676
1677 ret = wcnss_smd_tx(outbuffer, cal_msg->hdr.msg_len);
1678
1679 retry_count = 0;
1680 while ((ret == -ENOSPC) && (retry_count <= 3)) {
1681 pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
1682 __func__);
1683 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1684 count, cal_msg->hdr.msg_len,
1685 total_fragments, retry_count);
1686
1687 /* wait and try again */
1688 msleep(20);
1689 retry_count++;
1690 ret = wcnss_smd_tx(outbuffer,
1691 cal_msg->hdr.msg_len);
1692 }
1693
1694 if (ret < 0) {
1695 pr_err("wcnss: %s: smd tx failed\n", __func__);
1696 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1697 count, cal_msg->hdr.msg_len,
1698 total_fragments, retry_count);
1699 goto err_dnld;
1700 }
1701 }
1702
1703
1704err_dnld:
1705 /* free buffer */
1706 kfree(outbuffer);
1707
1708 return;
1709}
1710
1711
1712static void wcnss_nvbin_dnld_main(struct work_struct *worker)
1713{
1714 int retry = 0;
1715
1716 if (!FW_CALDATA_CAPABLE())
1717 goto nv_download;
1718
1719 if (!penv->fw_cal_available && WCNSS_CONFIG_UNSPECIFIED
1720 != has_calibrated_data && !penv->user_cal_available) {
1721 while (!penv->user_cal_available && retry++ < 5)
1722 msleep(500);
1723 }
1724
1725 /* only cal data is sent during ssr (if available) */
1726 if (penv->fw_cal_available && penv->ssr_boot) {
1727 pr_info_ratelimited("wcnss: cal download during SSR, using fw cal");
1728 wcnss_caldata_dnld(penv->fw_cal_data, penv->fw_cal_rcvd, false);
1729 return;
1730
1731 } else if (penv->user_cal_available && penv->ssr_boot) {
1732 pr_info_ratelimited("wcnss: cal download during SSR, using user cal");
1733 wcnss_caldata_dnld(penv->user_cal_data,
1734 penv->user_cal_rcvd, false);
1735 return;
1736
1737 } else if (penv->user_cal_available) {
1738 pr_info_ratelimited("wcnss: cal download during cold boot, using user cal");
1739 wcnss_caldata_dnld(penv->user_cal_data,
1740 penv->user_cal_rcvd, true);
1741 }
1742
1743nv_download:
1744 pr_info_ratelimited("wcnss: NV download");
1745 wcnss_nvbin_dnld();
1746
1747 return;
1748}
1749
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001750static int wcnss_pm_notify(struct notifier_block *b,
1751 unsigned long event, void *p)
1752{
1753 switch (event) {
1754 case PM_SUSPEND_PREPARE:
1755 down_write(&wcnss_pm_sem);
1756 break;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001757
Sameer Thalappil944c72d2013-08-14 17:56:17 -07001758 case PM_POST_SUSPEND:
1759 up_write(&wcnss_pm_sem);
1760 break;
1761 }
1762
1763 return NOTIFY_DONE;
1764}
1765
1766static struct notifier_block wcnss_pm_notifier = {
1767 .notifier_call = wcnss_pm_notify,
1768};
Sameer Thalappil1878d762013-04-24 14:54:39 -07001769
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001770static int
1771wcnss_trigger_config(struct platform_device *pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001772{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001773 int ret;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001774 struct qcom_wcnss_opts *pdata;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001775 unsigned long wcnss_phys_addr;
1776 int size = 0;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001777 struct resource *res;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001778 int has_pronto_hw = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001779 "qcom,has-pronto-hw");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001780
Sameer Thalappild0952f62013-05-03 10:18:29 -07001781 if (of_property_read_u32(pdev->dev.of_node,
1782 "qcom,wlan-rx-buff-count", &penv->wlan_rx_buff_count)) {
1783 penv->wlan_rx_buff_count = WCNSS_DEF_WLAN_RX_BUFF_COUNT;
1784 }
1785
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001786 /* make sure we are only triggered once */
1787 if (penv->triggered)
1788 return 0;
1789 penv->triggered = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001790
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001791 /* initialize the WCNSS device configuration */
1792 pdata = pdev->dev.platform_data;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001793 if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) {
1794 if (has_pronto_hw) {
1795 has_48mhz_xo = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001796 "qcom,has-48mhz-xo");
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001797 } else {
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001798 has_48mhz_xo = pdata->has_48mhz_xo;
1799 }
1800 }
Sameer Thalappilf00dbeb2013-03-01 18:33:30 -08001801 penv->wcnss_hw_type = (has_pronto_hw) ? WCNSS_PRONTO_HW : WCNSS_RIVA_HW;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001802 penv->wlan_config.use_48mhz_xo = has_48mhz_xo;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001803
Sameer Thalappil58cec312013-05-13 15:52:08 -07001804 if (WCNSS_CONFIG_UNSPECIFIED == has_autodetect_xo && has_pronto_hw) {
1805 has_autodetect_xo = of_property_read_bool(pdev->dev.of_node,
Sameer Thalappil820f87b2013-05-21 20:40:54 -07001806 "qcom,has-autodetect-xo");
Sameer Thalappil58cec312013-05-13 15:52:08 -07001807 }
1808
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001809 penv->thermal_mitigation = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001810 strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN);
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001811
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001812 /* Configure 5 wire GPIOs */
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001813 if (!has_pronto_hw) {
1814 penv->gpios_5wire = platform_get_resource_byname(pdev,
1815 IORESOURCE_IO, "wcnss_gpios_5wire");
1816
1817 /* allocate 5-wire GPIO resources */
1818 if (!penv->gpios_5wire) {
1819 dev_err(&pdev->dev, "insufficient IO resources\n");
1820 ret = -ENOENT;
1821 goto fail_gpio_res;
1822 }
1823 ret = wcnss_gpios_config(penv->gpios_5wire, true);
1824 } else
1825 ret = wcnss_pronto_gpios_config(&pdev->dev, true);
1826
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001827 if (ret) {
1828 dev_err(&pdev->dev, "WCNSS gpios config failed.\n");
1829 goto fail_gpio_res;
1830 }
1831
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001832 /* power up the WCNSS */
1833 ret = wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
Sameer Thalappil1d69b8022013-06-10 19:10:07 -07001834 WCNSS_WLAN_SWITCH_ON,
1835 &penv->iris_xo_mode_set);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001836 if (ret) {
1837 dev_err(&pdev->dev, "WCNSS Power-up failed.\n");
1838 goto fail_power;
1839 }
1840
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001841 /* allocate resources */
1842 penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1843 "wcnss_mmio");
1844 penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1845 "wcnss_wlantx_irq");
1846 penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1847 "wcnss_wlanrx_irq");
1848
1849 if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) {
1850 dev_err(&pdev->dev, "insufficient resources\n");
1851 ret = -ENOENT;
1852 goto fail_res;
1853 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001854 INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
1855 INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001856 INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_main);
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001857
Sameer Thalappilf138da52012-07-30 12:56:37 -07001858 wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");
1859
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001860 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
1861 size = 0x3000;
1862 wcnss_phys_addr = MSM_PRONTO_PHYS;
1863 } else {
1864 wcnss_phys_addr = MSM_RIVA_PHYS;
1865 size = SZ_256;
1866 }
1867
1868 penv->msm_wcnss_base = ioremap(wcnss_phys_addr, size);
1869 if (!penv->msm_wcnss_base) {
1870 ret = -ENOMEM;
1871 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001872 goto fail_ioremap;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001873 }
1874
Sameer Thalappild3aad702013-01-22 18:52:24 -08001875 if (wcnss_hardware_type() == WCNSS_RIVA_HW) {
1876 penv->riva_ccu_base = ioremap(MSM_RIVA_CCU_BASE, SZ_512);
1877 if (!penv->riva_ccu_base) {
1878 ret = -ENOMEM;
1879 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001880 goto fail_ioremap2;
Sameer Thalappild3aad702013-01-22 18:52:24 -08001881 }
1882 } else {
1883 penv->pronto_a2xb_base = ioremap(MSM_PRONTO_A2XB_BASE, SZ_512);
1884 if (!penv->pronto_a2xb_base) {
1885 ret = -ENOMEM;
1886 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001887 goto fail_ioremap2;
Sameer Thalappild3aad702013-01-22 18:52:24 -08001888 }
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001889 penv->pronto_ccpu_base = ioremap(MSM_PRONTO_CCPU_BASE, SZ_512);
1890 if (!penv->pronto_ccpu_base) {
1891 ret = -ENOMEM;
1892 pr_err("%s: ioremap wcnss physical failed\n", __func__);
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001893 goto fail_ioremap3;
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001894 }
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001895 /* for reset FIQ */
1896 res = platform_get_resource_byname(penv->pdev,
1897 IORESOURCE_MEM, "wcnss_fiq");
1898 if (!res) {
1899 dev_err(&pdev->dev, "insufficient irq mem resources\n");
1900 ret = -ENOENT;
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001901 goto fail_ioremap4;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001902 }
1903 penv->fiq_reg = ioremap_nocache(res->start, resource_size(res));
1904 if (!penv->fiq_reg) {
1905 pr_err("wcnss: %s: ioremap_nocache() failed fiq_reg addr:%pr\n",
1906 __func__, &res->start);
1907 ret = -ENOMEM;
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001908 goto fail_ioremap4;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001909 }
Sameer Thalappil767ff492013-06-19 15:25:21 -07001910 penv->pronto_saw2_base = ioremap_nocache(MSM_PRONTO_SAW2_BASE,
1911 SZ_32);
1912 if (!penv->pronto_saw2_base) {
1913 pr_err("%s: ioremap wcnss physical(saw2) failed\n",
1914 __func__);
1915 ret = -ENOMEM;
1916 goto fail_ioremap5;
1917 }
Sameer Thalappil66d2b392013-07-02 11:32:55 -07001918 penv->pronto_pll_base = ioremap_nocache(MSM_PRONTO_PLL_BASE,
1919 SZ_64);
1920 if (!penv->pronto_pll_base) {
1921 pr_err("%s: ioremap wcnss physical(pll) failed\n",
1922 __func__);
1923 ret = -ENOMEM;
1924 goto fail_ioremap6;
1925 }
1926
Sameer Thalappil670197c2013-07-19 16:31:14 -07001927 penv->wlan_tx_phy_aborts = ioremap(MSM_PRONTO_TXP_PHY_ABORT,
1928 SZ_8);
1929 if (!penv->wlan_tx_phy_aborts) {
1930 ret = -ENOMEM;
1931 pr_err("%s: ioremap wlan TX PHY failed\n", __func__);
1932 goto fail_ioremap7;
1933 }
1934 penv->wlan_brdg_err_source = ioremap(MSM_PRONTO_BRDG_ERR_SRC,
1935 SZ_8);
1936 if (!penv->wlan_brdg_err_source) {
1937 ret = -ENOMEM;
1938 pr_err("%s: ioremap wlan BRDG ERR failed\n", __func__);
1939 goto fail_ioremap8;
1940 }
1941
Sameer Thalappild3aad702013-01-22 18:52:24 -08001942 }
Sameer Thalappilf6fb1892013-09-06 17:33:30 -07001943 penv->adc_tm_dev = qpnp_get_adc_tm(&penv->pdev->dev, "wcnss");
1944 if (IS_ERR(penv->adc_tm_dev)) {
1945 pr_err("%s: adc get failed\n", __func__);
1946 penv->adc_tm_dev = NULL;
1947 } else {
1948 INIT_DELAYED_WORK(&penv->vbatt_work, wcnss_update_vbatt);
1949 penv->fw_vbatt_state = WCNSS_CONFIG_UNSPECIFIED;
1950 }
Sameer Thalappild3aad702013-01-22 18:52:24 -08001951
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001952 /* trigger initialization of the WCNSS */
1953 penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
1954 if (IS_ERR(penv->pil)) {
1955 dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n");
1956 ret = PTR_ERR(penv->pil);
Sameer Thalappilc78facf2013-07-30 12:07:31 -07001957 wcnss_pronto_log_debug_regs();
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001958 penv->pil = NULL;
1959 goto fail_pil;
1960 }
1961
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001962 return 0;
1963
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001964fail_pil:
1965 if (penv->riva_ccu_base)
1966 iounmap(penv->riva_ccu_base);
Sameer Thalappil670197c2013-07-19 16:31:14 -07001967 if (penv->wlan_brdg_err_source)
1968 iounmap(penv->wlan_brdg_err_source);
1969fail_ioremap8:
1970 if (penv->wlan_tx_phy_aborts)
1971 iounmap(penv->wlan_tx_phy_aborts);
1972fail_ioremap7:
Sameer Thalappil66d2b392013-07-02 11:32:55 -07001973 if (penv->pronto_pll_base)
1974 iounmap(penv->pronto_pll_base);
1975fail_ioremap6:
Sameer Thalappil767ff492013-06-19 15:25:21 -07001976 if (penv->pronto_saw2_base)
1977 iounmap(penv->pronto_saw2_base);
1978fail_ioremap5:
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001979 if (penv->fiq_reg)
1980 iounmap(penv->fiq_reg);
1981fail_ioremap4:
1982 if (penv->pronto_ccpu_base)
1983 iounmap(penv->pronto_ccpu_base);
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001984fail_ioremap3:
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001985 if (penv->pronto_a2xb_base)
1986 iounmap(penv->pronto_a2xb_base);
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001987fail_ioremap2:
Sameer Thalappil0ab82132013-06-10 10:10:05 -07001988 if (penv->msm_wcnss_base)
1989 iounmap(penv->msm_wcnss_base);
Sameer Thalappild3aad702013-01-22 18:52:24 -08001990fail_ioremap:
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001991 wake_lock_destroy(&penv->wcnss_wake_lock);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001992fail_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001993 wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
Sameer Thalappil1d69b8022013-06-10 19:10:07 -07001994 WCNSS_WLAN_SWITCH_OFF, NULL);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001995fail_power:
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001996 if (has_pronto_hw)
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001997 wcnss_pronto_gpios_config(&pdev->dev, false);
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001998 else
1999 wcnss_gpios_config(penv->gpios_5wire, false);
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07002000fail_gpio_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002001 penv = NULL;
2002 return ret;
2003}
2004
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002005static int wcnss_node_open(struct inode *inode, struct file *file)
2006{
2007 struct platform_device *pdev;
2008
Sameer Thalappil37c20682013-05-31 14:01:25 -07002009 if (!penv)
2010 return -EFAULT;
2011
Sameer Thalappil1878d762013-04-24 14:54:39 -07002012 /* first open is only to trigger WCNSS platform driver */
2013 if (!penv->triggered) {
2014 pr_info(DEVICE " triggered by userspace\n");
2015 pdev = penv->pdev;
2016 return wcnss_trigger_config(pdev);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002017
Sameer Thalappil1878d762013-04-24 14:54:39 -07002018 } else if (penv->device_opened) {
2019 pr_info(DEVICE " already opened\n");
2020 return -EBUSY;
2021 }
2022
2023 mutex_lock(&penv->dev_lock);
2024 penv->user_cal_rcvd = 0;
2025 penv->user_cal_read = 0;
2026 penv->user_cal_available = false;
2027 penv->user_cal_data = NULL;
2028 penv->device_opened = 1;
2029 mutex_unlock(&penv->dev_lock);
2030
2031 return 0;
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002032}
2033
Sameer Thalappil1878d762013-04-24 14:54:39 -07002034static ssize_t wcnss_wlan_read(struct file *fp, char __user
2035 *buffer, size_t count, loff_t *position)
2036{
2037 int rc = 0;
2038
Sameer Thalappil37c20682013-05-31 14:01:25 -07002039 if (!penv || !penv->device_opened)
Sameer Thalappil1878d762013-04-24 14:54:39 -07002040 return -EFAULT;
2041
2042 rc = wait_event_interruptible(penv->read_wait, penv->fw_cal_rcvd
2043 > penv->user_cal_read || penv->fw_cal_available);
2044
2045 if (rc < 0)
2046 return rc;
2047
2048 mutex_lock(&penv->dev_lock);
2049
2050 if (penv->fw_cal_available && penv->fw_cal_rcvd
2051 == penv->user_cal_read) {
2052 rc = 0;
2053 goto exit;
2054 }
2055
2056 if (count > penv->fw_cal_rcvd - penv->user_cal_read)
2057 count = penv->fw_cal_rcvd - penv->user_cal_read;
2058
2059 rc = copy_to_user(buffer, penv->fw_cal_data +
2060 penv->user_cal_read, count);
2061 if (rc == 0) {
2062 penv->user_cal_read += count;
2063 rc = count;
2064 }
2065
2066exit:
2067 mutex_unlock(&penv->dev_lock);
2068 return rc;
2069}
2070
2071/* first (valid) write to this device should be 4 bytes cal file size */
2072static ssize_t wcnss_wlan_write(struct file *fp, const char __user
2073 *user_buffer, size_t count, loff_t *position)
2074{
2075 int rc = 0;
2076 int size = 0;
2077
Sameer Thalappil37c20682013-05-31 14:01:25 -07002078 if (!penv || !penv->device_opened || penv->user_cal_available)
Sameer Thalappil1878d762013-04-24 14:54:39 -07002079 return -EFAULT;
2080
2081 if (penv->user_cal_rcvd == 0 && count >= 4
2082 && !penv->user_cal_data) {
2083 rc = copy_from_user((void *)&size, user_buffer, 4);
2084 if (size > MAX_CALIBRATED_DATA_SIZE) {
2085 pr_err(DEVICE " invalid size to write %d\n", size);
2086 return -EFAULT;
2087 }
2088
2089 rc += count;
2090 count -= 4;
2091 penv->user_cal_exp_size = size;
2092 penv->user_cal_data = kmalloc(size, GFP_KERNEL);
2093 if (penv->user_cal_data == NULL) {
2094 pr_err(DEVICE " no memory to write\n");
2095 return -ENOMEM;
2096 }
2097 if (0 == count)
2098 goto exit;
2099
2100 } else if (penv->user_cal_rcvd == 0 && count < 4)
2101 return -EFAULT;
2102
2103 if (MAX_CALIBRATED_DATA_SIZE < count + penv->user_cal_rcvd) {
2104 pr_err(DEVICE " invalid size to write %d\n", count +
2105 penv->user_cal_rcvd);
2106 rc = -ENOMEM;
2107 goto exit;
2108 }
2109 rc = copy_from_user((void *)penv->user_cal_data +
2110 penv->user_cal_rcvd, user_buffer, count);
2111 if (0 == rc) {
2112 penv->user_cal_rcvd += count;
2113 rc += count;
2114 }
2115 if (penv->user_cal_rcvd == penv->user_cal_exp_size) {
2116 penv->user_cal_available = true;
2117 pr_info_ratelimited("wcnss: user cal written");
2118 }
2119
2120exit:
2121 return rc;
2122}
2123
2124
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002125static const struct file_operations wcnss_node_fops = {
2126 .owner = THIS_MODULE,
2127 .open = wcnss_node_open,
Sameer Thalappil1878d762013-04-24 14:54:39 -07002128 .read = wcnss_wlan_read,
2129 .write = wcnss_wlan_write,
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002130};
2131
2132static struct miscdevice wcnss_misc = {
2133 .minor = MISC_DYNAMIC_MINOR,
2134 .name = DEVICE,
2135 .fops = &wcnss_node_fops,
2136};
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002137
2138static int __devinit
2139wcnss_wlan_probe(struct platform_device *pdev)
2140{
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002141 int ret = 0;
2142
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002143 /* verify we haven't been called more than once */
2144 if (penv) {
2145 dev_err(&pdev->dev, "cannot handle multiple devices.\n");
2146 return -ENODEV;
2147 }
2148
2149 /* create an environment to track the device */
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002150 penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002151 if (!penv) {
2152 dev_err(&pdev->dev, "cannot allocate device memory.\n");
2153 return -ENOMEM;
2154 }
2155 penv->pdev = pdev;
2156
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002157 /* register sysfs entries */
2158 ret = wcnss_create_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002159 if (ret) {
2160 penv = NULL;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002161 return -ENOENT;
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002162 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002163
Sameer Thalappil1878d762013-04-24 14:54:39 -07002164 mutex_init(&penv->dev_lock);
Sameer Thalappilf6fb1892013-09-06 17:33:30 -07002165 mutex_init(&penv->vbat_monitor_mutex);
Sameer Thalappil1878d762013-04-24 14:54:39 -07002166 init_waitqueue_head(&penv->read_wait);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002167
Sameer Thalappild3aad702013-01-22 18:52:24 -08002168 /* Since we were built into the kernel we'll be called as part
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002169 * of kernel initialization. We don't know if userspace
2170 * applications are available to service PIL at this time
2171 * (they probably are not), so we simply create a device node
2172 * here. When userspace is available it should touch the
2173 * device so that we know that WCNSS configuration can take
2174 * place
2175 */
2176 pr_info(DEVICE " probed in built-in mode\n");
2177 return misc_register(&wcnss_misc);
2178
Jeff Johnsonb3377e32011-11-18 23:32:27 -08002179}
2180
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002181static int __devexit
2182wcnss_wlan_remove(struct platform_device *pdev)
2183{
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08002184 wcnss_remove_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08002185 penv = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002186 return 0;
2187}
2188
2189
2190static const struct dev_pm_ops wcnss_wlan_pm_ops = {
2191 .suspend = wcnss_wlan_suspend,
2192 .resume = wcnss_wlan_resume,
2193};
2194
Sameer Thalappil8d686d42012-08-24 10:07:31 -07002195#ifdef CONFIG_WCNSS_CORE_PRONTO
2196static struct of_device_id msm_wcnss_pronto_match[] = {
2197 {.compatible = "qcom,wcnss_wlan"},
2198 {}
2199};
2200#endif
2201
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002202static struct platform_driver wcnss_wlan_driver = {
2203 .driver = {
2204 .name = DEVICE,
2205 .owner = THIS_MODULE,
2206 .pm = &wcnss_wlan_pm_ops,
Sameer Thalappil8d686d42012-08-24 10:07:31 -07002207#ifdef CONFIG_WCNSS_CORE_PRONTO
2208 .of_match_table = msm_wcnss_pronto_match,
2209#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002210 },
2211 .probe = wcnss_wlan_probe,
2212 .remove = __devexit_p(wcnss_wlan_remove),
2213};
2214
2215static int __init wcnss_wlan_init(void)
2216{
Sameer Thalappil24db5282012-09-10 11:58:33 -07002217 int ret = 0;
2218
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002219 platform_driver_register(&wcnss_wlan_driver);
2220 platform_driver_register(&wcnss_wlan_ctrl_driver);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07002221 platform_driver_register(&wcnss_ctrl_driver);
Sameer Thalappil944c72d2013-08-14 17:56:17 -07002222 register_pm_notifier(&wcnss_pm_notifier);
Sameer Thalappil24db5282012-09-10 11:58:33 -07002223#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
2224 ret = wcnss_prealloc_init();
2225 if (ret < 0)
2226 pr_err("wcnss: pre-allocation failed\n");
2227#endif
2228
2229 return ret;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002230}
2231
2232static void __exit wcnss_wlan_exit(void)
2233{
2234 if (penv) {
2235 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07002236 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002237 penv = NULL;
2238 }
2239
Sameer Thalappil24db5282012-09-10 11:58:33 -07002240#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
2241 wcnss_prealloc_deinit();
2242#endif
Sameer Thalappil944c72d2013-08-14 17:56:17 -07002243 unregister_pm_notifier(&wcnss_pm_notifier);
2244 platform_driver_unregister(&wcnss_ctrl_driver);
2245 platform_driver_unregister(&wcnss_wlan_ctrl_driver);
2246 platform_driver_unregister(&wcnss_wlan_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002247}
2248
2249module_init(wcnss_wlan_init);
2250module_exit(wcnss_wlan_exit);
2251
2252MODULE_LICENSE("GPL v2");
2253MODULE_VERSION(VERSION);
2254MODULE_DESCRIPTION(DEVICE "Driver");