blob: a75687b255b1ed88642324eb08652a54c7fa8699 [file] [log] [blame]
Sameer Thalappilf106a682013-02-16 20:41:11 -08001/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#include <linux/module.h>
Sameer Thalappilf106a682013-02-16 20:41:11 -080014#include <linux/firmware.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070015#include <linux/slab.h>
16#include <linux/err.h>
17#include <linux/platform_device.h>
Jeff Johnsonb3377e32011-11-18 23:32:27 -080018#include <linux/miscdevice.h>
19#include <linux/fs.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070020#include <linux/wcnss_wlan.h>
Ankur Nandwanib0039b02011-08-09 14:00:45 -070021#include <linux/platform_data/qcom_wcnss_device.h>
David Collinsb727f7d2011-09-12 11:54:49 -070022#include <linux/workqueue.h>
23#include <linux/jiffies.h>
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -070024#include <linux/gpio.h>
Sameer Thalappilf138da52012-07-30 12:56:37 -070025#include <linux/wakelock.h>
Sameer Thalappileeb8c412012-08-10 17:17:09 -070026#include <linux/delay.h>
Sameer Thalappil8d686d42012-08-24 10:07:31 -070027#include <linux/of.h>
28#include <linux/of_gpio.h>
Sameer Thalappiled3f7da2012-11-01 12:54:01 -070029#include <linux/clk.h>
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080030#include <linux/ratelimit.h>
Sameer Thalappil1878d762013-04-24 14:54:39 -070031#include <linux/kthread.h>
32#include <linux/wait.h>
33#include <linux/uaccess.h>
Naresh Jayaram08cee442013-04-26 19:50:00 +053034#include <linux/mfd/pm8xxx/misc.h>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070035
Sameer Thalappileeb8c412012-08-10 17:17:09 -070036#include <mach/msm_smd.h>
Sameer Thalappil19e02352012-09-24 15:26:12 -070037#include <mach/msm_iomap.h>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070038#include <mach/subsystem_restart.h>
39
Sameer Thalappil24db5282012-09-10 11:58:33 -070040#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
41#include "wcnss_prealloc.h"
42#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070043
44#define DEVICE "wcnss_wlan"
45#define VERSION "1.01"
46#define WCNSS_PIL_DEVICE "wcnss"
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070047
Ankur Nandwanib0039b02011-08-09 14:00:45 -070048/* module params */
49#define WCNSS_CONFIG_UNSPECIFIED (-1)
Sameer Thalappileeb8c412012-08-10 17:17:09 -070050
Ankur Nandwania4510492011-08-29 15:56:23 -070051static int has_48mhz_xo = WCNSS_CONFIG_UNSPECIFIED;
Jeff Johnsonb3377e32011-11-18 23:32:27 -080052module_param(has_48mhz_xo, int, S_IWUSR | S_IRUGO);
Ankur Nandwanib0039b02011-08-09 14:00:45 -070053MODULE_PARM_DESC(has_48mhz_xo, "Is an external 48 MHz XO present");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070054
Sameer Thalappil1878d762013-04-24 14:54:39 -070055static int has_calibrated_data = WCNSS_CONFIG_UNSPECIFIED;
56module_param(has_calibrated_data, int, S_IWUSR | S_IRUGO);
57MODULE_PARM_DESC(has_calibrated_data, "whether calibrated data file available");
58
Sameer Thalappil58cec312013-05-13 15:52:08 -070059static int has_autodetect_xo = WCNSS_CONFIG_UNSPECIFIED;
60module_param(has_autodetect_xo, int, S_IWUSR | S_IRUGO);
61MODULE_PARM_DESC(has_autodetect_xo, "Perform auto detect to configure IRIS XO");
62
Sheng Fangbb421672013-03-19 08:27:28 +080063static int do_not_cancel_vote = WCNSS_CONFIG_UNSPECIFIED;
64module_param(do_not_cancel_vote, int, S_IWUSR | S_IRUGO);
65MODULE_PARM_DESC(do_not_cancel_vote, "Do not cancel votes for wcnss");
66
Sameer Thalappiled3f7da2012-11-01 12:54:01 -070067static DEFINE_SPINLOCK(reg_spinlock);
68
69#define MSM_RIVA_PHYS 0x03204000
70#define MSM_PRONTO_PHYS 0xfb21b000
71
72#define RIVA_SPARE_OFFSET 0x0b4
73#define RIVA_SUSPEND_BIT BIT(24)
74
Sameer Thalappil77716e52012-11-08 13:41:04 -080075#define MSM_RIVA_CCU_BASE 0x03200800
76
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080077#define CCU_RIVA_INVALID_ADDR_OFFSET 0x100
78#define CCU_RIVA_LAST_ADDR0_OFFSET 0x104
79#define CCU_RIVA_LAST_ADDR1_OFFSET 0x108
80#define CCU_RIVA_LAST_ADDR2_OFFSET 0x10c
Sameer Thalappil77716e52012-11-08 13:41:04 -080081
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080082#define MSM_PRONTO_A2XB_BASE 0xfb100400
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080083#define A2XB_CFG_OFFSET 0x00
84#define A2XB_INT_SRC_OFFSET 0x0c
85#define A2XB_TSTBUS_CTRL_OFFSET 0x14
86#define A2XB_TSTBUS_OFFSET 0x18
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080087#define A2XB_ERR_INFO_OFFSET 0x1c
88
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080089#define WCNSS_TSTBUS_CTRL_EN BIT(0)
90#define WCNSS_TSTBUS_CTRL_AXIM (0x02 << 1)
91#define WCNSS_TSTBUS_CTRL_CMDFIFO (0x03 << 1)
92#define WCNSS_TSTBUS_CTRL_WRFIFO (0x04 << 1)
93#define WCNSS_TSTBUS_CTRL_RDFIFO (0x05 << 1)
94#define WCNSS_TSTBUS_CTRL_CTRL (0x07 << 1)
95#define WCNSS_TSTBUS_CTRL_AXIM_CFG0 (0x00 << 6)
96#define WCNSS_TSTBUS_CTRL_AXIM_CFG1 (0x01 << 6)
97#define WCNSS_TSTBUS_CTRL_CTRL_CFG0 (0x00 << 12)
98#define WCNSS_TSTBUS_CTRL_CTRL_CFG1 (0x01 << 12)
99
100#define MSM_PRONTO_CCPU_BASE 0xfb205050
101#define CCU_PRONTO_INVALID_ADDR_OFFSET 0x08
102#define CCU_PRONTO_LAST_ADDR0_OFFSET 0x0c
103#define CCU_PRONTO_LAST_ADDR1_OFFSET 0x10
104#define CCU_PRONTO_LAST_ADDR2_OFFSET 0x14
105
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700106#define WCNSS_CTRL_CHANNEL "WCNSS_CTRL"
Sameer Thalappil1878d762013-04-24 14:54:39 -0700107#define WCNSS_MAX_FRAME_SIZE (4*1024)
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700108#define WCNSS_VERSION_LEN 30
109
110/* message types */
111#define WCNSS_CTRL_MSG_START 0x01000000
Sameer Thalappil1878d762013-04-24 14:54:39 -0700112#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0)
113#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1)
114#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2)
115#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3)
116#define WCNSS_CALDATA_UPLD_REQ (WCNSS_CTRL_MSG_START + 4)
117#define WCNSS_CALDATA_UPLD_RSP (WCNSS_CTRL_MSG_START + 5)
118#define WCNSS_CALDATA_DNLD_REQ (WCNSS_CTRL_MSG_START + 6)
119#define WCNSS_CALDATA_DNLD_RSP (WCNSS_CTRL_MSG_START + 7)
Sameer Thalappilf106a682013-02-16 20:41:11 -0800120
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700121
122#define VALID_VERSION(version) \
123 ((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0)
124
Sameer Thalappil1878d762013-04-24 14:54:39 -0700125#define FW_CALDATA_CAPABLE() \
126 ((penv->fw_major >= 1) && (penv->fw_minor >= 5) ? 1 : 0)
127
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700128struct smd_msg_hdr {
Sameer Thalappilf106a682013-02-16 20:41:11 -0800129 unsigned int msg_type;
130 unsigned int msg_len;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700131};
132
133struct wcnss_version {
134 struct smd_msg_hdr hdr;
135 unsigned char major;
136 unsigned char minor;
137 unsigned char version;
138 unsigned char revision;
139};
140
Naresh Jayaram08cee442013-04-26 19:50:00 +0530141struct wcnss_pmic_dump {
142 char reg_name[10];
143 u16 reg_addr;
144};
145
146static struct wcnss_pmic_dump wcnss_pmic_reg_dump[] = {
147 {"S2", 0x1D8},
148 {"L4", 0xB4},
149 {"L10", 0xC0},
150 {"LVS2", 0x62},
151 {"S4", 0x1E8},
152 {"LVS7", 0x06C},
153 {"LVS1", 0x060},
154};
155
Sameer Thalappilf106a682013-02-16 20:41:11 -0800156#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
157
158/*
159 * On SMD channel 4K of maximum data can be transferred, including message
160 * header, so NV fragment size as next multiple of 1Kb is 3Kb.
161 */
162#define NV_FRAGMENT_SIZE 3072
Sameer Thalappil1878d762013-04-24 14:54:39 -0700163#define MAX_CALIBRATED_DATA_SIZE (64*1024)
164#define LAST_FRAGMENT (1 << 0)
165#define MESSAGE_TO_FOLLOW (1 << 1)
166#define CAN_RECEIVE_CALDATA (1 << 15)
167#define WCNSS_RESP_SUCCESS 1
168#define WCNSS_RESP_FAIL 0
169
Sameer Thalappilf106a682013-02-16 20:41:11 -0800170
171/* Macro to find the total number fragments of the NV bin Image */
172#define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \
173 (x / NV_FRAGMENT_SIZE) : ((x / NV_FRAGMENT_SIZE) + 1))
174
175struct nvbin_dnld_req_params {
176 /*
177 * Fragment sequence number of the NV bin Image. NV Bin Image
178 * might not fit into one message due to size limitation of
179 * the SMD channel FIFO so entire NV blob is chopped into
180 * multiple fragments starting with seqeunce number 0. The
181 * last fragment is indicated by marking is_last_fragment field
182 * to 1. At receiving side, NV blobs would be concatenated
183 * together without any padding bytes in between.
184 */
185 unsigned short frag_number;
186
187 /*
Sameer Thalappil1878d762013-04-24 14:54:39 -0700188 * bit 0: When set to 1 it indicates that no more fragments will
189 * be sent.
190 * bit 1: When set, a new message will be followed by this message
191 * bit 2- bit 14: Reserved
192 * bit 15: when set, it indicates that the sender is capable of
193 * receiving Calibrated data.
Sameer Thalappilf106a682013-02-16 20:41:11 -0800194 */
Sameer Thalappil1878d762013-04-24 14:54:39 -0700195 unsigned short msg_flags;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800196
197 /* NV Image size (number of bytes) */
198 unsigned int nvbin_buffer_size;
199
200 /*
201 * Following the 'nvbin_buffer_size', there should be
202 * nvbin_buffer_size bytes of NV bin Image i.e.
203 * uint8[nvbin_buffer_size].
204 */
205};
206
Sameer Thalappil1878d762013-04-24 14:54:39 -0700207
Sameer Thalappilf106a682013-02-16 20:41:11 -0800208struct nvbin_dnld_req_msg {
209 /*
210 * Note: The length specified in nvbin_dnld_req_msg messages
211 * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) +
212 * nvbin_buffer_size.
213 */
214 struct smd_msg_hdr hdr;
215 struct nvbin_dnld_req_params dnld_req_params;
216};
217
Sameer Thalappil1878d762013-04-24 14:54:39 -0700218struct cal_data_params {
219
220 /* The total size of the calibrated data, including all the
221 * fragments.
222 */
223 unsigned int total_size;
224 unsigned short frag_number;
225 /*
226 * bit 0: When set to 1 it indicates that no more fragments will
227 * be sent.
228 * bit 1: When set, a new message will be followed by this message
229 * bit 2- bit 15: Reserved
230 */
231 unsigned short msg_flags;
232 /*
233 * fragment size
234 */
235 unsigned int frag_size;
236 /*
237 * Following the frag_size, frag_size of fragmented
238 * data will be followed.
239 */
240};
241
242struct cal_data_msg {
243 /*
244 * The length specified in cal_data_msg should be
245 * hdr.msg_len = sizeof(cal_data_msg) + frag_size
246 */
247 struct smd_msg_hdr hdr;
248 struct cal_data_params cal_params;
249};
250
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700251static struct {
252 struct platform_device *pdev;
253 void *pil;
254 struct resource *mmio_res;
255 struct resource *tx_irq_res;
256 struct resource *rx_irq_res;
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700257 struct resource *gpios_5wire;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700258 const struct dev_pm_ops *pm_ops;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800259 int triggered;
260 int smd_channel_ready;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700261 smd_channel_t *smd_ch;
262 unsigned char wcnss_version[WCNSS_VERSION_LEN];
Sameer Thalappil1878d762013-04-24 14:54:39 -0700263 unsigned char fw_major;
264 unsigned char fw_minor;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800265 unsigned int serial_number;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800266 int thermal_mitigation;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700267 enum wcnss_hw_type wcnss_hw_type;
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700268 void (*tm_notify)(struct device *, int);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700269 struct wcnss_wlan_config wlan_config;
David Collinsb727f7d2011-09-12 11:54:49 -0700270 struct delayed_work wcnss_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700271 struct work_struct wcnssctrl_version_work;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800272 struct work_struct wcnssctrl_nvbin_dnld_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700273 struct work_struct wcnssctrl_rx_work;
Sameer Thalappilf138da52012-07-30 12:56:37 -0700274 struct wake_lock wcnss_wake_lock;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700275 void __iomem *msm_wcnss_base;
Sameer Thalappild3aad702013-01-22 18:52:24 -0800276 void __iomem *riva_ccu_base;
277 void __iomem *pronto_a2xb_base;
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800278 void __iomem *pronto_ccpu_base;
Sameer Thalappil58281ca2013-04-10 18:50:18 -0700279 void __iomem *fiq_reg;
Sameer Thalappil1878d762013-04-24 14:54:39 -0700280 int ssr_boot;
281 int nv_downloaded;
282 unsigned char *fw_cal_data;
283 unsigned char *user_cal_data;
284 int fw_cal_rcvd;
285 int fw_cal_exp_frag;
286 int fw_cal_available;
287 int user_cal_read;
288 int user_cal_available;
289 int user_cal_rcvd;
290 int user_cal_exp_size;
291 int device_opened;
292 struct mutex dev_lock;
293 wait_queue_head_t read_wait;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700294} *penv = NULL;
295
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800296static ssize_t wcnss_serial_number_show(struct device *dev,
297 struct device_attribute *attr, char *buf)
298{
299 if (!penv)
300 return -ENODEV;
301
302 return scnprintf(buf, PAGE_SIZE, "%08X\n", penv->serial_number);
303}
304
305static ssize_t wcnss_serial_number_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700306 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800307{
308 unsigned int value;
309
310 if (!penv)
311 return -ENODEV;
312
313 if (sscanf(buf, "%08X", &value) != 1)
314 return -EINVAL;
315
316 penv->serial_number = value;
317 return count;
318}
319
320static DEVICE_ATTR(serial_number, S_IRUSR | S_IWUSR,
321 wcnss_serial_number_show, wcnss_serial_number_store);
322
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800323
324static ssize_t wcnss_thermal_mitigation_show(struct device *dev,
325 struct device_attribute *attr, char *buf)
326{
327 if (!penv)
328 return -ENODEV;
329
330 return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation);
331}
332
333static ssize_t wcnss_thermal_mitigation_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700334 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800335{
336 int value;
337
338 if (!penv)
339 return -ENODEV;
340
341 if (sscanf(buf, "%d", &value) != 1)
342 return -EINVAL;
343 penv->thermal_mitigation = value;
Jeff Johnson61374652012-04-16 20:51:48 -0700344 if (penv->tm_notify)
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700345 (penv->tm_notify)(dev, value);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800346 return count;
347}
348
349static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR,
350 wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store);
351
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700352
353static ssize_t wcnss_version_show(struct device *dev,
354 struct device_attribute *attr, char *buf)
355{
356 if (!penv)
357 return -ENODEV;
358
359 return scnprintf(buf, PAGE_SIZE, "%s", penv->wcnss_version);
360}
361
362static DEVICE_ATTR(wcnss_version, S_IRUSR,
363 wcnss_version_show, NULL);
364
Naresh Jayaram08cee442013-04-26 19:50:00 +0530365void wcnss_riva_dump_pmic_regs(void)
366{
367 int i, rc;
368 u8 val;
369
370 for (i = 0; i < ARRAY_SIZE(wcnss_pmic_reg_dump); i++) {
371 val = 0;
372 rc = pm8xxx_read_register(wcnss_pmic_reg_dump[i].reg_addr,
373 &val);
374 if (rc)
375 pr_err("PMIC READ: Failed to read addr = %d\n",
376 wcnss_pmic_reg_dump[i].reg_addr);
377 else
378 pr_info_ratelimited("PMIC READ: %s addr = %x, value = %x\n",
379 wcnss_pmic_reg_dump[i].reg_name,
380 wcnss_pmic_reg_dump[i].reg_addr, val);
381 }
382}
383
Sameer Thalappil77716e52012-11-08 13:41:04 -0800384/* wcnss_reset_intr() is invoked when host drivers fails to
385 * communicate with WCNSS over SMD; so logging these registers
Sameer Thalappild3aad702013-01-22 18:52:24 -0800386 * helps to know WCNSS failure reason
387 */
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800388void wcnss_riva_log_debug_regs(void)
Sameer Thalappil77716e52012-11-08 13:41:04 -0800389{
Sameer Thalappil77716e52012-11-08 13:41:04 -0800390 void __iomem *ccu_reg;
391 u32 reg = 0;
392
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800393 ccu_reg = penv->riva_ccu_base + CCU_RIVA_INVALID_ADDR_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800394 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800395 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800396
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800397 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR0_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800398 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800399 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800400
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800401 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR1_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800402 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800403 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800404
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800405 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR2_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800406 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800407 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
Naresh Jayaram08cee442013-04-26 19:50:00 +0530408 wcnss_riva_dump_pmic_regs();
Sameer Thalappil77716e52012-11-08 13:41:04 -0800409
Sameer Thalappil77716e52012-11-08 13:41:04 -0800410}
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800411EXPORT_SYMBOL(wcnss_riva_log_debug_regs);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800412
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800413/* Log pronto debug registers before sending reset interrupt */
414void wcnss_pronto_log_debug_regs(void)
415{
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800416 void __iomem *reg_addr, *tst_addr, *tst_ctrl_addr;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800417 u32 reg = 0;
418
Sameer Thalappild3aad702013-01-22 18:52:24 -0800419 reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800420 reg = readl_relaxed(reg_addr);
421 pr_info_ratelimited("%s: A2XB_CFG_OFFSET %08x\n", __func__, reg);
422
Sameer Thalappild3aad702013-01-22 18:52:24 -0800423 reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800424 reg = readl_relaxed(reg_addr);
425 pr_info_ratelimited("%s: A2XB_INT_SRC_OFFSET %08x\n", __func__, reg);
426
Sameer Thalappild3aad702013-01-22 18:52:24 -0800427 reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800428 reg = readl_relaxed(reg_addr);
429 pr_info_ratelimited("%s: A2XB_ERR_INFO_OFFSET %08x\n", __func__, reg);
430
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800431 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_INVALID_ADDR_OFFSET;
432 reg = readl_relaxed(reg_addr);
433 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
434
435 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR0_OFFSET;
436 reg = readl_relaxed(reg_addr);
437 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
438
439 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR1_OFFSET;
440 reg = readl_relaxed(reg_addr);
441 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
442
443 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR2_OFFSET;
444 reg = readl_relaxed(reg_addr);
445 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
446
447 tst_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_OFFSET;
448 tst_ctrl_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_CTRL_OFFSET;
449
450 /* read data FIFO */
451 reg = 0;
452 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_RDFIFO;
453 writel_relaxed(reg, tst_ctrl_addr);
454 reg = readl_relaxed(tst_addr);
455 pr_info_ratelimited("%s: Read data FIFO testbus %08x\n",
456 __func__, reg);
457
458 /* command FIFO */
459 reg = 0;
460 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CMDFIFO;
461 writel_relaxed(reg, tst_ctrl_addr);
462 reg = readl_relaxed(tst_addr);
463 pr_info_ratelimited("%s: Command FIFO testbus %08x\n",
464 __func__, reg);
465
466 /* write data FIFO */
467 reg = 0;
468 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_WRFIFO;
469 writel_relaxed(reg, tst_ctrl_addr);
470 reg = readl_relaxed(tst_addr);
471 pr_info_ratelimited("%s: Rrite data FIFO testbus %08x\n",
472 __func__, reg);
473
474 /* AXIM SEL CFG0 */
475 reg = 0;
476 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
477 WCNSS_TSTBUS_CTRL_AXIM_CFG0;
478 writel_relaxed(reg, tst_ctrl_addr);
479 reg = readl_relaxed(tst_addr);
480 pr_info_ratelimited("%s: AXIM SEL CFG0 testbus %08x\n",
481 __func__, reg);
482
483 /* AXIM SEL CFG1 */
484 reg = 0;
485 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
486 WCNSS_TSTBUS_CTRL_AXIM_CFG1;
487 writel_relaxed(reg, tst_ctrl_addr);
488 reg = readl_relaxed(tst_addr);
489 pr_info_ratelimited("%s: AXIM SEL CFG1 testbus %08x\n",
490 __func__, reg);
491
492 /* CTRL SEL CFG0 */
493 reg = 0;
494 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
495 WCNSS_TSTBUS_CTRL_CTRL_CFG0;
496 writel_relaxed(reg, tst_ctrl_addr);
497 reg = readl_relaxed(tst_addr);
498 pr_info_ratelimited("%s: CTRL SEL CFG0 testbus %08x\n",
499 __func__, reg);
500
501 /* CTRL SEL CFG1 */
502 reg = 0;
503 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
504 WCNSS_TSTBUS_CTRL_CTRL_CFG1;
505 writel_relaxed(reg, tst_ctrl_addr);
506 reg = readl_relaxed(tst_addr);
507 pr_info_ratelimited("%s: CTRL SEL CFG1 testbus %08x\n", __func__, reg);
508
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800509}
510EXPORT_SYMBOL(wcnss_pronto_log_debug_regs);
511
512/* interface to reset wcnss by sending the reset interrupt */
Sameer Thalappil19e02352012-09-24 15:26:12 -0700513void wcnss_reset_intr(void)
514{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800515 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
516 wcnss_pronto_log_debug_regs();
Sameer Thalappil34920762013-03-21 15:43:28 -0700517 wmb();
Sameer Thalappil58281ca2013-04-10 18:50:18 -0700518 __raw_writel(1 << 16, penv->fiq_reg);
Sameer Thalappil34920762013-03-21 15:43:28 -0700519 } else {
520 wcnss_riva_log_debug_regs();
521 wmb();
522 __raw_writel(1 << 24, MSM_APCS_GCC_BASE + 0x8);
Sameer Thalappil19e02352012-09-24 15:26:12 -0700523 }
524}
525EXPORT_SYMBOL(wcnss_reset_intr);
526
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800527static int wcnss_create_sysfs(struct device *dev)
528{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800529 int ret;
530
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800531 if (!dev)
532 return -ENODEV;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800533
534 ret = device_create_file(dev, &dev_attr_serial_number);
535 if (ret)
536 return ret;
537
538 ret = device_create_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700539 if (ret)
540 goto remove_serial;
541
542 ret = device_create_file(dev, &dev_attr_wcnss_version);
543 if (ret)
544 goto remove_thermal;
545
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800546 return 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700547
548remove_thermal:
549 device_remove_file(dev, &dev_attr_thermal_mitigation);
550remove_serial:
551 device_remove_file(dev, &dev_attr_serial_number);
552
553 return ret;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800554}
555
556static void wcnss_remove_sysfs(struct device *dev)
557{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800558 if (dev) {
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800559 device_remove_file(dev, &dev_attr_serial_number);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800560 device_remove_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700561 device_remove_file(dev, &dev_attr_wcnss_version);
562 }
563}
564static void wcnss_smd_notify_event(void *data, unsigned int event)
565{
566 int len = 0;
567
568 if (penv != data) {
569 pr_err("wcnss: invalid env pointer in smd callback\n");
570 return;
571 }
572 switch (event) {
573 case SMD_EVENT_DATA:
574 len = smd_read_avail(penv->smd_ch);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700575 if (len < 0) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700576 pr_err("wcnss: failed to read from smd %d\n", len);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700577 return;
578 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700579 schedule_work(&penv->wcnssctrl_rx_work);
580 break;
581
582 case SMD_EVENT_OPEN:
583 pr_debug("wcnss: opening WCNSS SMD channel :%s",
584 WCNSS_CTRL_CHANNEL);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700585 schedule_work(&penv->wcnssctrl_version_work);
586
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700587 break;
588
589 case SMD_EVENT_CLOSE:
590 pr_debug("wcnss: closing WCNSS SMD channel :%s",
591 WCNSS_CTRL_CHANNEL);
Sameer Thalappil1878d762013-04-24 14:54:39 -0700592 /* This SMD is closed only during SSR */
593 penv->ssr_boot = true;
594 penv->nv_downloaded = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700595 break;
596
597 default:
598 break;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800599 }
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800600}
601
David Collinsb727f7d2011-09-12 11:54:49 -0700602static void wcnss_post_bootup(struct work_struct *work)
603{
Sheng Fangbb421672013-03-19 08:27:28 +0800604 if (do_not_cancel_vote == 1) {
605 pr_info("%s: Keeping APPS vote for Iris & WCNSS\n", __func__);
606 return;
607 }
608
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700609 pr_info("%s: Cancel APPS vote for Iris & WCNSS\n", __func__);
David Collinsb727f7d2011-09-12 11:54:49 -0700610
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700611 /* Since WCNSS is up, cancel any APPS vote for Iris & WCNSS VREGs */
David Collinsb727f7d2011-09-12 11:54:49 -0700612 wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config,
613 WCNSS_WLAN_SWITCH_OFF);
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700614
615}
616
617static int
618wcnss_pronto_gpios_config(struct device *dev, bool enable)
619{
620 int rc = 0;
621 int i, j;
622 int WCNSS_WLAN_NUM_GPIOS = 5;
623
624 for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) {
625 int gpio = of_get_gpio(dev->of_node, i);
626 if (enable) {
627 rc = gpio_request(gpio, "wcnss_wlan");
628 if (rc) {
629 pr_err("WCNSS gpio_request %d err %d\n",
630 gpio, rc);
631 goto fail;
632 }
633 } else
634 gpio_free(gpio);
635 }
636
637 return rc;
638
639fail:
640 for (j = WCNSS_WLAN_NUM_GPIOS-1; j >= 0; j--) {
641 int gpio = of_get_gpio(dev->of_node, i);
642 gpio_free(gpio);
643 }
644 return rc;
David Collinsb727f7d2011-09-12 11:54:49 -0700645}
646
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700647static int
648wcnss_gpios_config(struct resource *gpios_5wire, bool enable)
649{
650 int i, j;
651 int rc = 0;
652
653 for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) {
654 if (enable) {
655 rc = gpio_request(i, gpios_5wire->name);
656 if (rc) {
657 pr_err("WCNSS gpio_request %d err %d\n", i, rc);
658 goto fail;
659 }
660 } else
661 gpio_free(i);
662 }
663
664 return rc;
665
666fail:
667 for (j = i-1; j >= gpios_5wire->start; j--)
668 gpio_free(j);
669 return rc;
670}
671
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700672static int __devinit
673wcnss_wlan_ctrl_probe(struct platform_device *pdev)
674{
Sameer Thalappil667f43f2011-10-10 11:12:54 -0700675 if (!penv)
676 return -ENODEV;
677
678 penv->smd_channel_ready = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700679
680 pr_info("%s: SMD ctrl channel up\n", __func__);
681
David Collinsb727f7d2011-09-12 11:54:49 -0700682 /* Schedule a work to do any post boot up activity */
683 INIT_DELAYED_WORK(&penv->wcnss_work, wcnss_post_bootup);
684 schedule_delayed_work(&penv->wcnss_work, msecs_to_jiffies(10000));
685
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700686 return 0;
687}
688
Sameer Thalappil13f45182012-07-23 18:02:45 -0700689void wcnss_flush_delayed_boot_votes()
690{
Sameer Thalappilf106a682013-02-16 20:41:11 -0800691 flush_delayed_work(&penv->wcnss_work);
Sameer Thalappil13f45182012-07-23 18:02:45 -0700692}
693EXPORT_SYMBOL(wcnss_flush_delayed_boot_votes);
694
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700695static int __devexit
696wcnss_wlan_ctrl_remove(struct platform_device *pdev)
697{
698 if (penv)
699 penv->smd_channel_ready = 0;
700
701 pr_info("%s: SMD ctrl channel down\n", __func__);
702
703 return 0;
704}
705
706
707static struct platform_driver wcnss_wlan_ctrl_driver = {
708 .driver = {
709 .name = "WLAN_CTRL",
710 .owner = THIS_MODULE,
711 },
712 .probe = wcnss_wlan_ctrl_probe,
713 .remove = __devexit_p(wcnss_wlan_ctrl_remove),
714};
715
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700716static int __devexit
717wcnss_ctrl_remove(struct platform_device *pdev)
718{
719 if (penv && penv->smd_ch)
720 smd_close(penv->smd_ch);
721
722 return 0;
723}
724
725static int __devinit
726wcnss_ctrl_probe(struct platform_device *pdev)
727{
728 int ret = 0;
729
730 if (!penv)
731 return -ENODEV;
732
733 ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS,
734 &penv->smd_ch, penv, wcnss_smd_notify_event);
735 if (ret < 0) {
736 pr_err("wcnss: cannot open the smd command channel %s: %d\n",
737 WCNSS_CTRL_CHANNEL, ret);
738 return -ENODEV;
739 }
740 smd_disable_read_intr(penv->smd_ch);
741
742 return 0;
743}
744
745/* platform device for WCNSS_CTRL SMD channel */
746static struct platform_driver wcnss_ctrl_driver = {
747 .driver = {
748 .name = "WCNSS_CTRL",
749 .owner = THIS_MODULE,
750 },
751 .probe = wcnss_ctrl_probe,
752 .remove = __devexit_p(wcnss_ctrl_remove),
753};
754
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700755struct device *wcnss_wlan_get_device(void)
756{
757 if (penv && penv->pdev && penv->smd_channel_ready)
758 return &penv->pdev->dev;
759 return NULL;
760}
761EXPORT_SYMBOL(wcnss_wlan_get_device);
762
Sameer Thalappil409ed352011-12-07 10:53:31 -0800763struct platform_device *wcnss_get_platform_device(void)
764{
765 if (penv && penv->pdev)
766 return penv->pdev;
767 return NULL;
768}
769EXPORT_SYMBOL(wcnss_get_platform_device);
770
771struct wcnss_wlan_config *wcnss_get_wlan_config(void)
772{
773 if (penv && penv->pdev)
774 return &penv->wlan_config;
775 return NULL;
776}
777EXPORT_SYMBOL(wcnss_get_wlan_config);
778
Sameer Thalappil1878d762013-04-24 14:54:39 -0700779int wcnss_device_ready(void)
780{
781 if (penv && penv->pdev && penv->nv_downloaded)
782 return 1;
783 return 0;
784}
785EXPORT_SYMBOL(wcnss_device_ready);
786
787
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700788struct resource *wcnss_wlan_get_memory_map(struct device *dev)
789{
790 if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready)
791 return penv->mmio_res;
792 return NULL;
793}
794EXPORT_SYMBOL(wcnss_wlan_get_memory_map);
795
796int wcnss_wlan_get_dxe_tx_irq(struct device *dev)
797{
798 if (penv && dev && (dev == &penv->pdev->dev) &&
799 penv->tx_irq_res && penv->smd_channel_ready)
800 return penv->tx_irq_res->start;
801 return WCNSS_WLAN_IRQ_INVALID;
802}
803EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq);
804
805int wcnss_wlan_get_dxe_rx_irq(struct device *dev)
806{
807 if (penv && dev && (dev == &penv->pdev->dev) &&
808 penv->rx_irq_res && penv->smd_channel_ready)
809 return penv->rx_irq_res->start;
810 return WCNSS_WLAN_IRQ_INVALID;
811}
812EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq);
813
814void wcnss_wlan_register_pm_ops(struct device *dev,
815 const struct dev_pm_ops *pm_ops)
816{
817 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops)
818 penv->pm_ops = pm_ops;
819}
820EXPORT_SYMBOL(wcnss_wlan_register_pm_ops);
821
Sameer Thalappilecdd1002011-09-09 10:52:27 -0700822void wcnss_wlan_unregister_pm_ops(struct device *dev,
823 const struct dev_pm_ops *pm_ops)
824{
825 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) {
826 if (pm_ops->suspend != penv->pm_ops->suspend ||
827 pm_ops->resume != penv->pm_ops->resume)
828 pr_err("PM APIs dont match with registered APIs\n");
829 penv->pm_ops = NULL;
830 }
831}
832EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops);
833
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700834void wcnss_register_thermal_mitigation(struct device *dev,
835 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700836{
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700837 if (penv && dev && tm_notify)
Jeff Johnson61374652012-04-16 20:51:48 -0700838 penv->tm_notify = tm_notify;
839}
840EXPORT_SYMBOL(wcnss_register_thermal_mitigation);
841
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700842void wcnss_unregister_thermal_mitigation(
843 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700844{
845 if (penv && tm_notify) {
846 if (tm_notify != penv->tm_notify)
847 pr_err("tm_notify doesn't match registered\n");
848 penv->tm_notify = NULL;
849 }
850}
851EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation);
852
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800853unsigned int wcnss_get_serial_number(void)
854{
855 if (penv)
856 return penv->serial_number;
857 return 0;
858}
859EXPORT_SYMBOL(wcnss_get_serial_number);
860
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700861static int enable_wcnss_suspend_notify;
862
863static int enable_wcnss_suspend_notify_set(const char *val,
864 struct kernel_param *kp)
865{
866 int ret;
867
868 ret = param_set_int(val, kp);
869 if (ret)
870 return ret;
871
872 if (enable_wcnss_suspend_notify)
873 pr_debug("Suspend notification activated for wcnss\n");
874
875 return 0;
876}
877module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set,
878 param_get_int, &enable_wcnss_suspend_notify, S_IRUGO | S_IWUSR);
879
Sameer Thalappil58cec312013-05-13 15:52:08 -0700880int wcnss_xo_auto_detect_enabled(void)
881{
882 return (has_autodetect_xo == 1 ? 1 : 0);
883}
884
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700885
886void wcnss_suspend_notify(void)
887{
888 void __iomem *pmu_spare_reg;
889 u32 reg = 0;
890 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700891
892 if (!enable_wcnss_suspend_notify)
893 return;
894
895 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
896 return;
897
898 /* For Riva */
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700899 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
900 spin_lock_irqsave(&reg_spinlock, flags);
901 reg = readl_relaxed(pmu_spare_reg);
902 reg |= RIVA_SUSPEND_BIT;
903 writel_relaxed(reg, pmu_spare_reg);
904 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700905}
906EXPORT_SYMBOL(wcnss_suspend_notify);
907
908void wcnss_resume_notify(void)
909{
910 void __iomem *pmu_spare_reg;
911 u32 reg = 0;
912 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700913
914 if (!enable_wcnss_suspend_notify)
915 return;
916
917 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
918 return;
919
920 /* For Riva */
921 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
922
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700923 spin_lock_irqsave(&reg_spinlock, flags);
924 reg = readl_relaxed(pmu_spare_reg);
925 reg &= ~RIVA_SUSPEND_BIT;
926 writel_relaxed(reg, pmu_spare_reg);
927 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700928}
929EXPORT_SYMBOL(wcnss_resume_notify);
930
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700931static int wcnss_wlan_suspend(struct device *dev)
932{
933 if (penv && dev && (dev == &penv->pdev->dev) &&
934 penv->smd_channel_ready &&
935 penv->pm_ops && penv->pm_ops->suspend)
936 return penv->pm_ops->suspend(dev);
937 return 0;
938}
939
940static int wcnss_wlan_resume(struct device *dev)
941{
942 if (penv && dev && (dev == &penv->pdev->dev) &&
943 penv->smd_channel_ready &&
944 penv->pm_ops && penv->pm_ops->resume)
945 return penv->pm_ops->resume(dev);
946 return 0;
947}
948
Sameer Thalappilf138da52012-07-30 12:56:37 -0700949void wcnss_prevent_suspend()
950{
951 if (penv)
952 wake_lock(&penv->wcnss_wake_lock);
953}
954EXPORT_SYMBOL(wcnss_prevent_suspend);
955
956void wcnss_allow_suspend()
957{
958 if (penv)
959 wake_unlock(&penv->wcnss_wake_lock);
960}
961EXPORT_SYMBOL(wcnss_allow_suspend);
962
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700963int wcnss_hardware_type(void)
964{
965 if (penv)
966 return penv->wcnss_hw_type;
967 else
968 return -ENODEV;
969}
970EXPORT_SYMBOL(wcnss_hardware_type);
971
Sameer Thalappil1878d762013-04-24 14:54:39 -0700972int fw_cal_data_available(void)
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800973{
974 if (penv)
Sameer Thalappil1878d762013-04-24 14:54:39 -0700975 return penv->fw_cal_available;
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800976 else
977 return -ENODEV;
978}
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800979
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700980static int wcnss_smd_tx(void *data, int len)
981{
982 int ret = 0;
983
984 ret = smd_write_avail(penv->smd_ch);
985 if (ret < len) {
986 pr_err("wcnss: no space available for smd frame\n");
Sameer Thalappilf106a682013-02-16 20:41:11 -0800987 return -ENOSPC;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700988 }
989 ret = smd_write(penv->smd_ch, data, len);
990 if (ret < len) {
991 pr_err("wcnss: failed to write Command %d", len);
992 ret = -ENODEV;
993 }
994 return ret;
995}
996
Sameer Thalappil1878d762013-04-24 14:54:39 -0700997static unsigned char wcnss_fw_status(void)
998{
999 int len = 0;
1000 int rc = 0;
1001
1002 unsigned char fw_status = 0xFF;
1003
1004 len = smd_read_avail(penv->smd_ch);
1005 if (len < 1) {
1006 pr_err("%s: invalid firmware status", __func__);
1007 return fw_status;
1008 }
1009
1010 rc = smd_read(penv->smd_ch, &fw_status, 1);
1011 if (rc < 0) {
1012 pr_err("%s: incomplete data read from smd\n", __func__);
1013 return fw_status;
1014 }
1015 return fw_status;
1016}
1017
1018static void wcnss_send_cal_rsp(unsigned char fw_status)
1019{
1020 struct smd_msg_hdr *rsphdr;
1021 unsigned char *msg = NULL;
1022 int rc;
1023
1024 msg = kmalloc((sizeof(struct smd_msg_hdr) + 1), GFP_KERNEL);
1025 if (NULL == msg) {
1026 pr_err("wcnss: %s: failed to get memory\n", __func__);
1027 return;
1028 }
1029
1030 rsphdr = (struct smd_msg_hdr *)msg;
1031 rsphdr->msg_type = WCNSS_CALDATA_UPLD_RSP;
1032 rsphdr->msg_len = sizeof(struct smd_msg_hdr) + 1;
1033 memcpy(msg+sizeof(struct smd_msg_hdr), &fw_status, 1);
1034
1035 rc = wcnss_smd_tx(msg, rsphdr->msg_len);
1036 if (rc < 0)
1037 pr_err("wcnss: smd tx failed\n");
1038}
1039
1040/* Collect calibrated data from WCNSS */
1041void extract_cal_data(int len)
1042{
1043 int rc;
1044 struct cal_data_params calhdr;
1045 unsigned char fw_status = WCNSS_RESP_FAIL;
1046
1047 if (len < sizeof(struct cal_data_params)) {
1048 pr_err("wcnss: incomplete cal header length\n");
1049 return;
1050 }
1051
1052 rc = smd_read(penv->smd_ch, (unsigned char *)&calhdr,
1053 sizeof(struct cal_data_params));
1054 if (rc < sizeof(struct cal_data_params)) {
1055 pr_err("wcnss: incomplete cal header read from smd\n");
1056 return;
1057 }
1058
1059 if (penv->fw_cal_exp_frag != calhdr.frag_number) {
1060 pr_err("wcnss: Invalid frgament");
1061 goto exit;
1062 }
1063
1064 if (calhdr.frag_size > WCNSS_MAX_FRAME_SIZE) {
1065 pr_err("wcnss: Invalid fragment size");
1066 goto exit;
1067 }
1068
1069 if (0 == calhdr.frag_number) {
1070 if (calhdr.total_size > MAX_CALIBRATED_DATA_SIZE) {
1071 pr_err("wcnss: Invalid cal data size %d",
1072 calhdr.total_size);
1073 goto exit;
1074 }
1075 kfree(penv->fw_cal_data);
1076 penv->fw_cal_rcvd = 0;
1077 penv->fw_cal_data = kmalloc(calhdr.total_size,
1078 GFP_KERNEL);
1079 if (penv->fw_cal_data == NULL) {
1080 smd_read(penv->smd_ch, NULL, calhdr.frag_size);
1081 goto exit;
1082 }
1083 }
1084
1085 mutex_lock(&penv->dev_lock);
1086 if (penv->fw_cal_rcvd + calhdr.frag_size >
1087 MAX_CALIBRATED_DATA_SIZE) {
1088 pr_err("calibrated data size is more than expected %d",
1089 penv->fw_cal_rcvd + calhdr.frag_size);
1090 penv->fw_cal_exp_frag = 0;
1091 penv->fw_cal_rcvd = 0;
1092 smd_read(penv->smd_ch, NULL, calhdr.frag_size);
1093 goto unlock_exit;
1094 }
1095
1096 rc = smd_read(penv->smd_ch, penv->fw_cal_data + penv->fw_cal_rcvd,
1097 calhdr.frag_size);
1098 if (rc < calhdr.frag_size)
1099 goto unlock_exit;
1100
1101 penv->fw_cal_exp_frag++;
1102 penv->fw_cal_rcvd += calhdr.frag_size;
1103
1104 if (calhdr.msg_flags & LAST_FRAGMENT) {
1105 penv->fw_cal_exp_frag = 0;
1106 penv->fw_cal_available = true;
1107 pr_info("wcnss: cal data collection completed\n");
1108 }
1109 mutex_unlock(&penv->dev_lock);
1110 wake_up(&penv->read_wait);
1111
1112 if (penv->fw_cal_available) {
1113 fw_status = WCNSS_RESP_SUCCESS;
1114 wcnss_send_cal_rsp(fw_status);
1115 }
1116 return;
1117
1118unlock_exit:
1119 mutex_unlock(&penv->dev_lock);
1120
1121exit:
1122 wcnss_send_cal_rsp(fw_status);
1123 return;
1124}
1125
1126
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001127static void wcnssctrl_rx_handler(struct work_struct *worker)
1128{
1129 int len = 0;
1130 int rc = 0;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001131 unsigned char buf[sizeof(struct wcnss_version)];
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001132 struct smd_msg_hdr *phdr;
1133 struct wcnss_version *pversion;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001134 int hw_type;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001135 unsigned char fw_status = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001136
1137 len = smd_read_avail(penv->smd_ch);
1138 if (len > WCNSS_MAX_FRAME_SIZE) {
1139 pr_err("wcnss: frame larger than the allowed size\n");
1140 smd_read(penv->smd_ch, NULL, len);
1141 return;
1142 }
1143 if (len <= 0)
1144 return;
1145
Sameer Thalappil1878d762013-04-24 14:54:39 -07001146 rc = smd_read(penv->smd_ch, buf, sizeof(struct smd_msg_hdr));
1147 if (rc < sizeof(struct smd_msg_hdr)) {
1148 pr_err("wcnss: incomplete header read from smd\n");
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001149 return;
1150 }
Sameer Thalappil1878d762013-04-24 14:54:39 -07001151 len -= sizeof(struct smd_msg_hdr);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001152
1153 phdr = (struct smd_msg_hdr *)buf;
1154
Sameer Thalappilf106a682013-02-16 20:41:11 -08001155 switch (phdr->msg_type) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001156
1157 case WCNSS_VERSION_RSP:
Sameer Thalappil1878d762013-04-24 14:54:39 -07001158 if (len != sizeof(struct wcnss_version)
1159 - sizeof(struct smd_msg_hdr)) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001160 pr_err("wcnss: invalid version data from wcnss %d\n",
Sameer Thalappil1878d762013-04-24 14:54:39 -07001161 len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001162 return;
1163 }
Sameer Thalappil1878d762013-04-24 14:54:39 -07001164 rc = smd_read(penv->smd_ch, buf+sizeof(struct smd_msg_hdr),
1165 len);
1166 if (rc < len) {
1167 pr_err("wcnss: incomplete data read from smd\n");
1168 return;
1169 }
1170 pversion = (struct wcnss_version *)buf;
1171 penv->fw_major = pversion->major;
1172 penv->fw_minor = pversion->minor;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001173 snprintf(penv->wcnss_version, WCNSS_VERSION_LEN,
1174 "%02x%02x%02x%02x", pversion->major, pversion->minor,
1175 pversion->version, pversion->revision);
1176 pr_info("wcnss: version %s\n", penv->wcnss_version);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001177 /* schedule work to download nvbin to ccpu */
1178 hw_type = wcnss_hardware_type();
1179 switch (hw_type) {
1180 case WCNSS_RIVA_HW:
1181 /* supported only if riva major >= 1 and minor >= 4 */
1182 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
1183 pr_info("wcnss: schedule dnld work for riva\n");
1184 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
1185 }
1186 break;
1187
1188 case WCNSS_PRONTO_HW:
1189 /* supported only if pronto major >= 1 and minor >= 4 */
1190 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
1191 pr_info("wcnss: schedule dnld work for pronto\n");
1192 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
1193 }
1194 break;
1195
1196 default:
1197 pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n",
1198 hw_type);
1199 break;
1200 }
1201 break;
1202
1203 case WCNSS_NVBIN_DNLD_RSP:
Sameer Thalappil1878d762013-04-24 14:54:39 -07001204 penv->nv_downloaded = true;
1205 fw_status = wcnss_fw_status();
1206 pr_debug("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu %u\n",
1207 fw_status);
1208 break;
1209
1210 case WCNSS_CALDATA_DNLD_RSP:
1211 penv->nv_downloaded = true;
1212 fw_status = wcnss_fw_status();
1213 pr_debug("wcnss: received WCNSS_CALDATA_DNLD_RSP from ccpu %u\n",
1214 fw_status);
1215 break;
1216
1217 case WCNSS_CALDATA_UPLD_REQ:
1218 penv->fw_cal_available = 0;
1219 extract_cal_data(len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001220 break;
1221
1222 default:
Sameer Thalappilf106a682013-02-16 20:41:11 -08001223 pr_err("wcnss: invalid message type %d\n", phdr->msg_type);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001224 }
1225 return;
1226}
1227
1228static void wcnss_send_version_req(struct work_struct *worker)
1229{
1230 struct smd_msg_hdr smd_msg;
1231 int ret = 0;
1232
Sameer Thalappilf106a682013-02-16 20:41:11 -08001233 smd_msg.msg_type = WCNSS_VERSION_REQ;
1234 smd_msg.msg_len = sizeof(smd_msg);
1235 ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001236 if (ret < 0)
1237 pr_err("wcnss: smd tx failed\n");
1238
1239 return;
1240}
1241
Sameer Thalappil1878d762013-04-24 14:54:39 -07001242
1243static void wcnss_nvbin_dnld(void)
Sameer Thalappilf106a682013-02-16 20:41:11 -08001244{
1245 int ret = 0;
1246 struct nvbin_dnld_req_msg *dnld_req_msg;
1247 unsigned short total_fragments = 0;
1248 unsigned short count = 0;
1249 unsigned short retry_count = 0;
1250 unsigned short cur_frag_size = 0;
1251 unsigned char *outbuffer = NULL;
1252 const void *nv_blob_addr = NULL;
1253 unsigned int nv_blob_size = 0;
1254 const struct firmware *nv = NULL;
Sameer Thalappilf0d89ba2013-03-11 17:33:20 -07001255 struct device *dev = &penv->pdev->dev;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001256
1257 ret = request_firmware(&nv, NVBIN_FILE, dev);
1258
1259 if (ret || !nv || !nv->data || !nv->size) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001260 pr_err("wcnss: %s: request_firmware failed for %s\n",
1261 __func__, NVBIN_FILE);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001262 return;
1263 }
1264
1265 /*
1266 * First 4 bytes in nv blob is validity bitmap.
1267 * We cannot validate nv, so skip those 4 bytes.
1268 */
1269 nv_blob_addr = nv->data + 4;
1270 nv_blob_size = nv->size - 4;
1271
1272 total_fragments = TOTALFRAGMENTS(nv_blob_size);
1273
1274 pr_info("wcnss: NV bin size: %d, total_fragments: %d\n",
1275 nv_blob_size, total_fragments);
1276
1277 /* get buffer for nv bin dnld req message */
1278 outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) +
1279 NV_FRAGMENT_SIZE), GFP_KERNEL);
1280
1281 if (NULL == outbuffer) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001282 pr_err("wcnss: %s: failed to get buffer\n", __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001283 goto err_free_nv;
1284 }
1285
1286 dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
1287
1288 dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001289 dnld_req_msg->dnld_req_params.msg_flags = 0;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001290
1291 for (count = 0; count < total_fragments; count++) {
1292 dnld_req_msg->dnld_req_params.frag_number = count;
1293
1294 if (count == (total_fragments - 1)) {
1295 /* last fragment, take care of boundry condition */
1296 cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE;
1297 if (!cur_frag_size)
1298 cur_frag_size = NV_FRAGMENT_SIZE;
1299
Sameer Thalappil1878d762013-04-24 14:54:39 -07001300 dnld_req_msg->dnld_req_params.msg_flags |=
1301 LAST_FRAGMENT;
1302 dnld_req_msg->dnld_req_params.msg_flags |=
1303 CAN_RECEIVE_CALDATA;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001304 } else {
1305 cur_frag_size = NV_FRAGMENT_SIZE;
Sameer Thalappil1878d762013-04-24 14:54:39 -07001306 dnld_req_msg->dnld_req_params.msg_flags &=
1307 ~LAST_FRAGMENT;
Sameer Thalappilf106a682013-02-16 20:41:11 -08001308 }
1309
1310 dnld_req_msg->dnld_req_params.nvbin_buffer_size =
1311 cur_frag_size;
1312
1313 dnld_req_msg->hdr.msg_len =
1314 sizeof(struct nvbin_dnld_req_msg) + cur_frag_size;
1315
1316 /* copy NV fragment */
1317 memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)),
1318 (nv_blob_addr + count * NV_FRAGMENT_SIZE),
1319 cur_frag_size);
1320
1321 ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len);
1322
1323 retry_count = 0;
1324 while ((ret == -ENOSPC) && (retry_count <= 3)) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001325 pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
1326 __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001327 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1328 count, dnld_req_msg->hdr.msg_len,
1329 total_fragments, retry_count);
1330
1331 /* wait and try again */
1332 msleep(20);
1333 retry_count++;
1334 ret = wcnss_smd_tx(outbuffer,
1335 dnld_req_msg->hdr.msg_len);
1336 }
1337
1338 if (ret < 0) {
Sameer Thalappil1878d762013-04-24 14:54:39 -07001339 pr_err("wcnss: %s: smd tx failed\n", __func__);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001340 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1341 count, dnld_req_msg->hdr.msg_len,
1342 total_fragments, retry_count);
1343 goto err_dnld;
1344 }
1345 }
1346
1347err_dnld:
1348 /* free buffer */
1349 kfree(outbuffer);
1350
1351err_free_nv:
1352 /* release firmware */
1353 release_firmware(nv);
1354
1355 return;
1356}
1357
Sameer Thalappil1878d762013-04-24 14:54:39 -07001358
1359static void wcnss_caldata_dnld(const void *cal_data,
1360 unsigned int cal_data_size, bool msg_to_follow)
1361{
1362 int ret = 0;
1363 struct cal_data_msg *cal_msg;
1364 unsigned short total_fragments = 0;
1365 unsigned short count = 0;
1366 unsigned short retry_count = 0;
1367 unsigned short cur_frag_size = 0;
1368 unsigned char *outbuffer = NULL;
1369
1370 total_fragments = TOTALFRAGMENTS(cal_data_size);
1371
1372 outbuffer = kmalloc((sizeof(struct cal_data_msg) +
1373 NV_FRAGMENT_SIZE), GFP_KERNEL);
1374
1375 if (NULL == outbuffer) {
1376 pr_err("wcnss: %s: failed to get buffer\n", __func__);
1377 return;
1378 }
1379
1380 cal_msg = (struct cal_data_msg *)outbuffer;
1381
1382 cal_msg->hdr.msg_type = WCNSS_CALDATA_DNLD_REQ;
1383 cal_msg->cal_params.msg_flags = 0;
1384
1385 for (count = 0; count < total_fragments; count++) {
1386 cal_msg->cal_params.frag_number = count;
1387
1388 if (count == (total_fragments - 1)) {
1389 cur_frag_size = cal_data_size % NV_FRAGMENT_SIZE;
1390 if (!cur_frag_size)
1391 cur_frag_size = NV_FRAGMENT_SIZE;
1392
1393 cal_msg->cal_params.msg_flags
1394 |= LAST_FRAGMENT;
1395 if (msg_to_follow)
1396 cal_msg->cal_params.msg_flags |=
1397 MESSAGE_TO_FOLLOW;
1398 } else {
1399 cur_frag_size = NV_FRAGMENT_SIZE;
1400 cal_msg->cal_params.msg_flags &=
1401 ~LAST_FRAGMENT;
1402 }
1403
1404 cal_msg->cal_params.total_size = cal_data_size;
1405 cal_msg->cal_params.frag_size =
1406 cur_frag_size;
1407
1408 cal_msg->hdr.msg_len =
1409 sizeof(struct cal_data_msg) + cur_frag_size;
1410
1411 memcpy((outbuffer + sizeof(struct cal_data_msg)),
1412 (cal_data + count * NV_FRAGMENT_SIZE),
1413 cur_frag_size);
1414
1415 ret = wcnss_smd_tx(outbuffer, cal_msg->hdr.msg_len);
1416
1417 retry_count = 0;
1418 while ((ret == -ENOSPC) && (retry_count <= 3)) {
1419 pr_debug("wcnss: %s: smd tx failed, ENOSPC\n",
1420 __func__);
1421 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1422 count, cal_msg->hdr.msg_len,
1423 total_fragments, retry_count);
1424
1425 /* wait and try again */
1426 msleep(20);
1427 retry_count++;
1428 ret = wcnss_smd_tx(outbuffer,
1429 cal_msg->hdr.msg_len);
1430 }
1431
1432 if (ret < 0) {
1433 pr_err("wcnss: %s: smd tx failed\n", __func__);
1434 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1435 count, cal_msg->hdr.msg_len,
1436 total_fragments, retry_count);
1437 goto err_dnld;
1438 }
1439 }
1440
1441
1442err_dnld:
1443 /* free buffer */
1444 kfree(outbuffer);
1445
1446 return;
1447}
1448
1449
1450static void wcnss_nvbin_dnld_main(struct work_struct *worker)
1451{
1452 int retry = 0;
1453
1454 if (!FW_CALDATA_CAPABLE())
1455 goto nv_download;
1456
1457 if (!penv->fw_cal_available && WCNSS_CONFIG_UNSPECIFIED
1458 != has_calibrated_data && !penv->user_cal_available) {
1459 while (!penv->user_cal_available && retry++ < 5)
1460 msleep(500);
1461 }
1462
1463 /* only cal data is sent during ssr (if available) */
1464 if (penv->fw_cal_available && penv->ssr_boot) {
1465 pr_info_ratelimited("wcnss: cal download during SSR, using fw cal");
1466 wcnss_caldata_dnld(penv->fw_cal_data, penv->fw_cal_rcvd, false);
1467 return;
1468
1469 } else if (penv->user_cal_available && penv->ssr_boot) {
1470 pr_info_ratelimited("wcnss: cal download during SSR, using user cal");
1471 wcnss_caldata_dnld(penv->user_cal_data,
1472 penv->user_cal_rcvd, false);
1473 return;
1474
1475 } else if (penv->user_cal_available) {
1476 pr_info_ratelimited("wcnss: cal download during cold boot, using user cal");
1477 wcnss_caldata_dnld(penv->user_cal_data,
1478 penv->user_cal_rcvd, true);
1479 }
1480
1481nv_download:
1482 pr_info_ratelimited("wcnss: NV download");
1483 wcnss_nvbin_dnld();
1484
1485 return;
1486}
1487
1488
1489
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001490static int
1491wcnss_trigger_config(struct platform_device *pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001492{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001493 int ret;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001494 struct qcom_wcnss_opts *pdata;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001495 unsigned long wcnss_phys_addr;
1496 int size = 0;
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001497 struct resource *res;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001498 int has_pronto_hw = of_property_read_bool(pdev->dev.of_node,
1499 "qcom,has_pronto_hw");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001500
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001501 /* make sure we are only triggered once */
1502 if (penv->triggered)
1503 return 0;
1504 penv->triggered = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001505
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001506 /* initialize the WCNSS device configuration */
1507 pdata = pdev->dev.platform_data;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001508 if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) {
1509 if (has_pronto_hw) {
1510 has_48mhz_xo = of_property_read_bool(pdev->dev.of_node,
1511 "qcom,has_48mhz_xo");
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001512 } else {
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001513 has_48mhz_xo = pdata->has_48mhz_xo;
1514 }
1515 }
Sameer Thalappilf00dbeb2013-03-01 18:33:30 -08001516 penv->wcnss_hw_type = (has_pronto_hw) ? WCNSS_PRONTO_HW : WCNSS_RIVA_HW;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001517 penv->wlan_config.use_48mhz_xo = has_48mhz_xo;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001518
Sameer Thalappil58cec312013-05-13 15:52:08 -07001519 if (WCNSS_CONFIG_UNSPECIFIED == has_autodetect_xo && has_pronto_hw) {
1520 has_autodetect_xo = of_property_read_bool(pdev->dev.of_node,
1521 "qcom,has_autodetect_xo");
1522 }
1523
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001524 penv->thermal_mitigation = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001525 strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN);
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001526
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001527 /* Configure 5 wire GPIOs */
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001528 if (!has_pronto_hw) {
1529 penv->gpios_5wire = platform_get_resource_byname(pdev,
1530 IORESOURCE_IO, "wcnss_gpios_5wire");
1531
1532 /* allocate 5-wire GPIO resources */
1533 if (!penv->gpios_5wire) {
1534 dev_err(&pdev->dev, "insufficient IO resources\n");
1535 ret = -ENOENT;
1536 goto fail_gpio_res;
1537 }
1538 ret = wcnss_gpios_config(penv->gpios_5wire, true);
1539 } else
1540 ret = wcnss_pronto_gpios_config(&pdev->dev, true);
1541
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001542 if (ret) {
1543 dev_err(&pdev->dev, "WCNSS gpios config failed.\n");
1544 goto fail_gpio_res;
1545 }
1546
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001547 /* power up the WCNSS */
1548 ret = wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1549 WCNSS_WLAN_SWITCH_ON);
1550 if (ret) {
1551 dev_err(&pdev->dev, "WCNSS Power-up failed.\n");
1552 goto fail_power;
1553 }
1554
1555 /* trigger initialization of the WCNSS */
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001556 penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001557 if (IS_ERR(penv->pil)) {
1558 dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n");
1559 ret = PTR_ERR(penv->pil);
1560 penv->pil = NULL;
1561 goto fail_pil;
1562 }
1563
1564 /* allocate resources */
1565 penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1566 "wcnss_mmio");
1567 penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1568 "wcnss_wlantx_irq");
1569 penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1570 "wcnss_wlanrx_irq");
1571
1572 if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) {
1573 dev_err(&pdev->dev, "insufficient resources\n");
1574 ret = -ENOENT;
1575 goto fail_res;
1576 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001577 INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
1578 INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
Sameer Thalappil1878d762013-04-24 14:54:39 -07001579 INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_main);
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001580
Sameer Thalappilf138da52012-07-30 12:56:37 -07001581 wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");
1582
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001583 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
1584 size = 0x3000;
1585 wcnss_phys_addr = MSM_PRONTO_PHYS;
1586 } else {
1587 wcnss_phys_addr = MSM_RIVA_PHYS;
1588 size = SZ_256;
1589 }
1590
1591 penv->msm_wcnss_base = ioremap(wcnss_phys_addr, size);
1592 if (!penv->msm_wcnss_base) {
1593 ret = -ENOMEM;
1594 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1595 goto fail_wake;
1596 }
1597
Sameer Thalappild3aad702013-01-22 18:52:24 -08001598 if (wcnss_hardware_type() == WCNSS_RIVA_HW) {
1599 penv->riva_ccu_base = ioremap(MSM_RIVA_CCU_BASE, SZ_512);
1600 if (!penv->riva_ccu_base) {
1601 ret = -ENOMEM;
1602 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1603 goto fail_ioremap;
1604 }
1605 } else {
1606 penv->pronto_a2xb_base = ioremap(MSM_PRONTO_A2XB_BASE, SZ_512);
1607 if (!penv->pronto_a2xb_base) {
1608 ret = -ENOMEM;
1609 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1610 goto fail_ioremap;
1611 }
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001612 penv->pronto_ccpu_base = ioremap(MSM_PRONTO_CCPU_BASE, SZ_512);
1613 if (!penv->pronto_ccpu_base) {
1614 ret = -ENOMEM;
1615 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1616 goto fail_ioremap2;
1617 }
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001618 /* for reset FIQ */
1619 res = platform_get_resource_byname(penv->pdev,
1620 IORESOURCE_MEM, "wcnss_fiq");
1621 if (!res) {
1622 dev_err(&pdev->dev, "insufficient irq mem resources\n");
1623 ret = -ENOENT;
1624 goto fail_ioremap3;
1625 }
1626 penv->fiq_reg = ioremap_nocache(res->start, resource_size(res));
1627 if (!penv->fiq_reg) {
1628 pr_err("wcnss: %s: ioremap_nocache() failed fiq_reg addr:%pr\n",
1629 __func__, &res->start);
1630 ret = -ENOMEM;
1631 goto fail_ioremap3;
1632 }
Sameer Thalappild3aad702013-01-22 18:52:24 -08001633 }
1634
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001635 return 0;
1636
Sameer Thalappil58281ca2013-04-10 18:50:18 -07001637fail_ioremap3:
1638 iounmap(penv->pronto_ccpu_base);
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001639fail_ioremap2:
1640 iounmap(penv->pronto_a2xb_base);
Sameer Thalappild3aad702013-01-22 18:52:24 -08001641fail_ioremap:
1642 iounmap(penv->msm_wcnss_base);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001643fail_wake:
1644 wake_lock_destroy(&penv->wcnss_wake_lock);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001645fail_res:
1646 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001647 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001648fail_pil:
1649 wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1650 WCNSS_WLAN_SWITCH_OFF);
1651fail_power:
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001652 if (has_pronto_hw)
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001653 wcnss_pronto_gpios_config(&pdev->dev, false);
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001654 else
1655 wcnss_gpios_config(penv->gpios_5wire, false);
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001656fail_gpio_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001657 penv = NULL;
1658 return ret;
1659}
1660
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001661static int wcnss_node_open(struct inode *inode, struct file *file)
1662{
1663 struct platform_device *pdev;
1664
Sameer Thalappil1878d762013-04-24 14:54:39 -07001665 /* first open is only to trigger WCNSS platform driver */
1666 if (!penv->triggered) {
1667 pr_info(DEVICE " triggered by userspace\n");
1668 pdev = penv->pdev;
1669 return wcnss_trigger_config(pdev);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001670
Sameer Thalappil1878d762013-04-24 14:54:39 -07001671 } else if (penv->device_opened) {
1672 pr_info(DEVICE " already opened\n");
1673 return -EBUSY;
1674 }
1675
1676 mutex_lock(&penv->dev_lock);
1677 penv->user_cal_rcvd = 0;
1678 penv->user_cal_read = 0;
1679 penv->user_cal_available = false;
1680 penv->user_cal_data = NULL;
1681 penv->device_opened = 1;
1682 mutex_unlock(&penv->dev_lock);
1683
1684 return 0;
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001685}
1686
Sameer Thalappil1878d762013-04-24 14:54:39 -07001687static ssize_t wcnss_wlan_read(struct file *fp, char __user
1688 *buffer, size_t count, loff_t *position)
1689{
1690 int rc = 0;
1691
1692 if (!penv->device_opened)
1693 return -EFAULT;
1694
1695 rc = wait_event_interruptible(penv->read_wait, penv->fw_cal_rcvd
1696 > penv->user_cal_read || penv->fw_cal_available);
1697
1698 if (rc < 0)
1699 return rc;
1700
1701 mutex_lock(&penv->dev_lock);
1702
1703 if (penv->fw_cal_available && penv->fw_cal_rcvd
1704 == penv->user_cal_read) {
1705 rc = 0;
1706 goto exit;
1707 }
1708
1709 if (count > penv->fw_cal_rcvd - penv->user_cal_read)
1710 count = penv->fw_cal_rcvd - penv->user_cal_read;
1711
1712 rc = copy_to_user(buffer, penv->fw_cal_data +
1713 penv->user_cal_read, count);
1714 if (rc == 0) {
1715 penv->user_cal_read += count;
1716 rc = count;
1717 }
1718
1719exit:
1720 mutex_unlock(&penv->dev_lock);
1721 return rc;
1722}
1723
1724/* first (valid) write to this device should be 4 bytes cal file size */
1725static ssize_t wcnss_wlan_write(struct file *fp, const char __user
1726 *user_buffer, size_t count, loff_t *position)
1727{
1728 int rc = 0;
1729 int size = 0;
1730
1731 if (!penv->device_opened || penv->user_cal_available)
1732 return -EFAULT;
1733
1734 if (penv->user_cal_rcvd == 0 && count >= 4
1735 && !penv->user_cal_data) {
1736 rc = copy_from_user((void *)&size, user_buffer, 4);
1737 if (size > MAX_CALIBRATED_DATA_SIZE) {
1738 pr_err(DEVICE " invalid size to write %d\n", size);
1739 return -EFAULT;
1740 }
1741
1742 rc += count;
1743 count -= 4;
1744 penv->user_cal_exp_size = size;
1745 penv->user_cal_data = kmalloc(size, GFP_KERNEL);
1746 if (penv->user_cal_data == NULL) {
1747 pr_err(DEVICE " no memory to write\n");
1748 return -ENOMEM;
1749 }
1750 if (0 == count)
1751 goto exit;
1752
1753 } else if (penv->user_cal_rcvd == 0 && count < 4)
1754 return -EFAULT;
1755
1756 if (MAX_CALIBRATED_DATA_SIZE < count + penv->user_cal_rcvd) {
1757 pr_err(DEVICE " invalid size to write %d\n", count +
1758 penv->user_cal_rcvd);
1759 rc = -ENOMEM;
1760 goto exit;
1761 }
1762 rc = copy_from_user((void *)penv->user_cal_data +
1763 penv->user_cal_rcvd, user_buffer, count);
1764 if (0 == rc) {
1765 penv->user_cal_rcvd += count;
1766 rc += count;
1767 }
1768 if (penv->user_cal_rcvd == penv->user_cal_exp_size) {
1769 penv->user_cal_available = true;
1770 pr_info_ratelimited("wcnss: user cal written");
1771 }
1772
1773exit:
1774 return rc;
1775}
1776
1777
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001778static const struct file_operations wcnss_node_fops = {
1779 .owner = THIS_MODULE,
1780 .open = wcnss_node_open,
Sameer Thalappil1878d762013-04-24 14:54:39 -07001781 .read = wcnss_wlan_read,
1782 .write = wcnss_wlan_write,
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001783};
1784
1785static struct miscdevice wcnss_misc = {
1786 .minor = MISC_DYNAMIC_MINOR,
1787 .name = DEVICE,
1788 .fops = &wcnss_node_fops,
1789};
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001790
1791static int __devinit
1792wcnss_wlan_probe(struct platform_device *pdev)
1793{
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001794 int ret = 0;
1795
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001796 /* verify we haven't been called more than once */
1797 if (penv) {
1798 dev_err(&pdev->dev, "cannot handle multiple devices.\n");
1799 return -ENODEV;
1800 }
1801
1802 /* create an environment to track the device */
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001803 penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001804 if (!penv) {
1805 dev_err(&pdev->dev, "cannot allocate device memory.\n");
1806 return -ENOMEM;
1807 }
1808 penv->pdev = pdev;
1809
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001810 /* register sysfs entries */
1811 ret = wcnss_create_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001812 if (ret) {
1813 penv = NULL;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001814 return -ENOENT;
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001815 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001816
Sameer Thalappil1878d762013-04-24 14:54:39 -07001817 mutex_init(&penv->dev_lock);
1818 init_waitqueue_head(&penv->read_wait);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001819
Sameer Thalappild3aad702013-01-22 18:52:24 -08001820 /* Since we were built into the kernel we'll be called as part
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001821 * of kernel initialization. We don't know if userspace
1822 * applications are available to service PIL at this time
1823 * (they probably are not), so we simply create a device node
1824 * here. When userspace is available it should touch the
1825 * device so that we know that WCNSS configuration can take
1826 * place
1827 */
1828 pr_info(DEVICE " probed in built-in mode\n");
1829 return misc_register(&wcnss_misc);
1830
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001831}
1832
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001833static int __devexit
1834wcnss_wlan_remove(struct platform_device *pdev)
1835{
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001836 wcnss_remove_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001837 penv = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001838 return 0;
1839}
1840
1841
1842static const struct dev_pm_ops wcnss_wlan_pm_ops = {
1843 .suspend = wcnss_wlan_suspend,
1844 .resume = wcnss_wlan_resume,
1845};
1846
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001847#ifdef CONFIG_WCNSS_CORE_PRONTO
1848static struct of_device_id msm_wcnss_pronto_match[] = {
1849 {.compatible = "qcom,wcnss_wlan"},
1850 {}
1851};
1852#endif
1853
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001854static struct platform_driver wcnss_wlan_driver = {
1855 .driver = {
1856 .name = DEVICE,
1857 .owner = THIS_MODULE,
1858 .pm = &wcnss_wlan_pm_ops,
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001859#ifdef CONFIG_WCNSS_CORE_PRONTO
1860 .of_match_table = msm_wcnss_pronto_match,
1861#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001862 },
1863 .probe = wcnss_wlan_probe,
1864 .remove = __devexit_p(wcnss_wlan_remove),
1865};
1866
1867static int __init wcnss_wlan_init(void)
1868{
Sameer Thalappil24db5282012-09-10 11:58:33 -07001869 int ret = 0;
1870
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001871 platform_driver_register(&wcnss_wlan_driver);
1872 platform_driver_register(&wcnss_wlan_ctrl_driver);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001873 platform_driver_register(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001874
Sameer Thalappil24db5282012-09-10 11:58:33 -07001875#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1876 ret = wcnss_prealloc_init();
1877 if (ret < 0)
1878 pr_err("wcnss: pre-allocation failed\n");
1879#endif
1880
1881 return ret;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001882}
1883
1884static void __exit wcnss_wlan_exit(void)
1885{
1886 if (penv) {
1887 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001888 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001889
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001890
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001891 penv = NULL;
1892 }
1893
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001894 platform_driver_unregister(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001895 platform_driver_unregister(&wcnss_wlan_ctrl_driver);
1896 platform_driver_unregister(&wcnss_wlan_driver);
Sameer Thalappil24db5282012-09-10 11:58:33 -07001897#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1898 wcnss_prealloc_deinit();
1899#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001900}
1901
1902module_init(wcnss_wlan_init);
1903module_exit(wcnss_wlan_exit);
1904
1905MODULE_LICENSE("GPL v2");
1906MODULE_VERSION(VERSION);
1907MODULE_DESCRIPTION(DEVICE "Driver");