360 lines
9.0 KiB
C
360 lines
9.0 KiB
C
/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/qcomwlan_pwrif.h>
|
|
#include <linux/export.h>
|
|
|
|
#define GPIO_WLAN_DEEP_SLEEP_N 230
|
|
#define GPIO_WLAN_DEEP_SLEEP_N_DRAGON 82
|
|
#define WLAN_RESET_OUT 1
|
|
#define WLAN_RESET 0
|
|
|
|
static const char *id = "WLAN";
|
|
|
|
/**
|
|
* vos_chip_power_qrf8615() - WLAN Power Up Seq for WCN1314 rev 2.0 on QRF 8615
|
|
* @on - Turn WLAN ON/OFF (1 or 0)
|
|
*
|
|
* Power up/down WLAN by turning on/off various regs and asserting/deasserting
|
|
* Power-on-reset pin. Also, put XO A0 buffer as slave to wlan_clk_pwr_req while
|
|
* turning ON WLAN and vice-versa.
|
|
*
|
|
* This function returns 0 on success or a non-zero value on failure.
|
|
*/
|
|
int vos_chip_power_qrf8615(int on)
|
|
{
|
|
static char wlan_on;
|
|
static const char *vregs_qwlan_name[] = {
|
|
"8058_l20",
|
|
"8058_l8",
|
|
"8901_s4",
|
|
"8901_lvs1",
|
|
"8901_l0",
|
|
"8058_s2",
|
|
"8058_s1",
|
|
};
|
|
static const char *vregs_qwlan_pc_name[] = {
|
|
"8058_l20_pc",
|
|
"8058_l8_pc",
|
|
NULL,
|
|
NULL,
|
|
"8901_l0_pc",
|
|
"8058_s2_pc",
|
|
NULL,
|
|
};
|
|
static const int vregs_qwlan_val_min[] = {
|
|
1800000,
|
|
3050000,
|
|
1225000,
|
|
0,
|
|
1200000,
|
|
1300000,
|
|
500000,
|
|
};
|
|
static const int vregs_qwlan_val_max[] = {
|
|
1800000,
|
|
3050000,
|
|
1225000,
|
|
0,
|
|
1200000,
|
|
1300000,
|
|
1250000,
|
|
};
|
|
static const int vregs_qwlan_peek_current[] = {
|
|
4000,
|
|
150000,
|
|
60000,
|
|
0,
|
|
32000,
|
|
130000,
|
|
0,
|
|
};
|
|
static const bool vregs_is_pin_controlled_default[] = {
|
|
1,
|
|
1,
|
|
0,
|
|
0,
|
|
1,
|
|
1,
|
|
0,
|
|
};
|
|
static const bool vregs_is_pin_controlled_dragon[] = {
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
1,
|
|
0,
|
|
};
|
|
bool const *vregs_is_pin_controlled;
|
|
static struct regulator *vregs_qwlan[ARRAY_SIZE(vregs_qwlan_name)];
|
|
static struct regulator *vregs_pc_qwlan[ARRAY_SIZE(vregs_qwlan_name)];
|
|
static struct msm_xo_voter *wlan_clock;
|
|
int ret, i, rc = 0;
|
|
unsigned wlan_gpio_deep_sleep = GPIO_WLAN_DEEP_SLEEP_N;
|
|
|
|
vregs_is_pin_controlled = vregs_is_pin_controlled_default;
|
|
|
|
if (machine_is_msm8x60_dragon()) {
|
|
wlan_gpio_deep_sleep = GPIO_WLAN_DEEP_SLEEP_N_DRAGON;
|
|
vregs_is_pin_controlled = vregs_is_pin_controlled_dragon;
|
|
}
|
|
/* WLAN RESET and CLK settings */
|
|
if (on && !wlan_on) {
|
|
/*
|
|
* Program U12 GPIO expander pin IO1 to de-assert (drive 0)
|
|
* WLAN_EXT_POR_N to put WLAN in reset
|
|
*/
|
|
rc = gpio_request(wlan_gpio_deep_sleep, "WLAN_DEEP_SLEEP_N");
|
|
if (rc) {
|
|
pr_err("WLAN reset GPIO %d request failed\n",
|
|
wlan_gpio_deep_sleep);
|
|
goto fail;
|
|
}
|
|
rc = gpio_direction_output(wlan_gpio_deep_sleep,
|
|
WLAN_RESET);
|
|
if (rc < 0) {
|
|
pr_err("WLAN reset GPIO %d set output direction failed",
|
|
wlan_gpio_deep_sleep);
|
|
goto fail_gpio_dir_out;
|
|
}
|
|
|
|
/* Configure TCXO to be slave to WLAN_CLK_PWR_REQ */
|
|
if (wlan_clock == NULL) {
|
|
wlan_clock = msm_xo_get(MSM_XO_TCXO_A0, id);
|
|
if (IS_ERR(wlan_clock)) {
|
|
pr_err("Failed to get TCXO_A0 voter (%ld)\n",
|
|
PTR_ERR(wlan_clock));
|
|
goto fail_gpio_dir_out;
|
|
}
|
|
}
|
|
|
|
rc = msm_xo_mode_vote(wlan_clock, MSM_XO_MODE_PIN_CTRL);
|
|
if (rc < 0) {
|
|
pr_err("Configuring TCXO to Pin controllable failed"
|
|
"(%d)\n", rc);
|
|
goto fail_xo_mode_vote;
|
|
}
|
|
} else if (!on && wlan_on) {
|
|
if (wlan_clock != NULL)
|
|
msm_xo_mode_vote(wlan_clock, MSM_XO_MODE_OFF);
|
|
gpio_set_value_cansleep(wlan_gpio_deep_sleep, WLAN_RESET);
|
|
gpio_free(wlan_gpio_deep_sleep);
|
|
}
|
|
|
|
/* WLAN VREG settings */
|
|
for (i = 0; i < ARRAY_SIZE(vregs_qwlan_name); i++) {
|
|
if (on && !wlan_on) {
|
|
vregs_qwlan[i] = regulator_get(NULL,
|
|
vregs_qwlan_name[i]);
|
|
if (IS_ERR(vregs_qwlan[i])) {
|
|
pr_err("regulator get of %s failed (%ld)\n",
|
|
vregs_qwlan_name[i],
|
|
PTR_ERR(vregs_qwlan[i]));
|
|
rc = PTR_ERR(vregs_qwlan[i]);
|
|
goto vreg_get_fail;
|
|
}
|
|
if (vregs_qwlan_val_min[i] || vregs_qwlan_val_max[i]) {
|
|
rc = regulator_set_voltage(vregs_qwlan[i],
|
|
vregs_qwlan_val_min[i],
|
|
vregs_qwlan_val_max[i]);
|
|
if (rc) {
|
|
pr_err("regulator_set_voltage(%s) failed\n",
|
|
vregs_qwlan_name[i]);
|
|
goto vreg_fail;
|
|
}
|
|
}
|
|
/* vote for pin control (if needed) */
|
|
if (vregs_is_pin_controlled[i]) {
|
|
vregs_pc_qwlan[i] = regulator_get(NULL,
|
|
vregs_qwlan_pc_name[i]);
|
|
if (IS_ERR(vregs_pc_qwlan[i])) {
|
|
pr_err("regulator get of %s failed "
|
|
"(%ld)\n",
|
|
vregs_qwlan_pc_name[i],
|
|
PTR_ERR(vregs_pc_qwlan[i]));
|
|
rc = PTR_ERR(vregs_pc_qwlan[i]);
|
|
goto vreg_fail;
|
|
}
|
|
}
|
|
|
|
if (vregs_qwlan_peek_current[i]) {
|
|
rc = regulator_set_optimum_mode(vregs_qwlan[i],
|
|
vregs_qwlan_peek_current[i]);
|
|
if (rc < 0)
|
|
pr_err("vreg %s set optimum mode"
|
|
" failed to %d (%d)\n",
|
|
vregs_qwlan_name[i], rc,
|
|
vregs_qwlan_peek_current[i]);
|
|
}
|
|
rc = regulator_enable(vregs_qwlan[i]);
|
|
if (rc < 0) {
|
|
pr_err("vreg %s enable failed (%d)\n",
|
|
vregs_qwlan_name[i], rc);
|
|
goto vreg_fail;
|
|
}
|
|
if (vregs_is_pin_controlled[i]) {
|
|
rc = regulator_enable(vregs_pc_qwlan[i]);
|
|
if (rc < 0) {
|
|
pr_err("vreg %s enable failed (%d)\n",
|
|
vregs_qwlan_pc_name[i], rc);
|
|
goto vreg_fail;
|
|
}
|
|
}
|
|
} else if (!on && wlan_on) {
|
|
|
|
if (vregs_qwlan_peek_current[i]) {
|
|
/* For legacy reasons we pass 1mA current to
|
|
* put regulator in LPM mode.
|
|
*/
|
|
rc = regulator_set_optimum_mode(vregs_qwlan[i],
|
|
1000);
|
|
if (rc < 0)
|
|
pr_info("vreg %s set optimum mode"
|
|
"failed (%d)\n",
|
|
vregs_qwlan_name[i], rc);
|
|
rc = regulator_set_voltage(vregs_qwlan[i], 0 ,
|
|
vregs_qwlan_val_max[i]);
|
|
if (rc)
|
|
pr_err("regulator_set_voltage(%s)"
|
|
"failed (%d)\n",
|
|
vregs_qwlan_name[i], rc);
|
|
|
|
}
|
|
|
|
if (vregs_is_pin_controlled[i]) {
|
|
rc = regulator_disable(vregs_pc_qwlan[i]);
|
|
if (rc < 0) {
|
|
pr_err("vreg %s disable failed (%d)\n",
|
|
vregs_qwlan_pc_name[i], rc);
|
|
goto vreg_fail;
|
|
}
|
|
regulator_put(vregs_pc_qwlan[i]);
|
|
}
|
|
|
|
rc = regulator_disable(vregs_qwlan[i]);
|
|
if (rc < 0) {
|
|
pr_err("vreg %s disable failed (%d)\n",
|
|
vregs_qwlan_name[i], rc);
|
|
goto vreg_fail;
|
|
}
|
|
regulator_put(vregs_qwlan[i]);
|
|
}
|
|
}
|
|
if (on) {
|
|
gpio_set_value_cansleep(wlan_gpio_deep_sleep, WLAN_RESET_OUT);
|
|
wlan_on = true;
|
|
}
|
|
else
|
|
wlan_on = false;
|
|
return 0;
|
|
|
|
vreg_fail:
|
|
regulator_put(vregs_qwlan[i]);
|
|
if (vregs_is_pin_controlled[i])
|
|
regulator_put(vregs_pc_qwlan[i]);
|
|
vreg_get_fail:
|
|
i--;
|
|
while (i >= 0) {
|
|
ret = !on ? regulator_enable(vregs_qwlan[i]) :
|
|
regulator_disable(vregs_qwlan[i]);
|
|
if (ret < 0) {
|
|
pr_err("vreg %s %s failed (%d) in err path\n",
|
|
vregs_qwlan_name[i],
|
|
!on ? "enable" : "disable", ret);
|
|
}
|
|
if (vregs_is_pin_controlled[i]) {
|
|
ret = !on ? regulator_enable(vregs_pc_qwlan[i]) :
|
|
regulator_disable(vregs_pc_qwlan[i]);
|
|
if (ret < 0) {
|
|
pr_err("vreg %s %s failed (%d) in err path\n",
|
|
vregs_qwlan_pc_name[i],
|
|
!on ? "enable" : "disable", ret);
|
|
}
|
|
}
|
|
regulator_put(vregs_qwlan[i]);
|
|
if (vregs_is_pin_controlled[i])
|
|
regulator_put(vregs_pc_qwlan[i]);
|
|
i--;
|
|
}
|
|
if (!on)
|
|
goto fail;
|
|
fail_xo_mode_vote:
|
|
msm_xo_put(wlan_clock);
|
|
fail_gpio_dir_out:
|
|
gpio_free(wlan_gpio_deep_sleep);
|
|
fail:
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(vos_chip_power_qrf8615);
|
|
|
|
/**
|
|
* qcomwlan_pmic_xo_core_force_enable() - Force XO Core of PMIC to be ALWAYS ON
|
|
* @on - Force XO Core ON/OFF (1 or 0)
|
|
*
|
|
* The XO_CORE controls the XO feeding the TCXO buffers (A0, A1, etc.). WLAN
|
|
* wants to keep the XO core on even though our buffer A0 is in pin control
|
|
* because it can take a long time turn the XO back on and warm up the buffers.
|
|
* This helps in optimizing power in BMPS (power save) mode of WLAN.
|
|
* The WLAN driver wrapper function takes care that this API is not called
|
|
* consecutively.
|
|
*
|
|
* This function returns 0 on success or a non-zero value on failure.
|
|
*/
|
|
int qcomwlan_pmic_xo_core_force_enable(int on)
|
|
{
|
|
static struct msm_xo_voter *wlan_ps;
|
|
int rc = 0;
|
|
|
|
if (wlan_ps == NULL) {
|
|
wlan_ps = msm_xo_get(MSM_XO_CORE, id);
|
|
if (IS_ERR(wlan_ps)) {
|
|
pr_err("Failed to get XO CORE voter (%ld)\n",
|
|
PTR_ERR(wlan_ps));
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (on)
|
|
rc = msm_xo_mode_vote(wlan_ps, MSM_XO_MODE_ON);
|
|
else
|
|
rc = msm_xo_mode_vote(wlan_ps, MSM_XO_MODE_OFF);
|
|
|
|
if (rc < 0) {
|
|
pr_err("XO Core %s failed (%d)\n",
|
|
on ? "enable" : "disable", rc);
|
|
goto fail_xo_mode_vote;
|
|
}
|
|
return 0;
|
|
fail_xo_mode_vote:
|
|
msm_xo_put(wlan_ps);
|
|
fail:
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(qcomwlan_pmic_xo_core_force_enable);
|
|
|
|
|
|
/**
|
|
* qcomwlan_freq_change_1p3v_supply() - function to change the freq for 1.3V RF supply.
|
|
* @freq - freq of the 1.3V Supply
|
|
*
|
|
* This function returns 0 on success or a non-zero value on failure.
|
|
*/
|
|
|
|
int qcomwlan_freq_change_1p3v_supply(enum rpm_vreg_freq freq)
|
|
{
|
|
return rpm_vreg_set_frequency(RPM_VREG_ID_PM8058_S2, freq);
|
|
}
|
|
EXPORT_SYMBOL(qcomwlan_freq_change_1p3v_supply);
|