qcom-charger: smb2: support for micro USB mode

Add support to disable TypeC mode of charger hardware
and enable micro USB mode. Enabling micro USB mode involves
following changes:
- hvdcp needs to be kept enabled. Create a new votable hvdcp_enable
  and vote true when uUSB connector is in use.
- Add support of extcon for sending notification to USB driver.

Change-Id: I109c07cd15052f4be15fee203f7cbaf02b6fd5cf
Signed-off-by: Ashay Jaiswal <ashayj@codeaurora.org>
This commit is contained in:
Ashay Jaiswal 2016-12-07 19:39:38 +05:30
parent c0a8f9e80a
commit 7ac9349c5a
5 changed files with 266 additions and 76 deletions

View file

@ -152,6 +152,12 @@ Charger specific properties:
be based off battery voltage. For both SOC and battery voltage,
charger receives the signal from FG to resume charging.
- qcom,micro-usb
Usage: optional
Value type: <empty>
Definition: Boolean flag which indicates that the platform only support
micro usb port.
=============================================
Second Level Nodes - SMB2 Charger Peripherals
=============================================

View file

@ -362,6 +362,9 @@ static int smb2_parse_dt(struct smb2 *chip)
chip->dt.auto_recharge_soc = of_property_read_bool(node,
"qcom,auto-recharge-soc");
chg->micro_usb_mode = of_property_read_bool(node, "qcom,micro-usb");
return 0;
}
@ -429,16 +432,24 @@ static int smb2_usb_get_prop(struct power_supply *psy,
val->intval = chg->usb_psy_desc.type;
break;
case POWER_SUPPLY_PROP_TYPEC_MODE:
if (chip->bad_part)
if (chg->micro_usb_mode)
val->intval = POWER_SUPPLY_TYPEC_NONE;
else if (chip->bad_part)
val->intval = POWER_SUPPLY_TYPEC_SOURCE_DEFAULT;
else
rc = smblib_get_prop_typec_mode(chg, val);
break;
case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
rc = smblib_get_prop_typec_power_role(chg, val);
if (chg->micro_usb_mode)
val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
else
rc = smblib_get_prop_typec_power_role(chg, val);
break;
case POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION:
rc = smblib_get_prop_typec_cc_orientation(chg, val);
if (chg->micro_usb_mode)
val->intval = 0;
else
rc = smblib_get_prop_typec_cc_orientation(chg, val);
break;
case POWER_SUPPLY_PROP_PD_ALLOWED:
rc = smblib_get_prop_pd_allowed(chg, val);
@ -1070,6 +1081,99 @@ static int smb2_config_wipower_input_power(struct smb2 *chip, int uw)
return 0;
}
static int smb2_configure_typec(struct smb_charger *chg)
{
int rc;
/*
* trigger the usb-typec-change interrupt only when the CC state
* changes
*/
rc = smblib_write(chg, TYPE_C_INTRPT_ENB_REG,
TYPEC_CCSTATE_CHANGE_INT_EN_BIT);
if (rc < 0) {
dev_err(chg->dev,
"Couldn't configure Type-C interrupts rc=%d\n", rc);
return rc;
}
/* configure power role for dual-role */
rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
TYPEC_POWER_ROLE_CMD_MASK, 0);
if (rc < 0) {
dev_err(chg->dev,
"Couldn't configure power role for DRP rc=%d\n", rc);
return rc;
}
/*
* disable Type-C factory mode and stay in Attached.SRC state when VCONN
* over-current happens
*/
rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
FACTORY_MODE_DETECTION_EN_BIT | VCONN_OC_CFG_BIT, 0);
if (rc < 0) {
dev_err(chg->dev, "Couldn't configure Type-C rc=%d\n", rc);
return rc;
}
/* increase VCONN softstart */
rc = smblib_masked_write(chg, TYPE_C_CFG_2_REG,
VCONN_SOFTSTART_CFG_MASK, VCONN_SOFTSTART_CFG_MASK);
if (rc < 0) {
dev_err(chg->dev, "Couldn't increase VCONN softstart rc=%d\n",
rc);
return rc;
}
/* disable try.SINK mode */
rc = smblib_masked_write(chg, TYPE_C_CFG_3_REG, EN_TRYSINK_MODE_BIT, 0);
if (rc < 0) {
dev_err(chg->dev, "Couldn't set TRYSINK_MODE rc=%d\n", rc);
return rc;
}
rc = smblib_validate_initial_typec_legacy_status(chg);
if (rc < 0) {
dev_err(chg->dev, "Couldn't validate typec legacy status rc=%d\n",
rc);
return rc;
}
return rc;
}
static int smb2_disable_typec(struct smb_charger *chg)
{
int rc;
/* configure FSM in idle state */
rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
TYPEC_DISABLE_CMD_BIT, TYPEC_DISABLE_CMD_BIT);
if (rc < 0) {
dev_err(chg->dev, "Couldn't put FSM in idle rc=%d\n", rc);
return rc;
}
/* configure micro USB mode */
rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
TYPE_C_OR_U_USB_BIT, TYPE_C_OR_U_USB_BIT);
if (rc < 0) {
dev_err(chg->dev, "Couldn't enable micro USB mode rc=%d\n", rc);
return rc;
}
/* release FSM from idle state */
rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
TYPEC_DISABLE_CMD_BIT, 0);
if (rc < 0) {
dev_err(chg->dev, "Couldn't release FSM rc=%d\n", rc);
return rc;
}
return rc;
}
static int smb2_init_hw(struct smb2 *chip)
{
struct smb_charger *chg = &chip->chg;
@ -1131,14 +1235,18 @@ static int smb2_init_hw(struct smb2 *chip)
DCP_VOTER, true, chip->dt.usb_icl_ua);
vote(chg->dc_icl_votable,
DEFAULT_VOTER, true, chip->dt.dc_icl_ua);
vote(chg->hvdcp_disable_votable, DEFAULT_VOTER,
vote(chg->hvdcp_disable_votable_indirect, DEFAULT_VOTER,
chip->dt.hvdcp_disable, 0);
vote(chg->hvdcp_disable_votable, PD_INACTIVE_VOTER,
vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER,
true, 0);
vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER,
true, 0);
vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
true, 0);
vote(chg->pd_disallowed_votable_indirect, MICRO_USB_VOTER,
chg->micro_usb_mode, 0);
vote(chg->hvdcp_enable_votable, MICRO_USB_VOTER,
chg->micro_usb_mode, 0);
/* Configure charge enable for software control; active high */
rc = smblib_masked_write(chg, CHGR_CFG2_REG,
@ -1156,12 +1264,10 @@ static int smb2_init_hw(struct smb2 *chip)
return rc;
}
/*
* trigger the usb-typec-change interrupt only when the CC state
* changes
*/
rc = smblib_write(chg, TYPE_C_INTRPT_ENB_REG,
TYPEC_CCSTATE_CHANGE_INT_EN_BIT);
if (chg->micro_usb_mode)
rc = smb2_disable_typec(chg);
else
rc = smb2_configure_typec(chg);
if (rc < 0) {
dev_err(chg->dev,
"Couldn't configure Type-C interrupts rc=%d\n", rc);
@ -1186,42 +1292,6 @@ static int smb2_init_hw(struct smb2 *chip)
return rc;
}
/* configure power role for dual-role */
rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
TYPEC_POWER_ROLE_CMD_MASK, 0);
if (rc < 0) {
dev_err(chg->dev,
"Couldn't configure power role for DRP rc=%d\n", rc);
return rc;
}
/*
* disable Type-C factory mode and stay in Attached.SRC state when VCONN
* over-current happens
*/
rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
FACTORY_MODE_DETECTION_EN_BIT | VCONN_OC_CFG_BIT, 0);
if (rc < 0) {
dev_err(chg->dev, "Couldn't configure Type-C rc=%d\n", rc);
return rc;
}
/* increase VCONN softstart */
rc = smblib_masked_write(chg, TYPE_C_CFG_2_REG,
VCONN_SOFTSTART_CFG_MASK, VCONN_SOFTSTART_CFG_MASK);
if (rc < 0) {
dev_err(chg->dev, "Couldn't increase VCONN softstart rc=%d\n",
rc);
return rc;
}
/* disable try.SINK mode */
rc = smblib_masked_write(chg, TYPE_C_CFG_3_REG, EN_TRYSINK_MODE_BIT, 0);
if (rc < 0) {
dev_err(chg->dev, "Couldn't set TRYSINK_MODE rc=%d\n", rc);
return rc;
}
rc = smblib_masked_write(chg, QNOVO_PT_ENABLE_CMD_REG,
QNOVO_PT_ENABLE_CMD_BIT, QNOVO_PT_ENABLE_CMD_BIT);
if (rc < 0) {
@ -1316,13 +1386,6 @@ static int smb2_init_hw(struct smb2 *chip)
return rc;
}
rc = smblib_validate_initial_typec_legacy_status(chg);
if (rc < 0) {
dev_err(chg->dev, "Couldn't validate typec legacy status rc=%d\n",
rc);
return rc;
}
if (chip->dt.auto_recharge_soc) {
rc = smblib_masked_write(chg, FG_UPDATE_CFG_2_SEL_REG,
SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
@ -1801,6 +1864,22 @@ static int smb2_probe(struct platform_device *pdev)
goto cleanup;
}
/* extcon registration */
chg->extcon = devm_extcon_dev_allocate(chg->dev, smblib_extcon_cable);
if (IS_ERR(chg->extcon)) {
rc = PTR_ERR(chg->extcon);
dev_err(chg->dev, "failed to allocate extcon device rc=%d\n",
rc);
goto cleanup;
}
rc = devm_extcon_dev_register(chg->dev, chg->extcon);
if (rc < 0) {
dev_err(chg->dev, "failed to register extcon device rc=%d\n",
rc);
goto cleanup;
}
rc = smb2_init_dc_psy(chip);
if (rc < 0) {
pr_err("Couldn't initialize dc psy rc=%d\n", rc);

View file

@ -486,10 +486,13 @@ static int try_rerun_apsd_for_hvdcp(struct smb_charger *chg)
* PD_INACTIVE_VOTER on hvdcp_disable_votable indicates whether
* apsd rerun was tried earlier
*/
if (get_client_vote(chg->hvdcp_disable_votable, PD_INACTIVE_VOTER)) {
vote(chg->hvdcp_disable_votable, PD_INACTIVE_VOTER, false, 0);
if (get_client_vote(chg->hvdcp_disable_votable_indirect,
PD_INACTIVE_VOTER)) {
vote(chg->hvdcp_disable_votable_indirect,
PD_INACTIVE_VOTER, false, 0);
/* ensure hvdcp is enabled */
if (!get_effective_result(chg->hvdcp_disable_votable)) {
if (!get_effective_result(
chg->hvdcp_disable_votable_indirect)) {
apsd_result = smblib_get_apsd_result(chg);
if (apsd_result->bit & (QC_2P0_BIT | QC_3P0_BIT)) {
/* rerun APSD */
@ -587,6 +590,40 @@ int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param,
return 0;
}
static void smblib_uusb_removal(struct smb_charger *chg)
{
int rc;
/* reset both usbin current and voltage votes */
vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0);
vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0);
/* reset taper_end voter here */
vote(chg->pl_disable_votable, TAPER_END_VOTER, false, 0);
cancel_delayed_work_sync(&chg->hvdcp_detect_work);
/* reset AUTH_IRQ_EN_CFG_BIT */
rc = smblib_masked_write(chg, USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT);
if (rc < 0)
smblib_err(chg, "Couldn't enable QC auth setting rc=%d\n", rc);
/* reconfigure allowed voltage for HVDCP */
rc = smblib_write(chg, USBIN_ADAPTER_ALLOW_CFG_REG,
USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V);
if (rc < 0)
smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n",
rc);
chg->voltage_min_uv = MICRO_5V;
chg->voltage_max_uv = MICRO_5V;
/* clear USB ICL vote for USB_PSY_VOTER */
rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
if (rc < 0)
smblib_err(chg, "Couldn't un-vote for USB ICL rc=%d\n", rc);
}
/*********************
* VOTABLE CALLBACKS *
*********************/
@ -898,9 +935,9 @@ static int smblib_pl_enable_indirect_vote_callback(struct votable *votable,
return 0;
}
static int smblib_hvdcp_disable_vote_callback(struct votable *votable,
static int smblib_hvdcp_enable_vote_callback(struct votable *votable,
void *data,
int hvdcp_disable, const char *client)
int hvdcp_enable, const char *client)
{
struct smb_charger *chg = data;
int rc;
@ -912,7 +949,7 @@ static int smblib_hvdcp_disable_vote_callback(struct votable *votable,
* This ensures only qc 2.0 detection runs but no vbus
* negotiation happens.
*/
if (hvdcp_disable)
if (!hvdcp_enable)
val = HVDCP_EN_BIT;
rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
@ -922,13 +959,24 @@ static int smblib_hvdcp_disable_vote_callback(struct votable *votable,
val);
if (rc < 0) {
smblib_err(chg, "Couldn't %s hvdcp rc=%d\n",
hvdcp_disable ? "disable" : "enable", rc);
hvdcp_enable ? "enable" : "disable", rc);
return rc;
}
return 0;
}
static int smblib_hvdcp_disable_indirect_vote_callback(struct votable *votable,
void *data, int hvdcp_disable, const char *client)
{
struct smb_charger *chg = data;
vote(chg->hvdcp_enable_votable, HVDCP_INDIRECT_VOTER,
!hvdcp_disable, 0);
return 0;
}
static int smblib_apsd_disable_vote_callback(struct votable *votable,
void *data,
int apsd_disable, const char *client)
@ -2420,7 +2468,7 @@ irqreturn_t smblib_handle_usb_plugin(int irq, void *data)
/* fetch the DPDM regulator */
if (!chg->dpdm_reg && of_get_property(chg->dev->of_node,
"dpdm-supply", NULL)) {
"dpdm-supply", NULL)) {
chg->dpdm_reg = devm_regulator_get(chg->dev, "dpdm");
if (IS_ERR(chg->dpdm_reg)) {
smblib_err(chg, "Couldn't get dpdm regulator rc=%ld\n",
@ -2449,11 +2497,17 @@ irqreturn_t smblib_handle_usb_plugin(int irq, void *data)
smblib_err(chg, "Couldn't disable dpdm regulator rc=%d\n",
rc);
}
if (chg->micro_usb_mode) {
smblib_update_usb_type(chg);
extcon_set_cable_state_(chg->extcon, EXTCON_USB, false);
smblib_uusb_removal(chg);
}
}
power_supply_changed(chg->usb_psy);
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s %s\n",
irq_data->name, vbus_rising ? "attached" : "detached");
irq_data->name, vbus_rising ? "attached" : "detached");
return IRQ_HANDLED;
}
@ -2633,6 +2687,9 @@ static void smblib_handle_apsd_done(struct smb_charger *chg, bool rising)
switch (apsd_result->bit) {
case SDP_CHARGER_BIT:
case CDP_CHARGER_BIT:
if (chg->micro_usb_mode)
extcon_set_cable_state_(chg->extcon, EXTCON_USB,
true);
case OCP_CHARGER_BIT:
case FLOAT_CHARGER_BIT:
/* if not DCP then no hvdcp timeout happens. Enable pd here */
@ -2763,10 +2820,10 @@ static void smblib_handle_typec_removal(struct smb_charger *chg)
vote(chg->pd_disallowed_votable_indirect, VBUS_CC_SHORT_VOTER, true, 0);
/* reset votes from vbus_cc_short */
vote(chg->hvdcp_disable_votable, VBUS_CC_SHORT_VOTER, true, 0);
vote(chg->hvdcp_disable_votable, PD_INACTIVE_VOTER, true, 0);
vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER,
true, 0);
vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER,
true, 0);
/*
* cable could be removed during hard reset, remove its vote to
* disable apsd
@ -2808,7 +2865,8 @@ static void smblib_handle_typec_insertion(struct smb_charger *chg,
}
}
vote(chg->hvdcp_disable_votable, VBUS_CC_SHORT_VOTER, vbus_cc_short, 0);
vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER,
vbus_cc_short, 0);
vote(chg->pd_disallowed_votable_indirect, VBUS_CC_SHORT_VOTER,
vbus_cc_short, 0);
}
@ -2851,6 +2909,26 @@ static void smblib_handle_typec_debounce_done(struct smb_charger *chg,
smblib_typec_mode_name[pval.intval]);
}
irqreturn_t smblib_handle_usb_typec_change_for_uusb(struct smb_charger *chg)
{
int rc;
u8 stat;
rc = smblib_read(chg, TYPE_C_STATUS_3_REG, &stat);
if (rc < 0) {
smblib_err(chg, "Couldn't read TYPE_C_STATUS_3 rc=%d\n", rc);
return IRQ_HANDLED;
}
smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_3 = 0x%02x OTG=%d\n",
stat, !!(stat & (U_USB_GND_NOVBUS_BIT | U_USB_GND_BIT)));
extcon_set_cable_state_(chg->extcon, EXTCON_USB_HOST,
!!(stat & (U_USB_GND_NOVBUS_BIT | U_USB_GND_BIT)));
power_supply_changed(chg->usb_psy);
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_usb_typec_change(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
@ -2859,6 +2937,9 @@ irqreturn_t smblib_handle_usb_typec_change(int irq, void *data)
u8 stat4, stat5;
bool debounce_done, sink_attached, legacy_cable;
if (chg->micro_usb_mode)
return smblib_handle_usb_typec_change_for_uusb(chg);
/* WA - not when PD hard_reset WIP on cc2 in sink mode */
if (chg->cc2_sink_detach_flag == CC2_SINK_STD)
return IRQ_HANDLED;
@ -2884,7 +2965,7 @@ irqreturn_t smblib_handle_usb_typec_change(int irq, void *data)
if (stat4 & TYPEC_VBUS_ERROR_STATUS_BIT)
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s vbus-error\n",
irq_data->name);
irq_data->name);
power_supply_changed(chg->usb_psy);
smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat4);
@ -3234,12 +3315,22 @@ static int smblib_create_votables(struct smb_charger *chg)
return rc;
}
chg->hvdcp_disable_votable = create_votable("HVDCP_DISABLE",
chg->hvdcp_disable_votable_indirect = create_votable(
"HVDCP_DISABLE_INDIRECT",
VOTE_SET_ANY,
smblib_hvdcp_disable_indirect_vote_callback,
chg);
if (IS_ERR(chg->hvdcp_disable_votable_indirect)) {
rc = PTR_ERR(chg->hvdcp_disable_votable_indirect);
return rc;
}
chg->hvdcp_enable_votable = create_votable("HVDCP_ENABLE",
VOTE_SET_ANY,
smblib_hvdcp_disable_vote_callback,
smblib_hvdcp_enable_vote_callback,
chg);
if (IS_ERR(chg->hvdcp_disable_votable)) {
rc = PTR_ERR(chg->hvdcp_disable_votable);
if (IS_ERR(chg->hvdcp_enable_votable)) {
rc = PTR_ERR(chg->hvdcp_enable_votable);
return rc;
}

View file

@ -16,6 +16,7 @@
#include <linux/irqreturn.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/consumer.h>
#include <linux/extcon.h>
#include "storm-watch.h"
enum print_reason {
@ -48,6 +49,8 @@ enum print_reason {
#define LEGACY_CABLE_VOTER "LEGACY_CABLE_VOTER"
#define PD_INACTIVE_VOTER "PD_INACTIVE_VOTER"
#define BOOST_BACK_VOTER "BOOST_BACK_VOTER"
#define HVDCP_INDIRECT_VOTER "HVDCP_INDIRECT_VOTER"
#define MICRO_USB_VOTER "MICRO_USB_VOTER"
enum smb_mode {
PARALLEL_MASTER = 0,
@ -68,6 +71,12 @@ enum {
TYPEC_CC2_REMOVAL_WA_BIT = BIT(2),
};
static const unsigned int smblib_extcon_cable[] = {
EXTCON_USB,
EXTCON_USB_HOST,
EXTCON_NONE,
};
struct smb_regulator {
struct regulator_dev *rdev;
struct regulator_desc rdesc;
@ -181,7 +190,8 @@ struct smb_charger {
struct votable *pl_disable_votable;
struct votable *chg_disable_votable;
struct votable *pl_enable_votable_indirect;
struct votable *hvdcp_disable_votable;
struct votable *hvdcp_disable_votable_indirect;
struct votable *hvdcp_enable_votable;
struct votable *apsd_disable_votable;
/* work */
@ -213,12 +223,16 @@ struct smb_charger {
bool step_chg_enabled;
bool is_hdc;
bool chg_done;
bool micro_usb_mode;
int input_limited_fcc_ua;
/* workaround flag */
u32 wa_flags;
enum cc2_sink_type cc2_sink_detach_flag;
int boost_current_ua;
/* extcon for VBUS / ID notification to USB for uUSB */
struct extcon_dev *extcon;
};
int smblib_read(struct smb_charger *chg, u16 addr, u8 *val);

View file

@ -494,9 +494,9 @@ enum {
#define TYPE_C_STATUS_3_REG (USBIN_BASE + 0x0D)
#define ENABLE_BANDGAP_BIT BIT(7)
#define U_USB_GROUND_NOVBUS_BIT BIT(6)
#define U_USB_GND_NOVBUS_BIT BIT(6)
#define U_USB_FLOAT_NOVBUS_BIT BIT(5)
#define U_USB_GROUND_BIT BIT(4)
#define U_USB_GND_BIT BIT(4)
#define U_USB_FMB1_BIT BIT(3)
#define U_USB_FLOAT1_BIT BIT(2)
#define U_USB_FMB2_BIT BIT(1)