blob: 439b1f80ab203a3c3abd224bd8c9689142821caa [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 Thalappileeb8c412012-08-10 17:17:09 -0700160 smd_channel_t *smd_ch;
161 unsigned char wcnss_version[WCNSS_VERSION_LEN];
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800162 unsigned int serial_number;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800163 int thermal_mitigation;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700164 enum wcnss_hw_type wcnss_hw_type;
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700165 void (*tm_notify)(struct device *, int);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700166 struct wcnss_wlan_config wlan_config;
David Collinsb727f7d2011-09-12 11:54:49 -0700167 struct delayed_work wcnss_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700168 struct work_struct wcnssctrl_version_work;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800169 struct work_struct wcnssctrl_nvbin_dnld_work;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700170 struct work_struct wcnssctrl_rx_work;
Sameer Thalappilf138da52012-07-30 12:56:37 -0700171 struct wake_lock wcnss_wake_lock;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700172 void __iomem *msm_wcnss_base;
Sameer Thalappild3aad702013-01-22 18:52:24 -0800173 void __iomem *riva_ccu_base;
174 void __iomem *pronto_a2xb_base;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700175} *penv = NULL;
176
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800177static ssize_t wcnss_serial_number_show(struct device *dev,
178 struct device_attribute *attr, char *buf)
179{
180 if (!penv)
181 return -ENODEV;
182
183 return scnprintf(buf, PAGE_SIZE, "%08X\n", penv->serial_number);
184}
185
186static ssize_t wcnss_serial_number_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700187 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800188{
189 unsigned int value;
190
191 if (!penv)
192 return -ENODEV;
193
194 if (sscanf(buf, "%08X", &value) != 1)
195 return -EINVAL;
196
197 penv->serial_number = value;
198 return count;
199}
200
201static DEVICE_ATTR(serial_number, S_IRUSR | S_IWUSR,
202 wcnss_serial_number_show, wcnss_serial_number_store);
203
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800204
205static ssize_t wcnss_thermal_mitigation_show(struct device *dev,
206 struct device_attribute *attr, char *buf)
207{
208 if (!penv)
209 return -ENODEV;
210
211 return scnprintf(buf, PAGE_SIZE, "%u\n", penv->thermal_mitigation);
212}
213
214static ssize_t wcnss_thermal_mitigation_store(struct device *dev,
Sameer Thalappilf138da52012-07-30 12:56:37 -0700215 struct device_attribute *attr, const char *buf, size_t count)
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800216{
217 int value;
218
219 if (!penv)
220 return -ENODEV;
221
222 if (sscanf(buf, "%d", &value) != 1)
223 return -EINVAL;
224 penv->thermal_mitigation = value;
Jeff Johnson61374652012-04-16 20:51:48 -0700225 if (penv->tm_notify)
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700226 (penv->tm_notify)(dev, value);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800227 return count;
228}
229
230static DEVICE_ATTR(thermal_mitigation, S_IRUSR | S_IWUSR,
231 wcnss_thermal_mitigation_show, wcnss_thermal_mitigation_store);
232
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700233
234static ssize_t wcnss_version_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, "%s", penv->wcnss_version);
241}
242
243static DEVICE_ATTR(wcnss_version, S_IRUSR,
244 wcnss_version_show, NULL);
245
Sameer Thalappil77716e52012-11-08 13:41:04 -0800246/* wcnss_reset_intr() is invoked when host drivers fails to
247 * communicate with WCNSS over SMD; so logging these registers
Sameer Thalappild3aad702013-01-22 18:52:24 -0800248 * helps to know WCNSS failure reason
249 */
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800250void wcnss_riva_log_debug_regs(void)
Sameer Thalappil77716e52012-11-08 13:41:04 -0800251{
Sameer Thalappil77716e52012-11-08 13:41:04 -0800252 void __iomem *ccu_reg;
253 u32 reg = 0;
254
Sameer Thalappild3aad702013-01-22 18:52:24 -0800255 ccu_reg = penv->riva_ccu_base + CCU_INVALID_ADDR_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800256 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800257 pr_info_ratelimited("%s: CCU_CCPU_INVALID_ADDR %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800258
Sameer Thalappild3aad702013-01-22 18:52:24 -0800259 ccu_reg = penv->riva_ccu_base + CCU_LAST_ADDR0_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800260 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800261 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR0 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800262
Sameer Thalappild3aad702013-01-22 18:52:24 -0800263 ccu_reg = penv->riva_ccu_base + CCU_LAST_ADDR1_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800264 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800265 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR1 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800266
Sameer Thalappild3aad702013-01-22 18:52:24 -0800267 ccu_reg = penv->riva_ccu_base + CCU_LAST_ADDR2_OFFSET;
Sameer Thalappil77716e52012-11-08 13:41:04 -0800268 reg = readl_relaxed(ccu_reg);
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800269 pr_info_ratelimited("%s: CCU_CCPU_LAST_ADDR2 %08x\n", __func__, reg);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800270
Sameer Thalappil77716e52012-11-08 13:41:04 -0800271}
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800272EXPORT_SYMBOL(wcnss_riva_log_debug_regs);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800273
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800274/* Log pronto debug registers before sending reset interrupt */
275void wcnss_pronto_log_debug_regs(void)
276{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800277 void __iomem *reg_addr;
278 u32 reg = 0;
279
Sameer Thalappild3aad702013-01-22 18:52:24 -0800280 reg_addr = penv->pronto_a2xb_base + A2XB_CFG_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800281 reg = readl_relaxed(reg_addr);
282 pr_info_ratelimited("%s: A2XB_CFG_OFFSET %08x\n", __func__, reg);
283
Sameer Thalappild3aad702013-01-22 18:52:24 -0800284 reg_addr = penv->pronto_a2xb_base + A2XB_INT_SRC_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800285 reg = readl_relaxed(reg_addr);
286 pr_info_ratelimited("%s: A2XB_INT_SRC_OFFSET %08x\n", __func__, reg);
287
Sameer Thalappild3aad702013-01-22 18:52:24 -0800288 reg_addr = penv->pronto_a2xb_base + A2XB_ERR_INFO_OFFSET;
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800289 reg = readl_relaxed(reg_addr);
290 pr_info_ratelimited("%s: A2XB_ERR_INFO_OFFSET %08x\n", __func__, reg);
291
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800292}
293EXPORT_SYMBOL(wcnss_pronto_log_debug_regs);
294
295/* interface to reset wcnss by sending the reset interrupt */
Sameer Thalappil19e02352012-09-24 15:26:12 -0700296void wcnss_reset_intr(void)
297{
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800298 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
299 wcnss_pronto_log_debug_regs();
Sameer Thalappil19e02352012-09-24 15:26:12 -0700300 pr_err("%s: reset interrupt not supported\n", __func__);
Sameer Thalappil77716e52012-11-08 13:41:04 -0800301 return;
Sameer Thalappil19e02352012-09-24 15:26:12 -0700302 }
Sameer Thalappil1b3e6112012-12-14 15:16:07 -0800303 wcnss_riva_log_debug_regs();
Sameer Thalappil77716e52012-11-08 13:41:04 -0800304 wmb();
305 __raw_writel(1 << 24, MSM_APCS_GCC_BASE + 0x8);
Sameer Thalappil19e02352012-09-24 15:26:12 -0700306}
307EXPORT_SYMBOL(wcnss_reset_intr);
308
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800309static int wcnss_create_sysfs(struct device *dev)
310{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800311 int ret;
312
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800313 if (!dev)
314 return -ENODEV;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800315
316 ret = device_create_file(dev, &dev_attr_serial_number);
317 if (ret)
318 return ret;
319
320 ret = device_create_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700321 if (ret)
322 goto remove_serial;
323
324 ret = device_create_file(dev, &dev_attr_wcnss_version);
325 if (ret)
326 goto remove_thermal;
327
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800328 return 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700329
330remove_thermal:
331 device_remove_file(dev, &dev_attr_thermal_mitigation);
332remove_serial:
333 device_remove_file(dev, &dev_attr_serial_number);
334
335 return ret;
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800336}
337
338static void wcnss_remove_sysfs(struct device *dev)
339{
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800340 if (dev) {
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800341 device_remove_file(dev, &dev_attr_serial_number);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800342 device_remove_file(dev, &dev_attr_thermal_mitigation);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700343 device_remove_file(dev, &dev_attr_wcnss_version);
344 }
345}
346static void wcnss_smd_notify_event(void *data, unsigned int event)
347{
348 int len = 0;
349
350 if (penv != data) {
351 pr_err("wcnss: invalid env pointer in smd callback\n");
352 return;
353 }
354 switch (event) {
355 case SMD_EVENT_DATA:
356 len = smd_read_avail(penv->smd_ch);
357 if (len < 0)
358 pr_err("wcnss: failed to read from smd %d\n", len);
359 schedule_work(&penv->wcnssctrl_rx_work);
360 break;
361
362 case SMD_EVENT_OPEN:
363 pr_debug("wcnss: opening WCNSS SMD channel :%s",
364 WCNSS_CTRL_CHANNEL);
365 if (!VALID_VERSION(penv->wcnss_version))
366 schedule_work(&penv->wcnssctrl_version_work);
367 break;
368
369 case SMD_EVENT_CLOSE:
370 pr_debug("wcnss: closing WCNSS SMD channel :%s",
371 WCNSS_CTRL_CHANNEL);
372 break;
373
374 default:
375 break;
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800376 }
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800377}
378
David Collinsb727f7d2011-09-12 11:54:49 -0700379static void wcnss_post_bootup(struct work_struct *work)
380{
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700381 pr_info("%s: Cancel APPS vote for Iris & WCNSS\n", __func__);
David Collinsb727f7d2011-09-12 11:54:49 -0700382
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700383 /* Since WCNSS is up, cancel any APPS vote for Iris & WCNSS VREGs */
David Collinsb727f7d2011-09-12 11:54:49 -0700384 wcnss_wlan_power(&penv->pdev->dev, &penv->wlan_config,
385 WCNSS_WLAN_SWITCH_OFF);
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700386
387}
388
389static int
390wcnss_pronto_gpios_config(struct device *dev, bool enable)
391{
392 int rc = 0;
393 int i, j;
394 int WCNSS_WLAN_NUM_GPIOS = 5;
395
396 for (i = 0; i < WCNSS_WLAN_NUM_GPIOS; i++) {
397 int gpio = of_get_gpio(dev->of_node, i);
398 if (enable) {
399 rc = gpio_request(gpio, "wcnss_wlan");
400 if (rc) {
401 pr_err("WCNSS gpio_request %d err %d\n",
402 gpio, rc);
403 goto fail;
404 }
405 } else
406 gpio_free(gpio);
407 }
408
409 return rc;
410
411fail:
412 for (j = WCNSS_WLAN_NUM_GPIOS-1; j >= 0; j--) {
413 int gpio = of_get_gpio(dev->of_node, i);
414 gpio_free(gpio);
415 }
416 return rc;
David Collinsb727f7d2011-09-12 11:54:49 -0700417}
418
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700419static int
420wcnss_gpios_config(struct resource *gpios_5wire, bool enable)
421{
422 int i, j;
423 int rc = 0;
424
425 for (i = gpios_5wire->start; i <= gpios_5wire->end; i++) {
426 if (enable) {
427 rc = gpio_request(i, gpios_5wire->name);
428 if (rc) {
429 pr_err("WCNSS gpio_request %d err %d\n", i, rc);
430 goto fail;
431 }
432 } else
433 gpio_free(i);
434 }
435
436 return rc;
437
438fail:
439 for (j = i-1; j >= gpios_5wire->start; j--)
440 gpio_free(j);
441 return rc;
442}
443
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700444static int __devinit
445wcnss_wlan_ctrl_probe(struct platform_device *pdev)
446{
Sameer Thalappil667f43f2011-10-10 11:12:54 -0700447 if (!penv)
448 return -ENODEV;
449
450 penv->smd_channel_ready = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700451
452 pr_info("%s: SMD ctrl channel up\n", __func__);
453
David Collinsb727f7d2011-09-12 11:54:49 -0700454 /* Schedule a work to do any post boot up activity */
455 INIT_DELAYED_WORK(&penv->wcnss_work, wcnss_post_bootup);
456 schedule_delayed_work(&penv->wcnss_work, msecs_to_jiffies(10000));
457
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700458 return 0;
459}
460
Sameer Thalappil13f45182012-07-23 18:02:45 -0700461void wcnss_flush_delayed_boot_votes()
462{
Sameer Thalappilf106a682013-02-16 20:41:11 -0800463 flush_delayed_work(&penv->wcnss_work);
Sameer Thalappil13f45182012-07-23 18:02:45 -0700464}
465EXPORT_SYMBOL(wcnss_flush_delayed_boot_votes);
466
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700467static int __devexit
468wcnss_wlan_ctrl_remove(struct platform_device *pdev)
469{
470 if (penv)
471 penv->smd_channel_ready = 0;
472
473 pr_info("%s: SMD ctrl channel down\n", __func__);
474
475 return 0;
476}
477
478
479static struct platform_driver wcnss_wlan_ctrl_driver = {
480 .driver = {
481 .name = "WLAN_CTRL",
482 .owner = THIS_MODULE,
483 },
484 .probe = wcnss_wlan_ctrl_probe,
485 .remove = __devexit_p(wcnss_wlan_ctrl_remove),
486};
487
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700488static int __devexit
489wcnss_ctrl_remove(struct platform_device *pdev)
490{
491 if (penv && penv->smd_ch)
492 smd_close(penv->smd_ch);
493
494 return 0;
495}
496
497static int __devinit
498wcnss_ctrl_probe(struct platform_device *pdev)
499{
500 int ret = 0;
501
502 if (!penv)
503 return -ENODEV;
504
505 ret = smd_named_open_on_edge(WCNSS_CTRL_CHANNEL, SMD_APPS_WCNSS,
506 &penv->smd_ch, penv, wcnss_smd_notify_event);
507 if (ret < 0) {
508 pr_err("wcnss: cannot open the smd command channel %s: %d\n",
509 WCNSS_CTRL_CHANNEL, ret);
510 return -ENODEV;
511 }
512 smd_disable_read_intr(penv->smd_ch);
513
514 return 0;
515}
516
517/* platform device for WCNSS_CTRL SMD channel */
518static struct platform_driver wcnss_ctrl_driver = {
519 .driver = {
520 .name = "WCNSS_CTRL",
521 .owner = THIS_MODULE,
522 },
523 .probe = wcnss_ctrl_probe,
524 .remove = __devexit_p(wcnss_ctrl_remove),
525};
526
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700527struct device *wcnss_wlan_get_device(void)
528{
529 if (penv && penv->pdev && penv->smd_channel_ready)
530 return &penv->pdev->dev;
531 return NULL;
532}
533EXPORT_SYMBOL(wcnss_wlan_get_device);
534
Sameer Thalappil409ed352011-12-07 10:53:31 -0800535struct platform_device *wcnss_get_platform_device(void)
536{
537 if (penv && penv->pdev)
538 return penv->pdev;
539 return NULL;
540}
541EXPORT_SYMBOL(wcnss_get_platform_device);
542
543struct wcnss_wlan_config *wcnss_get_wlan_config(void)
544{
545 if (penv && penv->pdev)
546 return &penv->wlan_config;
547 return NULL;
548}
549EXPORT_SYMBOL(wcnss_get_wlan_config);
550
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700551struct resource *wcnss_wlan_get_memory_map(struct device *dev)
552{
553 if (penv && dev && (dev == &penv->pdev->dev) && penv->smd_channel_ready)
554 return penv->mmio_res;
555 return NULL;
556}
557EXPORT_SYMBOL(wcnss_wlan_get_memory_map);
558
559int wcnss_wlan_get_dxe_tx_irq(struct device *dev)
560{
561 if (penv && dev && (dev == &penv->pdev->dev) &&
562 penv->tx_irq_res && penv->smd_channel_ready)
563 return penv->tx_irq_res->start;
564 return WCNSS_WLAN_IRQ_INVALID;
565}
566EXPORT_SYMBOL(wcnss_wlan_get_dxe_tx_irq);
567
568int wcnss_wlan_get_dxe_rx_irq(struct device *dev)
569{
570 if (penv && dev && (dev == &penv->pdev->dev) &&
571 penv->rx_irq_res && penv->smd_channel_ready)
572 return penv->rx_irq_res->start;
573 return WCNSS_WLAN_IRQ_INVALID;
574}
575EXPORT_SYMBOL(wcnss_wlan_get_dxe_rx_irq);
576
577void wcnss_wlan_register_pm_ops(struct device *dev,
578 const struct dev_pm_ops *pm_ops)
579{
580 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops)
581 penv->pm_ops = pm_ops;
582}
583EXPORT_SYMBOL(wcnss_wlan_register_pm_ops);
584
Sameer Thalappilecdd1002011-09-09 10:52:27 -0700585void wcnss_wlan_unregister_pm_ops(struct device *dev,
586 const struct dev_pm_ops *pm_ops)
587{
588 if (penv && dev && (dev == &penv->pdev->dev) && pm_ops) {
589 if (pm_ops->suspend != penv->pm_ops->suspend ||
590 pm_ops->resume != penv->pm_ops->resume)
591 pr_err("PM APIs dont match with registered APIs\n");
592 penv->pm_ops = NULL;
593 }
594}
595EXPORT_SYMBOL(wcnss_wlan_unregister_pm_ops);
596
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700597void wcnss_register_thermal_mitigation(struct device *dev,
598 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700599{
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700600 if (penv && dev && tm_notify)
Jeff Johnson61374652012-04-16 20:51:48 -0700601 penv->tm_notify = tm_notify;
602}
603EXPORT_SYMBOL(wcnss_register_thermal_mitigation);
604
Sameer Thalappila112bbc2012-05-23 12:20:32 -0700605void wcnss_unregister_thermal_mitigation(
606 void (*tm_notify)(struct device *, int))
Jeff Johnson61374652012-04-16 20:51:48 -0700607{
608 if (penv && tm_notify) {
609 if (tm_notify != penv->tm_notify)
610 pr_err("tm_notify doesn't match registered\n");
611 penv->tm_notify = NULL;
612 }
613}
614EXPORT_SYMBOL(wcnss_unregister_thermal_mitigation);
615
Jeff Johnson8b1f6d02012-02-03 20:43:26 -0800616unsigned int wcnss_get_serial_number(void)
617{
618 if (penv)
619 return penv->serial_number;
620 return 0;
621}
622EXPORT_SYMBOL(wcnss_get_serial_number);
623
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700624static int enable_wcnss_suspend_notify;
625
626static int enable_wcnss_suspend_notify_set(const char *val,
627 struct kernel_param *kp)
628{
629 int ret;
630
631 ret = param_set_int(val, kp);
632 if (ret)
633 return ret;
634
635 if (enable_wcnss_suspend_notify)
636 pr_debug("Suspend notification activated for wcnss\n");
637
638 return 0;
639}
640module_param_call(enable_wcnss_suspend_notify, enable_wcnss_suspend_notify_set,
641 param_get_int, &enable_wcnss_suspend_notify, S_IRUGO | S_IWUSR);
642
643
644void wcnss_suspend_notify(void)
645{
646 void __iomem *pmu_spare_reg;
647 u32 reg = 0;
648 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700649
650 if (!enable_wcnss_suspend_notify)
651 return;
652
653 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
654 return;
655
656 /* For Riva */
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700657 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
658 spin_lock_irqsave(&reg_spinlock, flags);
659 reg = readl_relaxed(pmu_spare_reg);
660 reg |= RIVA_SUSPEND_BIT;
661 writel_relaxed(reg, pmu_spare_reg);
662 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700663}
664EXPORT_SYMBOL(wcnss_suspend_notify);
665
666void wcnss_resume_notify(void)
667{
668 void __iomem *pmu_spare_reg;
669 u32 reg = 0;
670 unsigned long flags;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700671
672 if (!enable_wcnss_suspend_notify)
673 return;
674
675 if (wcnss_hardware_type() == WCNSS_PRONTO_HW)
676 return;
677
678 /* For Riva */
679 pmu_spare_reg = penv->msm_wcnss_base + RIVA_SPARE_OFFSET;
680
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700681 spin_lock_irqsave(&reg_spinlock, flags);
682 reg = readl_relaxed(pmu_spare_reg);
683 reg &= ~RIVA_SUSPEND_BIT;
684 writel_relaxed(reg, pmu_spare_reg);
685 spin_unlock_irqrestore(&reg_spinlock, flags);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700686}
687EXPORT_SYMBOL(wcnss_resume_notify);
688
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700689static int wcnss_wlan_suspend(struct device *dev)
690{
691 if (penv && dev && (dev == &penv->pdev->dev) &&
692 penv->smd_channel_ready &&
693 penv->pm_ops && penv->pm_ops->suspend)
694 return penv->pm_ops->suspend(dev);
695 return 0;
696}
697
698static int wcnss_wlan_resume(struct device *dev)
699{
700 if (penv && dev && (dev == &penv->pdev->dev) &&
701 penv->smd_channel_ready &&
702 penv->pm_ops && penv->pm_ops->resume)
703 return penv->pm_ops->resume(dev);
704 return 0;
705}
706
Sameer Thalappilf138da52012-07-30 12:56:37 -0700707void wcnss_prevent_suspend()
708{
709 if (penv)
710 wake_lock(&penv->wcnss_wake_lock);
711}
712EXPORT_SYMBOL(wcnss_prevent_suspend);
713
714void wcnss_allow_suspend()
715{
716 if (penv)
717 wake_unlock(&penv->wcnss_wake_lock);
718}
719EXPORT_SYMBOL(wcnss_allow_suspend);
720
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700721int wcnss_hardware_type(void)
722{
723 if (penv)
724 return penv->wcnss_hw_type;
725 else
726 return -ENODEV;
727}
728EXPORT_SYMBOL(wcnss_hardware_type);
729
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700730static int wcnss_smd_tx(void *data, int len)
731{
732 int ret = 0;
733
734 ret = smd_write_avail(penv->smd_ch);
735 if (ret < len) {
736 pr_err("wcnss: no space available for smd frame\n");
Sameer Thalappilf106a682013-02-16 20:41:11 -0800737 return -ENOSPC;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700738 }
739 ret = smd_write(penv->smd_ch, data, len);
740 if (ret < len) {
741 pr_err("wcnss: failed to write Command %d", len);
742 ret = -ENODEV;
743 }
744 return ret;
745}
746
747static void wcnssctrl_rx_handler(struct work_struct *worker)
748{
749 int len = 0;
750 int rc = 0;
751 unsigned char buf[WCNSS_MAX_FRAME_SIZE];
752 struct smd_msg_hdr *phdr;
753 struct wcnss_version *pversion;
Sameer Thalappilf106a682013-02-16 20:41:11 -0800754 int hw_type;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700755
756 len = smd_read_avail(penv->smd_ch);
757 if (len > WCNSS_MAX_FRAME_SIZE) {
758 pr_err("wcnss: frame larger than the allowed size\n");
759 smd_read(penv->smd_ch, NULL, len);
760 return;
761 }
762 if (len <= 0)
763 return;
764
765 rc = smd_read(penv->smd_ch, buf, len);
766 if (rc < len) {
767 pr_err("wcnss: incomplete data read from smd\n");
768 return;
769 }
770
771 phdr = (struct smd_msg_hdr *)buf;
772
Sameer Thalappilf106a682013-02-16 20:41:11 -0800773 switch (phdr->msg_type) {
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700774
775 case WCNSS_VERSION_RSP:
776 pversion = (struct wcnss_version *)buf;
777 if (len != sizeof(struct wcnss_version)) {
778 pr_err("wcnss: invalid version data from wcnss %d\n",
779 len);
780 return;
781 }
782 snprintf(penv->wcnss_version, WCNSS_VERSION_LEN,
783 "%02x%02x%02x%02x", pversion->major, pversion->minor,
784 pversion->version, pversion->revision);
785 pr_info("wcnss: version %s\n", penv->wcnss_version);
Sameer Thalappilf106a682013-02-16 20:41:11 -0800786 /* schedule work to download nvbin to ccpu */
787 hw_type = wcnss_hardware_type();
788 switch (hw_type) {
789 case WCNSS_RIVA_HW:
790 /* supported only if riva major >= 1 and minor >= 4 */
791 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
792 pr_info("wcnss: schedule dnld work for riva\n");
793 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
794 }
795 break;
796
797 case WCNSS_PRONTO_HW:
798 /* supported only if pronto major >= 1 and minor >= 4 */
799 if ((pversion->major >= 1) && (pversion->minor >= 4)) {
800 pr_info("wcnss: schedule dnld work for pronto\n");
801 schedule_work(&penv->wcnssctrl_nvbin_dnld_work);
802 }
803 break;
804
805 default:
806 pr_info("wcnss: unknown hw type (%d), will not schedule dnld work\n",
807 hw_type);
808 break;
809 }
810 break;
811
812 case WCNSS_NVBIN_DNLD_RSP:
813 pr_info("wcnss: received WCNSS_NVBIN_DNLD_RSP from ccpu\n");
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700814 break;
815
816 default:
Sameer Thalappilf106a682013-02-16 20:41:11 -0800817 pr_err("wcnss: invalid message type %d\n", phdr->msg_type);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700818 }
819 return;
820}
821
822static void wcnss_send_version_req(struct work_struct *worker)
823{
824 struct smd_msg_hdr smd_msg;
825 int ret = 0;
826
Sameer Thalappilf106a682013-02-16 20:41:11 -0800827 smd_msg.msg_type = WCNSS_VERSION_REQ;
828 smd_msg.msg_len = sizeof(smd_msg);
829 ret = wcnss_smd_tx(&smd_msg, smd_msg.msg_len);
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700830 if (ret < 0)
831 pr_err("wcnss: smd tx failed\n");
832
833 return;
834}
835
Sameer Thalappilf106a682013-02-16 20:41:11 -0800836static void wcnss_nvbin_dnld_req(struct work_struct *worker)
837{
838 int ret = 0;
839 struct nvbin_dnld_req_msg *dnld_req_msg;
840 unsigned short total_fragments = 0;
841 unsigned short count = 0;
842 unsigned short retry_count = 0;
843 unsigned short cur_frag_size = 0;
844 unsigned char *outbuffer = NULL;
845 const void *nv_blob_addr = NULL;
846 unsigned int nv_blob_size = 0;
847 const struct firmware *nv = NULL;
848 struct device *dev = NULL;
849
850 dev = wcnss_wlan_get_device();
851
852 ret = request_firmware(&nv, NVBIN_FILE, dev);
853
854 if (ret || !nv || !nv->data || !nv->size) {
855 pr_err("wcnss: wcnss_nvbin_dnld_req: request_firmware failed for %s\n",
856 NVBIN_FILE);
857 return;
858 }
859
860 /*
861 * First 4 bytes in nv blob is validity bitmap.
862 * We cannot validate nv, so skip those 4 bytes.
863 */
864 nv_blob_addr = nv->data + 4;
865 nv_blob_size = nv->size - 4;
866
867 total_fragments = TOTALFRAGMENTS(nv_blob_size);
868
869 pr_info("wcnss: NV bin size: %d, total_fragments: %d\n",
870 nv_blob_size, total_fragments);
871
872 /* get buffer for nv bin dnld req message */
873 outbuffer = kmalloc((sizeof(struct nvbin_dnld_req_msg) +
874 NV_FRAGMENT_SIZE), GFP_KERNEL);
875
876 if (NULL == outbuffer) {
877 pr_err("wcnss: wcnss_nvbin_dnld_req: failed to get buffer\n");
878 goto err_free_nv;
879 }
880
881 dnld_req_msg = (struct nvbin_dnld_req_msg *)outbuffer;
882
883 dnld_req_msg->hdr.msg_type = WCNSS_NVBIN_DNLD_REQ;
884
885 for (count = 0; count < total_fragments; count++) {
886 dnld_req_msg->dnld_req_params.frag_number = count;
887
888 if (count == (total_fragments - 1)) {
889 /* last fragment, take care of boundry condition */
890 cur_frag_size = nv_blob_size % NV_FRAGMENT_SIZE;
891 if (!cur_frag_size)
892 cur_frag_size = NV_FRAGMENT_SIZE;
893
894 dnld_req_msg->dnld_req_params.is_last_fragment = 1;
895 } else {
896 cur_frag_size = NV_FRAGMENT_SIZE;
897 dnld_req_msg->dnld_req_params.is_last_fragment = 0;
898 }
899
900 dnld_req_msg->dnld_req_params.nvbin_buffer_size =
901 cur_frag_size;
902
903 dnld_req_msg->hdr.msg_len =
904 sizeof(struct nvbin_dnld_req_msg) + cur_frag_size;
905
906 /* copy NV fragment */
907 memcpy((outbuffer + sizeof(struct nvbin_dnld_req_msg)),
908 (nv_blob_addr + count * NV_FRAGMENT_SIZE),
909 cur_frag_size);
910
911 ret = wcnss_smd_tx(outbuffer, dnld_req_msg->hdr.msg_len);
912
913 retry_count = 0;
914 while ((ret == -ENOSPC) && (retry_count <= 3)) {
915 pr_debug("wcnss: wcnss_nvbin_dnld_req: smd tx failed, ENOSPC\n");
916 pr_debug("fragment: %d, len: %d, TotFragments: %d, retry_count: %d\n",
917 count, dnld_req_msg->hdr.msg_len,
918 total_fragments, retry_count);
919
920 /* wait and try again */
921 msleep(20);
922 retry_count++;
923 ret = wcnss_smd_tx(outbuffer,
924 dnld_req_msg->hdr.msg_len);
925 }
926
927 if (ret < 0) {
928 pr_err("wcnss: wcnss_nvbin_dnld_req: smd tx failed\n");
929 pr_err("fragment %d, len: %d, TotFragments: %d, retry_count: %d\n",
930 count, dnld_req_msg->hdr.msg_len,
931 total_fragments, retry_count);
932 goto err_dnld;
933 }
934 }
935
936err_dnld:
937 /* free buffer */
938 kfree(outbuffer);
939
940err_free_nv:
941 /* release firmware */
942 release_firmware(nv);
943
944 return;
945}
946
Jeff Johnsonb3377e32011-11-18 23:32:27 -0800947static int
948wcnss_trigger_config(struct platform_device *pdev)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700949{
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700950 int ret;
Ankur Nandwanib0039b02011-08-09 14:00:45 -0700951 struct qcom_wcnss_opts *pdata;
Sameer Thalappiled3f7da2012-11-01 12:54:01 -0700952 unsigned long wcnss_phys_addr;
953 int size = 0;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700954 int has_pronto_hw = of_property_read_bool(pdev->dev.of_node,
955 "qcom,has_pronto_hw");
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700956
Jeff Johnsonb3377e32011-11-18 23:32:27 -0800957 /* make sure we are only triggered once */
958 if (penv->triggered)
959 return 0;
960 penv->triggered = 1;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700961
Ankur Nandwanib0039b02011-08-09 14:00:45 -0700962 /* initialize the WCNSS device configuration */
963 pdata = pdev->dev.platform_data;
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700964 if (WCNSS_CONFIG_UNSPECIFIED == has_48mhz_xo) {
965 if (has_pronto_hw) {
966 has_48mhz_xo = of_property_read_bool(pdev->dev.of_node,
967 "qcom,has_48mhz_xo");
968 penv->wcnss_hw_type = WCNSS_PRONTO_HW;
969 } else {
970 penv->wcnss_hw_type = WCNSS_RIVA_HW;
971 has_48mhz_xo = pdata->has_48mhz_xo;
972 }
973 }
Ankur Nandwanib0039b02011-08-09 14:00:45 -0700974 penv->wlan_config.use_48mhz_xo = has_48mhz_xo;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700975
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800976 penv->thermal_mitigation = 0;
Sameer Thalappileeb8c412012-08-10 17:17:09 -0700977 strlcpy(penv->wcnss_version, "INVALID", WCNSS_VERSION_LEN);
Jeff Johnson5fda4f82012-01-09 14:15:34 -0800978
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700979 /* Configure 5 wire GPIOs */
Sameer Thalappil8d686d42012-08-24 10:07:31 -0700980 if (!has_pronto_hw) {
981 penv->gpios_5wire = platform_get_resource_byname(pdev,
982 IORESOURCE_IO, "wcnss_gpios_5wire");
983
984 /* allocate 5-wire GPIO resources */
985 if (!penv->gpios_5wire) {
986 dev_err(&pdev->dev, "insufficient IO resources\n");
987 ret = -ENOENT;
988 goto fail_gpio_res;
989 }
990 ret = wcnss_gpios_config(penv->gpios_5wire, true);
991 } else
992 ret = wcnss_pronto_gpios_config(&pdev->dev, true);
993
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -0700994 if (ret) {
995 dev_err(&pdev->dev, "WCNSS gpios config failed.\n");
996 goto fail_gpio_res;
997 }
998
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700999 /* power up the WCNSS */
1000 ret = wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1001 WCNSS_WLAN_SWITCH_ON);
1002 if (ret) {
1003 dev_err(&pdev->dev, "WCNSS Power-up failed.\n");
1004 goto fail_power;
1005 }
1006
1007 /* trigger initialization of the WCNSS */
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001008 penv->pil = subsystem_get(WCNSS_PIL_DEVICE);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001009 if (IS_ERR(penv->pil)) {
1010 dev_err(&pdev->dev, "Peripheral Loader failed on WCNSS.\n");
1011 ret = PTR_ERR(penv->pil);
1012 penv->pil = NULL;
1013 goto fail_pil;
1014 }
1015
1016 /* allocate resources */
1017 penv->mmio_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
1018 "wcnss_mmio");
1019 penv->tx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1020 "wcnss_wlantx_irq");
1021 penv->rx_irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
1022 "wcnss_wlanrx_irq");
1023
1024 if (!(penv->mmio_res && penv->tx_irq_res && penv->rx_irq_res)) {
1025 dev_err(&pdev->dev, "insufficient resources\n");
1026 ret = -ENOENT;
1027 goto fail_res;
1028 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001029 INIT_WORK(&penv->wcnssctrl_rx_work, wcnssctrl_rx_handler);
1030 INIT_WORK(&penv->wcnssctrl_version_work, wcnss_send_version_req);
Sameer Thalappilf106a682013-02-16 20:41:11 -08001031 INIT_WORK(&penv->wcnssctrl_nvbin_dnld_work, wcnss_nvbin_dnld_req);
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001032
Sameer Thalappilf138da52012-07-30 12:56:37 -07001033 wake_lock_init(&penv->wcnss_wake_lock, WAKE_LOCK_SUSPEND, "wcnss");
1034
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001035 if (wcnss_hardware_type() == WCNSS_PRONTO_HW) {
1036 size = 0x3000;
1037 wcnss_phys_addr = MSM_PRONTO_PHYS;
1038 } else {
1039 wcnss_phys_addr = MSM_RIVA_PHYS;
1040 size = SZ_256;
1041 }
1042
1043 penv->msm_wcnss_base = ioremap(wcnss_phys_addr, size);
1044 if (!penv->msm_wcnss_base) {
1045 ret = -ENOMEM;
1046 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1047 goto fail_wake;
1048 }
1049
Sameer Thalappild3aad702013-01-22 18:52:24 -08001050 if (wcnss_hardware_type() == WCNSS_RIVA_HW) {
1051 penv->riva_ccu_base = ioremap(MSM_RIVA_CCU_BASE, SZ_512);
1052 if (!penv->riva_ccu_base) {
1053 ret = -ENOMEM;
1054 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1055 goto fail_ioremap;
1056 }
1057 } else {
1058 penv->pronto_a2xb_base = ioremap(MSM_PRONTO_A2XB_BASE, SZ_512);
1059 if (!penv->pronto_a2xb_base) {
1060 ret = -ENOMEM;
1061 pr_err("%s: ioremap wcnss physical failed\n", __func__);
1062 goto fail_ioremap;
1063 }
1064 }
1065
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001066 return 0;
1067
Sameer Thalappild3aad702013-01-22 18:52:24 -08001068fail_ioremap:
1069 iounmap(penv->msm_wcnss_base);
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001070fail_wake:
1071 wake_lock_destroy(&penv->wcnss_wake_lock);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001072fail_res:
1073 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001074 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001075fail_pil:
1076 wcnss_wlan_power(&pdev->dev, &penv->wlan_config,
1077 WCNSS_WLAN_SWITCH_OFF);
1078fail_power:
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001079 if (has_pronto_hw)
Sameer Thalappiled3f7da2012-11-01 12:54:01 -07001080 wcnss_pronto_gpios_config(&pdev->dev, false);
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001081 else
1082 wcnss_gpios_config(penv->gpios_5wire, false);
Ankur Nandwaniad0d9ac2011-09-26 11:49:25 -07001083fail_gpio_res:
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001084 penv = NULL;
1085 return ret;
1086}
1087
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001088#ifndef MODULE
1089static int wcnss_node_open(struct inode *inode, struct file *file)
1090{
1091 struct platform_device *pdev;
1092
1093 pr_info(DEVICE " triggered by userspace\n");
1094
1095 pdev = penv->pdev;
1096 return wcnss_trigger_config(pdev);
1097}
1098
1099static const struct file_operations wcnss_node_fops = {
1100 .owner = THIS_MODULE,
1101 .open = wcnss_node_open,
1102};
1103
1104static struct miscdevice wcnss_misc = {
1105 .minor = MISC_DYNAMIC_MINOR,
1106 .name = DEVICE,
1107 .fops = &wcnss_node_fops,
1108};
1109#endif /* ifndef MODULE */
1110
1111
1112static int __devinit
1113wcnss_wlan_probe(struct platform_device *pdev)
1114{
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001115 int ret = 0;
1116
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001117 /* verify we haven't been called more than once */
1118 if (penv) {
1119 dev_err(&pdev->dev, "cannot handle multiple devices.\n");
1120 return -ENODEV;
1121 }
1122
1123 /* create an environment to track the device */
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001124 penv = devm_kzalloc(&pdev->dev, sizeof(*penv), GFP_KERNEL);
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001125 if (!penv) {
1126 dev_err(&pdev->dev, "cannot allocate device memory.\n");
1127 return -ENOMEM;
1128 }
1129 penv->pdev = pdev;
1130
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001131 /* register sysfs entries */
1132 ret = wcnss_create_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001133 if (ret) {
1134 penv = NULL;
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001135 return -ENOENT;
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001136 }
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001137
1138
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001139#ifdef MODULE
1140
Sameer Thalappild3aad702013-01-22 18:52:24 -08001141 /* Since we were built as a module, we are running because
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001142 * the module was loaded, therefore we assume userspace
1143 * applications are available to service PIL, so we can
1144 * trigger the WCNSS configuration now
1145 */
1146 pr_info(DEVICE " probed in MODULE mode\n");
1147 return wcnss_trigger_config(pdev);
1148
1149#else
1150
Sameer Thalappild3aad702013-01-22 18:52:24 -08001151 /* Since we were built into the kernel we'll be called as part
Jeff Johnsonb3377e32011-11-18 23:32:27 -08001152 * of kernel initialization. We don't know if userspace
1153 * applications are available to service PIL at this time
1154 * (they probably are not), so we simply create a device node
1155 * here. When userspace is available it should touch the
1156 * device so that we know that WCNSS configuration can take
1157 * place
1158 */
1159 pr_info(DEVICE " probed in built-in mode\n");
1160 return misc_register(&wcnss_misc);
1161
1162#endif
1163}
1164
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001165static int __devexit
1166wcnss_wlan_remove(struct platform_device *pdev)
1167{
Jeff Johnson8b1f6d02012-02-03 20:43:26 -08001168 wcnss_remove_sysfs(&pdev->dev);
Sameer Thalappil7e61a6d2012-11-14 11:28:41 -08001169 penv = NULL;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001170 return 0;
1171}
1172
1173
1174static const struct dev_pm_ops wcnss_wlan_pm_ops = {
1175 .suspend = wcnss_wlan_suspend,
1176 .resume = wcnss_wlan_resume,
1177};
1178
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001179#ifdef CONFIG_WCNSS_CORE_PRONTO
1180static struct of_device_id msm_wcnss_pronto_match[] = {
1181 {.compatible = "qcom,wcnss_wlan"},
1182 {}
1183};
1184#endif
1185
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001186static struct platform_driver wcnss_wlan_driver = {
1187 .driver = {
1188 .name = DEVICE,
1189 .owner = THIS_MODULE,
1190 .pm = &wcnss_wlan_pm_ops,
Sameer Thalappil8d686d42012-08-24 10:07:31 -07001191#ifdef CONFIG_WCNSS_CORE_PRONTO
1192 .of_match_table = msm_wcnss_pronto_match,
1193#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001194 },
1195 .probe = wcnss_wlan_probe,
1196 .remove = __devexit_p(wcnss_wlan_remove),
1197};
1198
1199static int __init wcnss_wlan_init(void)
1200{
Sameer Thalappil24db5282012-09-10 11:58:33 -07001201 int ret = 0;
1202
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001203 platform_driver_register(&wcnss_wlan_driver);
1204 platform_driver_register(&wcnss_wlan_ctrl_driver);
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001205 platform_driver_register(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001206
Sameer Thalappil24db5282012-09-10 11:58:33 -07001207#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1208 ret = wcnss_prealloc_init();
1209 if (ret < 0)
1210 pr_err("wcnss: pre-allocation failed\n");
1211#endif
1212
1213 return ret;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001214}
1215
1216static void __exit wcnss_wlan_exit(void)
1217{
1218 if (penv) {
1219 if (penv->pil)
Stephen Boyd77db8bb2012-06-27 15:15:16 -07001220 subsystem_put(penv->pil);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001221
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001222
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001223 penv = NULL;
1224 }
1225
Sameer Thalappileeb8c412012-08-10 17:17:09 -07001226 platform_driver_unregister(&wcnss_ctrl_driver);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001227 platform_driver_unregister(&wcnss_wlan_ctrl_driver);
1228 platform_driver_unregister(&wcnss_wlan_driver);
Sameer Thalappil24db5282012-09-10 11:58:33 -07001229#ifdef CONFIG_WCNSS_MEM_PRE_ALLOC
1230 wcnss_prealloc_deinit();
1231#endif
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001232}
1233
1234module_init(wcnss_wlan_init);
1235module_exit(wcnss_wlan_exit);
1236
1237MODULE_LICENSE("GPL v2");
1238MODULE_VERSION(VERSION);
1239MODULE_DESCRIPTION(DEVICE "Driver");