994 lines
22 KiB
C
994 lines
22 KiB
C
|
/*
|
||
|
* Copyright (c) 2004-2012 Atheros Communications Inc.
|
||
|
*
|
||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
* copyright notice and this permission notice appear in all copies.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
*/
|
||
|
#ifdef CONFIG_ANDROID_8960_SDIO
|
||
|
#include "core.h"
|
||
|
|
||
|
/* BeginMMC polling stuff */
|
||
|
#ifdef CONFIG_MMC_MSM_SDC3_POLLING
|
||
|
#define MMC_MSM_DEV "msm_sdcc.3"
|
||
|
#else
|
||
|
#define MMC_MSM_DEV "msm_sdcc.4"
|
||
|
#endif
|
||
|
/* End MMC polling stuff */
|
||
|
#endif /* #ifdef CONFIG_ANDROID_8960_SDIO */
|
||
|
|
||
|
#include "core.h"
|
||
|
#include "debug.h"
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/wlan_plat.h>
|
||
|
#include <mach/msm_bus.h>
|
||
|
#include <linux/gpio.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_gpio.h>
|
||
|
#include <linux/regulator/consumer.h>
|
||
|
#include <linux/regulator/driver.h>
|
||
|
#include <linux/usb/msm_hsusb.h>
|
||
|
|
||
|
#define GET_INODE_FROM_FILEP(filp) ((filp)->f_path.dentry->d_inode)
|
||
|
|
||
|
int android_readwrite_file(const char *filename, char *rbuf,
|
||
|
const char *wbuf, size_t length)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct file *filp = (struct file *)-ENOENT;
|
||
|
mm_segment_t oldfs;
|
||
|
oldfs = get_fs();
|
||
|
set_fs(KERNEL_DS);
|
||
|
|
||
|
do {
|
||
|
int mode = (wbuf) ? O_WRONLY : O_RDONLY;
|
||
|
filp = filp_open(filename, mode, S_IRUSR);
|
||
|
|
||
|
if (IS_ERR(filp) || !filp->f_op) {
|
||
|
ret = -ENOENT;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!filp->f_op->write || !filp->f_op->read) {
|
||
|
filp_close(filp, NULL);
|
||
|
ret = -ENOENT;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (length == 0) {
|
||
|
/* Read the length of the file only */
|
||
|
struct inode *inode;
|
||
|
|
||
|
inode = GET_INODE_FROM_FILEP(filp);
|
||
|
if (!inode) {
|
||
|
printk(KERN_ERR
|
||
|
"android_readwrite_file: Error 2\n");
|
||
|
ret = -ENOENT;
|
||
|
break;
|
||
|
}
|
||
|
ret = i_size_read(inode->i_mapping->host);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (wbuf) {
|
||
|
ret = filp->f_op->write(
|
||
|
filp, wbuf, length, &filp->f_pos);
|
||
|
if (ret < 0) {
|
||
|
printk(KERN_ERR
|
||
|
"android_readwrite_file: Error 3\n");
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
ret = filp->f_op->read(
|
||
|
filp, rbuf, length, &filp->f_pos);
|
||
|
if (ret < 0) {
|
||
|
printk(KERN_ERR
|
||
|
"android_readwrite_file: Error 4\n");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} while (0);
|
||
|
|
||
|
if (!IS_ERR(filp))
|
||
|
filp_close(filp, NULL);
|
||
|
|
||
|
set_fs(oldfs);
|
||
|
printk(KERN_ERR "android_readwrite_file: ret=%d\n", ret);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_ANDROID_8960_SDIO
|
||
|
static struct wifi_platform_data *wifi_control_data;
|
||
|
struct semaphore wifi_control_sem;
|
||
|
|
||
|
int wifi_set_power(int on, unsigned long msec)
|
||
|
{
|
||
|
if (wifi_control_data && wifi_control_data->set_power)
|
||
|
wifi_control_data->set_power(on);
|
||
|
|
||
|
if (msec)
|
||
|
mdelay(msec);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int wifi_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct wifi_platform_data *wifi_ctrl =
|
||
|
(struct wifi_platform_data *)(pdev->dev.platform_data);
|
||
|
|
||
|
wifi_control_data = wifi_ctrl;
|
||
|
|
||
|
wifi_set_power(1, 0); /* Power On */
|
||
|
|
||
|
up(&wifi_control_sem);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int wifi_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct wifi_platform_data *wifi_ctrl =
|
||
|
(struct wifi_platform_data *)(pdev->dev.platform_data);
|
||
|
|
||
|
wifi_control_data = wifi_ctrl;
|
||
|
|
||
|
wifi_set_power(0, 0); /* Power Off */
|
||
|
|
||
|
up(&wifi_control_sem);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct platform_driver wifi_device = {
|
||
|
.probe = wifi_probe,
|
||
|
.remove = wifi_remove,
|
||
|
.driver = {
|
||
|
.name = "ath6kl_power",
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void __init ath6kl_sdio_init_msm(void)
|
||
|
{
|
||
|
char buf[3];
|
||
|
int length;
|
||
|
int ret;
|
||
|
|
||
|
sema_init(&wifi_control_sem, 1);
|
||
|
down(&wifi_control_sem);
|
||
|
|
||
|
ret = platform_driver_probe(&wifi_device, wifi_probe);
|
||
|
if (ret) {
|
||
|
printk(KERN_INFO "platform_driver_register failed\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Waiting callback after platform_driver_register */
|
||
|
if (down_timeout(&wifi_control_sem, msecs_to_jiffies(5000)) != 0) {
|
||
|
ret = -EINVAL;
|
||
|
printk(KERN_INFO "platform_driver_register timeout\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
length = snprintf(buf, sizeof(buf), "%d\n", 1 ? 1 : 0);
|
||
|
android_readwrite_file("/sys/devices/platform/" MMC_MSM_DEV
|
||
|
"/polling", NULL, buf, length);
|
||
|
length = snprintf(buf, sizeof(buf), "%d\n", 0 ? 1 : 0);
|
||
|
android_readwrite_file("/sys/devices/platform/" MMC_MSM_DEV
|
||
|
"/polling", NULL, buf, length);
|
||
|
|
||
|
mdelay(500);
|
||
|
}
|
||
|
|
||
|
void __exit ath6kl_sdio_exit_msm(void)
|
||
|
{
|
||
|
char buf[3];
|
||
|
int length;
|
||
|
int ret;
|
||
|
|
||
|
platform_driver_unregister(&wifi_device);
|
||
|
|
||
|
/* Waiting callback after platform_driver_register */
|
||
|
if (down_timeout(&wifi_control_sem, msecs_to_jiffies(5000)) != 0) {
|
||
|
ret = -EINVAL;
|
||
|
printk(KERN_INFO "platform_driver_unregister timeout\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
length = snprintf(buf, sizeof(buf), "%d\n", 1 ? 1 : 0);
|
||
|
/* fall back to polling */
|
||
|
android_readwrite_file("/sys/devices/platform/" MMC_MSM_DEV
|
||
|
"/polling", NULL, buf, length);
|
||
|
length = snprintf(buf, sizeof(buf), "%d\n", 0 ? 1 : 0);
|
||
|
/* fall back to polling */
|
||
|
android_readwrite_file("/sys/devices/platform/" MMC_MSM_DEV
|
||
|
"/polling", NULL, buf, length);
|
||
|
mdelay(1000);
|
||
|
|
||
|
}
|
||
|
#else
|
||
|
|
||
|
#ifdef ATH6KL_BUS_VOTE
|
||
|
static u32 bus_perf_client;
|
||
|
static struct msm_bus_scale_pdata *ath6kl_bus_scale_pdata;
|
||
|
|
||
|
u8 *platform_has_vreg;
|
||
|
#endif
|
||
|
|
||
|
#define VDD_PA_MAX_VOLTAGE 3300000
|
||
|
#define VDD_PA_MIN_VOLTAGE 3000000
|
||
|
#define VDD_IO_MAX_VOLTAGE 1800000
|
||
|
#define VDD_IO_MIN_VOLTAGE 1800000
|
||
|
#define VDD_PA_CURRENT 720000
|
||
|
#define VDD_IO_CURRENT 30000
|
||
|
|
||
|
struct ath6kl_power_vreg_data {
|
||
|
/* voltage regulator handle */
|
||
|
struct regulator *reg;
|
||
|
|
||
|
/* regulator name */
|
||
|
const char *name;
|
||
|
|
||
|
/* voltage levels to be set */
|
||
|
unsigned int low_vol_level;
|
||
|
unsigned int high_vol_level;
|
||
|
|
||
|
/*
|
||
|
* is set voltage supported for this regulator?
|
||
|
* false => set voltage is not supported
|
||
|
* true => set voltage is supported
|
||
|
*
|
||
|
* Some regulators (like gpio-regulators, LVS (low voltage swtiches)
|
||
|
* PMIC regulators) dont have the capability to call
|
||
|
* regulator_set_voltage or regulator_set_optimum_mode
|
||
|
* Use this variable to indicate if its a such regulator or not
|
||
|
*/
|
||
|
bool set_voltage_sup;
|
||
|
|
||
|
/* is this regulator enabled? */
|
||
|
bool is_enabled;
|
||
|
};
|
||
|
|
||
|
struct ath6kl_reg_data {
|
||
|
/* Regulator Name */
|
||
|
const char *name;
|
||
|
|
||
|
/* voltage level at which AR chip can operate */
|
||
|
u32 low_vol_level;
|
||
|
u32 high_vol_level;
|
||
|
|
||
|
/* Voltage regulator handle */
|
||
|
struct regulator *reg;
|
||
|
};
|
||
|
|
||
|
struct ath6kl_platform_data {
|
||
|
struct platform_device *pdev;
|
||
|
struct ath6kl_power_vreg_data *wifi_chip_pwd;
|
||
|
struct ath6kl_power_vreg_data *wifi_vddpa;
|
||
|
struct ath6kl_power_vreg_data *wifi_vddio;
|
||
|
int bt_gpio_sys_rst;
|
||
|
};
|
||
|
|
||
|
struct ath6kl_platform_data *gpdata;
|
||
|
|
||
|
#define MAX_PROP_SIZE 32
|
||
|
static int ath6kl_dt_parse_vreg_info(struct device *dev,
|
||
|
struct ath6kl_power_vreg_data **vreg_data, const char *vreg_name)
|
||
|
{
|
||
|
int len, ret = 0;
|
||
|
const __be32 *prop;
|
||
|
char prop_name[MAX_PROP_SIZE];
|
||
|
struct ath6kl_power_vreg_data *vreg;
|
||
|
struct device_node *np = dev->of_node;
|
||
|
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "vreg dev tree parse for %s\n", vreg_name);
|
||
|
|
||
|
snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", vreg_name);
|
||
|
if (of_parse_phandle(np, prop_name, 0)) {
|
||
|
vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL);
|
||
|
if (!vreg) {
|
||
|
ath6kl_err("No memory for vreg: %s\n", vreg_name);
|
||
|
ret = -ENOMEM;
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
vreg->name = vreg_name;
|
||
|
|
||
|
snprintf(prop_name, MAX_PROP_SIZE,
|
||
|
"%s-voltage-level", vreg_name);
|
||
|
prop = of_get_property(np, prop_name, &len);
|
||
|
if (!prop || (len != (2 * sizeof(__be32)))) {
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "%s %s property\n",
|
||
|
prop ? "invalid format" : "no", prop_name);
|
||
|
} else {
|
||
|
vreg->low_vol_level = be32_to_cpup(&prop[0]);
|
||
|
vreg->high_vol_level = be32_to_cpup(&prop[1]);
|
||
|
}
|
||
|
|
||
|
*vreg_data = vreg;
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "%s: vol=[%d %d]uV\n",
|
||
|
vreg->name, vreg->low_vol_level,
|
||
|
vreg->high_vol_level);
|
||
|
} else
|
||
|
ath6kl_info("%s: is not provided in device tree\n", vreg_name);
|
||
|
|
||
|
err:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int ath6kl_vreg_disable(struct ath6kl_power_vreg_data *vreg)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
|
||
|
if (!vreg)
|
||
|
return rc;
|
||
|
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "vreg_disable for : %s\n", vreg->name);
|
||
|
|
||
|
if (vreg->is_enabled) {
|
||
|
rc = regulator_disable(vreg->reg);
|
||
|
if (rc) {
|
||
|
ath6kl_err("regulator_disable(%s) failed. rc=%d\n",
|
||
|
vreg->name, rc);
|
||
|
goto out;
|
||
|
}
|
||
|
vreg->is_enabled = false;
|
||
|
|
||
|
if (vreg->set_voltage_sup) {
|
||
|
/* Set the min voltage to 0 */
|
||
|
rc = regulator_set_voltage(vreg->reg,
|
||
|
0,
|
||
|
vreg->high_vol_level);
|
||
|
if (rc) {
|
||
|
ath6kl_err("vreg_set_vol(%s) failed rc=%d\n",
|
||
|
vreg->name, rc);
|
||
|
goto out;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int ath6kl_vreg_init(struct ath6kl_power_vreg_data *vreg)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
struct device *dev = &(gpdata->pdev->dev);
|
||
|
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "vreg_get for : %s\n", vreg->name);
|
||
|
|
||
|
/* Get the regulator handle */
|
||
|
vreg->reg = regulator_get(dev, vreg->name);
|
||
|
if (IS_ERR(vreg->reg)) {
|
||
|
rc = PTR_ERR(vreg->reg);
|
||
|
ath6kl_err("%s: regulator_get(%s) failed. rc=%d\n",
|
||
|
__func__, vreg->name, rc);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if ((regulator_count_voltages(vreg->reg) > 0)
|
||
|
&& (vreg->low_vol_level) && (vreg->high_vol_level))
|
||
|
vreg->set_voltage_sup = 1;
|
||
|
|
||
|
out:
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int ath6kl_vreg_enable(struct ath6kl_power_vreg_data *vreg)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "vreg_en for : %s\n", vreg->name);
|
||
|
|
||
|
if (!vreg->is_enabled) {
|
||
|
if (vreg->set_voltage_sup) {
|
||
|
rc = regulator_set_voltage(vreg->reg,
|
||
|
vreg->low_vol_level,
|
||
|
vreg->high_vol_level);
|
||
|
if (rc) {
|
||
|
ath6kl_err("vreg_set_vol(%s) failed rc=%d\n",
|
||
|
vreg->name, rc);
|
||
|
goto out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rc = regulator_enable(vreg->reg);
|
||
|
if (rc) {
|
||
|
ath6kl_err("regulator_enable(%s) failed. rc=%d\n",
|
||
|
vreg->name, rc);
|
||
|
goto out;
|
||
|
}
|
||
|
vreg->is_enabled = true;
|
||
|
}
|
||
|
out:
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int ath6kl_configure_vreg(struct ath6kl_power_vreg_data *vreg)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "config %s\n", vreg->name);
|
||
|
|
||
|
/* Get the regulator handle for vreg */
|
||
|
if (!(vreg->reg)) {
|
||
|
rc = ath6kl_vreg_init(vreg);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
rc = ath6kl_vreg_enable(vreg);
|
||
|
if (rc < 0)
|
||
|
return rc;
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int ath6kl_platform_power(struct ath6kl_platform_data *pdata, int on)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "%s on: %d\n", __func__, on);
|
||
|
|
||
|
if (on) {
|
||
|
if (pdata->wifi_vddpa != NULL) {
|
||
|
/* Set voltage to 3.3v */
|
||
|
rc = ath6kl_configure_vreg(pdata->wifi_vddpa);
|
||
|
if (rc < 0) {
|
||
|
ath6kl_err("power on wifi_vddpa error\n");
|
||
|
goto chip_pwd_fail;
|
||
|
}
|
||
|
|
||
|
regulator_set_voltage(pdata->wifi_vddpa->reg,
|
||
|
VDD_PA_MAX_VOLTAGE, VDD_PA_MAX_VOLTAGE);
|
||
|
|
||
|
regulator_set_optimum_mode(pdata->wifi_vddpa->reg,
|
||
|
VDD_PA_CURRENT);
|
||
|
}
|
||
|
|
||
|
if (pdata->wifi_vddio != NULL) {
|
||
|
/* Set voltage to 1.8v */
|
||
|
rc = ath6kl_configure_vreg(pdata->wifi_vddio);
|
||
|
if (rc < 0) {
|
||
|
ath6kl_err("power on wifi_vddio error\n");
|
||
|
goto chip_pwd_fail;
|
||
|
}
|
||
|
|
||
|
regulator_set_voltage(pdata->wifi_vddio->reg,
|
||
|
VDD_IO_MAX_VOLTAGE, VDD_IO_MAX_VOLTAGE);
|
||
|
|
||
|
regulator_set_optimum_mode(pdata->wifi_vddio->reg,
|
||
|
VDD_IO_CURRENT);
|
||
|
}
|
||
|
|
||
|
/* delay a while for regulator setting done */
|
||
|
mdelay(100);
|
||
|
|
||
|
rc = ath6kl_configure_vreg(pdata->wifi_chip_pwd);
|
||
|
if (rc < 0) {
|
||
|
ath6kl_err("power on chip_pwd error\n");
|
||
|
goto chip_pwd_fail;
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
rc = ath6kl_vreg_disable(pdata->wifi_chip_pwd);
|
||
|
|
||
|
/* Set voltage to 3.0v */
|
||
|
if (pdata->wifi_vddpa != NULL &&
|
||
|
!IS_ERR(pdata->wifi_vddpa->reg)) {
|
||
|
regulator_set_voltage(pdata->wifi_vddpa->reg,
|
||
|
VDD_PA_MIN_VOLTAGE, VDD_PA_MAX_VOLTAGE);
|
||
|
regulator_set_optimum_mode(pdata->wifi_vddpa->reg,
|
||
|
0);
|
||
|
rc = ath6kl_vreg_disable(pdata->wifi_vddpa);
|
||
|
}
|
||
|
|
||
|
/* Set voltage to 1.8v */
|
||
|
if (pdata->wifi_vddio != NULL &&
|
||
|
!IS_ERR(pdata->wifi_vddio->reg)) {
|
||
|
regulator_set_voltage(pdata->wifi_vddio->reg,
|
||
|
VDD_IO_MIN_VOLTAGE, VDD_IO_MAX_VOLTAGE);
|
||
|
regulator_set_optimum_mode(pdata->wifi_vddio->reg,
|
||
|
0);
|
||
|
rc = ath6kl_vreg_disable(pdata->wifi_vddio);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
|
||
|
chip_pwd_fail:
|
||
|
ath6kl_vreg_disable(pdata->wifi_chip_pwd);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
#ifdef ATH6KL_HSIC_RECOVER
|
||
|
static void ath6kl_trigger_bt_restart(void)
|
||
|
{
|
||
|
int status = 0;
|
||
|
char buf[32];
|
||
|
size_t len;
|
||
|
|
||
|
len = snprintf(buf, sizeof(buf), "%s", "bt_restart");
|
||
|
status = _readwrite_file("/data/connectivity/bt_restart", NULL,
|
||
|
buf, len, (O_WRONLY | O_CREAT));
|
||
|
if (status < 0)
|
||
|
ath6kl_info("write failed with status code 0x%x\n", status);
|
||
|
}
|
||
|
|
||
|
int ath6kl_hsic_is_bt_on(void)
|
||
|
{
|
||
|
int rc;
|
||
|
int bt_reset_gpio_val = 0;
|
||
|
|
||
|
if (gpdata->bt_gpio_sys_rst < 0)
|
||
|
return 0;
|
||
|
|
||
|
rc = gpio_request(gpdata->bt_gpio_sys_rst, "bt_sys_rst_n");
|
||
|
if (rc) {
|
||
|
ath6kl_err("unable to request gpio %d (%d)\n",
|
||
|
gpdata->bt_gpio_sys_rst, rc);
|
||
|
bt_reset_gpio_val = gpio_get_value(gpdata->bt_gpio_sys_rst);
|
||
|
} else {
|
||
|
bt_reset_gpio_val = gpio_get_value(gpdata->bt_gpio_sys_rst);
|
||
|
gpio_free(gpdata->bt_gpio_sys_rst);
|
||
|
}
|
||
|
|
||
|
return bt_reset_gpio_val;
|
||
|
}
|
||
|
|
||
|
void ath6kl_hsic_rediscovery(void)
|
||
|
{
|
||
|
#ifdef ATH6KL_BUS_VOTE
|
||
|
int rc = 0;
|
||
|
int is_bt_gpio_on = 0;
|
||
|
|
||
|
if (ath6kl_driver_unloaded == 1)
|
||
|
return;
|
||
|
|
||
|
/* mpq did not use verg reset */
|
||
|
if (machine_is_apq8064_dma() ||
|
||
|
machine_is_apq8064_bueller()) {
|
||
|
mdelay(100);
|
||
|
ath6kl_hsic_bind(0);
|
||
|
|
||
|
/* delay a while */
|
||
|
mdelay(1000);
|
||
|
ath6kl_hsic_bind(1);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
is_bt_gpio_on = ath6kl_hsic_is_bt_on();
|
||
|
|
||
|
ath6kl_info("%s, BT_RESET:%d\n", __func__, is_bt_gpio_on);
|
||
|
|
||
|
if (is_bt_gpio_on == 1) {
|
||
|
ath6kl_trigger_bt_restart();
|
||
|
} else {
|
||
|
rc = ath6kl_vreg_disable(gpdata->wifi_chip_pwd);
|
||
|
mdelay(100);
|
||
|
ath6kl_hsic_bind(0);
|
||
|
|
||
|
/* delay a while */
|
||
|
mdelay(1000);
|
||
|
rc = ath6kl_configure_vreg(gpdata->wifi_chip_pwd);
|
||
|
if (rc < 0) {
|
||
|
ath6kl_err("power on chip_pwd error\n");
|
||
|
ath6kl_vreg_disable(gpdata->wifi_chip_pwd);
|
||
|
}
|
||
|
ath6kl_hsic_bind(1);
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#ifdef ATH6KL_BUS_VOTE
|
||
|
|
||
|
static bool previous;
|
||
|
static int ath6kl_toggle_radio(void *data, int on)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
int (*power_control)(int enable);
|
||
|
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "%s toggle ratio %s\n",
|
||
|
__func__, on ? "on" : "off");
|
||
|
|
||
|
power_control = data;
|
||
|
if (previous != on)
|
||
|
ret = (*power_control)(on);
|
||
|
if (!ret)
|
||
|
previous = on;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int ath6kl_hsic_bind(int bind)
|
||
|
{
|
||
|
char buf[16];
|
||
|
int length;
|
||
|
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "%s, bind: %d\n", __func__, bind);
|
||
|
|
||
|
if (bind) {
|
||
|
length = snprintf(buf, sizeof(buf), "%s\n", "msm_hsic_host");
|
||
|
|
||
|
android_readwrite_file(
|
||
|
"/sys/bus/platform/drivers/msm_hsic_host/bind",
|
||
|
NULL, buf, length);
|
||
|
} else {
|
||
|
length = snprintf(buf, sizeof(buf), "%s\n", "msm_hsic_host");
|
||
|
|
||
|
android_readwrite_file(
|
||
|
"/sys/bus/platform/drivers/msm_hsic_host/unbind",
|
||
|
NULL, buf, length);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
struct work_struct enum_war_work;
|
||
|
|
||
|
void ath6kl_hsic_enum_war_schedule(void)
|
||
|
{
|
||
|
if (ath6kl_driver_unloaded != 1) {
|
||
|
ath6kl_info("%s: schedule worker thread\n", __func__);
|
||
|
schedule_work(&enum_war_work);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void ath6kl_enum_war_work(struct work_struct *work)
|
||
|
{
|
||
|
int ret;
|
||
|
int is_bt_gpio_on;
|
||
|
|
||
|
/* If driver is unloaded, skip the WAR */
|
||
|
if (ath6kl_driver_unloaded == 1)
|
||
|
return;
|
||
|
|
||
|
/* mpq did not use verg reset */
|
||
|
if (machine_is_apq8064_dma() ||
|
||
|
machine_is_apq8064_bueller()) {
|
||
|
ath6kl_toggle_radio(gpdata->pdev->dev.platform_data, 0);
|
||
|
ath6kl_hsic_bind(0);
|
||
|
|
||
|
/* delay a while */
|
||
|
mdelay(1000);
|
||
|
ath6kl_toggle_radio(gpdata->pdev->dev.platform_data, 1);
|
||
|
ath6kl_hsic_bind(1);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
is_bt_gpio_on = ath6kl_hsic_is_bt_on();
|
||
|
|
||
|
ath6kl_info("%s, BT_RESET:%d\n", __func__, is_bt_gpio_on);
|
||
|
|
||
|
if (is_bt_gpio_on == 1) {
|
||
|
ath6kl_trigger_bt_restart();
|
||
|
} else {
|
||
|
ret = ath6kl_platform_power(gpdata, 0);
|
||
|
|
||
|
if (ret == 0 && ath6kl_bt_on == 0)
|
||
|
ath6kl_hsic_bind(0);
|
||
|
|
||
|
msleep(200);
|
||
|
|
||
|
ret = ath6kl_platform_power(gpdata, 1);
|
||
|
|
||
|
if (ret == 0 && ath6kl_bt_on == 0)
|
||
|
ath6kl_hsic_bind(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int ath6kl_hsic_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct ath6kl_platform_data *pdata = NULL;
|
||
|
struct device *dev = &pdev->dev;
|
||
|
int ret = 0;
|
||
|
|
||
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
||
|
|
||
|
if (!pdata) {
|
||
|
ath6kl_err("%s: Could not allocate memory for platform data\n",
|
||
|
__func__);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
if (machine_is_apq8064_dma()) {
|
||
|
ath6kl_dbg(ATH6KL_DBG_BOOT, "%s\n", __func__);
|
||
|
previous = 0;
|
||
|
ath6kl_toggle_radio(pdev->dev.platform_data, 1);
|
||
|
pdata->pdev = pdev;
|
||
|
gpdata = pdata;
|
||
|
} else {
|
||
|
ath6kl_bus_scale_pdata = msm_bus_cl_get_pdata(pdev);
|
||
|
bus_perf_client =
|
||
|
msm_bus_scale_register_client(
|
||
|
ath6kl_bus_scale_pdata);
|
||
|
msm_bus_scale_client_update_request(bus_perf_client, 4);
|
||
|
|
||
|
if (ath6kl_dt_parse_vreg_info(dev, &pdata->wifi_chip_pwd,
|
||
|
"qca,wifi-chip-pwd") != 0) {
|
||
|
ath6kl_err("%s: parse vreg info for %s error\n",
|
||
|
"chip_pwd", __func__);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if (ath6kl_dt_parse_vreg_info(dev, &pdata->wifi_vddpa,
|
||
|
"qca,wifi-vddpa") != 0) {
|
||
|
ath6kl_err("%s: parse vreg info for %s error\n",
|
||
|
"vddpa", __func__);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if (ath6kl_dt_parse_vreg_info(dev, &pdata->wifi_vddio,
|
||
|
"qca,wifi-vddio") != 0) {
|
||
|
ath6kl_err("%s: parse vreg info for %s error\n",
|
||
|
"vddio", __func__);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
pdata->bt_gpio_sys_rst = of_get_named_gpio(pdev->dev.of_node,
|
||
|
"qca,bt-reset-gpio", 0);
|
||
|
if (pdata->bt_gpio_sys_rst < 0) {
|
||
|
ath6kl_err("%s: bt-reset-gpio not"
|
||
|
"provided in device tree\n", __func__);
|
||
|
}
|
||
|
|
||
|
pdata->pdev = pdev;
|
||
|
platform_set_drvdata(pdev, pdata);
|
||
|
gpdata = pdata;
|
||
|
|
||
|
if (pdata->wifi_chip_pwd != NULL) {
|
||
|
ret = ath6kl_platform_power(pdata, 1);
|
||
|
|
||
|
if (ret == 0 && ath6kl_bt_on == 0)
|
||
|
ath6kl_hsic_bind(1);
|
||
|
|
||
|
*platform_has_vreg = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Initialize the worker */
|
||
|
INIT_WORK(&enum_war_work, ath6kl_enum_war_work);
|
||
|
|
||
|
return ret;
|
||
|
|
||
|
err:
|
||
|
if (pdata != NULL)
|
||
|
devm_kfree(dev, pdata);
|
||
|
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
static int ath6kl_hsic_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct ath6kl_platform_data *pdata = platform_get_drvdata(pdev);
|
||
|
|
||
|
if (machine_is_apq8064_dma()) {
|
||
|
ath6kl_toggle_radio(pdev->dev.platform_data, 0);
|
||
|
} else {
|
||
|
msm_bus_scale_client_update_request(bus_perf_client, 1);
|
||
|
if (bus_perf_client)
|
||
|
msm_bus_scale_unregister_client(bus_perf_client);
|
||
|
|
||
|
if (pdata->wifi_chip_pwd != NULL) {
|
||
|
int ret;
|
||
|
|
||
|
ret = ath6kl_platform_power(pdata, 0);
|
||
|
|
||
|
if (pdata->wifi_chip_pwd->reg)
|
||
|
regulator_put(pdata->wifi_chip_pwd->reg);
|
||
|
|
||
|
if (pdata->wifi_vddpa != NULL && pdata->wifi_vddpa->reg)
|
||
|
regulator_put(pdata->wifi_vddpa->reg);
|
||
|
|
||
|
if (pdata->wifi_vddio != NULL && pdata->wifi_vddio->reg)
|
||
|
regulator_put(pdata->wifi_vddio->reg);
|
||
|
|
||
|
if (ret == 0 && ath6kl_bt_on == 0)
|
||
|
ath6kl_hsic_bind(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id ath6kl_hsic_dt_match[] = {
|
||
|
{ .compatible = "qca,ar6004-hsic",},
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
MODULE_DEVICE_TABLE(of, ath6kl_hsic_dt_match);
|
||
|
|
||
|
static struct platform_driver ath6kl_hsic_device = {
|
||
|
.probe = ath6kl_hsic_probe,
|
||
|
.remove = ath6kl_hsic_remove,
|
||
|
.driver = {
|
||
|
.name = "ath6kl_hsic",
|
||
|
.of_match_table = ath6kl_hsic_dt_match,
|
||
|
}
|
||
|
};
|
||
|
|
||
|
int ath6kl_hsic_init_msm(u8 *has_vreg)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
platform_has_vreg = has_vreg;
|
||
|
ret = platform_driver_register(&ath6kl_hsic_device);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void ath6kl_hsic_exit_msm(void)
|
||
|
{
|
||
|
platform_driver_unregister(&ath6kl_hsic_device);
|
||
|
}
|
||
|
#else
|
||
|
|
||
|
struct semaphore wifi_control_sem;
|
||
|
|
||
|
static int ath6kl_sdio_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct ath6kl_platform_data *pdata = NULL;
|
||
|
struct device *dev = &pdev->dev;
|
||
|
int ret = 0;
|
||
|
int length;
|
||
|
char buf[3];
|
||
|
|
||
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
||
|
|
||
|
if (!pdata) {
|
||
|
ath6kl_err("%s: Could not allocate memory for platform data\n",
|
||
|
__func__);
|
||
|
up(&wifi_control_sem);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
if (ath6kl_dt_parse_vreg_info(dev, &pdata->wifi_chip_pwd,
|
||
|
"qca,wifi-chip-pwd") != 0) {
|
||
|
ath6kl_err("%s: parse vreg info error\n", __func__);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if (ath6kl_dt_parse_vreg_info(dev, &pdata->wifi_vddpa,
|
||
|
"qca,wifi-vddpa") != 0) {
|
||
|
ath6kl_err("%s: parse vreg info for %s error\n",
|
||
|
"vddpa", __func__);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
if (ath6kl_dt_parse_vreg_info(dev, &pdata->wifi_vddio,
|
||
|
"qca,wifi-vddio") != 0) {
|
||
|
ath6kl_err("%s: parse vreg info for %s error\n",
|
||
|
"vddio", __func__);
|
||
|
goto err;
|
||
|
}
|
||
|
|
||
|
pdata->pdev = pdev;
|
||
|
platform_set_drvdata(pdev, pdata);
|
||
|
gpdata = pdata;
|
||
|
|
||
|
if (pdata->wifi_chip_pwd != NULL) {
|
||
|
ret = ath6kl_platform_power(pdata, 1);
|
||
|
if (ret == 0) {
|
||
|
mdelay(50);
|
||
|
length = snprintf(buf, sizeof(buf), "%d\n", 1 ? 1 : 0);
|
||
|
android_readwrite_file(
|
||
|
"/sys/devices/msm_sdcc.3/polling",
|
||
|
NULL, buf, length);
|
||
|
length = snprintf(buf, sizeof(buf), "%d\n", 0 ? 1 : 0);
|
||
|
android_readwrite_file(
|
||
|
"/sys/devices/msm_sdcc.3/polling",
|
||
|
NULL, buf, length);
|
||
|
mdelay(500);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
up(&wifi_control_sem);
|
||
|
return ret;
|
||
|
|
||
|
err:
|
||
|
if (pdata != NULL)
|
||
|
devm_kfree(dev, pdata);
|
||
|
|
||
|
up(&wifi_control_sem);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
static int ath6kl_sdio_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
char buf[3];
|
||
|
int length;
|
||
|
struct ath6kl_platform_data *pdata = platform_get_drvdata(pdev);
|
||
|
|
||
|
if (pdata->wifi_chip_pwd != NULL &&
|
||
|
!IS_ERR(pdata->wifi_chip_pwd->reg)) {
|
||
|
|
||
|
ath6kl_platform_power(pdata, 0);
|
||
|
regulator_put(pdata->wifi_chip_pwd->reg);
|
||
|
|
||
|
if (pdata->wifi_vddpa != NULL &&
|
||
|
pdata->wifi_vddpa->reg)
|
||
|
regulator_put(pdata->wifi_vddpa->reg);
|
||
|
|
||
|
if (pdata->wifi_vddio != NULL &&
|
||
|
pdata->wifi_vddio->reg)
|
||
|
regulator_put(pdata->wifi_vddio->reg);
|
||
|
|
||
|
mdelay(50);
|
||
|
length = snprintf(buf, sizeof(buf), "%d\n", 1 ? 1 : 0);
|
||
|
android_readwrite_file("/sys/devices/msm_sdcc.3/polling",
|
||
|
NULL, buf, length);
|
||
|
length = snprintf(buf, sizeof(buf), "%d\n", 0 ? 1 : 0);
|
||
|
android_readwrite_file("/sys/devices/msm_sdcc.3/polling",
|
||
|
NULL, buf, length);
|
||
|
mdelay(500);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
up(&wifi_control_sem);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id ath6kl_sdio_dt_match[] = {
|
||
|
{ .compatible = "qca,ar6004-sdio",},
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
MODULE_DEVICE_TABLE(of, ath6kl_sdio_dt_match);
|
||
|
|
||
|
static struct platform_driver ath6kl_sdio_device = {
|
||
|
.probe = ath6kl_sdio_probe,
|
||
|
.remove = ath6kl_sdio_remove,
|
||
|
.driver = {
|
||
|
.name = "ath6kl_sdio",
|
||
|
.of_match_table = ath6kl_sdio_dt_match,
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void ath6kl_sdio_init_msm(void)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
sema_init(&wifi_control_sem, 1);
|
||
|
down(&wifi_control_sem);
|
||
|
|
||
|
ret = platform_driver_register(&ath6kl_sdio_device);
|
||
|
|
||
|
/* Waiting callback after platform_driver_register */
|
||
|
if (down_timeout(&wifi_control_sem, msecs_to_jiffies(5000)) != 0) {
|
||
|
ret = -EINVAL;
|
||
|
printk(KERN_INFO "platform_driver_register timeout\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
void ath6kl_sdio_exit_msm(void)
|
||
|
{
|
||
|
platform_driver_unregister(&ath6kl_sdio_device);
|
||
|
|
||
|
/* Waiting callback after platform_driver_register */
|
||
|
if (down_timeout(&wifi_control_sem, msecs_to_jiffies(5000)) != 0) {
|
||
|
printk(KERN_INFO "platform_driver_unregister timeout\n");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif /* #ifdef ATH6KL_BUS_VOTE */
|
||
|
|
||
|
#endif
|