blob: 0158235bd07433a13e695450a5f0deb6bfaf8c87 [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
Sheng Fangbb421672013-03-19 08:27:28 +080051static int do_not_cancel_vote = WCNSS_CONFIG_UNSPECIFIED;
52module_param(do_not_cancel_vote, int, S_IWUSR | S_IRUGO);
53MODULE_PARM_DESC(do_not_cancel_vote, "Do not cancel votes for wcnss");
54
Sameer Thalappiled3f7da2012-11-01 12:54:01 -070055static DEFINE_SPINLOCK(reg_spinlock);
56
57#define MSM_RIVA_PHYS 0x03204000
58#define MSM_PRONTO_PHYS 0xfb21b000
59
60#define RIVA_SPARE_OFFSET 0x0b4
61#define RIVA_SUSPEND_BIT BIT(24)
62
Sameer Thalappil77716e52012-11-08 13:41:04 -080063#define MSM_RIVA_CCU_BASE 0x03200800
64
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080065#define CCU_RIVA_INVALID_ADDR_OFFSET 0x100
66#define CCU_RIVA_LAST_ADDR0_OFFSET 0x104
67#define CCU_RIVA_LAST_ADDR1_OFFSET 0x108
68#define CCU_RIVA_LAST_ADDR2_OFFSET 0x10c
Sameer Thalappil77716e52012-11-08 13:41:04 -080069
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080070#define MSM_PRONTO_A2XB_BASE 0xfb100400
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080071#define A2XB_CFG_OFFSET 0x00
72#define A2XB_INT_SRC_OFFSET 0x0c
73#define A2XB_TSTBUS_CTRL_OFFSET 0x14
74#define A2XB_TSTBUS_OFFSET 0x18
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080075#define A2XB_ERR_INFO_OFFSET 0x1c
76
Sameer Thalappil16cae9b2013-03-04 18:02:50 -080077#define WCNSS_TSTBUS_CTRL_EN BIT(0)
78#define WCNSS_TSTBUS_CTRL_AXIM (0x02 << 1)
79#define WCNSS_TSTBUS_CTRL_CMDFIFO (0x03 << 1)
80#define WCNSS_TSTBUS_CTRL_WRFIFO (0x04 << 1)
81#define WCNSS_TSTBUS_CTRL_RDFIFO (0x05 << 1)
82#define WCNSS_TSTBUS_CTRL_CTRL (0x07 << 1)
83#define WCNSS_TSTBUS_CTRL_AXIM_CFG0 (0x00 << 6)
84#define WCNSS_TSTBUS_CTRL_AXIM_CFG1 (0x01 << 6)
85#define WCNSS_TSTBUS_CTRL_CTRL_CFG0 (0x00 << 12)
86#define WCNSS_TSTBUS_CTRL_CTRL_CFG1 (0x01 << 12)
87
88#define MSM_PRONTO_CCPU_BASE 0xfb205050
89#define CCU_PRONTO_INVALID_ADDR_OFFSET 0x08
90#define CCU_PRONTO_LAST_ADDR0_OFFSET 0x0c
91#define CCU_PRONTO_LAST_ADDR1_OFFSET 0x10
92#define CCU_PRONTO_LAST_ADDR2_OFFSET 0x14
93
Sameer Thalappilf5007652013-03-21 16:25:36 -070094#define MSM_PRONTO_CCPU_CTL_BASE 0xfb21d000
95#define BOOT_REMAP_OFFSET 0x04
96
Sameer Thalappileeb8c412012-08-10 17:17:09 -070097#define WCNSS_CTRL_CHANNEL "WCNSS_CTRL"
98#define WCNSS_MAX_FRAME_SIZE 500
99#define WCNSS_VERSION_LEN 30
100
101/* message types */
102#define WCNSS_CTRL_MSG_START 0x01000000
103#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0)
104#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1)
Sameer Thalappilf106a682013-02-16 20:41:11 -0800105#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2)
106#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3)
107
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700108
109#define VALID_VERSION(version) \
110 ((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0)
111
112struct smd_msg_hdr {
Sameer Thalappilf106a682013-02-16 20:41:11 -0800113 unsigned int msg_type;
114 unsigned int msg_len;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700115};
116
117struct wcnss_version {
118 struct smd_msg_hdr hdr;
119 unsigned char major;
120 unsigned char minor;
121 unsigned char version;
122 unsigned char revision;
123};
124
Sameer Thalappilf106a682013-02-16 20:41:11 -0800125#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
126
127/*
128 * On SMD channel 4K of maximum data can be transferred, including message
129 * header, so NV fragment size as next multiple of 1Kb is 3Kb.
130 */
131#define NV_FRAGMENT_SIZE 3072
132
133/* Macro to find the total number fragments of the NV bin Image */
134#define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \
135 (x / NV_FRAGMENT_SIZE) : ((x / NV_FRAGMENT_SIZE) + 1))
136
137struct nvbin_dnld_req_params {
138 /*
139 * Fragment sequence number of the NV bin Image. NV Bin Image
140 * might not fit into one message due to size limitation of
141 * the SMD channel FIFO so entire NV blob is chopped into
142 * multiple fragments starting with seqeunce number 0. The
143 * last fragment is indicated by marking is_last_fragment field
144 * to 1. At receiving side, NV blobs would be concatenated
145 * together without any padding bytes in between.
146 */
147 unsigned short frag_number;
148
149 /*
150 * When set to 1 it indicates that no more fragments will
151 * be sent. Receiver shall send back response message after
152 * the last fragment.
153 */
154 unsigned short is_last_fragment;
155
156 /* NV Image size (number of bytes) */
157 unsigned int nvbin_buffer_size;
158
159 /*
160 * Following the 'nvbin_buffer_size', there should be
161 * nvbin_buffer_size bytes of NV bin Image i.e.
162 * uint8[nvbin_buffer_size].
163 */
164};
165
166struct nvbin_dnld_req_msg {
167 /*
168 * Note: The length specified in nvbin_dnld_req_msg messages
169 * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) +
170 * nvbin_buffer_size.
171 */
172 struct smd_msg_hdr hdr;
173 struct nvbin_dnld_req_params dnld_req_params;
174};
175
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700176static struct {
177 struct platform_device *pdev;
178 void *pil;
179 struct resource *mmio_res;
180 struct resource *tx_irq_res;
181 struct resource *rx_irq_res;
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700182 struct resource *gpios_5wire;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700183 const struct dev_pm_ops *pm_ops;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800184 int triggered;
185 int smd_channel_ready;
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800186 int cold_boot_done;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700187 smd_channel_t *smd_ch;
188 unsigned char wcnss_version[WCNSS_VERSION_LEN];
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800189 unsigned int serial_number;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800190 int thermal_mitigation;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700191 enum wcnss_hw_type wcnss_hw_type;
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700192 void (*tm_notify)(struct device *, int);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700193 struct wcnss_wlan_config wlan_config;
David Collinsb727f7d2011-09-12 11:54:49 -0700194 struct delayed_work wcnss_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700195 struct work_struct wcnssctrl_version_work;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800196 struct work_struct wcnssctrl_nvbin_dnld_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700197 struct work_struct wcnssctrl_rx_work;
Sameer Thalappilf138da52012-07-30 12:56:37 -0700198 struct wake_lock wcnss_wake_lock;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700199 void __iomem *msm_wcnss_base;
Sameer Thalappild3aad702013-01-22 18:52:24 -0800200 void __iomem *riva_ccu_base;
201 void __iomem *pronto_a2xb_base;
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800202 void __iomem *pronto_ccpu_base;
Sameer Thalappilf5007652013-03-21 16:25:36 -0700203 void __iomem *pronto_ctl_base;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700204} *penv = NULL;
205
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800206static ssize_t wcnss_serial_number_show(struct device *dev,
207 struct device_attribute *attr, char *buf)
208{
209 if (!penv)
210 return -ENODEV;
211
212 return scnprintf(buf, PAGE_SIZE, "%08X\n", penv->serial_number);
213}
214
215static ssize_t wcnss_serial_number_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700216 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800217{
218 unsigned int value;
219
220 if (!penv)
221 return -ENODEV;
222
223 if (sscanf(buf, "%08X", &value) != 1)
224 return -EINVAL;
225
226 penv->serial_number = value;
227 return count;
228}
229
230static DEVICE_ATTR(serial_number, S_IRUSR | S_IWUSR,
231 wcnss_serial_number_show, wcnss_serial_number_store);
232
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800233
234static ssize_t wcnss_thermal_mitigation_show(struct device *dev,
235 struct device_attribute *attr, char *buf)
236{
237 if (!penv)
238 return -ENODEV;
239
240 return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation);
241}
242
243static ssize_t wcnss_thermal_mitigation_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700244 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800245{
246 int value;
247
248 if (!penv)
249 return -ENODEV;
250
251 if (sscanf(buf, "%d", &value) != 1)
252 return -EINVAL;
253 penv->thermal_mitigation = value;
Jeff Johnson61374652012-04-16 20:51:48 -0700254 if (penv->tm_notify)
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700255 (penv->tm_notify)(dev, value);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800256 return count;
257}
258
259static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR,
260 wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store);
261
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700262
263static ssize_t wcnss_version_show(struct device *dev,
264 struct device_attribute *attr, char *buf)
265{
266 if (!penv)
267 return -ENODEV;
268
269 return scnprintf(buf, PAGE_SIZE, "%s", penv->wcnss_version);
270}
271
272static DEVICE_ATTR(wcnss_version, S_IRUSR,
273 wcnss_version_show, NULL);
274
Sameer Thalappil77716e52012-11-08 13:41:04 -0800275/* wcnss_reset_intr() is invoked when host drivers fails to
276 * communicate with WCNSS over SMD; so logging these registers
Sameer Thalappild3aad702013-01-22 18:52:24 -0800277 * helps to know WCNSS failure reason
278 */
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800279void wcnss_riva_log_debug_regs(void)
Sameer Thalappil77716e52012-11-08 13:41:04 -0800280{
Sameer Thalappil77716e52012-11-08 13:41:04 -0800281 void __iomem *ccu_reg;
282 u32 reg = 0;
283
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800284 ccu_reg = penv->riva_ccu_base + CCU_RIVA_INVALID_ADDR_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_INVALID_ADDR %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_ADDR0_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_ADDR0 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800291
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800292 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR1_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800293 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800294 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800295
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800296 ccu_reg = penv->riva_ccu_base + CCU_RIVA_LAST_ADDR2_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800297 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800298 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800299
Sameer Thalappil77716e52012-11-08 13:41:04 -0800300}
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800301EXPORT_SYMBOL(wcnss_riva_log_debug_regs);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800302
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800303/* Log pronto debug registers before sending reset interrupt */
304void wcnss_pronto_log_debug_regs(void)
305{
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800306 void __iomem *reg_addr, *tst_addr, *tst_ctrl_addr;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800307 u32 reg = 0;
308
Sameer Thalappild3aad702013-01-22 18:52:24 -0800309 reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800310 reg = readl_relaxed(reg_addr);
311 pr_info_ratelimited("%s: A2XB_CFG_OFFSET %08x\n", __func__, reg);
312
Sameer Thalappild3aad702013-01-22 18:52:24 -0800313 reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800314 reg = readl_relaxed(reg_addr);
315 pr_info_ratelimited("%s: A2XB_INT_SRC_OFFSET %08x\n", __func__, reg);
316
Sameer Thalappild3aad702013-01-22 18:52:24 -0800317 reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800318 reg = readl_relaxed(reg_addr);
319 pr_info_ratelimited("%s: A2XB_ERR_INFO_OFFSET %08x\n", __func__, reg);
320
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800321 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_INVALID_ADDR_OFFSET;
322 reg = readl_relaxed(reg_addr);
323 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
324
325 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR0_OFFSET;
326 reg = readl_relaxed(reg_addr);
327 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
328
329 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR1_OFFSET;
330 reg = readl_relaxed(reg_addr);
331 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
332
333 reg_addr = penv->pronto_ccpu_base + CCU_PRONTO_LAST_ADDR2_OFFSET;
334 reg = readl_relaxed(reg_addr);
335 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
336
Sameer Thalappilf5007652013-03-21 16:25:36 -0700337 reg_addr = penv->pronto_ctl_base + BOOT_REMAP_OFFSET;
338 reg = readl_relaxed(reg_addr);
339 pr_info_ratelimited("%s: BOOT_REMAP_ADDR %08x\n", __func__, reg);
340
Sameer Thalappil16cae9b2013-03-04 18:02:50 -0800341 tst_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_OFFSET;
342 tst_ctrl_addr = penv->pronto_a2xb_base + A2XB_TSTBUS_CTRL_OFFSET;
343
344 /* read data FIFO */
345 reg = 0;
346 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_RDFIFO;
347 writel_relaxed(reg, tst_ctrl_addr);
348 reg = readl_relaxed(tst_addr);
349 pr_info_ratelimited("%s: Read data FIFO testbus %08x\n",
350 __func__, reg);
351
352 /* command FIFO */
353 reg = 0;
354 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CMDFIFO;
355 writel_relaxed(reg, tst_ctrl_addr);
356 reg = readl_relaxed(tst_addr);
357 pr_info_ratelimited("%s: Command FIFO testbus %08x\n",
358 __func__, reg);
359
360 /* write data FIFO */
361 reg = 0;
362 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_WRFIFO;
363 writel_relaxed(reg, tst_ctrl_addr);
364 reg = readl_relaxed(tst_addr);
365 pr_info_ratelimited("%s: Rrite data FIFO testbus %08x\n",
366 __func__, reg);
367
368 /* AXIM SEL CFG0 */
369 reg = 0;
370 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
371 WCNSS_TSTBUS_CTRL_AXIM_CFG0;
372 writel_relaxed(reg, tst_ctrl_addr);
373 reg = readl_relaxed(tst_addr);
374 pr_info_ratelimited("%s: AXIM SEL CFG0 testbus %08x\n",
375 __func__, reg);
376
377 /* AXIM SEL CFG1 */
378 reg = 0;
379 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_AXIM |
380 WCNSS_TSTBUS_CTRL_AXIM_CFG1;
381 writel_relaxed(reg, tst_ctrl_addr);
382 reg = readl_relaxed(tst_addr);
383 pr_info_ratelimited("%s: AXIM SEL CFG1 testbus %08x\n",
384 __func__, reg);
385
386 /* CTRL SEL CFG0 */
387 reg = 0;
388 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
389 WCNSS_TSTBUS_CTRL_CTRL_CFG0;
390 writel_relaxed(reg, tst_ctrl_addr);
391 reg = readl_relaxed(tst_addr);
392 pr_info_ratelimited("%s: CTRL SEL CFG0 testbus %08x\n",
393 __func__, reg);
394
395 /* CTRL SEL CFG1 */
396 reg = 0;
397 reg = reg | WCNSS_TSTBUS_CTRL_EN | WCNSS_TSTBUS_CTRL_CTRL |
398 WCNSS_TSTBUS_CTRL_CTRL_CFG1;
399 writel_relaxed(reg, tst_ctrl_addr);
400 reg = readl_relaxed(tst_addr);
401 pr_info_ratelimited("%s: CTRL SEL CFG1 testbus %08x\n", __func__, reg);
402
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800403}
404EXPORT_SYMBOL(wcnss_pronto_log_debug_regs);
405
406/* interface to reset wcnss by sending the reset interrupt */
Sameer Thalappil19e02352012-09-24 15:26:12 -0700407void wcnss_reset_intr(void)
408{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800409 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
410 wcnss_pronto_log_debug_regs();
Sameer Thalappil34920762013-03-21 15:43:28 -0700411 wmb();
412 __raw_writel(1 << 16, MSM_APCS_GCC_BASE + 0x8);
413 } else {
414 wcnss_riva_log_debug_regs();
415 wmb();
416 __raw_writel(1 << 24, MSM_APCS_GCC_BASE + 0x8);
Sameer Thalappil19e02352012-09-24 15:26:12 -0700417 }
418}
419EXPORT_SYMBOL(wcnss_reset_intr);
420
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800421static int wcnss_create_sysfs(struct device *dev)
422{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800423 int ret;
424
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800425 if (!dev)
426 return -ENODEV;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800427
428 ret = device_create_file(dev, &dev_attr_serial_number);
429 if (ret)
430 return ret;
431
432 ret = device_create_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700433 if (ret)
434 goto remove_serial;
435
436 ret = device_create_file(dev, &dev_attr_wcnss_version);
437 if (ret)
438 goto remove_thermal;
439
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800440 return 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700441
442remove_thermal:
443 device_remove_file(dev, &dev_attr_thermal_mitigation);
444remove_serial:
445 device_remove_file(dev, &dev_attr_serial_number);
446
447 return ret;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800448}
449
450static void wcnss_remove_sysfs(struct device *dev)
451{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800452 if (dev) {
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800453 device_remove_file(dev, &dev_attr_serial_number);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800454 device_remove_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700455 device_remove_file(dev, &dev_attr_wcnss_version);
456 }
457}
458static void wcnss_smd_notify_event(void *data, unsigned int event)
459{
460 int len = 0;
461
462 if (penv != data) {
463 pr_err("wcnss: invalid env pointer in smd callback\n");
464 return;
465 }
466 switch (event) {
467 case SMD_EVENT_DATA:
468 len = smd_read_avail(penv->smd_ch);
469 if (len < 0)
470 pr_err("wcnss: failed to read from smd %d\n", len);
471 schedule_work(&penv->wcnssctrl_rx_work);
472 break;
473
474 case SMD_EVENT_OPEN:
475 pr_debug("wcnss: opening WCNSS SMD channel :%s",
476 WCNSS_CTRL_CHANNEL);
477 if (!VALID_VERSION(penv->wcnss_version))
478 schedule_work(&penv->wcnssctrl_version_work);
479 break;
480
481 case SMD_EVENT_CLOSE:
482 pr_debug("wcnss: closing WCNSS SMD channel :%s",
483 WCNSS_CTRL_CHANNEL);
484 break;
485
486 default:
487 break;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800488 }
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800489}
490
David Collinsb727f7d2011-09-12 11:54:49 -0700491static void wcnss_post_bootup(struct work_struct *work)
492{
Sheng Fangbb421672013-03-19 08:27:28 +0800493 if (do_not_cancel_vote == 1) {
494 pr_info("%s: Keeping APPS vote for Iris & WCNSS\n", __func__);
495 return;
496 }
497
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700498 pr_info("%s: Cancel APPS vote for Iris & WCNSS\n", __func__);
David Collinsb727f7d2011-09-12 11:54:49 -0700499
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700500 /* Since WCNSS is up, cancel any APPS vote for Iris & WCNSS VREGs */
David Collinsb727f7d2011-09-12 11:54:49 -0700501 wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config,
502 WCNSS_WLAN_SWITCH_OFF);
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700503
504}
505
506static int
507wcnss_pronto_gpios_config(struct device *dev, bool enable)
508{
509 int rc = 0;
510 int i, j;
511 int WCNSS_WLAN_NUM_GPIOS = 5;
512
513 for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) {
514 int gpio = of_get_gpio(dev->of_node, i);
515 if (enable) {
516 rc = gpio_request(gpio, "wcnss_wlan");
517 if (rc) {
518 pr_err("WCNSS gpio_request %d err %d\n",
519 gpio, rc);
520 goto fail;
521 }
522 } else
523 gpio_free(gpio);
524 }
525
526 return rc;
527
528fail:
529 for (j = WCNSS_WLAN_NUM_GPIOS-1; j >= 0; j--) {
530 int gpio = of_get_gpio(dev->of_node, i);
531 gpio_free(gpio);
532 }
533 return rc;
David Collinsb727f7d2011-09-12 11:54:49 -0700534}
535
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700536static int
537wcnss_gpios_config(struct resource *gpios_5wire, bool enable)
538{
539 int i, j;
540 int rc = 0;
541
542 for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) {
543 if (enable) {
544 rc = gpio_request(i, gpios_5wire->name);
545 if (rc) {
546 pr_err("WCNSS gpio_request %d err %d\n", i, rc);
547 goto fail;
548 }
549 } else
550 gpio_free(i);
551 }
552
553 return rc;
554
555fail:
556 for (j = i-1; j >= gpios_5wire->start; j--)
557 gpio_free(j);
558 return rc;
559}
560
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700561static int __devinit
562wcnss_wlan_ctrl_probe(struct platform_device *pdev)
563{
Sameer Thalappil667f43f2011-10-10 11:12:54 -0700564 if (!penv)
565 return -ENODEV;
566
567 penv->smd_channel_ready = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700568
569 pr_info("%s: SMD ctrl channel up\n", __func__);
570
David Collinsb727f7d2011-09-12 11:54:49 -0700571 /* Schedule a work to do any post boot up activity */
572 INIT_DELAYED_WORK(&penv->wcnss_work, wcnss_post_bootup);
573 schedule_delayed_work(&penv->wcnss_work, msecs_to_jiffies(10000));
574
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700575 return 0;
576}
577
Sameer Thalappil13f45182012-07-23 18:02:45 -0700578void wcnss_flush_delayed_boot_votes()
579{
Sameer Thalappilf106a682013-02-16 20:41:11 -0800580 flush_delayed_work(&penv->wcnss_work);
Sameer Thalappil13f45182012-07-23 18:02:45 -0700581}
582EXPORT_SYMBOL(wcnss_flush_delayed_boot_votes);
583
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700584static int __devexit
585wcnss_wlan_ctrl_remove(struct platform_device *pdev)
586{
587 if (penv)
588 penv->smd_channel_ready = 0;
589
590 pr_info("%s: SMD ctrl channel down\n", __func__);
591
592 return 0;
593}
594
595
596static struct platform_driver wcnss_wlan_ctrl_driver = {
597 .driver = {
598 .name = "WLAN_CTRL",
599 .owner = THIS_MODULE,
600 },
601 .probe = wcnss_wlan_ctrl_probe,
602 .remove = __devexit_p(wcnss_wlan_ctrl_remove),
603};
604
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700605static int __devexit
606wcnss_ctrl_remove(struct platform_device *pdev)
607{
608 if (penv && penv->smd_ch)
609 smd_close(penv->smd_ch);
610
611 return 0;
612}
613
614static int __devinit
615wcnss_ctrl_probe(struct platform_device *pdev)
616{
617 int ret = 0;
618
619 if (!penv)
620 return -ENODEV;
621
622 ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS,
623 &penv->smd_ch, penv, wcnss_smd_notify_event);
624 if (ret < 0) {
625 pr_err("wcnss: cannot open the smd command channel %s: %d\n",
626 WCNSS_CTRL_CHANNEL, ret);
627 return -ENODEV;
628 }
629 smd_disable_read_intr(penv->smd_ch);
630
631 return 0;
632}
633
634/* platform device for WCNSS_CTRL SMD channel */
635static struct platform_driver wcnss_ctrl_driver = {
636 .driver = {
637 .name = "WCNSS_CTRL",
638 .owner = THIS_MODULE,
639 },
640 .probe = wcnss_ctrl_probe,
641 .remove = __devexit_p(wcnss_ctrl_remove),
642};
643
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700644struct device *wcnss_wlan_get_device(void)
645{
646 if (penv && penv->pdev && penv->smd_channel_ready)
647 return &penv->pdev->dev;
648 return NULL;
649}
650EXPORT_SYMBOL(wcnss_wlan_get_device);
651
Sameer Thalappil409ed352011-12-07 10:53:31 -0800652struct platform_device *wcnss_get_platform_device(void)
653{
654 if (penv && penv->pdev)
655 return penv->pdev;
656 return NULL;
657}
658EXPORT_SYMBOL(wcnss_get_platform_device);
659
660struct wcnss_wlan_config *wcnss_get_wlan_config(void)
661{
662 if (penv && penv->pdev)
663 return &penv->wlan_config;
664 return NULL;
665}
666EXPORT_SYMBOL(wcnss_get_wlan_config);
667
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700668struct resource *wcnss_wlan_get_memory_map(struct device *dev)
669{
670 if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready)
671 return penv->mmio_res;
672 return NULL;
673}
674EXPORT_SYMBOL(wcnss_wlan_get_memory_map);
675
676int wcnss_wlan_get_dxe_tx_irq(struct device *dev)
677{
678 if (penv && dev && (dev == &penv->pdev->dev) &&
679 penv->tx_irq_res && penv->smd_channel_ready)
680 return penv->tx_irq_res->start;
681 return WCNSS_WLAN_IRQ_INVALID;
682}
683EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq);
684
685int wcnss_wlan_get_dxe_rx_irq(struct device *dev)
686{
687 if (penv && dev && (dev == &penv->pdev->dev) &&
688 penv->rx_irq_res && penv->smd_channel_ready)
689 return penv->rx_irq_res->start;
690 return WCNSS_WLAN_IRQ_INVALID;
691}
692EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq);
693
694void wcnss_wlan_register_pm_ops(struct device *dev,
695 const struct dev_pm_ops *pm_ops)
696{
697 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops)
698 penv->pm_ops = pm_ops;
699}
700EXPORT_SYMBOL(wcnss_wlan_register_pm_ops);
701
Sameer Thalappilecdd1002011-09-09 10:52:27 -0700702void wcnss_wlan_unregister_pm_ops(struct device *dev,
703 const struct dev_pm_ops *pm_ops)
704{
705 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) {
706 if (pm_ops->suspend != penv->pm_ops->suspend ||
707 pm_ops->resume != penv->pm_ops->resume)
708 pr_err("PM APIs dont match with registered APIs\n");
709 penv->pm_ops = NULL;
710 }
711}
712EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops);
713
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700714void wcnss_register_thermal_mitigation(struct device *dev,
715 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700716{
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700717 if (penv && dev && tm_notify)
Jeff Johnson61374652012-04-16 20:51:48 -0700718 penv->tm_notify = tm_notify;
719}
720EXPORT_SYMBOL(wcnss_register_thermal_mitigation);
721
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700722void wcnss_unregister_thermal_mitigation(
723 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700724{
725 if (penv && tm_notify) {
726 if (tm_notify != penv->tm_notify)
727 pr_err("tm_notify doesn't match registered\n");
728 penv->tm_notify = NULL;
729 }
730}
731EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation);
732
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800733unsigned int wcnss_get_serial_number(void)
734{
735 if (penv)
736 return penv->serial_number;
737 return 0;
738}
739EXPORT_SYMBOL(wcnss_get_serial_number);
740
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700741static int enable_wcnss_suspend_notify;
742
743static int enable_wcnss_suspend_notify_set(const char *val,
744 struct kernel_param *kp)
745{
746 int ret;
747
748 ret = param_set_int(val, kp);
749 if (ret)
750 return ret;
751
752 if (enable_wcnss_suspend_notify)
753 pr_debug("Suspend notification activated for wcnss\n");
754
755 return 0;
756}
757module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set,
758 param_get_int, &enable_wcnss_suspend_notify, S_IRUGO | S_IWUSR);
759
760
761void wcnss_suspend_notify(void)
762{
763 void __iomem *pmu_spare_reg;
764 u32 reg = 0;
765 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700766
767 if (!enable_wcnss_suspend_notify)
768 return;
769
770 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
771 return;
772
773 /* For Riva */
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700774 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
775 spin_lock_irqsave(&reg_spinlock, flags);
776 reg = readl_relaxed(pmu_spare_reg);
777 reg |= RIVA_SUSPEND_BIT;
778 writel_relaxed(reg, pmu_spare_reg);
779 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700780}
781EXPORT_SYMBOL(wcnss_suspend_notify);
782
783void wcnss_resume_notify(void)
784{
785 void __iomem *pmu_spare_reg;
786 u32 reg = 0;
787 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700788
789 if (!enable_wcnss_suspend_notify)
790 return;
791
792 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
793 return;
794
795 /* For Riva */
796 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
797
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700798 spin_lock_irqsave(&reg_spinlock, flags);
799 reg = readl_relaxed(pmu_spare_reg);
800 reg &= ~RIVA_SUSPEND_BIT;
801 writel_relaxed(reg, pmu_spare_reg);
802 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700803}
804EXPORT_SYMBOL(wcnss_resume_notify);
805
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700806static int wcnss_wlan_suspend(struct device *dev)
807{
808 if (penv && dev && (dev == &penv->pdev->dev) &&
809 penv->smd_channel_ready &&
810 penv->pm_ops && penv->pm_ops->suspend)
811 return penv->pm_ops->suspend(dev);
812 return 0;
813}
814
815static int wcnss_wlan_resume(struct device *dev)
816{
817 if (penv && dev && (dev == &penv->pdev->dev) &&
818 penv->smd_channel_ready &&
819 penv->pm_ops && penv->pm_ops->resume)
820 return penv->pm_ops->resume(dev);
821 return 0;
822}
823
Sameer Thalappilf138da52012-07-30 12:56:37 -0700824void wcnss_prevent_suspend()
825{
826 if (penv)
827 wake_lock(&penv->wcnss_wake_lock);
828}
829EXPORT_SYMBOL(wcnss_prevent_suspend);
830
831void wcnss_allow_suspend()
832{
833 if (penv)
834 wake_unlock(&penv->wcnss_wake_lock);
835}
836EXPORT_SYMBOL(wcnss_allow_suspend);
837
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700838int wcnss_hardware_type(void)
839{
840 if (penv)
841 return penv->wcnss_hw_type;
842 else
843 return -ENODEV;
844}
845EXPORT_SYMBOL(wcnss_hardware_type);
846
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800847int wcnss_cold_boot_done(void)
848{
849 if (penv)
850 return penv->cold_boot_done;
851 else
852 return -ENODEV;
853}
854EXPORT_SYMBOL(wcnss_cold_boot_done);
855
856
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700857static int wcnss_smd_tx(void *data, int len)
858{
859 int ret = 0;
860
861 ret = smd_write_avail(penv->smd_ch);
862 if (ret < len) {
863 pr_err("wcnss: no space available for smd frame\n");
Sameer Thalappilf106a682013-02-16 20:41:11 -0800864 return -ENOSPC;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700865 }
866 ret = smd_write(penv->smd_ch, data, len);
867 if (ret < len) {
868 pr_err("wcnss: failed to write Command %d", len);
869 ret = -ENODEV;
870 }
871 return ret;
872}
873
874static void wcnssctrl_rx_handler(struct work_struct *worker)
875{
876 int len = 0;
877 int rc = 0;
878 unsigned char buf[WCNSS_MAX_FRAME_SIZE];
879 struct smd_msg_hdr *phdr;
880 struct wcnss_version *pversion;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800881 int hw_type;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700882
883 len = smd_read_avail(penv->smd_ch);
884 if (len > WCNSS_MAX_FRAME_SIZE) {
885 pr_err("wcnss: frame larger than the allowed size\n");
886 smd_read(penv->smd_ch, NULL, len);
887 return;
888 }
889 if (len <= 0)
890 return;
891
892 rc = smd_read(penv->smd_ch, buf, len);
893 if (rc < len) {
894 pr_err("wcnss: incomplete data read from smd\n");
895 return;
896 }
897
898 phdr = (struct smd_msg_hdr *)buf;
899
Sameer Thalappilf106a682013-02-16 20:41:11 -0800900 switch (phdr->msg_type) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700901
902 case WCNSS_VERSION_RSP:
903 pversion = (struct wcnss_version *)buf;
904 if (len != sizeof(struct wcnss_version)) {
905 pr_err("wcnss: invalid version data from wcnss %d\n",
906 len);
907 return;
908 }
909 snprintf(penv->wcnss_version, WCNSS_VERSION_LEN,
910 "%02x%02x%02x%02x", pversion->major, pversion->minor,
911 pversion->version, pversion->revision);
912 pr_info("wcnss: version %s\n", penv->wcnss_version);
Sameer Thalappilf106a682013-02-16 20:41:11 -0800913 /* schedule work to download nvbin to ccpu */
914 hw_type = wcnss_hardware_type();
915 switch (hw_type) {
916 case WCNSS_RIVA_HW:
917 /* supported only if riva major >= 1 and minor >= 4 */
918 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
919 pr_info("wcnss: schedule dnld work for riva\n");
920 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
921 }
922 break;
923
924 case WCNSS_PRONTO_HW:
925 /* supported only if pronto major >= 1 and minor >= 4 */
926 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
927 pr_info("wcnss: schedule dnld work for pronto\n");
928 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
929 }
930 break;
931
932 default:
933 pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n",
934 hw_type);
935 break;
936 }
937 break;
938
939 case WCNSS_NVBIN_DNLD_RSP:
940 pr_info("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu\n");
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700941 break;
942
943 default:
Sameer Thalappilf106a682013-02-16 20:41:11 -0800944 pr_err("wcnss: invalid message type %d\n", phdr->msg_type);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700945 }
946 return;
947}
948
949static void wcnss_send_version_req(struct work_struct *worker)
950{
951 struct smd_msg_hdr smd_msg;
952 int ret = 0;
953
Sameer Thalappilf106a682013-02-16 20:41:11 -0800954 smd_msg.msg_type = WCNSS_VERSION_REQ;
955 smd_msg.msg_len = sizeof(smd_msg);
956 ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700957 if (ret < 0)
958 pr_err("wcnss: smd tx failed\n");
959
960 return;
961}
962
Sameer Thalappilf106a682013-02-16 20:41:11 -0800963static void wcnss_nvbin_dnld_req(struct work_struct *worker)
964{
965 int ret = 0;
966 struct nvbin_dnld_req_msg *dnld_req_msg;
967 unsigned short total_fragments = 0;
968 unsigned short count = 0;
969 unsigned short retry_count = 0;
970 unsigned short cur_frag_size = 0;
971 unsigned char *outbuffer = NULL;
972 const void *nv_blob_addr = NULL;
973 unsigned int nv_blob_size = 0;
974 const struct firmware *nv = NULL;
Sameer Thalappilf0d89ba2013-03-11 17:33:20 -0700975 struct device *dev = &penv->pdev->dev;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800976
977 ret = request_firmware(&nv, NVBIN_FILE, dev);
978
979 if (ret || !nv || !nv->data || !nv->size) {
980 pr_err("wcnss: wcnss_nvbin_dnld_req: request_firmware failed for %s\n",
981 NVBIN_FILE);
982 return;
983 }
984
985 /*
986 * First 4 bytes in nv blob is validity bitmap.
987 * We cannot validate nv, so skip those 4 bytes.
988 */
989 nv_blob_addr = nv->data + 4;
990 nv_blob_size = nv->size - 4;
991
992 total_fragments = TOTALFRAGMENTS(nv_blob_size);
993
994 pr_info("wcnss: NV bin size: %d, total_fragments: %d\n",
995 nv_blob_size, total_fragments);
996
997 /* get buffer for nv bin dnld req message */
998 outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) +
999 NV_FRAGMENT_SIZE), GFP_KERNEL);
1000
1001 if (NULL == outbuffer) {
1002 pr_err("wcnss: wcnss_nvbin_dnld_req: failed to get buffer\n");
1003 goto err_free_nv;
1004 }
1005
1006 dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
1007
1008 dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
1009
1010 for (count = 0; count < total_fragments; count++) {
1011 dnld_req_msg->dnld_req_params.frag_number = count;
1012
1013 if (count == (total_fragments - 1)) {
1014 /* last fragment, take care of boundry condition */
1015 cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE;
1016 if (!cur_frag_size)
1017 cur_frag_size = NV_FRAGMENT_SIZE;
1018
1019 dnld_req_msg->dnld_req_params.is_last_fragment = 1;
1020 } else {
1021 cur_frag_size = NV_FRAGMENT_SIZE;
1022 dnld_req_msg->dnld_req_params.is_last_fragment = 0;
1023 }
1024
1025 dnld_req_msg->dnld_req_params.nvbin_buffer_size =
1026 cur_frag_size;
1027
1028 dnld_req_msg->hdr.msg_len =
1029 sizeof(struct nvbin_dnld_req_msg) + cur_frag_size;
1030
1031 /* copy NV fragment */
1032 memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)),
1033 (nv_blob_addr + count * NV_FRAGMENT_SIZE),
1034 cur_frag_size);
1035
1036 ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len);
1037
1038 retry_count = 0;
1039 while ((ret == -ENOSPC) && (retry_count <= 3)) {
1040 pr_debug("wcnss: wcnss_nvbin_dnld_req: smd tx failed, ENOSPC\n");
1041 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
1042 count, dnld_req_msg->hdr.msg_len,
1043 total_fragments, retry_count);
1044
1045 /* wait and try again */
1046 msleep(20);
1047 retry_count++;
1048 ret = wcnss_smd_tx(outbuffer,
1049 dnld_req_msg->hdr.msg_len);
1050 }
1051
1052 if (ret < 0) {
1053 pr_err("wcnss: wcnss_nvbin_dnld_req: smd tx failed\n");
1054 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
1055 count, dnld_req_msg->hdr.msg_len,
1056 total_fragments, retry_count);
1057 goto err_dnld;
1058 }
1059 }
1060
1061err_dnld:
1062 /* free buffer */
1063 kfree(outbuffer);
1064
1065err_free_nv:
1066 /* release firmware */
1067 release_firmware(nv);
1068
1069 return;
1070}
1071
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001072static int
1073wcnss_trigger_config(struct platform_device *pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001074{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001075 int ret;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001076 struct qcom_wcnss_opts *pdata;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001077 unsigned long wcnss_phys_addr;
1078 int size = 0;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001079 int has_pronto_hw = of_property_read_bool(pdev->dev.of_node,
1080 "qcom,has_pronto_hw");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001081
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001082 /* make sure we are only triggered once */
1083 if (penv->triggered)
1084 return 0;
1085 penv->triggered = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001086
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001087 /* initialize the WCNSS device configuration */
1088 pdata = pdev->dev.platform_data;
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001089 if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) {
1090 if (has_pronto_hw) {
1091 has_48mhz_xo = of_property_read_bool(pdev->dev.of_node,
1092 "qcom,has_48mhz_xo");
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001093 } else {
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001094 has_48mhz_xo = pdata->has_48mhz_xo;
1095 }
1096 }
Sameer Thalappilf00dbeb2013-03-01 18:33:30 -08001097 penv->wcnss_hw_type = (has_pronto_hw) ? WCNSS_PRONTO_HW : WCNSS_RIVA_HW;
Ankur Nandwanib0039b02011-08-09 14:00:45 -07001098 penv->wlan_config.use_48mhz_xo = has_48mhz_xo;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001099
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001100 penv->thermal_mitigation = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001101 strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN);
Jeff Johnson5fda4f82012-01-09 14:15:34 -08001102
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001103 /* Configure 5 wire GPIOs */
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001104 if (!has_pronto_hw) {
1105 penv->gpios_5wire = platform_get_resource_byname(pdev,
1106 IORESOURCE_IO, "wcnss_gpios_5wire");
1107
1108 /* allocate 5-wire GPIO resources */
1109 if (!penv->gpios_5wire) {
1110 dev_err(&pdev->dev, "insufficient IO resources\n");
1111 ret = -ENOENT;
1112 goto fail_gpio_res;
1113 }
1114 ret = wcnss_gpios_config(penv->gpios_5wire, true);
1115 } else
1116 ret = wcnss_pronto_gpios_config(&pdev->dev, true);
1117
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001118 if (ret) {
1119 dev_err(&pdev->dev, "WCNSS gpios config failed.\n");
1120 goto fail_gpio_res;
1121 }
1122
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001123 /* power up the WCNSS */
1124 ret = wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1125 WCNSS_WLAN_SWITCH_ON);
1126 if (ret) {
1127 dev_err(&pdev->dev, "WCNSS Power-up failed.\n");
1128 goto fail_power;
1129 }
1130
1131 /* trigger initialization of the WCNSS */
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001132 penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001133 if (IS_ERR(penv->pil)) {
1134 dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n");
1135 ret = PTR_ERR(penv->pil);
1136 penv->pil = NULL;
1137 goto fail_pil;
1138 }
1139
1140 /* allocate resources */
1141 penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1142 "wcnss_mmio");
1143 penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1144 "wcnss_wlantx_irq");
1145 penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1146 "wcnss_wlanrx_irq");
1147
1148 if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) {
1149 dev_err(&pdev->dev, "insufficient resources\n");
1150 ret = -ENOENT;
1151 goto fail_res;
1152 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001153 INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
1154 INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001155 INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_req);
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001156
Sameer Thalappilf138da52012-07-30 12:56:37 -07001157 wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");
1158
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001159 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
1160 size = 0x3000;
1161 wcnss_phys_addr = MSM_PRONTO_PHYS;
1162 } else {
1163 wcnss_phys_addr = MSM_RIVA_PHYS;
1164 size = SZ_256;
1165 }
1166
1167 penv->msm_wcnss_base = ioremap(wcnss_phys_addr, size);
1168 if (!penv->msm_wcnss_base) {
1169 ret = -ENOMEM;
1170 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1171 goto fail_wake;
1172 }
1173
Sameer Thalappild3aad702013-01-22 18:52:24 -08001174 if (wcnss_hardware_type() == WCNSS_RIVA_HW) {
1175 penv->riva_ccu_base = ioremap(MSM_RIVA_CCU_BASE, SZ_512);
1176 if (!penv->riva_ccu_base) {
1177 ret = -ENOMEM;
1178 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1179 goto fail_ioremap;
1180 }
1181 } else {
1182 penv->pronto_a2xb_base = ioremap(MSM_PRONTO_A2XB_BASE, SZ_512);
1183 if (!penv->pronto_a2xb_base) {
1184 ret = -ENOMEM;
1185 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1186 goto fail_ioremap;
1187 }
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001188 penv->pronto_ccpu_base = ioremap(MSM_PRONTO_CCPU_BASE, SZ_512);
1189 if (!penv->pronto_ccpu_base) {
1190 ret = -ENOMEM;
1191 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1192 goto fail_ioremap2;
1193 }
Sameer Thalappilf5007652013-03-21 16:25:36 -07001194 penv->pronto_ctl_base = ioremap(MSM_PRONTO_CCPU_CTL_BASE,
1195 SZ_32);
1196 if (!penv->pronto_ctl_base) {
1197 ret = -ENOMEM;
1198 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1199 goto fail_ioremap3;
1200 }
Sameer Thalappild3aad702013-01-22 18:52:24 -08001201 }
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001202 penv->cold_boot_done = 1;
Sameer Thalappild3aad702013-01-22 18:52:24 -08001203
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001204 return 0;
1205
Sameer Thalappilf5007652013-03-21 16:25:36 -07001206fail_ioremap3:
1207 iounmap(penv->pronto_ccpu_base);
Sameer Thalappil16cae9b2013-03-04 18:02:50 -08001208fail_ioremap2:
1209 iounmap(penv->pronto_a2xb_base);
Sameer Thalappild3aad702013-01-22 18:52:24 -08001210fail_ioremap:
1211 iounmap(penv->msm_wcnss_base);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001212fail_wake:
1213 wake_lock_destroy(&penv->wcnss_wake_lock);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001214fail_res:
1215 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001216 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001217fail_pil:
1218 wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1219 WCNSS_WLAN_SWITCH_OFF);
1220fail_power:
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001221 if (has_pronto_hw)
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001222 wcnss_pronto_gpios_config(&pdev->dev, false);
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001223 else
1224 wcnss_gpios_config(penv->gpios_5wire, false);
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001225fail_gpio_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001226 penv = NULL;
1227 return ret;
1228}
1229
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001230#ifndef MODULE
1231static int wcnss_node_open(struct inode *inode, struct file *file)
1232{
1233 struct platform_device *pdev;
1234
1235 pr_info(DEVICE " triggered by userspace\n");
1236
1237 pdev = penv->pdev;
1238 return wcnss_trigger_config(pdev);
1239}
1240
1241static const struct file_operations wcnss_node_fops = {
1242 .owner = THIS_MODULE,
1243 .open = wcnss_node_open,
1244};
1245
1246static struct miscdevice wcnss_misc = {
1247 .minor = MISC_DYNAMIC_MINOR,
1248 .name = DEVICE,
1249 .fops = &wcnss_node_fops,
1250};
1251#endif /* ifndef MODULE */
1252
1253
1254static int __devinit
1255wcnss_wlan_probe(struct platform_device *pdev)
1256{
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001257 int ret = 0;
1258
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001259 /* verify we haven't been called more than once */
1260 if (penv) {
1261 dev_err(&pdev->dev, "cannot handle multiple devices.\n");
1262 return -ENODEV;
1263 }
1264
1265 /* create an environment to track the device */
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001266 penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001267 if (!penv) {
1268 dev_err(&pdev->dev, "cannot allocate device memory.\n");
1269 return -ENOMEM;
1270 }
1271 penv->pdev = pdev;
1272
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001273 /* register sysfs entries */
1274 ret = wcnss_create_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001275 if (ret) {
1276 penv = NULL;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001277 return -ENOENT;
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001278 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001279
1280
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001281#ifdef MODULE
1282
Sameer Thalappild3aad702013-01-22 18:52:24 -08001283 /* Since we were built as a module, we are running because
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001284 * the module was loaded, therefore we assume userspace
1285 * applications are available to service PIL, so we can
1286 * trigger the WCNSS configuration now
1287 */
1288 pr_info(DEVICE " probed in MODULE mode\n");
1289 return wcnss_trigger_config(pdev);
1290
1291#else
1292
Sameer Thalappild3aad702013-01-22 18:52:24 -08001293 /* Since we were built into the kernel we'll be called as part
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001294 * of kernel initialization. We don't know if userspace
1295 * applications are available to service PIL at this time
1296 * (they probably are not), so we simply create a device node
1297 * here. When userspace is available it should touch the
1298 * device so that we know that WCNSS configuration can take
1299 * place
1300 */
1301 pr_info(DEVICE " probed in built-in mode\n");
1302 return misc_register(&wcnss_misc);
1303
1304#endif
1305}
1306
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001307static int __devexit
1308wcnss_wlan_remove(struct platform_device *pdev)
1309{
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001310 wcnss_remove_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001311 penv = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001312 return 0;
1313}
1314
1315
1316static const struct dev_pm_ops wcnss_wlan_pm_ops = {
1317 .suspend = wcnss_wlan_suspend,
1318 .resume = wcnss_wlan_resume,
1319};
1320
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001321#ifdef CONFIG_WCNSS_CORE_PRONTO
1322static struct of_device_id msm_wcnss_pronto_match[] = {
1323 {.compatible = "qcom,wcnss_wlan"},
1324 {}
1325};
1326#endif
1327
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001328static struct platform_driver wcnss_wlan_driver = {
1329 .driver = {
1330 .name = DEVICE,
1331 .owner = THIS_MODULE,
1332 .pm = &wcnss_wlan_pm_ops,
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001333#ifdef CONFIG_WCNSS_CORE_PRONTO
1334 .of_match_table = msm_wcnss_pronto_match,
1335#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001336 },
1337 .probe = wcnss_wlan_probe,
1338 .remove = __devexit_p(wcnss_wlan_remove),
1339};
1340
1341static int __init wcnss_wlan_init(void)
1342{
Sameer Thalappil24db5282012-09-10 11:58:33 -07001343 int ret = 0;
1344
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001345 platform_driver_register(&wcnss_wlan_driver);
1346 platform_driver_register(&wcnss_wlan_ctrl_driver);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001347 platform_driver_register(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001348
Sameer Thalappil24db5282012-09-10 11:58:33 -07001349#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1350 ret = wcnss_prealloc_init();
1351 if (ret < 0)
1352 pr_err("wcnss: pre-allocation failed\n");
1353#endif
1354
1355 return ret;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001356}
1357
1358static void __exit wcnss_wlan_exit(void)
1359{
1360 if (penv) {
1361 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001362 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001363
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001364
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001365 penv = NULL;
1366 }
1367
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001368 platform_driver_unregister(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001369 platform_driver_unregister(&wcnss_wlan_ctrl_driver);
1370 platform_driver_unregister(&wcnss_wlan_driver);
Sameer Thalappil24db5282012-09-10 11:58:33 -07001371#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1372 wcnss_prealloc_deinit();
1373#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001374}
1375
1376module_init(wcnss_wlan_init);
1377module_exit(wcnss_wlan_exit);
1378
1379MODULE_LICENSE("GPL v2");
1380MODULE_VERSION(VERSION);
1381MODULE_DESCRIPTION(DEVICE "Driver");