blob: 74b36c6884f58ed3c7e56b1535a0316482475369 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2021 Google, LLC
*
*/
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/regmap.h>
#include "max77759.h"
#include "max77759_charger.h"
/* ----------------------------------------------------------------------- */
static int gs101_ext_bst_mode(struct max77759_usecase_data *uc_data, int mode);
static int max77759_chgr_reg_write(struct i2c_client *client, u8 reg, u8 value)
{
struct max77759_chgr_data *data;
if (!client)
return -ENODEV;
data = i2c_get_clientdata(client);
if (!data || !data->regmap)
return -ENODEV;
return regmap_write(data->regmap, reg, value);
}
static int max77759_chgr_reg_read(struct i2c_client *client, u8 reg, u8 *value)
{
struct max77759_chgr_data *data;
int ret, ival;
if (!client)
return -ENODEV;
data = i2c_get_clientdata(client);
if (!data || !data->regmap)
return -ENODEV;
ret = regmap_read(data->regmap, reg, &ival);
if (ret == 0)
*value = 0xFF & ival;
return ret;
}
static int max77759_chgr_reg_update(struct i2c_client *client,
u8 reg, u8 mask, u8 value)
{
struct max77759_chgr_data *data;
if (!client)
return -ENODEV;
data = i2c_get_clientdata(client);
if (!data || !data->regmap)
return -ENODEV;
return regmap_write_bits(data->regmap, reg, mask, value);
}
static int max77759_chgr_mode_write(struct i2c_client *client,
enum max77759_charger_modes mode)
{
struct max77759_chgr_data *data;
if (!client)
return -ENODEV;
data = i2c_get_clientdata(client);
if (!data || !data->regmap)
return -ENODEV;
return regmap_write_bits(data->regmap, MAX77759_CHG_CNFG_00,
MAX77759_CHG_CNFG_00_MODE_MASK,
mode);
}
/* 1 if changed, 0 if not changed, or < 0 on error */
static int max77759_chgr_prot(struct regmap *regmap, bool enable)
{
u8 value = enable ? 0 : MAX77759_CHG_CNFG_06_CHGPROT_MASK;
u8 prot;
int ret, ival;
ret = regmap_read(regmap, MAX77759_CHG_CNFG_06, &ival);
if (ret < 0)
return -EIO;
prot = 0xFF & ival;
if ((prot & MAX77759_CHG_CNFG_06_CHGPROT_MASK) == value)
return 0;
ret = regmap_write_bits(regmap, MAX77759_CHG_CNFG_06,
MAX77759_CHG_CNFG_06_CHGPROT_MASK,
value);
if (ret < 0)
return -EIO;
return 1;
}
static int max77759_chgr_insel_write(struct i2c_client *client, u8 mask, u8 value)
{
struct max77759_chgr_data *data;
int ret, prot;
if (!client)
return -ENODEV;
data = i2c_get_clientdata(client);
if (!data || !data->regmap)
return -ENODEV;
prot = max77759_chgr_prot(data->regmap, false);
if (prot < 0)
return -EIO;
/* changing [CHGIN|WCIN]_INSEL: works when protection is disabled */
ret = regmap_write_bits(data->regmap, MAX77759_CHG_CNFG_12, mask, value);
if (ret < 0 || prot == 0)
return ret;
prot = max77759_chgr_prot(data->regmap, true);
if (prot < 0) {
pr_err("%s: cannot restore protection bits (%d)\n",
__func__, prot);
return prot;
};
return ret;
}
/* ----------------------------------------------------------------------- */
/* control VENDOR_EXTBST_CTRL (from TCPCI module) */
static int gs101_ls_mode(struct max77759_usecase_data *uc_data, int mode)
{
int ret;
pr_debug("%s: mode=%d ext_bst_ctl=%d lsw1_c=%d lsw1_o=%d\n", __func__, mode,
uc_data->ext_bst_ctl, uc_data->lsw1_is_closed,
uc_data->lsw1_is_open);
if (uc_data->ext_bst_ctl < 0)
return 0;
/* VENDOR_EXTBST_CTRL control LSW1, the read will check the state */
gpio_set_value_cansleep(uc_data->ext_bst_ctl, mode);
/* b/182953320 load switch is optional */
if (uc_data->lsw1_is_open < 0 || uc_data->lsw1_is_closed < 0)
return 0;
/* ret <= 0 if *_is* is not true and > 1 if true */
switch (mode) {
case 0:
/* the OVP open right away */
ret = gpio_get_value_cansleep(uc_data->lsw1_is_open);
if (ret <= 0 && uc_data->ls1_en > 0) {
const int max_count = 3;
int loops;
/* do it manually and re-read after 20ms */
for (loops = 0; loops < max_count; loops++) {
gpio_set_value_cansleep(uc_data->ls1_en, 0);
usleep_range(20 * USEC_PER_MSEC, 20 * USEC_PER_MSEC + 100);
ret = gpio_get_value_cansleep(uc_data->lsw1_is_open);
pr_debug("%s: open lsw1 attempt %d/%d ret=%d\n",
__func__, loops, max_count, ret);
if (ret > 0)
break;
}
}
break;
case 1:
/* it takes 11 ms to turn on the OVP */
usleep_range(11 * USEC_PER_MSEC, 11 * USEC_PER_MSEC + 100);
ret = gpio_get_value_cansleep(uc_data->lsw1_is_closed);
break;
default:
return -EINVAL;
}
return (ret <= 0) ? -EIO : 0;
}
/* OVP LS2 */
#define OVP_LS2_MODE_OFF 0
#define OVP_LS2_MODE_ON 1
static int gs101_ls2_mode(struct max77759_usecase_data *uc_data, int mode)
{
pr_debug("%s: ls2_en=%d mode=%d\n", __func__, uc_data->ls2_en, mode);
if (uc_data->ls2_en >= 0)
gpio_set_value_cansleep(uc_data->ls2_en, !!mode);
return 0;
}
/* control external boost mode
* can be done controlling ls1, ls2
*/
#define EXT_MODE_OFF 0
#define EXT_MODE_OTG_5_0V 1
#define EXT_MODE_OTG_7_5V 2
/*
* bst_on=GPIO5 on Max77759 on canopy and on all whitefins,
* bst_sel=Granville
*/
static int gs101_ext_mode(struct max77759_usecase_data *uc_data, int mode)
{
int ret = 0;
pr_debug("%s: mode=%d on=%d sel=%d\n", __func__, mode,
uc_data->bst_on, uc_data->bst_sel);
if (uc_data->bst_on < 0)
return 0;
switch (mode) {
case EXT_MODE_OFF:
gpio_set_value_cansleep(uc_data->bst_on, 0);
break;
case EXT_MODE_OTG_5_0V:
if (uc_data->bst_sel > 0)
gpio_set_value_cansleep(uc_data->bst_sel, 0);
msleep(100);
gpio_set_value_cansleep(uc_data->bst_on, 1);
break;
case EXT_MODE_OTG_7_5V: /* TODO: verify this */
if (uc_data->bst_sel > 0)
gpio_set_value_cansleep(uc_data->bst_sel, 1);
msleep(100);
gpio_set_value_cansleep(uc_data->bst_on, 1);
break;
default:
return -EINVAL;
}
return ret;
}
int gs101_wlc_en(struct max77759_usecase_data *uc_data, bool wlc_on)
{
int ret = 0;
pr_debug("%s: cpout_en=%d wlc_en=%d wlc_vbus_en=%d wlc_on=%d\n", __func__,
uc_data->cpout_en, uc_data->wlc_en, uc_data->wlc_vbus_en, wlc_on);
if (uc_data->cpout_en >= 0) {
gpio_set_value_cansleep(uc_data->cpout_en, wlc_on);
} else if (!wlc_on) {
/*
* when
* uc_data->cpout_en != -EPROBE_DEFER && uc_data->wlc_en
* could use uc_data->wlc_en with:
* gpio_set_value_cansleep(uc_data->wlc_en, !!wlc_on);
*
* BUT need to resolve the race on start since toggling
* ->wlc_en might not be undone by using ->cpout_en
*/
}
/* b/202526678 */
if (uc_data->wlc_vbus_en >= 0)
gpio_set_value_cansleep(uc_data->wlc_vbus_en, wlc_on);
return ret;
}
EXPORT_SYMBOL_GPL(gs101_wlc_en);
/* RTX reverse wireless charging */
static int gs101_wlc_tx_enable(struct max77759_usecase_data *uc_data,
bool enable)
{
int ret = 0;
if (enable) {
if (!uc_data->wlctx_bst_en_first)
ret = gs101_ls2_mode(uc_data, OVP_LS2_MODE_ON);
if (ret == 0)
ret = gs101_ext_mode(uc_data, EXT_MODE_OTG_7_5V);
if (ret == 0 && uc_data->wlctx_bst_en_first)
ret = gs101_ls2_mode(uc_data, OVP_LS2_MODE_ON);
if (ret < 0)
return ret;
msleep(100);
/* p9412 will not be in RX when powered from EXT */
ret = gs101_wlc_en(uc_data, true);
if (ret < 0)
return ret;
if (uc_data->cpout21_en >= 0)
gpio_set_value_cansleep(uc_data->cpout21_en, 0);
} else {
/* p9412 is already off from insel */
ret = gs101_wlc_en(uc_data, false);
if (ret < 0)
return ret;
/* NOTE: turn off WLC, no need to reset cpout */
if (!uc_data->wlctx_bst_en_first)
ret = gs101_ext_mode(uc_data, EXT_MODE_OFF);
if (ret == 0)
ret = gs101_ls2_mode(uc_data, OVP_LS2_MODE_OFF);
if (ret == 0 && uc_data->wlctx_bst_en_first)
ret = gs101_ext_mode(uc_data, EXT_MODE_OFF);
/* STBY will re-enable WLC */
}
return ret;
}
/* change p9412 CPOUT and adjust WCIN_REG */
#define GS101_WLCRX_CPOUT_DFLT 0
#define GS101_WLCRX_CPOUT_5_2V 1
static int gs101_cpout_mode(struct max77759_usecase_data *uc_data, int mode)
{
int ret;
/* do not change MW unless p9412 can be changed as well */
if (uc_data->cpout_ctl < 0)
return 0;
if (mode == GS101_WLCRX_CPOUT_5_2V) {
/* p9412: set CPOUT==5.2 only if on BPP */
gpio_set_value_cansleep(uc_data->cpout_ctl, 1);
/* NOTE: no DC_IN to MW when WCIN_REG==4_85 unless CPOUT==5.2 */
ret = max77759_chgr_reg_update(uc_data->client, MAX77759_CHG_CNFG_12,
MAX77759_CHG_CNFG_12_WCIN_REG_MASK,
MAX77759_CHG_CNFG_12_WCIN_REG_4_85);
} else {
/* p9412: reset CPOUT to default */
gpio_set_value_cansleep(uc_data->cpout_ctl, 0);
ret = max77759_chgr_reg_update(uc_data->client, MAX77759_CHG_CNFG_12,
MAX77759_CHG_CNFG_12_WCIN_REG_MASK,
MAX77759_CHG_CNFG_12_WCIN_REG_4_5);
}
return ret;
}
static int gs101_otg_bypass_enable(struct max77759_usecase_data *uc_data, u8 vbyp)
{
int ret;
ret = max77759_chgr_reg_write(uc_data->client, MAX77759_CHG_CNFG_11, vbyp);
if (ret < 0) {
pr_err("%s: cannot set vbypset (%d)\n", __func__, ret);
return ret;
}
return max77759_chgr_reg_update(uc_data->client, MAX77759_CHG_CNFG_18,
MAX77759_CHG_CNFG_18_OTG_V_PGM,
MAX77759_CHG_CNFG_18_OTG_V_PGM);
}
static int gs101_otg_bypass_disable(struct max77759_usecase_data *uc_data)
{
return max77759_chgr_reg_update(uc_data->client, MAX77759_CHG_CNFG_18,
MAX77759_CHG_CNFG_18_OTG_V_PGM, 0);
}
static int gs101_otg_update_ilim(struct max77759_usecase_data *uc_data, int enable)
{
u8 ilim;
if (uc_data->otg_orig == uc_data->otg_ilim)
return 0;
if (enable) {
int rc;
rc = max77759_chgr_reg_read(uc_data->client, MAX77759_CHG_CNFG_05,
&uc_data->otg_orig);
if (rc < 0) {
pr_err("%s: cannot read otg_ilim (%d), use default\n",
__func__, rc);
uc_data->otg_orig = MAX77759_CHG_CNFG_05_OTG_ILIM_1500MA;
} else {
uc_data->otg_orig &= MAX77759_CHG_CNFG_05_OTG_ILIM_MASK;
}
ilim = uc_data->otg_ilim;
} else {
ilim = uc_data->otg_orig;
}
return max77759_chgr_reg_update(uc_data->client, MAX77759_CHG_CNFG_05,
MAX77759_CHG_CNFG_05_OTG_ILIM_MASK,
ilim);
}
static int gs101_otg_frs(struct max77759_usecase_data *uc_data, int enable)
{
int ret;
ret = max77759_chgr_mode_write(uc_data->client, MAX77759_CHGR_MODE_OTG_BOOST_ON);
if (ret < 0) {
pr_err("%s: cannot set CNFG_00 to 0xa ret:%d\n", __func__, ret);
return ret;
}
ret = gs101_otg_update_ilim(uc_data, enable);
if (ret < 0)
pr_err("%s: cannot update otg_ilim: %d\n", __func__, ret);
return ret;
}
/*
* Transition to standby (if needed) at the beginning of the sequences
* @return <0 on error, 0 on success. ->use_case becomes GSU_MODE_STANDBY
* if the transition is necessary (and successful).
*/
int gs101_to_standby(struct max77759_usecase_data *uc_data, int use_case)
{
const int from_uc = uc_data->use_case;
bool need_stby = false;
bool from_otg = false;
int ret;
switch (from_uc) {
case GSU_MODE_USB_CHG:
need_stby = use_case != GSU_MODE_USB_CHG_WLC_TX &&
use_case != GSU_MODE_WLC_RX &&
use_case != GSU_MODE_DOCK &&
use_case != GSU_MODE_USB_DC &&
use_case != GSU_MODE_USB_OTG_FRS;
break;
case GSU_MODE_WLC_RX:
need_stby = use_case != GSU_MODE_USB_OTG_WLC_RX &&
use_case != GSU_MODE_WLC_DC;
break;
case GSU_MODE_WLC_TX:
need_stby = use_case != GSU_MODE_USB_OTG_WLC_TX &&
use_case != GSU_MODE_USB_CHG_WLC_TX &&
use_case != GSU_MODE_USB_DC_WLC_TX &&
use_case != GSU_MODE_USB_OTG_FRS;
break;
case GSU_MODE_USB_CHG_WLC_TX:
need_stby = use_case != GSU_MODE_USB_CHG &&
use_case != GSU_MODE_USB_OTG_WLC_TX &&
use_case != GSU_MODE_USB_DC;
break;
case GSU_MODE_USB_OTG:
from_otg = true;
if (use_case == GSU_MODE_USB_OTG_FRS)
break;
if (use_case == GSU_MODE_USB_OTG_WLC_TX)
break;
if (use_case == GSU_MODE_USB_OTG_WLC_RX)
break;
if (use_case == GSU_MODE_USB_OTG_WLC_DC)
break;
/* From 5. USB OTG to anything else, go to stby */
ret = gs101_ext_bst_mode(uc_data, 0);
if (ret == 0)
ret = gs101_ls_mode(uc_data, 0);
if (ret == 0)
ret = gs101_ext_mode(uc_data, 0);
if (ret < 0)
return -EIO;
/* TODO:Discharge IN/OUT with AO37 is done in TCPM */
msleep(100);
need_stby = true;
break;
case GSU_MODE_USB_OTG_WLC_RX:
from_otg = true;
need_stby = use_case != GSU_MODE_WLC_RX &&
use_case != GSU_MODE_DOCK &&
use_case != GSU_MODE_USB_OTG;
break;
case GSU_MODE_USB_DC:
need_stby = use_case != GSU_MODE_USB_DC;
break;
case GSU_MODE_WLC_DC:
need_stby = use_case != GSU_MODE_WLC_DC;
break;
case GSU_MODE_USB_OTG_FRS:
from_otg = true;
need_stby = use_case != GSU_MODE_USB_OTG_FRS &&
use_case != GSU_MODE_USB_OTG_WLC_TX;
break;
/*
* if (use_case == GSU_MODE_USB_OTG)
* break;
*/
case GSU_RAW_MODE:
need_stby = true;
break;
case GSU_MODE_USB_OTG_WLC_TX:
from_otg = true;
need_stby = false;
break;
case GSU_MODE_STANDBY:
default:
need_stby = false;
break;
}
if (use_case == GSU_MODE_USB_WLC_RX || use_case == GSU_RAW_MODE)
need_stby = true;
pr_info("%s: use_case=%d->%d from_otg=%d need_stby=%d\n", __func__,
from_uc, use_case, from_otg, need_stby);
if (!need_stby)
return 0;
/* there are no ways out of OTG FRS */
if (from_uc == GSU_MODE_USB_OTG_FRS) {
ret = gs101_otg_frs(uc_data, true);
if (ret < 0) {
pr_err("%s: cannot turn off OTG_FRS (%d)\n", __func__, ret);
return ret;
}
}
/* from WLC_TX to STBY */
if (from_uc == GSU_MODE_WLC_TX) {
ret = gs101_wlc_tx_enable(uc_data, false);
if (ret < 0) {
pr_err("%s: cannot tun off wlc_tx (%d)\n", __func__, ret);
return ret;
}
/* re-enable wlc IC if disabled */
ret = gs101_wlc_en(uc_data, true);
if (ret < 0)
pr_err("%s: cannot enable WLC (%d)\n", __func__, ret);
}
/* from WLC-DC to STBY */
if (from_uc == GSU_MODE_WLC_DC) {
if (uc_data->dc_sw_gpio > 0)
gpio_set_value_cansleep(uc_data->dc_sw_gpio, 0);
ret = gs101_ext_mode(uc_data, EXT_MODE_OFF);
if (ret < 0) {
pr_debug("%s: cannot change extmode ret:%d\n",
__func__, ret);
return ret;
}
}
/*
* There is no direct transition to STBY from BPP_RX+OTG but we might
* get here on error and when forcing raw values. This makes sure that
* CPOUT is set to default.
*/
if (from_uc == GSU_MODE_USB_OTG_WLC_RX) {
ret = gs101_cpout_mode(uc_data, GS101_WLCRX_CPOUT_DFLT);
if (ret < 0)
pr_err("%s: cannot reset cpout (%d)\n", __func__, ret);
}
/* b/178458456 exit from all OTG cases need to reset the limit */
if (uc_data->otg_enable > 0)
gpio_set_value_cansleep(uc_data->otg_enable, 0);
/* transition to STBY (might need to be up) */
ret = max77759_chgr_mode_write(uc_data->client, MAX77759_CHGR_MODE_ALL_OFF);
if (ret < 0)
return -EIO;
uc_data->use_case = GSU_MODE_STANDBY;
return ret;
}
EXPORT_SYMBOL_GPL(gs101_to_standby);
/* enable/disable soft-start. No need soft start from OTG->OTG_FRS */
static int gs101_ramp_bypass(struct max77759_usecase_data *uc_data, bool enable)
{
const u8 value = enable ? MAX77759_CHG_CNFG_00_BYPV_RAMP_BYPASS : 0;
if (uc_data->is_a1 <= 0)
return 0;
return max77759_chgr_reg_update(uc_data->client, MAX77759_CHG_CNFG_00,
MAX77759_CHG_CNFG_00_BYPV_RAMP_BYPASS,
value);
}
/* cleanup from every usecase */
int gs101_force_standby(struct max77759_usecase_data *uc_data)
{
const u8 insel_mask = MAX77759_CHG_CNFG_12_CHGINSEL_MASK |
MAX77759_CHG_CNFG_12_WCINSEL_MASK;
const u8 insel_value = MAX77759_CHG_CNFG_12_CHGINSEL |
MAX77759_CHG_CNFG_12_WCINSEL;
int ret;
pr_debug("%s: recovery\n", __func__);
ret = gs101_ls_mode(uc_data, 0);
if (ret < 0)
pr_err("%s: cannot change ls_mode (%d)\n",
__func__, ret);
ret = gs101_ext_mode(uc_data, 0);
if (ret < 0)
pr_err("%s: cannot change ext mode (%d)\n",
__func__, ret);
ret = gs101_ramp_bypass(uc_data, false);
if (ret < 0)
pr_err("%s: cannot reset ramp_bypass (%d)\n",
__func__, ret);
ret = max77759_chgr_mode_write(uc_data->client, MAX77759_CHGR_MODE_ALL_OFF);
if (ret < 0)
pr_err("%s: cannot reset mode register (%d)\n",
__func__, ret);
ret = max77759_chgr_insel_write(uc_data->client, insel_mask, insel_value);
if (ret < 0)
pr_err("%s: cannot reset insel (%d)\n",
__func__, ret);
return 0;
}
EXPORT_SYMBOL_GPL(gs101_force_standby);
/* b/188488966 */
static int gs101_frs_to_otg(struct max77759_usecase_data *uc_data)
{
int closed, ret;
ret = gs101_ext_mode(uc_data, EXT_MODE_OTG_5_0V);
if (ret < 0)
goto exit_done;
msleep(100);
if (uc_data->ls1_en > 0)
gpio_set_value_cansleep(uc_data->ls1_en, 1);
msleep(100);
if (uc_data->lsw1_is_closed >= 0)
closed = gpio_get_value_cansleep(uc_data->lsw1_is_closed);
exit_done:
pr_debug("%s: ls1_en=%d lsw1_is_closed=%d closed=%d ret=%d\n",
__func__, uc_data->ls1_en, uc_data->lsw1_is_closed,
closed, ret);
return ret;
}
/* From OTG <-> OTG_FRS */
static int gs101_otg_mode(struct max77759_usecase_data *uc_data, int to)
{
int ret = -EINVAL;
pr_debug("%s: to=%d\n", __func__, to);
/* no transition needed if only use external boost OTG */
if (uc_data->ext_otg_only > 0)
return 0;
if (to == GSU_MODE_USB_OTG) {
ret = gs101_ext_mode(uc_data, EXT_MODE_OTG_5_0V);
if (ret < 0)
return ret;
usleep_range(5 * USEC_PER_MSEC, 5 * USEC_PER_MSEC + 100);
ret = max77759_chgr_mode_write(uc_data->client,
MAX77759_CHGR_MODE_ALL_OFF);
} else if (to == GSU_MODE_USB_OTG_FRS) {
int rc;
ret = gs101_ramp_bypass(uc_data, true);
if (ret == 0)
ret = max77759_chgr_mode_write(uc_data->client,
MAX77759_CHGR_MODE_OTG_BOOST_ON);
if (ret < 0)
return ret;
usleep_range(5 * USEC_PER_MSEC, 5 * USEC_PER_MSEC + 100);
rc = gs101_ramp_bypass(uc_data, false);
if (rc < 0)
pr_err("%s: cannot clear bypass rc:%d\n", __func__, rc);
/* b/192986752 make sure that LSW1 is open before going to FRS */
rc = gs101_ls_mode(uc_data, 0);
if (rc < 0)
pr_err("%s: cannot clear lsw1 rc:%d\n", __func__, rc);
ret = gs101_ext_mode(uc_data, EXT_MODE_OFF);
if (ret < 0)
return ret;
}
return ret;
}
/*
* This must follow different paths depending on the platforms.
*
* When vin_is_valid is implemented the code uses the NBC workaround for MW
* and the OVP. The code defaults to just setting the MODE register (at the
* end of the use case) and toggling bst_on/bst_sel and setting ext_bst_ctl
* otherwise.
*
* NOTE: the USB stack expects VBUS to be on after voting for the usecase.
*/
static int gs101_otg_enable(struct max77759_usecase_data *uc_data, int mode)
{
int ret, retn;
/* the code default to write to the MODE register */
if (uc_data->vin_is_valid >= 0) {
/* b/178458456 */
if (uc_data->otg_enable > 0)
gpio_set_value_cansleep(uc_data->otg_enable, 1);
/* NBC workaround */
ret = gs101_ls_mode(uc_data, 1);
if (ret < 0) {
pr_debug("%s: cannot close load switch (%d)\n",
__func__, ret);
return ret;
}
ret = gs101_otg_bypass_enable(uc_data, uc_data->otg_value);
if (ret < 0) {
pr_debug("%s: cannot set otg voltage (%d)\n", __func__, ret);
return ret;
}
ret = max77759_chgr_mode_write(uc_data->client,
MAX77759_CHGR_MODE_OTG_BOOST_ON);
if (ret < 0) {
pr_debug("%s: cannot set CNFG_00 to 0xa ret:%d\n",
__func__, ret);
return ret;
}
ret = gpio_get_value_cansleep(uc_data->vin_is_valid);
if (ret == 0) {
pr_debug("%s: VIN not VALID\n", __func__);
ret = -EIO;
goto reset_otg_voltage;
}
ret = gs101_ext_mode(uc_data, mode);
if (ret < 0)
pr_debug("%s: cannot change extmode ret:%d\n",
__func__, ret);
} else {
ret = gs101_otg_bypass_enable(uc_data, uc_data->otg_value);
if (ret < 0) {
pr_debug("%s: cannot set otg voltage (%d)\n", __func__, ret);
return ret;
}
/* ext mode is defined when ext boost is avalaible */
ret = gs101_ext_mode(uc_data, mode);
if (ret < 0) {
pr_debug("%s: cannot change extmode ret:%d\n",
__func__, ret);
goto reset_otg_voltage;
}
usleep_range(5 * USEC_PER_MSEC, 5 * USEC_PER_MSEC + 100);
/* load switch from MW */
ret = gs101_ls_mode(uc_data, 1);
if (ret < 0) {
pr_debug("%s: cannot close load switch (%d)\n",
__func__, ret);
goto reset_otg_voltage;
}
/* time for VBUS to be on (could check PWRSTAT in MW) */
msleep(30);
/* b/178458456 */
if (uc_data->otg_enable > 0)
gpio_set_value_cansleep(uc_data->otg_enable, 0);
}
goto exit;
reset_otg_voltage:
retn = gs101_otg_bypass_disable(uc_data);
if (retn < 0)
pr_debug("%s: cannot reset otg voltage (%d)\n", __func__, retn);
exit:
return ret;
}
/* configure ilim wlctx */
static int gs101_wlctx_otg_en(struct max77759_usecase_data *uc_data, bool enable)
{
int ret;
if (enable) {
/* this should be already set */
if (uc_data->sw_en >= 0)
gpio_set_value_cansleep(uc_data->sw_en, 1);
ret = gs101_otg_update_ilim(uc_data, true);
if (ret < 0)
pr_err("%s: cannot update otg_ilim (%d)\n", __func__, ret);
ret = gs101_otg_bypass_enable(uc_data, uc_data->otg_vbyp);
if (ret < 0)
pr_err("%s: cannot set otg_vbyp (%d)\n", __func__, ret);
} else {
/* TODO: Discharge IN/OUT nodes with AO37 should be done in TCPM */
msleep(100);
/* "bad things will happen (tm)" if you force off ->sw_en */
ret = gs101_otg_update_ilim(uc_data, false);
if (ret < 0)
pr_err("%s: cannot restore otg_ilim (%d)\n", __func__, ret);
/* TODO: restore initial value on !MAX77759_CHG_CNFG_11 */
ret = gs101_otg_bypass_disable(uc_data);
if (ret < 0)
pr_err("%s: cannot reset otg_v_pgm (%d)\n", __func__, ret);
ret = 0;
}
return ret;
}
static int gs101_ext_bst_mode(struct max77759_usecase_data *uc_data, int mode)
{
struct max77759_chgr_data *data;
pr_debug("%s: ext_bst_mode=%d mode=%d\n", __func__, uc_data->ext_bst_mode, mode);
if (uc_data->ext_bst_mode < 0)
return 0;
if (!uc_data->client)
goto write_bst_mode;
data = i2c_get_clientdata(uc_data->client);
if (data && data->otg_fccm_reset) {
pr_info("Set FCCM on 77759's callback\n");
return 0;
}
write_bst_mode:
gpio_set_value_cansleep(uc_data->ext_bst_mode, mode);
return 0;
}
/*
* Case USB_chg USB_otg WLC_chg WLC_TX PMIC_Charger Ext_B LSxx Name
* -------------------------------------------------------------------------------------
* 4-1 0 1 10 0 IF-PMIC-WCIN 1 1/0 USB_OTG_WLC_RX
* 4-2 0 1 01 0 DC WCIN 1 1/0 USB_OTG_WLC_DC
* 5-1 0 1 0 0 0 1 1/0 USB_OTG
* 5-2 0 1 0 0 OTG_5V 0 0/0 USB_OTG_FRS
* 7-2 0 1 0 1 OTG_5V 2 0/1 USB_OTG_WLC_TX
* -------------------------------------------------------------------------------------
* WLC_chg = 0 off, 1 = on, 2 = PPS
* Ext_Boost = 0 off, 1 = OTG 5V, 2 = WTX 7.5
*
* 5-1: mode=0x0 in MW, EXT_B=1, LS1=1, LS2=0, IDLE <-> OTG (ext)
* 5-2: mode=0xa in MW, EXT_B=0, LS1=0, LS2=0, IDLE <-> OTG_FRS
* 7-2: mode=0xa in MW, EXT_B=2, LS1=0, LS2=1
*
* AO37 + GPIO5 MW (canopy 3, whitev2p2)
* . AO_ls1 <&max20339_gpio 0 GPIO_ACTIVE_HIGH> - bit0
* . AO_ls2 <&max20339_gpio 1 GPIO_ACTIVE_HIGH> - bit4
*
* ls1 can be controlled poking the AO37 OR using a MW_GPIO _> EXT_BST_EN
*
* max77759,bst_on = <&max777x9_gpio 4 GPIO_ACTIVE_HIGH>
* max77759,bst-sel = <&gpp27 3 GPIO_ACTIVE_HIGH>
* max77759,bst_on=0, max77759,bst_sel=x => OFF
* max77759,bst_on=1, max77759,bst_sel=0 => 5V
* max77759,bst_on=1, max77759,bst_sel=1 => 7.5V
*
* Ext_Boost = 0 off
* MW_gpio5 : Ext_B = 0, MW_gpio5 -> LOW
* AO_ls1/ls2 : 0/0
*
* Ext_Boost = 1 = OTG 5V
* MW_gpio5 : Ext_B = 1, MW_gpio5 -> HIGH
* AO_ls1/ls2 : 1/0
*
* Ext_Boost = 2 WTX 7.5
* MW_gpio5 : Ext_B = 2, MW_gpio5 -> HIGH
* AO_ls1/ls2 : 0/1
*
* NOTE: do not call with (cb_data->wlc_rx && cb_data->wlc_tx)
*/
static int gs101_standby_to_otg(struct max77759_usecase_data *uc_data, int use_case)
{
int ret;
const int mode = (uc_data->ext_otg_only || use_case != GSU_MODE_USB_OTG_FRS) ?
EXT_MODE_OTG_5_0V : EXT_MODE_OFF;
ret = gs101_otg_enable(uc_data, mode);
if (ret == 0 && uc_data->ext_otg_only)
ret = gs101_ext_bst_mode(uc_data, 1);
if (ret == 0)
usleep_range(5 * USEC_PER_MSEC, 5 * USEC_PER_MSEC + 100);
/*
* Assumption: gs101_to_usecase() will write back cached values to
* CHG_CNFG_00.Mode. At the moment, the cached value at
* max77759_mode_callback is 0. If the cached value changes to something
* other than 0, then, the code has to be revisited.
*/
return ret;
}
/* was b/179816224 WLC_RX -> WLC_RX + OTG (Transition #10) */
static int gs101_wlcrx_to_wlcrx_otg(struct max77759_usecase_data *uc_data)
{
pr_warn("%s: disabled\n", __func__);
return 0;
}
static int gs101_to_otg_usecase(struct max77759_usecase_data *uc_data, int use_case)
{
const int from_uc = uc_data->use_case;
int ret = 0;
switch (from_uc) {
/* 5-1: #3: stby to USB OTG, mode = 1 */
/* 5-2: #3: stby to USB OTG_FRS, mode = 0 */
case GSU_MODE_STANDBY:
ret = gs101_standby_to_otg(uc_data, use_case);
if (ret < 0) {
pr_err("%s: cannot enable OTG ret:%d\n", __func__, ret);
return ret;
}
break;
/* b/186535439 : USB_CHG->USB_OTG_FRS*/
case GSU_MODE_USB_CHG:
case GSU_MODE_USB_CHG_WLC_TX:
/* need to go through stby out of this */
if (use_case != GSU_MODE_USB_OTG_FRS && use_case != GSU_MODE_USB_OTG_WLC_TX)
return -EINVAL;
ret = gs101_otg_frs(uc_data, true);
break;
case GSU_MODE_WLC_TX:
/* b/179820595: WLC_TX -> WLC_TX + OTG */
if (use_case == GSU_MODE_USB_OTG_WLC_TX) {
ret = max77759_chgr_mode_write(uc_data->client, MAX77759_CHGR_MODE_OTG_BOOST_ON);
if (ret < 0) {
pr_err("%s: cannot set CNFG_00 to 0xa ret:%d\n", __func__, ret);
return ret;
}
}
break;
case GSU_MODE_WLC_RX:
case GSU_MODE_DOCK:
if (use_case == GSU_MODE_USB_OTG_WLC_RX) {
if (uc_data->rx_otg_en)
ret = gs101_standby_to_otg(uc_data, use_case);
else
ret = gs101_wlcrx_to_wlcrx_otg(uc_data);
}
break;
case GSU_MODE_USB_OTG:
/* b/179820595: OTG -> WLC_TX + OTG (see b/181371696) */
if (use_case == GSU_MODE_USB_OTG_WLC_TX) {
ret = gs101_otg_mode(uc_data, GSU_MODE_USB_OTG_FRS);
if (ret == 0)
ret = gs101_wlc_tx_enable(uc_data, true);
}
/* b/179816224: OTG -> WLC_RX + OTG */
if (use_case == GSU_MODE_USB_OTG_WLC_RX) {
ret = gs101_cpout_mode(uc_data, GS101_WLCRX_CPOUT_5_2V);
if (ret == 0)
ret = gs101_ext_bst_mode(uc_data, 1);
}
break;
case GSU_MODE_USB_OTG_WLC_TX:
/* b/179820595: WLC_TX + OTG -> OTG */
if (use_case == GSU_MODE_USB_OTG) {
ret = gs101_wlc_tx_enable(uc_data, false);
if (ret == 0)
ret = gs101_frs_to_otg(uc_data);
}
break;
case GSU_MODE_USB_OTG_WLC_RX:
/* b/179816224: WLC_RX + OTG -> OTG */
if (use_case == GSU_MODE_USB_OTG && !uc_data->ext_otg_only) {
/* it's in STBY, no need to reset gs101_otg_mode() */
ret = gs101_ext_bst_mode(uc_data, 0);
if (ret == 0)
ret = gs101_cpout_mode(uc_data, GS101_WLCRX_CPOUT_DFLT);
}
break;
/* TODO: */
case GSU_MODE_USB_OTG_FRS: {
if (use_case == GSU_MODE_USB_OTG_WLC_TX) {
ret = gs101_wlc_tx_enable(uc_data, true);
break;
}
/*
* OTG source handover: OTG_FRS -> OTG
* from EXT_BST (Regular OTG) to IF-PMIC OTG (FRS OTG)
*/
if (use_case != GSU_MODE_USB_OTG)
return -EINVAL;
/* TODO: */
} break;
default:
return -ENOTSUPP;
}
return ret;
}
/* handles the transition data->use_case ==> use_case */
int gs101_to_usecase(struct max77759_usecase_data *uc_data, int use_case)
{
const int from_uc = uc_data->use_case;
int ret = 0;
switch (use_case) {
case GSU_MODE_USB_OTG:
case GSU_MODE_USB_OTG_FRS:
case GSU_MODE_USB_OTG_WLC_RX:
case GSU_MODE_USB_OTG_WLC_DC:
case GSU_MODE_USB_OTG_WLC_TX:
ret = gs101_to_otg_usecase(uc_data, use_case);
break;
case GSU_MODE_WLC_TX:
case GSU_MODE_USB_CHG_WLC_TX:
/* Coex Case #4, WLC_TX + OTG -> WLC_TX */
if (from_uc == GSU_MODE_USB_OTG_WLC_TX) {
ret = max77759_chgr_mode_write(uc_data->client,
MAX77759_CHGR_MODE_ALL_OFF);
if (ret == 0)
ret = gs101_wlctx_otg_en(uc_data, false);
} else {
ret = gs101_wlc_tx_enable(uc_data, true);
}
break;
case GSU_MODE_WLC_RX:
case GSU_MODE_DOCK:
if (from_uc == GSU_MODE_USB_OTG_WLC_RX) {
/* to_stby brought to stby */
ret = gs101_ext_bst_mode(uc_data, 0);
if (uc_data->ext_otg_only) {
if (ret == 0)
ret = gs101_ls_mode(uc_data, 0);
if (ret == 0)
ret = gs101_ext_mode(uc_data, 0);
} else {
if (ret == 0)
ret = gs101_cpout_mode(uc_data, GS101_WLCRX_CPOUT_DFLT);
if (ret == 0)
ret = gs101_otg_mode(uc_data, GSU_MODE_USB_OTG);
}
}
if (from_uc == GSU_MODE_WLC_DC) {
ret = gs101_ext_mode(uc_data, EXT_MODE_OFF);
if (ret < 0) {
pr_debug("%s: cannot change extmode ret:%d\n",
__func__, ret);
return ret;
}
}
break;
case GSU_MODE_USB_CHG:
case GSU_MODE_USB_DC:
if (from_uc == GSU_MODE_WLC_TX || from_uc == GSU_MODE_USB_CHG_WLC_TX) {
ret = gs101_wlc_tx_enable(uc_data, false);
}
/* b/202767016: charge over USB-C, set to low */
if (uc_data->pogo_ovp_en > 0)
gpio_set_value_cansleep(uc_data->pogo_ovp_en, 0);
break;
case GSU_MODE_USB_WLC_RX:
case GSU_RAW_MODE:
/* just write the value to the register (it's in stby) */
break;
case GSU_MODE_WLC_DC:
if (uc_data->dc_sw_gpio > 0)
gpio_set_value_cansleep(uc_data->dc_sw_gpio, 1);
ret = gs101_ext_mode(uc_data, EXT_MODE_OTG_5_0V);
if (ret < 0) {
pr_debug("%s: cannot change extmode ret:%d\n",
__func__, ret);
return ret;
}
break;
default:
break;
}
return ret;
}
EXPORT_SYMBOL_GPL(gs101_to_usecase);
static int max77759_otg_ilim_ma_to_code(u8 *code, int otg_ilim)
{
if (otg_ilim == 0)
*code = 0;
else if (otg_ilim >= 500 && otg_ilim <= 1500)
*code = 1 + (otg_ilim - 500) / 100;
else
return -EINVAL;
return 0;
}
int max77759_otg_vbyp_mv_to_code(u8 *code, int vbyp)
{
if (vbyp >= 12000)
*code = 0x8c;
else if (vbyp >= 5000)
*code = (vbyp - 5000) / 50;
else
return -EINVAL;
return 0;
}
EXPORT_SYMBOL_GPL(max77759_otg_vbyp_mv_to_code);
#define GS101_OTG_ILIM_DEFAULT_MA 1500
#define GS101_OTG_VBYPASS_DEFAULT_MV 5100
/* lazy init on the switches */
static bool gs101_setup_usecases_done(struct max77759_usecase_data *uc_data)
{
return (uc_data->cpout_en != -EPROBE_DEFER) &&
(uc_data->cpout_ctl != -EPROBE_DEFER) &&
(uc_data->wlc_vbus_en != -EPROBE_DEFER) &&
(uc_data->ext_bst_ctl != -EPROBE_DEFER) &&
(uc_data->bst_sel != -EPROBE_DEFER);
/* TODO: handle platform specific differences..
uc_data->ls2_en != -EPROBE_DEFER &&
uc_data->lsw1_is_closed != -EPROBE_DEFER &&
uc_data->lsw1_is_open != -EPROBE_DEFER &&
uc_data->vin_is_valid != -EPROBE_DEFER &&
uc_data->cpout_ctl != -EPROBE_DEFER &&
uc_data->cpout_en != -EPROBE_DEFER &&
uc_data->cpout21_en != -EPROBE_DEFER
uc_data->bst_on != -EPROBE_DEFER &&
uc_data->bst_sel != -EPROBE_DEFER &&
uc_data->ext_bst_ctl != -EPROBE_DEFER;
*/
}
static void gs101_setup_default_usecase(struct max77759_usecase_data *uc_data)
{
int ret;
uc_data->is_a1 = -1;
uc_data->bst_on = -EPROBE_DEFER;
uc_data->bst_sel = -EPROBE_DEFER;
uc_data->ext_bst_ctl = -EPROBE_DEFER;
uc_data->pogo_ovp_en = -EPROBE_DEFER;
uc_data->ls1_en = -EPROBE_DEFER;
uc_data->ls2_en = -EPROBE_DEFER;
uc_data->sw_en = -EPROBE_DEFER;
uc_data->vin_is_valid = -EPROBE_DEFER;
uc_data->lsw1_is_closed = -EPROBE_DEFER;
uc_data->lsw1_is_open = -EPROBE_DEFER;
uc_data->otg_enable = -EPROBE_DEFER;
uc_data->wlc_en = -EPROBE_DEFER;
uc_data->wlc_vbus_en = -EPROBE_DEFER;
uc_data->cpout_en = -EPROBE_DEFER;
uc_data->cpout_ctl = -EPROBE_DEFER;
uc_data->cpout21_en = -EPROBE_DEFER;
uc_data->ext_bst_mode = -EPROBE_DEFER;
uc_data->dc_sw_gpio = -EPROBE_DEFER;
uc_data->init_done = false;
/* TODO: override in bootloader and remove */
ret = max77759_otg_ilim_ma_to_code(&uc_data->otg_ilim,
GS101_OTG_ILIM_DEFAULT_MA);
if (ret < 0)
uc_data->otg_ilim = MAX77759_CHG_CNFG_05_OTG_ILIM_1500MA;
ret = max77759_chgr_reg_read(uc_data->client, MAX77759_CHG_CNFG_05,
&uc_data->otg_orig);
if (ret == 0) {
uc_data->otg_orig &= MAX77759_CHG_CNFG_05_OTG_ILIM_MASK;
} else {
uc_data->otg_orig = uc_data->otg_ilim;
}
ret = max77759_otg_vbyp_mv_to_code(&uc_data->otg_vbyp,
GS101_OTG_VBYPASS_DEFAULT_MV);
if (ret < 0)
uc_data->otg_vbyp = MAX77759_CHG_CNFG_11_OTG_VBYP_5100MV;
}
bool gs101_setup_usecases(struct max77759_usecase_data *uc_data,
struct device_node *node)
{
if (!node) {
gs101_setup_default_usecase(uc_data);
return false;
}
/* control external boost if present */
if (uc_data->bst_on == -EPROBE_DEFER)
uc_data->bst_on = of_get_named_gpio(node, "max77759,bst-on", 0);
if (uc_data->bst_sel == -EPROBE_DEFER)
uc_data->bst_sel = of_get_named_gpio(node, "max77759,bst-sel", 0);
if (uc_data->ext_bst_ctl == -EPROBE_DEFER)
uc_data->ext_bst_ctl = of_get_named_gpio(node, "max77759,extbst-ctl", 0);
/* for enabling charging over pogo */
if (uc_data->pogo_ovp_en == -EPROBE_DEFER)
uc_data->pogo_ovp_en = of_get_named_gpio(node, "max77759,pogo-ovp-en", 0);
/* NBC workaround */
if (uc_data->vin_is_valid == -EPROBE_DEFER)
uc_data->vin_is_valid = of_get_named_gpio(node, "max77759,vin-is_valid", 0);
if (uc_data->lsw1_is_closed == -EPROBE_DEFER)
uc_data->lsw1_is_closed = of_get_named_gpio(node, "max77759,lsw1-is_closed", 0);
if (uc_data->lsw1_is_open == -EPROBE_DEFER)
uc_data->lsw1_is_open = of_get_named_gpio(node, "max77759,lsw1-is_open", 0);
/* all OTG cases, change INOVLO */
if (uc_data->otg_enable == -EPROBE_DEFER)
uc_data->otg_enable = of_get_named_gpio(node, "max77759,otg-enable", 0);
/* wlc_rx: disable when chgin, CPOUT is safe */
if (uc_data->wlc_en == -EPROBE_DEFER)
uc_data->wlc_en = of_get_named_gpio(node, "max77759,wlc-en", 0);
if (uc_data->wlc_vbus_en == -EPROBE_DEFER)
uc_data->wlc_vbus_en = of_get_named_gpio(node, "max77759,wlc-vbus_en", 0);
/* wlc_rx -> wlc_rx+otg disable cpout */
if (uc_data->cpout_en == -EPROBE_DEFER)
uc_data->cpout_en = of_get_named_gpio(node, "max77759,cpout-en", 0);
/* to 5.2V in p9412 */
if (uc_data->cpout_ctl == -EPROBE_DEFER)
uc_data->cpout_ctl = of_get_named_gpio(node, "max77759,cpout-ctl", 0);
/* ->wlc_tx disable 2:1 cpout */
if (uc_data->cpout21_en == -EPROBE_DEFER)
uc_data->cpout21_en = of_get_named_gpio(node, "max77759,cpout_21-en", 0);
if (uc_data->ls1_en == -EPROBE_DEFER)
uc_data->ls1_en = of_get_named_gpio(node, "max77759,ls1-en", 0);
if (uc_data->ls2_en == -EPROBE_DEFER)
uc_data->ls2_en = of_get_named_gpio(node, "max77759,ls2-en", 0);
/* OTG+RTXL: IN-OUT switch of AO37 (forced always) */
if (uc_data->sw_en == -EPROBE_DEFER)
uc_data->sw_en = of_get_named_gpio(node, "max77759,sw-en", 0);
/* OPTIONAL: only in P1.1+ (TPS61372) */
if (uc_data->ext_bst_mode == -EPROBE_DEFER)
uc_data->ext_bst_mode = of_get_named_gpio(node, "max77759,extbst-mode", 0);
/* OPTIONAL: support wlc_rx -> wlc_rx+otg */
uc_data->rx_otg_en = of_property_read_bool(node, "max77759,rx-to-rx-otg-en");
/* OPTIONAL: support external boost OTG only */
uc_data->ext_otg_only = of_property_read_bool(node, "max77759,ext-otg-only");
/* OPTIONAL: use bst_on first on/off sequence */
uc_data->wlctx_bst_en_first = of_property_read_bool(node, "max77759,bst-lsw-sequence");
if (uc_data->dc_sw_gpio == -EPROBE_DEFER)
uc_data->dc_sw_gpio = of_get_named_gpio(node, "max77759,gpio_dc_switch", 0);
return gs101_setup_usecases_done(uc_data);
}
EXPORT_SYMBOL_GPL(gs101_setup_usecases);
void gs101_dump_usecasase_config(struct max77759_usecase_data *uc_data)
{
pr_info("bst_on:%d, bst_sel:%d, ext_bst_ctl:%d\n",
uc_data->bst_on, uc_data->bst_sel, uc_data->ext_bst_ctl);
pr_info("vin_valid:%d lsw1_o:%d lsw1_c:%d\n", uc_data->vin_is_valid,
uc_data->lsw1_is_open, uc_data->lsw1_is_closed);
pr_info("wlc_en:%d wlc_vbus_en:%d cpout_en:%d cpout_ctl:%d cpout21_en=%d\n",
uc_data->wlc_en, uc_data->wlc_vbus_en,
uc_data->cpout_en, uc_data->cpout_ctl, uc_data->cpout21_en);
pr_info("ls2_en:%d sw_en:%d ext_bst_mode:%d dc_sw_en:%d\n",
uc_data->ls2_en, uc_data->sw_en, uc_data->ext_bst_mode, uc_data->dc_sw_gpio);
pr_info("rx_to_rx_otg:%d ext_otg_only:%d\n",
uc_data->rx_otg_en, uc_data->ext_otg_only);
}
EXPORT_SYMBOL_GPL(gs101_dump_usecasase_config);