blob: ed4e246691193bfc211bdec2fbd739711a0a06e0 [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
61#define CCU_INVALID_ADDR_OFFSET 0x100
62#define CCU_LAST_ADDR0_OFFSET 0x104
63#define CCU_LAST_ADDR1_OFFSET 0x108
64#define CCU_LAST_ADDR2_OFFSET 0x10c
65
Sameer Thalappil1b3e6112012-12-14 15:16:07 -080066#define MSM_PRONTO_A2XB_BASE 0xfb100400
67#define A2XB_CFG_OFFSET 0x00
68#define A2XB_INT_SRC_OFFSET 0x0c
69#define A2XB_ERR_INFO_OFFSET 0x1c
70
Sameer Thalappileeb8c412012-08-10 17:17:09 -070071#define WCNSS_CTRL_CHANNEL "WCNSS_CTRL"
72#define WCNSS_MAX_FRAME_SIZE 500
73#define WCNSS_VERSION_LEN 30
74
75/* message types */
76#define WCNSS_CTRL_MSG_START 0x01000000
77#define WCNSS_VERSION_REQ (WCNSS_CTRL_MSG_START + 0)
78#define WCNSS_VERSION_RSP (WCNSS_CTRL_MSG_START + 1)
Sameer Thalappilf106a682013-02-16 20:41:11 -080079#define WCNSS_NVBIN_DNLD_REQ (WCNSS_CTRL_MSG_START + 2)
80#define WCNSS_NVBIN_DNLD_RSP (WCNSS_CTRL_MSG_START + 3)
81
Sameer Thalappileeb8c412012-08-10 17:17:09 -070082
83#define VALID_VERSION(version) \
84 ((strncmp(version, "INVALID", WCNSS_VERSION_LEN)) ? 1 : 0)
85
86struct smd_msg_hdr {
Sameer Thalappilf106a682013-02-16 20:41:11 -080087 unsigned int msg_type;
88 unsigned int msg_len;
Sameer Thalappileeb8c412012-08-10 17:17:09 -070089};
90
91struct wcnss_version {
92 struct smd_msg_hdr hdr;
93 unsigned char major;
94 unsigned char minor;
95 unsigned char version;
96 unsigned char revision;
97};
98
Sameer Thalappilf106a682013-02-16 20:41:11 -080099#define NVBIN_FILE "wlan/prima/WCNSS_qcom_wlan_nv.bin"
100
101/*
102 * On SMD channel 4K of maximum data can be transferred, including message
103 * header, so NV fragment size as next multiple of 1Kb is 3Kb.
104 */
105#define NV_FRAGMENT_SIZE 3072
106
107/* Macro to find the total number fragments of the NV bin Image */
108#define TOTALFRAGMENTS(x) (((x % NV_FRAGMENT_SIZE) == 0) ? \
109 (x / NV_FRAGMENT_SIZE) : ((x / NV_FRAGMENT_SIZE) + 1))
110
111struct nvbin_dnld_req_params {
112 /*
113 * Fragment sequence number of the NV bin Image. NV Bin Image
114 * might not fit into one message due to size limitation of
115 * the SMD channel FIFO so entire NV blob is chopped into
116 * multiple fragments starting with seqeunce number 0. The
117 * last fragment is indicated by marking is_last_fragment field
118 * to 1. At receiving side, NV blobs would be concatenated
119 * together without any padding bytes in between.
120 */
121 unsigned short frag_number;
122
123 /*
124 * When set to 1 it indicates that no more fragments will
125 * be sent. Receiver shall send back response message after
126 * the last fragment.
127 */
128 unsigned short is_last_fragment;
129
130 /* NV Image size (number of bytes) */
131 unsigned int nvbin_buffer_size;
132
133 /*
134 * Following the 'nvbin_buffer_size', there should be
135 * nvbin_buffer_size bytes of NV bin Image i.e.
136 * uint8[nvbin_buffer_size].
137 */
138};
139
140struct nvbin_dnld_req_msg {
141 /*
142 * Note: The length specified in nvbin_dnld_req_msg messages
143 * should be hdr.msg_len = sizeof(nvbin_dnld_req_msg) +
144 * nvbin_buffer_size.
145 */
146 struct smd_msg_hdr hdr;
147 struct nvbin_dnld_req_params dnld_req_params;
148};
149
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700150static struct {
151 struct platform_device *pdev;
152 void *pil;
153 struct resource *mmio_res;
154 struct resource *tx_irq_res;
155 struct resource *rx_irq_res;
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700156 struct resource *gpios_5wire;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700157 const struct dev_pm_ops *pm_ops;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800158 int triggered;
159 int smd_channel_ready;
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800160 int cold_boot_done;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700161 smd_channel_t *smd_ch;
162 unsigned char wcnss_version[WCNSS_VERSION_LEN];
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800163 unsigned int serial_number;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800164 int thermal_mitigation;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700165 enum wcnss_hw_type wcnss_hw_type;
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700166 void (*tm_notify)(struct device *, int);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700167 struct wcnss_wlan_config wlan_config;
David Collinsb727f7d2011-09-12 11:54:49 -0700168 struct delayed_work wcnss_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700169 struct work_struct wcnssctrl_version_work;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800170 struct work_struct wcnssctrl_nvbin_dnld_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700171 struct work_struct wcnssctrl_rx_work;
Sameer Thalappilf138da52012-07-30 12:56:37 -0700172 struct wake_lock wcnss_wake_lock;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700173 void __iomem *msm_wcnss_base;
Sameer Thalappild3aad702013-01-22 18:52:24 -0800174 void __iomem *riva_ccu_base;
175 void __iomem *pronto_a2xb_base;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700176} *penv = NULL;
177
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800178static ssize_t wcnss_serial_number_show(struct device *dev,
179 struct device_attribute *attr, char *buf)
180{
181 if (!penv)
182 return -ENODEV;
183
184 return scnprintf(buf, PAGE_SIZE, "%08X\n", penv->serial_number);
185}
186
187static ssize_t wcnss_serial_number_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700188 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800189{
190 unsigned int value;
191
192 if (!penv)
193 return -ENODEV;
194
195 if (sscanf(buf, "%08X", &value) != 1)
196 return -EINVAL;
197
198 penv->serial_number = value;
199 return count;
200}
201
202static DEVICE_ATTR(serial_number, S_IRUSR | S_IWUSR,
203 wcnss_serial_number_show, wcnss_serial_number_store);
204
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800205
206static ssize_t wcnss_thermal_mitigation_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, "%u\n", penv->thermal_mitigation);
213}
214
215static ssize_t wcnss_thermal_mitigation_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700216 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800217{
218 int value;
219
220 if (!penv)
221 return -ENODEV;
222
223 if (sscanf(buf, "%d", &value) != 1)
224 return -EINVAL;
225 penv->thermal_mitigation = value;
Jeff Johnson61374652012-04-16 20:51:48 -0700226 if (penv->tm_notify)
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700227 (penv->tm_notify)(dev, value);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800228 return count;
229}
230
231static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR,
232 wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store);
233
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700234
235static ssize_t wcnss_version_show(struct device *dev,
236 struct device_attribute *attr, char *buf)
237{
238 if (!penv)
239 return -ENODEV;
240
241 return scnprintf(buf, PAGE_SIZE, "%s", penv->wcnss_version);
242}
243
244static DEVICE_ATTR(wcnss_version, S_IRUSR,
245 wcnss_version_show, NULL);
246
Sameer Thalappil77716e52012-11-08 13:41:04 -0800247/* wcnss_reset_intr() is invoked when host drivers fails to
248 * communicate with WCNSS over SMD; so logging these registers
Sameer Thalappild3aad702013-01-22 18:52:24 -0800249 * helps to know WCNSS failure reason
250 */
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800251void wcnss_riva_log_debug_regs(void)
Sameer Thalappil77716e52012-11-08 13:41:04 -0800252{
Sameer Thalappil77716e52012-11-08 13:41:04 -0800253 void __iomem *ccu_reg;
254 u32 reg = 0;
255
Sameer Thalappild3aad702013-01-22 18:52:24 -0800256 ccu_reg = penv->riva_ccu_base + CCU_INVALID_ADDR_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800257 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800258 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800259
Sameer Thalappild3aad702013-01-22 18:52:24 -0800260 ccu_reg = penv->riva_ccu_base + CCU_LAST_ADDR0_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800261 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800262 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800263
Sameer Thalappild3aad702013-01-22 18:52:24 -0800264 ccu_reg = penv->riva_ccu_base + CCU_LAST_ADDR1_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800265 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800266 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800267
Sameer Thalappild3aad702013-01-22 18:52:24 -0800268 ccu_reg = penv->riva_ccu_base + CCU_LAST_ADDR2_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800269 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800270 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800271
Sameer Thalappil77716e52012-11-08 13:41:04 -0800272}
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800273EXPORT_SYMBOL(wcnss_riva_log_debug_regs);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800274
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800275/* Log pronto debug registers before sending reset interrupt */
276void wcnss_pronto_log_debug_regs(void)
277{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800278 void __iomem *reg_addr;
279 u32 reg = 0;
280
Sameer Thalappild3aad702013-01-22 18:52:24 -0800281 reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800282 reg = readl_relaxed(reg_addr);
283 pr_info_ratelimited("%s: A2XB_CFG_OFFSET %08x\n", __func__, reg);
284
Sameer Thalappild3aad702013-01-22 18:52:24 -0800285 reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800286 reg = readl_relaxed(reg_addr);
287 pr_info_ratelimited("%s: A2XB_INT_SRC_OFFSET %08x\n", __func__, reg);
288
Sameer Thalappild3aad702013-01-22 18:52:24 -0800289 reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800290 reg = readl_relaxed(reg_addr);
291 pr_info_ratelimited("%s: A2XB_ERR_INFO_OFFSET %08x\n", __func__, reg);
292
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800293}
294EXPORT_SYMBOL(wcnss_pronto_log_debug_regs);
295
296/* interface to reset wcnss by sending the reset interrupt */
Sameer Thalappil19e02352012-09-24 15:26:12 -0700297void wcnss_reset_intr(void)
298{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800299 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
300 wcnss_pronto_log_debug_regs();
Sameer Thalappil19e02352012-09-24 15:26:12 -0700301 pr_err("%s: reset interrupt not supported\n", __func__);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800302 return;
Sameer Thalappil19e02352012-09-24 15:26:12 -0700303 }
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800304 wcnss_riva_log_debug_regs();
Sameer Thalappil77716e52012-11-08 13:41:04 -0800305 wmb();
306 __raw_writel(1 << 24, MSM_APCS_GCC_BASE + 0x8);
Sameer Thalappil19e02352012-09-24 15:26:12 -0700307}
308EXPORT_SYMBOL(wcnss_reset_intr);
309
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800310static int wcnss_create_sysfs(struct device *dev)
311{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800312 int ret;
313
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800314 if (!dev)
315 return -ENODEV;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800316
317 ret = device_create_file(dev, &dev_attr_serial_number);
318 if (ret)
319 return ret;
320
321 ret = device_create_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700322 if (ret)
323 goto remove_serial;
324
325 ret = device_create_file(dev, &dev_attr_wcnss_version);
326 if (ret)
327 goto remove_thermal;
328
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800329 return 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700330
331remove_thermal:
332 device_remove_file(dev, &dev_attr_thermal_mitigation);
333remove_serial:
334 device_remove_file(dev, &dev_attr_serial_number);
335
336 return ret;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800337}
338
339static void wcnss_remove_sysfs(struct device *dev)
340{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800341 if (dev) {
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800342 device_remove_file(dev, &dev_attr_serial_number);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800343 device_remove_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700344 device_remove_file(dev, &dev_attr_wcnss_version);
345 }
346}
347static void wcnss_smd_notify_event(void *data, unsigned int event)
348{
349 int len = 0;
350
351 if (penv != data) {
352 pr_err("wcnss: invalid env pointer in smd callback\n");
353 return;
354 }
355 switch (event) {
356 case SMD_EVENT_DATA:
357 len = smd_read_avail(penv->smd_ch);
358 if (len < 0)
359 pr_err("wcnss: failed to read from smd %d\n", len);
360 schedule_work(&penv->wcnssctrl_rx_work);
361 break;
362
363 case SMD_EVENT_OPEN:
364 pr_debug("wcnss: opening WCNSS SMD channel :%s",
365 WCNSS_CTRL_CHANNEL);
366 if (!VALID_VERSION(penv->wcnss_version))
367 schedule_work(&penv->wcnssctrl_version_work);
368 break;
369
370 case SMD_EVENT_CLOSE:
371 pr_debug("wcnss: closing WCNSS SMD channel :%s",
372 WCNSS_CTRL_CHANNEL);
373 break;
374
375 default:
376 break;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800377 }
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800378}
379
David Collinsb727f7d2011-09-12 11:54:49 -0700380static void wcnss_post_bootup(struct work_struct *work)
381{
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700382 pr_info("%s: Cancel APPS vote for Iris & WCNSS\n", __func__);
David Collinsb727f7d2011-09-12 11:54:49 -0700383
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700384 /* Since WCNSS is up, cancel any APPS vote for Iris & WCNSS VREGs */
David Collinsb727f7d2011-09-12 11:54:49 -0700385 wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config,
386 WCNSS_WLAN_SWITCH_OFF);
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700387
388}
389
390static int
391wcnss_pronto_gpios_config(struct device *dev, bool enable)
392{
393 int rc = 0;
394 int i, j;
395 int WCNSS_WLAN_NUM_GPIOS = 5;
396
397 for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) {
398 int gpio = of_get_gpio(dev->of_node, i);
399 if (enable) {
400 rc = gpio_request(gpio, "wcnss_wlan");
401 if (rc) {
402 pr_err("WCNSS gpio_request %d err %d\n",
403 gpio, rc);
404 goto fail;
405 }
406 } else
407 gpio_free(gpio);
408 }
409
410 return rc;
411
412fail:
413 for (j = WCNSS_WLAN_NUM_GPIOS-1; j >= 0; j--) {
414 int gpio = of_get_gpio(dev->of_node, i);
415 gpio_free(gpio);
416 }
417 return rc;
David Collinsb727f7d2011-09-12 11:54:49 -0700418}
419
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700420static int
421wcnss_gpios_config(struct resource *gpios_5wire, bool enable)
422{
423 int i, j;
424 int rc = 0;
425
426 for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) {
427 if (enable) {
428 rc = gpio_request(i, gpios_5wire->name);
429 if (rc) {
430 pr_err("WCNSS gpio_request %d err %d\n", i, rc);
431 goto fail;
432 }
433 } else
434 gpio_free(i);
435 }
436
437 return rc;
438
439fail:
440 for (j = i-1; j >= gpios_5wire->start; j--)
441 gpio_free(j);
442 return rc;
443}
444
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700445static int __devinit
446wcnss_wlan_ctrl_probe(struct platform_device *pdev)
447{
Sameer Thalappil667f43f2011-10-10 11:12:54 -0700448 if (!penv)
449 return -ENODEV;
450
451 penv->smd_channel_ready = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700452
453 pr_info("%s: SMD ctrl channel up\n", __func__);
454
David Collinsb727f7d2011-09-12 11:54:49 -0700455 /* Schedule a work to do any post boot up activity */
456 INIT_DELAYED_WORK(&penv->wcnss_work, wcnss_post_bootup);
457 schedule_delayed_work(&penv->wcnss_work, msecs_to_jiffies(10000));
458
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700459 return 0;
460}
461
Sameer Thalappil13f45182012-07-23 18:02:45 -0700462void wcnss_flush_delayed_boot_votes()
463{
Sameer Thalappilf106a682013-02-16 20:41:11 -0800464 flush_delayed_work(&penv->wcnss_work);
Sameer Thalappil13f45182012-07-23 18:02:45 -0700465}
466EXPORT_SYMBOL(wcnss_flush_delayed_boot_votes);
467
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700468static int __devexit
469wcnss_wlan_ctrl_remove(struct platform_device *pdev)
470{
471 if (penv)
472 penv->smd_channel_ready = 0;
473
474 pr_info("%s: SMD ctrl channel down\n", __func__);
475
476 return 0;
477}
478
479
480static struct platform_driver wcnss_wlan_ctrl_driver = {
481 .driver = {
482 .name = "WLAN_CTRL",
483 .owner = THIS_MODULE,
484 },
485 .probe = wcnss_wlan_ctrl_probe,
486 .remove = __devexit_p(wcnss_wlan_ctrl_remove),
487};
488
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700489static int __devexit
490wcnss_ctrl_remove(struct platform_device *pdev)
491{
492 if (penv && penv->smd_ch)
493 smd_close(penv->smd_ch);
494
495 return 0;
496}
497
498static int __devinit
499wcnss_ctrl_probe(struct platform_device *pdev)
500{
501 int ret = 0;
502
503 if (!penv)
504 return -ENODEV;
505
506 ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS,
507 &penv->smd_ch, penv, wcnss_smd_notify_event);
508 if (ret < 0) {
509 pr_err("wcnss: cannot open the smd command channel %s: %d\n",
510 WCNSS_CTRL_CHANNEL, ret);
511 return -ENODEV;
512 }
513 smd_disable_read_intr(penv->smd_ch);
514
515 return 0;
516}
517
518/* platform device for WCNSS_CTRL SMD channel */
519static struct platform_driver wcnss_ctrl_driver = {
520 .driver = {
521 .name = "WCNSS_CTRL",
522 .owner = THIS_MODULE,
523 },
524 .probe = wcnss_ctrl_probe,
525 .remove = __devexit_p(wcnss_ctrl_remove),
526};
527
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700528struct device *wcnss_wlan_get_device(void)
529{
530 if (penv && penv->pdev && penv->smd_channel_ready)
531 return &penv->pdev->dev;
532 return NULL;
533}
534EXPORT_SYMBOL(wcnss_wlan_get_device);
535
Sameer Thalappil409ed352011-12-07 10:53:31 -0800536struct platform_device *wcnss_get_platform_device(void)
537{
538 if (penv && penv->pdev)
539 return penv->pdev;
540 return NULL;
541}
542EXPORT_SYMBOL(wcnss_get_platform_device);
543
544struct wcnss_wlan_config *wcnss_get_wlan_config(void)
545{
546 if (penv && penv->pdev)
547 return &penv->wlan_config;
548 return NULL;
549}
550EXPORT_SYMBOL(wcnss_get_wlan_config);
551
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700552struct resource *wcnss_wlan_get_memory_map(struct device *dev)
553{
554 if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready)
555 return penv->mmio_res;
556 return NULL;
557}
558EXPORT_SYMBOL(wcnss_wlan_get_memory_map);
559
560int wcnss_wlan_get_dxe_tx_irq(struct device *dev)
561{
562 if (penv && dev && (dev == &penv->pdev->dev) &&
563 penv->tx_irq_res && penv->smd_channel_ready)
564 return penv->tx_irq_res->start;
565 return WCNSS_WLAN_IRQ_INVALID;
566}
567EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq);
568
569int wcnss_wlan_get_dxe_rx_irq(struct device *dev)
570{
571 if (penv && dev && (dev == &penv->pdev->dev) &&
572 penv->rx_irq_res && penv->smd_channel_ready)
573 return penv->rx_irq_res->start;
574 return WCNSS_WLAN_IRQ_INVALID;
575}
576EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq);
577
578void wcnss_wlan_register_pm_ops(struct device *dev,
579 const struct dev_pm_ops *pm_ops)
580{
581 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops)
582 penv->pm_ops = pm_ops;
583}
584EXPORT_SYMBOL(wcnss_wlan_register_pm_ops);
585
Sameer Thalappilecdd1002011-09-09 10:52:27 -0700586void wcnss_wlan_unregister_pm_ops(struct device *dev,
587 const struct dev_pm_ops *pm_ops)
588{
589 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) {
590 if (pm_ops->suspend != penv->pm_ops->suspend ||
591 pm_ops->resume != penv->pm_ops->resume)
592 pr_err("PM APIs dont match with registered APIs\n");
593 penv->pm_ops = NULL;
594 }
595}
596EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops);
597
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700598void wcnss_register_thermal_mitigation(struct device *dev,
599 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700600{
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700601 if (penv && dev && tm_notify)
Jeff Johnson61374652012-04-16 20:51:48 -0700602 penv->tm_notify = tm_notify;
603}
604EXPORT_SYMBOL(wcnss_register_thermal_mitigation);
605
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700606void wcnss_unregister_thermal_mitigation(
607 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700608{
609 if (penv && tm_notify) {
610 if (tm_notify != penv->tm_notify)
611 pr_err("tm_notify doesn't match registered\n");
612 penv->tm_notify = NULL;
613 }
614}
615EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation);
616
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800617unsigned int wcnss_get_serial_number(void)
618{
619 if (penv)
620 return penv->serial_number;
621 return 0;
622}
623EXPORT_SYMBOL(wcnss_get_serial_number);
624
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700625static int enable_wcnss_suspend_notify;
626
627static int enable_wcnss_suspend_notify_set(const char *val,
628 struct kernel_param *kp)
629{
630 int ret;
631
632 ret = param_set_int(val, kp);
633 if (ret)
634 return ret;
635
636 if (enable_wcnss_suspend_notify)
637 pr_debug("Suspend notification activated for wcnss\n");
638
639 return 0;
640}
641module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set,
642 param_get_int, &enable_wcnss_suspend_notify, S_IRUGO | S_IWUSR);
643
644
645void wcnss_suspend_notify(void)
646{
647 void __iomem *pmu_spare_reg;
648 u32 reg = 0;
649 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700650
651 if (!enable_wcnss_suspend_notify)
652 return;
653
654 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
655 return;
656
657 /* For Riva */
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700658 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
659 spin_lock_irqsave(&reg_spinlock, flags);
660 reg = readl_relaxed(pmu_spare_reg);
661 reg |= RIVA_SUSPEND_BIT;
662 writel_relaxed(reg, pmu_spare_reg);
663 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700664}
665EXPORT_SYMBOL(wcnss_suspend_notify);
666
667void wcnss_resume_notify(void)
668{
669 void __iomem *pmu_spare_reg;
670 u32 reg = 0;
671 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700672
673 if (!enable_wcnss_suspend_notify)
674 return;
675
676 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
677 return;
678
679 /* For Riva */
680 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
681
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700682 spin_lock_irqsave(&reg_spinlock, flags);
683 reg = readl_relaxed(pmu_spare_reg);
684 reg &= ~RIVA_SUSPEND_BIT;
685 writel_relaxed(reg, pmu_spare_reg);
686 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700687}
688EXPORT_SYMBOL(wcnss_resume_notify);
689
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700690static int wcnss_wlan_suspend(struct device *dev)
691{
692 if (penv && dev && (dev == &penv->pdev->dev) &&
693 penv->smd_channel_ready &&
694 penv->pm_ops && penv->pm_ops->suspend)
695 return penv->pm_ops->suspend(dev);
696 return 0;
697}
698
699static int wcnss_wlan_resume(struct device *dev)
700{
701 if (penv && dev && (dev == &penv->pdev->dev) &&
702 penv->smd_channel_ready &&
703 penv->pm_ops && penv->pm_ops->resume)
704 return penv->pm_ops->resume(dev);
705 return 0;
706}
707
Sameer Thalappilf138da52012-07-30 12:56:37 -0700708void wcnss_prevent_suspend()
709{
710 if (penv)
711 wake_lock(&penv->wcnss_wake_lock);
712}
713EXPORT_SYMBOL(wcnss_prevent_suspend);
714
715void wcnss_allow_suspend()
716{
717 if (penv)
718 wake_unlock(&penv->wcnss_wake_lock);
719}
720EXPORT_SYMBOL(wcnss_allow_suspend);
721
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700722int wcnss_hardware_type(void)
723{
724 if (penv)
725 return penv->wcnss_hw_type;
726 else
727 return -ENODEV;
728}
729EXPORT_SYMBOL(wcnss_hardware_type);
730
Sameer Thalappilcf566bb2013-02-26 17:10:25 -0800731int wcnss_cold_boot_done(void)
732{
733 if (penv)
734 return penv->cold_boot_done;
735 else
736 return -ENODEV;
737}
738EXPORT_SYMBOL(wcnss_cold_boot_done);
739
740
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700741static int wcnss_smd_tx(void *data, int len)
742{
743 int ret = 0;
744
745 ret = smd_write_avail(penv->smd_ch);
746 if (ret < len) {
747 pr_err("wcnss: no space available for smd frame\n");
Sameer Thalappilf106a682013-02-16 20:41:11 -0800748 return -ENOSPC;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700749 }
750 ret = smd_write(penv->smd_ch, data, len);
751 if (ret < len) {
752 pr_err("wcnss: failed to write Command %d", len);
753 ret = -ENODEV;
754 }
755 return ret;
756}
757
758static void wcnssctrl_rx_handler(struct work_struct *worker)
759{
760 int len = 0;
761 int rc = 0;
762 unsigned char buf[WCNSS_MAX_FRAME_SIZE];
763 struct smd_msg_hdr *phdr;
764 struct wcnss_version *pversion;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800765 int hw_type;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700766
767 len = smd_read_avail(penv->smd_ch);
768 if (len > WCNSS_MAX_FRAME_SIZE) {
769 pr_err("wcnss: frame larger than the allowed size\n");
770 smd_read(penv->smd_ch, NULL, len);
771 return;
772 }
773 if (len <= 0)
774 return;
775
776 rc = smd_read(penv->smd_ch, buf, len);
777 if (rc < len) {
778 pr_err("wcnss: incomplete data read from smd\n");
779 return;
780 }
781
782 phdr = (struct smd_msg_hdr *)buf;
783
Sameer Thalappilf106a682013-02-16 20:41:11 -0800784 switch (phdr->msg_type) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700785
786 case WCNSS_VERSION_RSP:
787 pversion = (struct wcnss_version *)buf;
788 if (len != sizeof(struct wcnss_version)) {
789 pr_err("wcnss: invalid version data from wcnss %d\n",
790 len);
791 return;
792 }
793 snprintf(penv->wcnss_version, WCNSS_VERSION_LEN,
794 "%02x%02x%02x%02x", pversion->major, pversion->minor,
795 pversion->version, pversion->revision);
796 pr_info("wcnss: version %s\n", penv->wcnss_version);
Sameer Thalappilf106a682013-02-16 20:41:11 -0800797 /* schedule work to download nvbin to ccpu */
798 hw_type = wcnss_hardware_type();
799 switch (hw_type) {
800 case WCNSS_RIVA_HW:
801 /* supported only if riva major >= 1 and minor >= 4 */
802 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
803 pr_info("wcnss: schedule dnld work for riva\n");
804 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
805 }
806 break;
807
808 case WCNSS_PRONTO_HW:
809 /* supported only if pronto major >= 1 and minor >= 4 */
810 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
811 pr_info("wcnss: schedule dnld work for pronto\n");
812 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
813 }
814 break;
815
816 default:
817 pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n",
818 hw_type);
819 break;
820 }
821 break;
822
823 case WCNSS_NVBIN_DNLD_RSP:
824 pr_info("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu\n");
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700825 break;
826
827 default:
Sameer Thalappilf106a682013-02-16 20:41:11 -0800828 pr_err("wcnss: invalid message type %d\n", phdr->msg_type);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700829 }
830 return;
831}
832
833static void wcnss_send_version_req(struct work_struct *worker)
834{
835 struct smd_msg_hdr smd_msg;
836 int ret = 0;
837
Sameer Thalappilf106a682013-02-16 20:41:11 -0800838 smd_msg.msg_type = WCNSS_VERSION_REQ;
839 smd_msg.msg_len = sizeof(smd_msg);
840 ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700841 if (ret < 0)
842 pr_err("wcnss: smd tx failed\n");
843
844 return;
845}
846
Sameer Thalappilf106a682013-02-16 20:41:11 -0800847static void wcnss_nvbin_dnld_req(struct work_struct *worker)
848{
849 int ret = 0;
850 struct nvbin_dnld_req_msg *dnld_req_msg;
851 unsigned short total_fragments = 0;
852 unsigned short count = 0;
853 unsigned short retry_count = 0;
854 unsigned short cur_frag_size = 0;
855 unsigned char *outbuffer = NULL;
856 const void *nv_blob_addr = NULL;
857 unsigned int nv_blob_size = 0;
858 const struct firmware *nv = NULL;
859 struct device *dev = NULL;
860
861 dev = wcnss_wlan_get_device();
862
863 ret = request_firmware(&nv, NVBIN_FILE, dev);
864
865 if (ret || !nv || !nv->data || !nv->size) {
866 pr_err("wcnss: wcnss_nvbin_dnld_req: request_firmware failed for %s\n",
867 NVBIN_FILE);
868 return;
869 }
870
871 /*
872 * First 4 bytes in nv blob is validity bitmap.
873 * We cannot validate nv, so skip those 4 bytes.
874 */
875 nv_blob_addr = nv->data + 4;
876 nv_blob_size = nv->size - 4;
877
878 total_fragments = TOTALFRAGMENTS(nv_blob_size);
879
880 pr_info("wcnss: NV bin size: %d, total_fragments: %d\n",
881 nv_blob_size, total_fragments);
882
883 /* get buffer for nv bin dnld req message */
884 outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) +
885 NV_FRAGMENT_SIZE), GFP_KERNEL);
886
887 if (NULL == outbuffer) {
888 pr_err("wcnss: wcnss_nvbin_dnld_req: failed to get buffer\n");
889 goto err_free_nv;
890 }
891
892 dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
893
894 dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
895
896 for (count = 0; count < total_fragments; count++) {
897 dnld_req_msg->dnld_req_params.frag_number = count;
898
899 if (count == (total_fragments - 1)) {
900 /* last fragment, take care of boundry condition */
901 cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE;
902 if (!cur_frag_size)
903 cur_frag_size = NV_FRAGMENT_SIZE;
904
905 dnld_req_msg->dnld_req_params.is_last_fragment = 1;
906 } else {
907 cur_frag_size = NV_FRAGMENT_SIZE;
908 dnld_req_msg->dnld_req_params.is_last_fragment = 0;
909 }
910
911 dnld_req_msg->dnld_req_params.nvbin_buffer_size =
912 cur_frag_size;
913
914 dnld_req_msg->hdr.msg_len =
915 sizeof(struct nvbin_dnld_req_msg) + cur_frag_size;
916
917 /* copy NV fragment */
918 memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)),
919 (nv_blob_addr + count * NV_FRAGMENT_SIZE),
920 cur_frag_size);
921
922 ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len);
923
924 retry_count = 0;
925 while ((ret == -ENOSPC) && (retry_count <= 3)) {
926 pr_debug("wcnss: wcnss_nvbin_dnld_req: smd tx failed, ENOSPC\n");
927 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
928 count, dnld_req_msg->hdr.msg_len,
929 total_fragments, retry_count);
930
931 /* wait and try again */
932 msleep(20);
933 retry_count++;
934 ret = wcnss_smd_tx(outbuffer,
935 dnld_req_msg->hdr.msg_len);
936 }
937
938 if (ret < 0) {
939 pr_err("wcnss: wcnss_nvbin_dnld_req: smd tx failed\n");
940 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
941 count, dnld_req_msg->hdr.msg_len,
942 total_fragments, retry_count);
943 goto err_dnld;
944 }
945 }
946
947err_dnld:
948 /* free buffer */
949 kfree(outbuffer);
950
951err_free_nv:
952 /* release firmware */
953 release_firmware(nv);
954
955 return;
956}
957
Jeff Johnsonb3377e32011-11-18 23:32:27 -0800958static int
959wcnss_trigger_config(struct platform_device *pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700960{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700961 int ret;
Ankur Nandwanib0039b02011-08-09 14:00:45 -0700962 struct qcom_wcnss_opts *pdata;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700963 unsigned long wcnss_phys_addr;
964 int size = 0;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700965 int has_pronto_hw = of_property_read_bool(pdev->dev.of_node,
966 "qcom,has_pronto_hw");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700967
Jeff Johnsonb3377e32011-11-18 23:32:27 -0800968 /* make sure we are only triggered once */
969 if (penv->triggered)
970 return 0;
971 penv->triggered = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700972
Ankur Nandwanib0039b02011-08-09 14:00:45 -0700973 /* initialize the WCNSS device configuration */
974 pdata = pdev->dev.platform_data;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700975 if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) {
976 if (has_pronto_hw) {
977 has_48mhz_xo = of_property_read_bool(pdev->dev.of_node,
978 "qcom,has_48mhz_xo");
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700979 } else {
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700980 has_48mhz_xo = pdata->has_48mhz_xo;
981 }
982 }
Sameer Thalappilf00dbeb2013-03-01 18:33:30 -0800983 penv->wcnss_hw_type = (has_pronto_hw) ? WCNSS_PRONTO_HW : WCNSS_RIVA_HW;
Ankur Nandwanib0039b02011-08-09 14:00:45 -0700984 penv->wlan_config.use_48mhz_xo = has_48mhz_xo;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700985
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800986 penv->thermal_mitigation = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700987 strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800988
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700989 /* Configure 5 wire GPIOs */
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700990 if (!has_pronto_hw) {
991 penv->gpios_5wire = platform_get_resource_byname(pdev,
992 IORESOURCE_IO, "wcnss_gpios_5wire");
993
994 /* allocate 5-wire GPIO resources */
995 if (!penv->gpios_5wire) {
996 dev_err(&pdev->dev, "insufficient IO resources\n");
997 ret = -ENOENT;
998 goto fail_gpio_res;
999 }
1000 ret = wcnss_gpios_config(penv->gpios_5wire, true);
1001 } else
1002 ret = wcnss_pronto_gpios_config(&pdev->dev, true);
1003
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001004 if (ret) {
1005 dev_err(&pdev->dev, "WCNSS gpios config failed.\n");
1006 goto fail_gpio_res;
1007 }
1008
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001009 /* power up the WCNSS */
1010 ret = wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1011 WCNSS_WLAN_SWITCH_ON);
1012 if (ret) {
1013 dev_err(&pdev->dev, "WCNSS Power-up failed.\n");
1014 goto fail_power;
1015 }
1016
1017 /* trigger initialization of the WCNSS */
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001018 penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001019 if (IS_ERR(penv->pil)) {
1020 dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n");
1021 ret = PTR_ERR(penv->pil);
1022 penv->pil = NULL;
1023 goto fail_pil;
1024 }
1025
1026 /* allocate resources */
1027 penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1028 "wcnss_mmio");
1029 penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1030 "wcnss_wlantx_irq");
1031 penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1032 "wcnss_wlanrx_irq");
1033
1034 if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) {
1035 dev_err(&pdev->dev, "insufficient resources\n");
1036 ret = -ENOENT;
1037 goto fail_res;
1038 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001039 INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
1040 INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001041 INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_req);
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001042
Sameer Thalappilf138da52012-07-30 12:56:37 -07001043 wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");
1044
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001045 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
1046 size = 0x3000;
1047 wcnss_phys_addr = MSM_PRONTO_PHYS;
1048 } else {
1049 wcnss_phys_addr = MSM_RIVA_PHYS;
1050 size = SZ_256;
1051 }
1052
1053 penv->msm_wcnss_base = ioremap(wcnss_phys_addr, size);
1054 if (!penv->msm_wcnss_base) {
1055 ret = -ENOMEM;
1056 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1057 goto fail_wake;
1058 }
1059
Sameer Thalappild3aad702013-01-22 18:52:24 -08001060 if (wcnss_hardware_type() == WCNSS_RIVA_HW) {
1061 penv->riva_ccu_base = ioremap(MSM_RIVA_CCU_BASE, SZ_512);
1062 if (!penv->riva_ccu_base) {
1063 ret = -ENOMEM;
1064 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1065 goto fail_ioremap;
1066 }
1067 } else {
1068 penv->pronto_a2xb_base = ioremap(MSM_PRONTO_A2XB_BASE, SZ_512);
1069 if (!penv->pronto_a2xb_base) {
1070 ret = -ENOMEM;
1071 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1072 goto fail_ioremap;
1073 }
1074 }
Sameer Thalappilcf566bb2013-02-26 17:10:25 -08001075 penv->cold_boot_done = 1;
Sameer Thalappild3aad702013-01-22 18:52:24 -08001076
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001077 return 0;
1078
Sameer Thalappild3aad702013-01-22 18:52:24 -08001079fail_ioremap:
1080 iounmap(penv->msm_wcnss_base);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001081fail_wake:
1082 wake_lock_destroy(&penv->wcnss_wake_lock);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001083fail_res:
1084 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001085 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001086fail_pil:
1087 wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1088 WCNSS_WLAN_SWITCH_OFF);
1089fail_power:
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001090 if (has_pronto_hw)
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001091 wcnss_pronto_gpios_config(&pdev->dev, false);
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001092 else
1093 wcnss_gpios_config(penv->gpios_5wire, false);
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001094fail_gpio_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001095 penv = NULL;
1096 return ret;
1097}
1098
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001099#ifndef MODULE
1100static int wcnss_node_open(struct inode *inode, struct file *file)
1101{
1102 struct platform_device *pdev;
1103
1104 pr_info(DEVICE " triggered by userspace\n");
1105
1106 pdev = penv->pdev;
1107 return wcnss_trigger_config(pdev);
1108}
1109
1110static const struct file_operations wcnss_node_fops = {
1111 .owner = THIS_MODULE,
1112 .open = wcnss_node_open,
1113};
1114
1115static struct miscdevice wcnss_misc = {
1116 .minor = MISC_DYNAMIC_MINOR,
1117 .name = DEVICE,
1118 .fops = &wcnss_node_fops,
1119};
1120#endif /* ifndef MODULE */
1121
1122
1123static int __devinit
1124wcnss_wlan_probe(struct platform_device *pdev)
1125{
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001126 int ret = 0;
1127
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001128 /* verify we haven't been called more than once */
1129 if (penv) {
1130 dev_err(&pdev->dev, "cannot handle multiple devices.\n");
1131 return -ENODEV;
1132 }
1133
1134 /* create an environment to track the device */
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001135 penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001136 if (!penv) {
1137 dev_err(&pdev->dev, "cannot allocate device memory.\n");
1138 return -ENOMEM;
1139 }
1140 penv->pdev = pdev;
1141
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001142 /* register sysfs entries */
1143 ret = wcnss_create_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001144 if (ret) {
1145 penv = NULL;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001146 return -ENOENT;
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001147 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001148
1149
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001150#ifdef MODULE
1151
Sameer Thalappild3aad702013-01-22 18:52:24 -08001152 /* Since we were built as a module, we are running because
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001153 * the module was loaded, therefore we assume userspace
1154 * applications are available to service PIL, so we can
1155 * trigger the WCNSS configuration now
1156 */
1157 pr_info(DEVICE " probed in MODULE mode\n");
1158 return wcnss_trigger_config(pdev);
1159
1160#else
1161
Sameer Thalappild3aad702013-01-22 18:52:24 -08001162 /* Since we were built into the kernel we'll be called as part
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001163 * of kernel initialization. We don't know if userspace
1164 * applications are available to service PIL at this time
1165 * (they probably are not), so we simply create a device node
1166 * here. When userspace is available it should touch the
1167 * device so that we know that WCNSS configuration can take
1168 * place
1169 */
1170 pr_info(DEVICE " probed in built-in mode\n");
1171 return misc_register(&wcnss_misc);
1172
1173#endif
1174}
1175
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001176static int __devexit
1177wcnss_wlan_remove(struct platform_device *pdev)
1178{
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001179 wcnss_remove_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001180 penv = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001181 return 0;
1182}
1183
1184
1185static const struct dev_pm_ops wcnss_wlan_pm_ops = {
1186 .suspend = wcnss_wlan_suspend,
1187 .resume = wcnss_wlan_resume,
1188};
1189
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001190#ifdef CONFIG_WCNSS_CORE_PRONTO
1191static struct of_device_id msm_wcnss_pronto_match[] = {
1192 {.compatible = "qcom,wcnss_wlan"},
1193 {}
1194};
1195#endif
1196
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001197static struct platform_driver wcnss_wlan_driver = {
1198 .driver = {
1199 .name = DEVICE,
1200 .owner = THIS_MODULE,
1201 .pm = &wcnss_wlan_pm_ops,
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001202#ifdef CONFIG_WCNSS_CORE_PRONTO
1203 .of_match_table = msm_wcnss_pronto_match,
1204#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001205 },
1206 .probe = wcnss_wlan_probe,
1207 .remove = __devexit_p(wcnss_wlan_remove),
1208};
1209
1210static int __init wcnss_wlan_init(void)
1211{
Sameer Thalappil24db5282012-09-10 11:58:33 -07001212 int ret = 0;
1213
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001214 platform_driver_register(&wcnss_wlan_driver);
1215 platform_driver_register(&wcnss_wlan_ctrl_driver);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001216 platform_driver_register(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001217
Sameer Thalappil24db5282012-09-10 11:58:33 -07001218#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1219 ret = wcnss_prealloc_init();
1220 if (ret < 0)
1221 pr_err("wcnss: pre-allocation failed\n");
1222#endif
1223
1224 return ret;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001225}
1226
1227static void __exit wcnss_wlan_exit(void)
1228{
1229 if (penv) {
1230 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001231 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001232
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001233
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001234 penv = NULL;
1235 }
1236
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001237 platform_driver_unregister(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001238 platform_driver_unregister(&wcnss_wlan_ctrl_driver);
1239 platform_driver_unregister(&wcnss_wlan_driver);
Sameer Thalappil24db5282012-09-10 11:58:33 -07001240#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1241 wcnss_prealloc_deinit();
1242#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001243}
1244
1245module_init(wcnss_wlan_init);
1246module_exit(wcnss_wlan_exit);
1247
1248MODULE_LICENSE("GPL v2");
1249MODULE_VERSION(VERSION);
1250MODULE_DESCRIPTION(DEVICE "Driver");