blob: ee7630454b8dd2a11581abe4d6f7acacdfa42693 [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>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070031
Sameer Thalappileeb8c412012-08-10 17:17:09 -070032#include <mach/msm_smd.h>
Sameer Thalappil19e02352012-09-24 15:26:12 -070033#include <mach/msm_iomap.h>
Stephen Boyd77db8bb2012-06-27 15:15:16 -070034#include <mach/subsystem_restart.h>
35
Sameer Thalappil24db5282012-09-10 11:58:33 -070036#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
37#include "wcnss_prealloc.h"
38#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070039
40#define DEVICE "wcnss_wlan"
41#define VERSION "1.01"
42#define WCNSS_PIL_DEVICE "wcnss"
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070043
Ankur Nandwanib0039b02011-08-09 14:00:45 -070044/* module params */
45#define WCNSS_CONFIG_UNSPECIFIED (-1)
Sameer Thalappileeb8c412012-08-10 17:17:09 -070046
Ankur Nandwania4510492011-08-29 15:56:23 -070047static int has_48mhz_xo = WCNSS_CONFIG_UNSPECIFIED;
Jeff Johnsonb3377e32011-11-18 23:32:27 -080048module_param(has_48mhz_xo, int, S_IWUSR | S_IRUGO);
Ankur Nandwanib0039b02011-08-09 14:00:45 -070049MODULE_PARM_DESC(has_48mhz_xo, "Is an external 48 MHz XO present");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070050
Sameer Thalappiled3f7da2012-11-01 12:54:01 -070051static DEFINE_SPINLOCK(reg_spinlock);
52
53#define MSM_RIVA_PHYS 0x03204000
54#define MSM_PRONTO_PHYS 0xfb21b000
55
56#define RIVA_SPARE_OFFSET 0x0b4
57#define RIVA_SUSPEND_BIT BIT(24)
58
Sameer Thalappil77716e52012-11-08 13:41:04 -080059#define MSM_RIVA_CCU_BASE 0x03200800
60
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080061#define CCU_RIVA_INVALID_ADDR_OFFSET 0x100
62#define CCU_RIVA_LAST_ADDR0_OFFSET 0x104
63#define CCU_RIVA_LAST_ADDR1_OFFSET 0x108
64#define CCU_RIVA_LAST_ADDR2_OFFSET 0x10c
Sameer Thalappil77716e52012-11-08 13:41:04 -080065
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080066#define MSM_PRONTO_A2XB_BASE 0xfb100400
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080067#define A2XB_CFG_OFFSET 0x00
68#define A2XB_INT_SRC_OFFSET 0x0c
69#define A2XB_TSTBUS_CTRL_OFFSET 0x14
70#define A2XB_TSTBUS_OFFSET 0x18
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080071#define A2XB_ERR_INFO_OFFSET 0x1c
72
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080073#define WCNSS_TSTBUS_CTRL_EN BIT(0)
74#define WCNSS_TSTBUS_CTRL_AXIM (0x02 << 1)
75#define WCNSS_TSTBUS_CTRL_CMDFIFO (0x03 << 1)
76#define WCNSS_TSTBUS_CTRL_WRFIFO (0x04 << 1)
77#define WCNSS_TSTBUS_CTRL_RDFIFO (0x05 << 1)
78#define WCNSS_TSTBUS_CTRL_CTRL (0x07 << 1)
79#define WCNSS_TSTBUS_CTRL_AXIM_CFG0 (0x00 << 6)
80#define WCNSS_TSTBUS_CTRL_AXIM_CFG1 (0x01 << 6)
81#define WCNSS_TSTBUS_CTRL_CTRL_CFG0 (0x00 << 12)
82#define WCNSS_TSTBUS_CTRL_CTRL_CFG1 (0x01 << 12)
83
84#define MSM_PRONTO_CCPU_BASE 0xfb205050
85#define CCU_PRONTO_INVALID_ADDR_OFFSET 0x08
86#define CCU_PRONTO_LAST_ADDR0_OFFSET 0x0c
87#define CCU_PRONTO_LAST_ADDR1_OFFSET 0x10
88#define CCU_PRONTO_LAST_ADDR2_OFFSET 0x14
89
Sameer Thalappileeb8c412012-08-10 17:17:09 -070090#define WCNSS_CTRL_CHANNEL "WCNSS_CTRL"
91#define WCNSS_MAX_FRAME_SIZE 500
92#define WCNSS_VERSION_LEN 30
93
94/* message types */
95#define WCNSS_CTRL_MSG_START 0x01000000
96#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0)
97#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1)
Sameer Thalappilf106a682013-02-16 20:41:11 -080098#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2)
99#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3)
100
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700101
102#define VALID_VERSION(version) \
103 ((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0)
104
105struct smd_msg_hdr {
Sameer Thalappilf106a682013-02-16 20:41:11 -0800106 unsigned int msg_type;
107 unsigned int msg_len;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700108};
109
110struct wcnss_version {
111 struct smd_msg_hdr hdr;
112 unsigned char major;
113 unsigned char minor;
114 unsigned char version;
115 unsigned char revision;
116};
117
Sameer Thalappilf106a682013-02-16 20:41:11 -0800118#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
119
120/*
121 * On SMD channel 4K of maximum data can be transferred, including message
122 * header, so NV fragment size as next multiple of 1Kb is 3Kb.
123 */
124#define NV_FRAGMENT_SIZE 3072
125
126/* Macro to find the total number fragments of the NV bin Image */
127#define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \
128 (x / NV_FRAGMENT_SIZE) : ((x / NV_FRAGMENT_SIZE) + 1))
129
130struct nvbin_dnld_req_params {
131 /*
132 * Fragment sequence number of the NV bin Image. NV Bin Image
133 * might not fit into one message due to size limitation of
134 * the SMD channel FIFO so entire NV blob is chopped into
135 * multiple fragments starting with seqeunce number 0. The
136 * last fragment is indicated by marking is_last_fragment field
137 * to 1. At receiving side, NV blobs would be concatenated
138 * together without any padding bytes in between.
139 */
140 unsigned short frag_number;
141
142 /*
143 * When set to 1 it indicates that no more fragments will
144 * be sent. Receiver shall send back response message after
145 * the last fragment.
146 */
147 unsigned short is_last_fragment;
148
149 /* NV Image size (number of bytes) */
150 unsigned int nvbin_buffer_size;
151
152 /*
153 * Following the 'nvbin_buffer_size', there should be
154 * nvbin_buffer_size bytes of NV bin Image i.e.
155 * uint8[nvbin_buffer_size].
156 */
157};
158
159struct nvbin_dnld_req_msg {
160 /*
161 * Note: The length specified in nvbin_dnld_req_msg messages
162 * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) +
163 * nvbin_buffer_size.
164 */
165 struct smd_msg_hdr hdr;
166 struct nvbin_dnld_req_params dnld_req_params;
167};
168
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700169static struct {
170 struct platform_device *pdev;
171 void *pil;
172 struct resource *mmio_res;
173 struct resource *tx_irq_res;
174 struct resource *rx_irq_res;
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700175 struct resource *gpios_5wire;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700176 const struct dev_pm_ops *pm_ops;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800177 int triggered;
178 int smd_channel_ready;
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800179 int cold_boot_done;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700180 smd_channel_t *smd_ch;
181 unsigned char wcnss_version[WCNSS_VERSION_LEN];
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800182 unsigned int serial_number;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800183 int thermal_mitigation;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700184 enum wcnss_hw_type wcnss_hw_type;
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700185 void (*tm_notify)(struct device *, int);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700186 struct wcnss_wlan_config wlan_config;
David Collinsb727f7d2011-09-12 11:54:49 -0700187 struct delayed_work wcnss_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700188 struct work_struct wcnssctrl_version_work;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800189 struct work_struct wcnssctrl_nvbin_dnld_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700190 struct work_struct wcnssctrl_rx_work;
Sameer Thalappilf138da52012-07-30 12:56:37 -0700191 struct wake_lock wcnss_wake_lock;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700192 void __iomem *msm_wcnss_base;
Sameer Thalappild3aad702013-01-22 18:52:24 -0800193 void __iomem *riva_ccu_base;
194 void __iomem *pronto_a2xb_base;
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800195 void __iomem *pronto_ccpu_base;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700196} *penv = NULL;
197
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800198static ssize_t wcnss_serial_number_show(struct device *dev,
199 struct device_attribute *attr, char *buf)
200{
201 if (!penv)
202 return -ENODEV;
203
204 return scnprintf(buf, PAGE_SIZE, "%08X\n", penv->serial_number);
205}
206
207static ssize_t wcnss_serial_number_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700208 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800209{
210 unsigned int value;
211
212 if (!penv)
213 return -ENODEV;
214
215 if (sscanf(buf, "%08X", &value) != 1)
216 return -EINVAL;
217
218 penv->serial_number = value;
219 return count;
220}
221
222static DEVICE_ATTR(serial_number, S_IRUSR | S_IWUSR,
223 wcnss_serial_number_show, wcnss_serial_number_store);
224
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800225
226static ssize_t wcnss_thermal_mitigation_show(struct device *dev,
227 struct device_attribute *attr, char *buf)
228{
229 if (!penv)
230 return -ENODEV;
231
232 return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation);
233}
234
235static ssize_t wcnss_thermal_mitigation_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700236 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800237{
238 int value;
239
240 if (!penv)
241 return -ENODEV;
242
243 if (sscanf(buf, "%d", &value) != 1)
244 return -EINVAL;
245 penv->thermal_mitigation = value;
Jeff Johnson61374652012-04-16 20:51:48 -0700246 if (penv->tm_notify)
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700247 (penv->tm_notify)(dev, value);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800248 return count;
249}
250
251static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR,
252 wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store);
253
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700254
255static ssize_t wcnss_version_show(struct device *dev,
256 struct device_attribute *attr, char *buf)
257{
258 if (!penv)
259 return -ENODEV;
260
261 return scnprintf(buf, PAGE_SIZE, "%s", penv->wcnss_version);
262}
263
264static DEVICE_ATTR(wcnss_version, S_IRUSR,
265 wcnss_version_show, NULL);
266
Sameer Thalappil77716e52012-11-08 13:41:04 -0800267/* wcnss_reset_intr() is invoked when host drivers fails to
268 * communicate with WCNSS over SMD; so logging these registers
Sameer Thalappild3aad702013-01-22 18:52:24 -0800269 * helps to know WCNSS failure reason
270 */
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800271void wcnss_riva_log_debug_regs(void)
Sameer Thalappil77716e52012-11-08 13:41:04 -0800272{
Sameer Thalappil77716e52012-11-08 13:41:04 -0800273 void __iomem *ccu_reg;
274 u32 reg = 0;
275
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800276 ccu_reg = penv->riva_ccu_base + CCU_RIVA_INVALID_ADDR_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800277 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800278 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800279
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800280 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR0_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800281 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800282 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800283
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800284 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR1_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800285 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800286 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800287
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800288 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR2_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800289 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800290 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800291
Sameer Thalappil77716e52012-11-08 13:41:04 -0800292}
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800293EXPORT_SYMBOL(wcnss_riva_log_debug_regs);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800294
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800295/* Log pronto debug registers before sending reset interrupt */
296void wcnss_pronto_log_debug_regs(void)
297{
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800298 void __iomem *reg_addr, *tst_addr, *tst_ctrl_addr;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800299 u32 reg = 0;
300
Sameer Thalappild3aad702013-01-22 18:52:24 -0800301 reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800302 reg = readl_relaxed(reg_addr);
303 pr_info_ratelimited("%s: A2XB_CFG_OFFSET %08x\n", __func__, reg);
304
Sameer Thalappild3aad702013-01-22 18:52:24 -0800305 reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800306 reg = readl_relaxed(reg_addr);
307 pr_info_ratelimited("%s: A2XB_INT_SRC_OFFSET %08x\n", __func__, reg);
308
Sameer Thalappild3aad702013-01-22 18:52:24 -0800309 reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800310 reg = readl_relaxed(reg_addr);
311 pr_info_ratelimited("%s: A2XB_ERR_INFO_OFFSET %08x\n", __func__, reg);
312
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800313 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_INVALID_ADDR_OFFSET;
314 reg = readl_relaxed(reg_addr);
315 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
316
317 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR0_OFFSET;
318 reg = readl_relaxed(reg_addr);
319 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
320
321 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR1_OFFSET;
322 reg = readl_relaxed(reg_addr);
323 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
324
325 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR2_OFFSET;
326 reg = readl_relaxed(reg_addr);
327 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
328
329 tst_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_OFFSET;
330 tst_ctrl_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_CTRL_OFFSET;
331
332 /* read data FIFO */
333 reg = 0;
334 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_RDFIFO;
335 writel_relaxed(reg, tst_ctrl_addr);
336 reg = readl_relaxed(tst_addr);
337 pr_info_ratelimited("%s: Read data FIFO testbus %08x\n",
338 __func__, reg);
339
340 /* command FIFO */
341 reg = 0;
342 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CMDFIFO;
343 writel_relaxed(reg, tst_ctrl_addr);
344 reg = readl_relaxed(tst_addr);
345 pr_info_ratelimited("%s: Command FIFO testbus %08x\n",
346 __func__, reg);
347
348 /* write data FIFO */
349 reg = 0;
350 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_WRFIFO;
351 writel_relaxed(reg, tst_ctrl_addr);
352 reg = readl_relaxed(tst_addr);
353 pr_info_ratelimited("%s: Rrite data FIFO testbus %08x\n",
354 __func__, reg);
355
356 /* AXIM SEL CFG0 */
357 reg = 0;
358 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
359 WCNSS_TSTBUS_CTRL_AXIM_CFG0;
360 writel_relaxed(reg, tst_ctrl_addr);
361 reg = readl_relaxed(tst_addr);
362 pr_info_ratelimited("%s: AXIM SEL CFG0 testbus %08x\n",
363 __func__, reg);
364
365 /* AXIM SEL CFG1 */
366 reg = 0;
367 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
368 WCNSS_TSTBUS_CTRL_AXIM_CFG1;
369 writel_relaxed(reg, tst_ctrl_addr);
370 reg = readl_relaxed(tst_addr);
371 pr_info_ratelimited("%s: AXIM SEL CFG1 testbus %08x\n",
372 __func__, reg);
373
374 /* CTRL SEL CFG0 */
375 reg = 0;
376 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
377 WCNSS_TSTBUS_CTRL_CTRL_CFG0;
378 writel_relaxed(reg, tst_ctrl_addr);
379 reg = readl_relaxed(tst_addr);
380 pr_info_ratelimited("%s: CTRL SEL CFG0 testbus %08x\n",
381 __func__, reg);
382
383 /* CTRL SEL CFG1 */
384 reg = 0;
385 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
386 WCNSS_TSTBUS_CTRL_CTRL_CFG1;
387 writel_relaxed(reg, tst_ctrl_addr);
388 reg = readl_relaxed(tst_addr);
389 pr_info_ratelimited("%s: CTRL SEL CFG1 testbus %08x\n", __func__, reg);
390
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800391}
392EXPORT_SYMBOL(wcnss_pronto_log_debug_regs);
393
394/* interface to reset wcnss by sending the reset interrupt */
Sameer Thalappil19e02352012-09-24 15:26:12 -0700395void wcnss_reset_intr(void)
396{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800397 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
398 wcnss_pronto_log_debug_regs();
Sameer Thalappil19e02352012-09-24 15:26:12 -0700399 pr_err("%s: reset interrupt not supported\n", __func__);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800400 return;
Sameer Thalappil19e02352012-09-24 15:26:12 -0700401 }
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800402 wcnss_riva_log_debug_regs();
Sameer Thalappil77716e52012-11-08 13:41:04 -0800403 wmb();
404 __raw_writel(1 << 24, MSM_APCS_GCC_BASE + 0x8);
Sameer Thalappil19e02352012-09-24 15:26:12 -0700405}
406EXPORT_SYMBOL(wcnss_reset_intr);
407
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800408static int wcnss_create_sysfs(struct device *dev)
409{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800410 int ret;
411
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800412 if (!dev)
413 return -ENODEV;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800414
415 ret = device_create_file(dev, &dev_attr_serial_number);
416 if (ret)
417 return ret;
418
419 ret = device_create_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700420 if (ret)
421 goto remove_serial;
422
423 ret = device_create_file(dev, &dev_attr_wcnss_version);
424 if (ret)
425 goto remove_thermal;
426
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800427 return 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700428
429remove_thermal:
430 device_remove_file(dev, &dev_attr_thermal_mitigation);
431remove_serial:
432 device_remove_file(dev, &dev_attr_serial_number);
433
434 return ret;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800435}
436
437static void wcnss_remove_sysfs(struct device *dev)
438{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800439 if (dev) {
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800440 device_remove_file(dev, &dev_attr_serial_number);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800441 device_remove_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700442 device_remove_file(dev, &dev_attr_wcnss_version);
443 }
444}
445static void wcnss_smd_notify_event(void *data, unsigned int event)
446{
447 int len = 0;
448
449 if (penv != data) {
450 pr_err("wcnss: invalid env pointer in smd callback\n");
451 return;
452 }
453 switch (event) {
454 case SMD_EVENT_DATA:
455 len = smd_read_avail(penv->smd_ch);
456 if (len < 0)
457 pr_err("wcnss: failed to read from smd %d\n", len);
458 schedule_work(&penv->wcnssctrl_rx_work);
459 break;
460
461 case SMD_EVENT_OPEN:
462 pr_debug("wcnss: opening WCNSS SMD channel :%s",
463 WCNSS_CTRL_CHANNEL);
464 if (!VALID_VERSION(penv->wcnss_version))
465 schedule_work(&penv->wcnssctrl_version_work);
466 break;
467
468 case SMD_EVENT_CLOSE:
469 pr_debug("wcnss: closing WCNSS SMD channel :%s",
470 WCNSS_CTRL_CHANNEL);
471 break;
472
473 default:
474 break;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800475 }
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800476}
477
David Collinsb727f7d2011-09-12 11:54:49 -0700478static void wcnss_post_bootup(struct work_struct *work)
479{
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700480 pr_info("%s: Cancel APPS vote for Iris & WCNSS\n", __func__);
David Collinsb727f7d2011-09-12 11:54:49 -0700481
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700482 /* Since WCNSS is up, cancel any APPS vote for Iris & WCNSS VREGs */
David Collinsb727f7d2011-09-12 11:54:49 -0700483 wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config,
484 WCNSS_WLAN_SWITCH_OFF);
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700485
486}
487
488static int
489wcnss_pronto_gpios_config(struct device *dev, bool enable)
490{
491 int rc = 0;
492 int i, j;
493 int WCNSS_WLAN_NUM_GPIOS = 5;
494
495 for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) {
496 int gpio = of_get_gpio(dev->of_node, i);
497 if (enable) {
498 rc = gpio_request(gpio, "wcnss_wlan");
499 if (rc) {
500 pr_err("WCNSS gpio_request %d err %d\n",
501 gpio, rc);
502 goto fail;
503 }
504 } else
505 gpio_free(gpio);
506 }
507
508 return rc;
509
510fail:
511 for (j = WCNSS_WLAN_NUM_GPIOS-1; j >= 0; j--) {
512 int gpio = of_get_gpio(dev->of_node, i);
513 gpio_free(gpio);
514 }
515 return rc;
David Collinsb727f7d2011-09-12 11:54:49 -0700516}
517
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700518static int
519wcnss_gpios_config(struct resource *gpios_5wire, bool enable)
520{
521 int i, j;
522 int rc = 0;
523
524 for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) {
525 if (enable) {
526 rc = gpio_request(i, gpios_5wire->name);
527 if (rc) {
528 pr_err("WCNSS gpio_request %d err %d\n", i, rc);
529 goto fail;
530 }
531 } else
532 gpio_free(i);
533 }
534
535 return rc;
536
537fail:
538 for (j = i-1; j >= gpios_5wire->start; j--)
539 gpio_free(j);
540 return rc;
541}
542
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700543static int __devinit
544wcnss_wlan_ctrl_probe(struct platform_device *pdev)
545{
Sameer Thalappil667f43f2011-10-10 11:12:54 -0700546 if (!penv)
547 return -ENODEV;
548
549 penv->smd_channel_ready = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700550
551 pr_info("%s: SMD ctrl channel up\n", __func__);
552
David Collinsb727f7d2011-09-12 11:54:49 -0700553 /* Schedule a work to do any post boot up activity */
554 INIT_DELAYED_WORK(&penv->wcnss_work, wcnss_post_bootup);
555 schedule_delayed_work(&penv->wcnss_work, msecs_to_jiffies(10000));
556
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700557 return 0;
558}
559
Sameer Thalappil13f45182012-07-23 18:02:45 -0700560void wcnss_flush_delayed_boot_votes()
561{
Sameer Thalappilf106a682013-02-16 20:41:11 -0800562 flush_delayed_work(&penv->wcnss_work);
Sameer Thalappil13f45182012-07-23 18:02:45 -0700563}
564EXPORT_SYMBOL(wcnss_flush_delayed_boot_votes);
565
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700566static int __devexit
567wcnss_wlan_ctrl_remove(struct platform_device *pdev)
568{
569 if (penv)
570 penv->smd_channel_ready = 0;
571
572 pr_info("%s: SMD ctrl channel down\n", __func__);
573
574 return 0;
575}
576
577
578static struct platform_driver wcnss_wlan_ctrl_driver = {
579 .driver = {
580 .name = "WLAN_CTRL",
581 .owner = THIS_MODULE,
582 },
583 .probe = wcnss_wlan_ctrl_probe,
584 .remove = __devexit_p(wcnss_wlan_ctrl_remove),
585};
586
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700587static int __devexit
588wcnss_ctrl_remove(struct platform_device *pdev)
589{
590 if (penv && penv->smd_ch)
591 smd_close(penv->smd_ch);
592
593 return 0;
594}
595
596static int __devinit
597wcnss_ctrl_probe(struct platform_device *pdev)
598{
599 int ret = 0;
600
601 if (!penv)
602 return -ENODEV;
603
604 ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS,
605 &penv->smd_ch, penv, wcnss_smd_notify_event);
606 if (ret < 0) {
607 pr_err("wcnss: cannot open the smd command channel %s: %d\n",
608 WCNSS_CTRL_CHANNEL, ret);
609 return -ENODEV;
610 }
611 smd_disable_read_intr(penv->smd_ch);
612
613 return 0;
614}
615
616/* platform device for WCNSS_CTRL SMD channel */
617static struct platform_driver wcnss_ctrl_driver = {
618 .driver = {
619 .name = "WCNSS_CTRL",
620 .owner = THIS_MODULE,
621 },
622 .probe = wcnss_ctrl_probe,
623 .remove = __devexit_p(wcnss_ctrl_remove),
624};
625
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700626struct device *wcnss_wlan_get_device(void)
627{
628 if (penv && penv->pdev && penv->smd_channel_ready)
629 return &penv->pdev->dev;
630 return NULL;
631}
632EXPORT_SYMBOL(wcnss_wlan_get_device);
633
Sameer Thalappil409ed352011-12-07 10:53:31 -0800634struct platform_device *wcnss_get_platform_device(void)
635{
636 if (penv && penv->pdev)
637 return penv->pdev;
638 return NULL;
639}
640EXPORT_SYMBOL(wcnss_get_platform_device);
641
642struct wcnss_wlan_config *wcnss_get_wlan_config(void)
643{
644 if (penv && penv->pdev)
645 return &penv->wlan_config;
646 return NULL;
647}
648EXPORT_SYMBOL(wcnss_get_wlan_config);
649
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700650struct resource *wcnss_wlan_get_memory_map(struct device *dev)
651{
652 if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready)
653 return penv->mmio_res;
654 return NULL;
655}
656EXPORT_SYMBOL(wcnss_wlan_get_memory_map);
657
658int wcnss_wlan_get_dxe_tx_irq(struct device *dev)
659{
660 if (penv && dev && (dev == &penv->pdev->dev) &&
661 penv->tx_irq_res && penv->smd_channel_ready)
662 return penv->tx_irq_res->start;
663 return WCNSS_WLAN_IRQ_INVALID;
664}
665EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq);
666
667int wcnss_wlan_get_dxe_rx_irq(struct device *dev)
668{
669 if (penv && dev && (dev == &penv->pdev->dev) &&
670 penv->rx_irq_res && penv->smd_channel_ready)
671 return penv->rx_irq_res->start;
672 return WCNSS_WLAN_IRQ_INVALID;
673}
674EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq);
675
676void wcnss_wlan_register_pm_ops(struct device *dev,
677 const struct dev_pm_ops *pm_ops)
678{
679 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops)
680 penv->pm_ops = pm_ops;
681}
682EXPORT_SYMBOL(wcnss_wlan_register_pm_ops);
683
Sameer Thalappilecdd1002011-09-09 10:52:27 -0700684void wcnss_wlan_unregister_pm_ops(struct device *dev,
685 const struct dev_pm_ops *pm_ops)
686{
687 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) {
688 if (pm_ops->suspend != penv->pm_ops->suspend ||
689 pm_ops->resume != penv->pm_ops->resume)
690 pr_err("PM APIs dont match with registered APIs\n");
691 penv->pm_ops = NULL;
692 }
693}
694EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops);
695
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700696void wcnss_register_thermal_mitigation(struct device *dev,
697 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700698{
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700699 if (penv && dev && tm_notify)
Jeff Johnson61374652012-04-16 20:51:48 -0700700 penv->tm_notify = tm_notify;
701}
702EXPORT_SYMBOL(wcnss_register_thermal_mitigation);
703
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700704void wcnss_unregister_thermal_mitigation(
705 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700706{
707 if (penv && tm_notify) {
708 if (tm_notify != penv->tm_notify)
709 pr_err("tm_notify doesn't match registered\n");
710 penv->tm_notify = NULL;
711 }
712}
713EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation);
714
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800715unsigned int wcnss_get_serial_number(void)
716{
717 if (penv)
718 return penv->serial_number;
719 return 0;
720}
721EXPORT_SYMBOL(wcnss_get_serial_number);
722
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700723static int enable_wcnss_suspend_notify;
724
725static int enable_wcnss_suspend_notify_set(const char *val,
726 struct kernel_param *kp)
727{
728 int ret;
729
730 ret = param_set_int(val, kp);
731 if (ret)
732 return ret;
733
734 if (enable_wcnss_suspend_notify)
735 pr_debug("Suspend notification activated for wcnss\n");
736
737 return 0;
738}
739module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set,
740 param_get_int, &enable_wcnss_suspend_notify, S_IRUGO | S_IWUSR);
741
742
743void wcnss_suspend_notify(void)
744{
745 void __iomem *pmu_spare_reg;
746 u32 reg = 0;
747 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700748
749 if (!enable_wcnss_suspend_notify)
750 return;
751
752 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
753 return;
754
755 /* For Riva */
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700756 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
757 spin_lock_irqsave(&reg_spinlock, flags);
758 reg = readl_relaxed(pmu_spare_reg);
759 reg |= RIVA_SUSPEND_BIT;
760 writel_relaxed(reg, pmu_spare_reg);
761 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700762}
763EXPORT_SYMBOL(wcnss_suspend_notify);
764
765void wcnss_resume_notify(void)
766{
767 void __iomem *pmu_spare_reg;
768 u32 reg = 0;
769 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700770
771 if (!enable_wcnss_suspend_notify)
772 return;
773
774 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
775 return;
776
777 /* For Riva */
778 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
779
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700780 spin_lock_irqsave(&reg_spinlock, flags);
781 reg = readl_relaxed(pmu_spare_reg);
782 reg &= ~RIVA_SUSPEND_BIT;
783 writel_relaxed(reg, pmu_spare_reg);
784 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700785}
786EXPORT_SYMBOL(wcnss_resume_notify);
787
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700788static int wcnss_wlan_suspend(struct device *dev)
789{
790 if (penv && dev && (dev == &penv->pdev->dev) &&
791 penv->smd_channel_ready &&
792 penv->pm_ops && penv->pm_ops->suspend)
793 return penv->pm_ops->suspend(dev);
794 return 0;
795}
796
797static int wcnss_wlan_resume(struct device *dev)
798{
799 if (penv && dev && (dev == &penv->pdev->dev) &&
800 penv->smd_channel_ready &&
801 penv->pm_ops && penv->pm_ops->resume)
802 return penv->pm_ops->resume(dev);
803 return 0;
804}
805
Sameer Thalappilf138da52012-07-30 12:56:37 -0700806void wcnss_prevent_suspend()
807{
808 if (penv)
809 wake_lock(&penv->wcnss_wake_lock);
810}
811EXPORT_SYMBOL(wcnss_prevent_suspend);
812
813void wcnss_allow_suspend()
814{
815 if (penv)
816 wake_unlock(&penv->wcnss_wake_lock);
817}
818EXPORT_SYMBOL(wcnss_allow_suspend);
819
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700820int wcnss_hardware_type(void)
821{
822 if (penv)
823 return penv->wcnss_hw_type;
824 else
825 return -ENODEV;
826}
827EXPORT_SYMBOL(wcnss_hardware_type);
828
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800829int wcnss_cold_boot_done(void)
830{
831 if (penv)
832 return penv->cold_boot_done;
833 else
834 return -ENODEV;
835}
836EXPORT_SYMBOL(wcnss_cold_boot_done);
837
838
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700839static int wcnss_smd_tx(void *data, int len)
840{
841 int ret = 0;
842
843 ret = smd_write_avail(penv->smd_ch);
844 if (ret < len) {
845 pr_err("wcnss: no space available for smd frame\n");
Sameer Thalappilf106a682013-02-16 20:41:11 -0800846 return -ENOSPC;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700847 }
848 ret = smd_write(penv->smd_ch, data, len);
849 if (ret < len) {
850 pr_err("wcnss: failed to write Command %d", len);
851 ret = -ENODEV;
852 }
853 return ret;
854}
855
856static void wcnssctrl_rx_handler(struct work_struct *worker)
857{
858 int len = 0;
859 int rc = 0;
860 unsigned char buf[WCNSS_MAX_FRAME_SIZE];
861 struct smd_msg_hdr *phdr;
862 struct wcnss_version *pversion;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800863 int hw_type;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700864
865 len = smd_read_avail(penv->smd_ch);
866 if (len > WCNSS_MAX_FRAME_SIZE) {
867 pr_err("wcnss: frame larger than the allowed size\n");
868 smd_read(penv->smd_ch, NULL, len);
869 return;
870 }
871 if (len <= 0)
872 return;
873
874 rc = smd_read(penv->smd_ch, buf, len);
875 if (rc < len) {
876 pr_err("wcnss: incomplete data read from smd\n");
877 return;
878 }
879
880 phdr = (struct smd_msg_hdr *)buf;
881
Sameer Thalappilf106a682013-02-16 20:41:11 -0800882 switch (phdr->msg_type) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700883
884 case WCNSS_VERSION_RSP:
885 pversion = (struct wcnss_version *)buf;
886 if (len != sizeof(struct wcnss_version)) {
887 pr_err("wcnss: invalid version data from wcnss %d\n",
888 len);
889 return;
890 }
891 snprintf(penv->wcnss_version, WCNSS_VERSION_LEN,
892 "%02x%02x%02x%02x", pversion->major, pversion->minor,
893 pversion->version, pversion->revision);
894 pr_info("wcnss: version %s\n", penv->wcnss_version);
Sameer Thalappilf106a682013-02-16 20:41:11 -0800895 /* schedule work to download nvbin to ccpu */
896 hw_type = wcnss_hardware_type();
897 switch (hw_type) {
898 case WCNSS_RIVA_HW:
899 /* supported only if riva major >= 1 and minor >= 4 */
900 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
901 pr_info("wcnss: schedule dnld work for riva\n");
902 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
903 }
904 break;
905
906 case WCNSS_PRONTO_HW:
907 /* supported only if pronto major >= 1 and minor >= 4 */
908 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
909 pr_info("wcnss: schedule dnld work for pronto\n");
910 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
911 }
912 break;
913
914 default:
915 pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n",
916 hw_type);
917 break;
918 }
919 break;
920
921 case WCNSS_NVBIN_DNLD_RSP:
922 pr_info("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu\n");
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700923 break;
924
925 default:
Sameer Thalappilf106a682013-02-16 20:41:11 -0800926 pr_err("wcnss: invalid message type %d\n", phdr->msg_type);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700927 }
928 return;
929}
930
931static void wcnss_send_version_req(struct work_struct *worker)
932{
933 struct smd_msg_hdr smd_msg;
934 int ret = 0;
935
Sameer Thalappilf106a682013-02-16 20:41:11 -0800936 smd_msg.msg_type = WCNSS_VERSION_REQ;
937 smd_msg.msg_len = sizeof(smd_msg);
938 ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700939 if (ret < 0)
940 pr_err("wcnss: smd tx failed\n");
941
942 return;
943}
944
Sameer Thalappilf106a682013-02-16 20:41:11 -0800945static void wcnss_nvbin_dnld_req(struct work_struct *worker)
946{
947 int ret = 0;
948 struct nvbin_dnld_req_msg *dnld_req_msg;
949 unsigned short total_fragments = 0;
950 unsigned short count = 0;
951 unsigned short retry_count = 0;
952 unsigned short cur_frag_size = 0;
953 unsigned char *outbuffer = NULL;
954 const void *nv_blob_addr = NULL;
955 unsigned int nv_blob_size = 0;
956 const struct firmware *nv = NULL;
Sameer Thalappilf0d89ba2013-03-11 17:33:20 -0700957 struct device *dev = &penv->pdev->dev;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800958
959 ret = request_firmware(&nv, NVBIN_FILE, dev);
960
961 if (ret || !nv || !nv->data || !nv->size) {
962 pr_err("wcnss: wcnss_nvbin_dnld_req: request_firmware failed for %s\n",
963 NVBIN_FILE);
964 return;
965 }
966
967 /*
968 * First 4 bytes in nv blob is validity bitmap.
969 * We cannot validate nv, so skip those 4 bytes.
970 */
971 nv_blob_addr = nv->data + 4;
972 nv_blob_size = nv->size - 4;
973
974 total_fragments = TOTALFRAGMENTS(nv_blob_size);
975
976 pr_info("wcnss: NV bin size: %d, total_fragments: %d\n",
977 nv_blob_size, total_fragments);
978
979 /* get buffer for nv bin dnld req message */
980 outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) +
981 NV_FRAGMENT_SIZE), GFP_KERNEL);
982
983 if (NULL == outbuffer) {
984 pr_err("wcnss: wcnss_nvbin_dnld_req: failed to get buffer\n");
985 goto err_free_nv;
986 }
987
988 dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
989
990 dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
991
992 for (count = 0; count < total_fragments; count++) {
993 dnld_req_msg->dnld_req_params.frag_number = count;
994
995 if (count == (total_fragments - 1)) {
996 /* last fragment, take care of boundry condition */
997 cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE;
998 if (!cur_frag_size)
999 cur_frag_size = NV_FRAGMENT_SIZE;
1000
1001 dnld_req_msg->dnld_req_params.is_last_fragment = 1;
1002 } else {
1003 cur_frag_size = NV_FRAGMENT_SIZE;
1004 dnld_req_msg->dnld_req_params.is_last_fragment = 0;
1005 }
1006
1007 dnld_req_msg->dnld_req_params.nvbin_buffer_size =
1008 cur_frag_size;
1009
1010 dnld_req_msg->hdr.msg_len =
1011 sizeof(struct nvbin_dnld_req_msg) + cur_frag_size;
1012
1013 /* copy NV fragment */
1014 memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)),
1015 (nv_blob_addr + count * NV_FRAGMENT_SIZE),
1016 cur_frag_size);
1017
1018 ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len);
1019
1020 retry_count = 0;
1021 while ((ret == -ENOSPC) && (retry_count <= 3)) {
1022 pr_debug("wcnss: wcnss_nvbin_dnld_req: smd tx failed, ENOSPC\n");
1023 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1024 count, dnld_req_msg->hdr.msg_len,
1025 total_fragments, retry_count);
1026
1027 /* wait and try again */
1028 msleep(20);
1029 retry_count++;
1030 ret = wcnss_smd_tx(outbuffer,
1031 dnld_req_msg->hdr.msg_len);
1032 }
1033
1034 if (ret < 0) {
1035 pr_err("wcnss: wcnss_nvbin_dnld_req: smd tx failed\n");
1036 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1037 count, dnld_req_msg->hdr.msg_len,
1038 total_fragments, retry_count);
1039 goto err_dnld;
1040 }
1041 }
1042
1043err_dnld:
1044 /* free buffer */
1045 kfree(outbuffer);
1046
1047err_free_nv:
1048 /* release firmware */
1049 release_firmware(nv);
1050
1051 return;
1052}
1053
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001054static int
1055wcnss_trigger_config(struct platform_device *pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001056{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001057 int ret;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001058 struct qcom_wcnss_opts *pdata;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001059 unsigned long wcnss_phys_addr;
1060 int size = 0;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001061 int has_pronto_hw = of_property_read_bool(pdev->dev.of_node,
1062 "qcom,has_pronto_hw");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001063
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001064 /* make sure we are only triggered once */
1065 if (penv->triggered)
1066 return 0;
1067 penv->triggered = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001068
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001069 /* initialize the WCNSS device configuration */
1070 pdata = pdev->dev.platform_data;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001071 if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) {
1072 if (has_pronto_hw) {
1073 has_48mhz_xo = of_property_read_bool(pdev->dev.of_node,
1074 "qcom,has_48mhz_xo");
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001075 } else {
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001076 has_48mhz_xo = pdata->has_48mhz_xo;
1077 }
1078 }
Sameer Thalappilf00dbeb2013-03-01 18:33:30 -08001079 penv->wcnss_hw_type = (has_pronto_hw) ? WCNSS_PRONTO_HW : WCNSS_RIVA_HW;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001080 penv->wlan_config.use_48mhz_xo = has_48mhz_xo;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001081
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001082 penv->thermal_mitigation = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001083 strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN);
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001084
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001085 /* Configure 5 wire GPIOs */
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001086 if (!has_pronto_hw) {
1087 penv->gpios_5wire = platform_get_resource_byname(pdev,
1088 IORESOURCE_IO, "wcnss_gpios_5wire");
1089
1090 /* allocate 5-wire GPIO resources */
1091 if (!penv->gpios_5wire) {
1092 dev_err(&pdev->dev, "insufficient IO resources\n");
1093 ret = -ENOENT;
1094 goto fail_gpio_res;
1095 }
1096 ret = wcnss_gpios_config(penv->gpios_5wire, true);
1097 } else
1098 ret = wcnss_pronto_gpios_config(&pdev->dev, true);
1099
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001100 if (ret) {
1101 dev_err(&pdev->dev, "WCNSS gpios config failed.\n");
1102 goto fail_gpio_res;
1103 }
1104
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001105 /* power up the WCNSS */
1106 ret = wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1107 WCNSS_WLAN_SWITCH_ON);
1108 if (ret) {
1109 dev_err(&pdev->dev, "WCNSS Power-up failed.\n");
1110 goto fail_power;
1111 }
1112
1113 /* trigger initialization of the WCNSS */
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001114 penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001115 if (IS_ERR(penv->pil)) {
1116 dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n");
1117 ret = PTR_ERR(penv->pil);
1118 penv->pil = NULL;
1119 goto fail_pil;
1120 }
1121
1122 /* allocate resources */
1123 penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1124 "wcnss_mmio");
1125 penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1126 "wcnss_wlantx_irq");
1127 penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1128 "wcnss_wlanrx_irq");
1129
1130 if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) {
1131 dev_err(&pdev->dev, "insufficient resources\n");
1132 ret = -ENOENT;
1133 goto fail_res;
1134 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001135 INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
1136 INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001137 INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_req);
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001138
Sameer Thalappilf138da52012-07-30 12:56:37 -07001139 wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");
1140
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001141 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
1142 size = 0x3000;
1143 wcnss_phys_addr = MSM_PRONTO_PHYS;
1144 } else {
1145 wcnss_phys_addr = MSM_RIVA_PHYS;
1146 size = SZ_256;
1147 }
1148
1149 penv->msm_wcnss_base = ioremap(wcnss_phys_addr, size);
1150 if (!penv->msm_wcnss_base) {
1151 ret = -ENOMEM;
1152 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1153 goto fail_wake;
1154 }
1155
Sameer Thalappild3aad702013-01-22 18:52:24 -08001156 if (wcnss_hardware_type() == WCNSS_RIVA_HW) {
1157 penv->riva_ccu_base = ioremap(MSM_RIVA_CCU_BASE, SZ_512);
1158 if (!penv->riva_ccu_base) {
1159 ret = -ENOMEM;
1160 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1161 goto fail_ioremap;
1162 }
1163 } else {
1164 penv->pronto_a2xb_base = ioremap(MSM_PRONTO_A2XB_BASE, SZ_512);
1165 if (!penv->pronto_a2xb_base) {
1166 ret = -ENOMEM;
1167 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1168 goto fail_ioremap;
1169 }
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001170 penv->pronto_ccpu_base = ioremap(MSM_PRONTO_CCPU_BASE, SZ_512);
1171 if (!penv->pronto_ccpu_base) {
1172 ret = -ENOMEM;
1173 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1174 goto fail_ioremap2;
1175 }
Sameer Thalappild3aad702013-01-22 18:52:24 -08001176 }
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001177 penv->cold_boot_done = 1;
Sameer Thalappild3aad702013-01-22 18:52:24 -08001178
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001179 return 0;
1180
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001181fail_ioremap2:
1182 iounmap(penv->pronto_a2xb_base);
Sameer Thalappild3aad702013-01-22 18:52:24 -08001183fail_ioremap:
1184 iounmap(penv->msm_wcnss_base);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001185fail_wake:
1186 wake_lock_destroy(&penv->wcnss_wake_lock);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001187fail_res:
1188 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001189 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001190fail_pil:
1191 wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1192 WCNSS_WLAN_SWITCH_OFF);
1193fail_power:
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001194 if (has_pronto_hw)
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001195 wcnss_pronto_gpios_config(&pdev->dev, false);
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001196 else
1197 wcnss_gpios_config(penv->gpios_5wire, false);
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001198fail_gpio_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001199 penv = NULL;
1200 return ret;
1201}
1202
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001203#ifndef MODULE
1204static int wcnss_node_open(struct inode *inode, struct file *file)
1205{
1206 struct platform_device *pdev;
1207
1208 pr_info(DEVICE " triggered by userspace\n");
1209
1210 pdev = penv->pdev;
1211 return wcnss_trigger_config(pdev);
1212}
1213
1214static const struct file_operations wcnss_node_fops = {
1215 .owner = THIS_MODULE,
1216 .open = wcnss_node_open,
1217};
1218
1219static struct miscdevice wcnss_misc = {
1220 .minor = MISC_DYNAMIC_MINOR,
1221 .name = DEVICE,
1222 .fops = &wcnss_node_fops,
1223};
1224#endif /* ifndef MODULE */
1225
1226
1227static int __devinit
1228wcnss_wlan_probe(struct platform_device *pdev)
1229{
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001230 int ret = 0;
1231
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001232 /* verify we haven't been called more than once */
1233 if (penv) {
1234 dev_err(&pdev->dev, "cannot handle multiple devices.\n");
1235 return -ENODEV;
1236 }
1237
1238 /* create an environment to track the device */
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001239 penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001240 if (!penv) {
1241 dev_err(&pdev->dev, "cannot allocate device memory.\n");
1242 return -ENOMEM;
1243 }
1244 penv->pdev = pdev;
1245
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001246 /* register sysfs entries */
1247 ret = wcnss_create_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001248 if (ret) {
1249 penv = NULL;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001250 return -ENOENT;
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001251 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001252
1253
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001254#ifdef MODULE
1255
Sameer Thalappild3aad702013-01-22 18:52:24 -08001256 /* Since we were built as a module, we are running because
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001257 * the module was loaded, therefore we assume userspace
1258 * applications are available to service PIL, so we can
1259 * trigger the WCNSS configuration now
1260 */
1261 pr_info(DEVICE " probed in MODULE mode\n");
1262 return wcnss_trigger_config(pdev);
1263
1264#else
1265
Sameer Thalappild3aad702013-01-22 18:52:24 -08001266 /* Since we were built into the kernel we'll be called as part
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001267 * of kernel initialization. We don't know if userspace
1268 * applications are available to service PIL at this time
1269 * (they probably are not), so we simply create a device node
1270 * here. When userspace is available it should touch the
1271 * device so that we know that WCNSS configuration can take
1272 * place
1273 */
1274 pr_info(DEVICE " probed in built-in mode\n");
1275 return misc_register(&wcnss_misc);
1276
1277#endif
1278}
1279
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001280static int __devexit
1281wcnss_wlan_remove(struct platform_device *pdev)
1282{
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001283 wcnss_remove_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001284 penv = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001285 return 0;
1286}
1287
1288
1289static const struct dev_pm_ops wcnss_wlan_pm_ops = {
1290 .suspend = wcnss_wlan_suspend,
1291 .resume = wcnss_wlan_resume,
1292};
1293
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001294#ifdef CONFIG_WCNSS_CORE_PRONTO
1295static struct of_device_id msm_wcnss_pronto_match[] = {
1296 {.compatible = "qcom,wcnss_wlan"},
1297 {}
1298};
1299#endif
1300
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001301static struct platform_driver wcnss_wlan_driver = {
1302 .driver = {
1303 .name = DEVICE,
1304 .owner = THIS_MODULE,
1305 .pm = &wcnss_wlan_pm_ops,
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001306#ifdef CONFIG_WCNSS_CORE_PRONTO
1307 .of_match_table = msm_wcnss_pronto_match,
1308#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001309 },
1310 .probe = wcnss_wlan_probe,
1311 .remove = __devexit_p(wcnss_wlan_remove),
1312};
1313
1314static int __init wcnss_wlan_init(void)
1315{
Sameer Thalappil24db5282012-09-10 11:58:33 -07001316 int ret = 0;
1317
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001318 platform_driver_register(&wcnss_wlan_driver);
1319 platform_driver_register(&wcnss_wlan_ctrl_driver);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001320 platform_driver_register(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001321
Sameer Thalappil24db5282012-09-10 11:58:33 -07001322#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1323 ret = wcnss_prealloc_init();
1324 if (ret < 0)
1325 pr_err("wcnss: pre-allocation failed\n");
1326#endif
1327
1328 return ret;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001329}
1330
1331static void __exit wcnss_wlan_exit(void)
1332{
1333 if (penv) {
1334 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001335 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001336
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001337
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001338 penv = NULL;
1339 }
1340
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001341 platform_driver_unregister(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001342 platform_driver_unregister(&wcnss_wlan_ctrl_driver);
1343 platform_driver_unregister(&wcnss_wlan_driver);
Sameer Thalappil24db5282012-09-10 11:58:33 -07001344#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1345 wcnss_prealloc_deinit();
1346#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001347}
1348
1349module_init(wcnss_wlan_init);
1350module_exit(wcnss_wlan_exit);
1351
1352MODULE_LICENSE("GPL v2");
1353MODULE_VERSION(VERSION);
1354MODULE_DESCRIPTION(DEVICE "Driver");