blob: 52b1a51d8a1a33587f923b2ef74793325927d15b [file] [log] [blame]
/* Copyright (c) 2010-2012, Code Aurora Forum. 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);