From 69ea8b0c64ef3f19399ada33f719d6bb924189a3 Mon Sep 17 00:00:00 2001 From: Nicholas Troast Date: Wed, 5 Oct 2016 13:34:03 -0700 Subject: [PATCH 01/11] ARM: dts: msm: enable parallel battery current IIO for msmcobalt The SMB138X TADC can provide the parallel charger with the measured battery current. Enable it. Change-Id: I32e5e5ed4ab3f09635f4fe512a1e9b75436ab88e Signed-off-by: Nicholas Troast --- arch/arm/boot/dts/qcom/msmcobalt-mtp.dtsi | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/arm/boot/dts/qcom/msmcobalt-mtp.dtsi b/arch/arm/boot/dts/qcom/msmcobalt-mtp.dtsi index f9bb6e512d33..7948dc3489cb 100644 --- a/arch/arm/boot/dts/qcom/msmcobalt-mtp.dtsi +++ b/arch/arm/boot/dts/qcom/msmcobalt-mtp.dtsi @@ -289,9 +289,11 @@ reg = <0x1000 0x700>; io-channels = <&smb138x_tadc 2>, - <&smb138x_tadc 12>; + <&smb138x_tadc 12>, + <&smb138x_tadc 3>; io-channel-names = "charger_temp", - "charger_temp_max"; + "charger_temp_max", + "batt_i"; }; }; }; From 3de357133d6fc4761d55b82786c0abae1ad51b6e Mon Sep 17 00:00:00 2001 From: Nicholas Troast Date: Fri, 23 Sep 2016 11:23:23 -0700 Subject: [PATCH 02/11] smb-lib: set ICL to 900mA when USB 3.0 has enumerated 900mA can be drawn from a USB 3.0 port that has been enumerated. Do it. Change-Id: I94e5d36feb3cd54bcc46e8c56933340cad839c1f Signed-off-by: Nicholas Troast Signed-off-by: Abhijeet Dharmapurikar --- drivers/power/qcom-charger/smb-lib.c | 64 +++++++++++++++++++--------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index ce76260be6f6..d6479d62e498 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -601,38 +601,64 @@ static int smblib_fv_vote_callback(struct votable *votable, void *data, return 0; } -#define USBIN_25MA 25000 -#define USBIN_100MA 100000 +#define USBIN_25MA 25000 +#define USBIN_100MA 100000 +#define USBIN_150MA 150000 +#define USBIN_500MA 500000 +#define USBIN_900MA 900000 static int smblib_usb_icl_vote_callback(struct votable *votable, void *data, int icl_ua, const char *client) { struct smb_charger *chg = data; int rc = 0; - bool suspend; + bool suspend = (icl_ua < USBIN_25MA); + u8 icl_options = 0; - if (icl_ua < 0) { - smblib_dbg(chg, PR_MISC, "No Voter hence suspending\n"); - icl_ua = 0; + if (suspend) + goto out; + + if (chg->usb_psy_desc.type != POWER_SUPPLY_TYPE_USB) { + rc = smblib_set_charge_param(chg, &chg->param.usb_icl, icl_ua); + if (rc < 0) { + dev_err(chg->dev, "Couldn't set HC ICL rc=%d\n", rc); + return rc; + } + + goto out; } - suspend = (icl_ua < USBIN_25MA); - if (suspend) - goto suspend; - - if (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB) - rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG, - USB51_MODE_BIT, - (icl_ua > USBIN_100MA) ? USB51_MODE_BIT : 0); - else - rc = smblib_set_charge_param(chg, &chg->param.usb_icl, icl_ua); + /* power source is SDP */ + switch (icl_ua) { + case USBIN_100MA: + /* USB 2.0 100mA */ + icl_options = 0; + break; + case USBIN_150MA: + /* USB 3.0 150mA */ + icl_options = CFG_USB3P0_SEL_BIT; + break; + case USBIN_500MA: + /* USB 2.0 500mA */ + icl_options = USB51_MODE_BIT; + break; + case USBIN_900MA: + /* USB 3.0 900mA */ + icl_options = CFG_USB3P0_SEL_BIT | USB51_MODE_BIT; + break; + default: + dev_err(chg->dev, "ICL %duA isn't supported for SDP\n", icl_ua); + icl_options = 0; + break; + } +out: + rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG, + CFG_USB3P0_SEL_BIT | USB51_MODE_BIT, icl_options); if (rc < 0) { - dev_err(chg->dev, - "Couldn't set USB input current limit rc=%d\n", rc); + dev_err(chg->dev, "Couldn't set ICL opetions rc=%d\n", rc); return rc; } -suspend: rc = vote(chg->usb_suspend_votable, PD_VOTER, suspend, 0); if (rc < 0) { dev_err(chg->dev, "Couldn't %s input rc=%d\n", From bb659da62a697316f567ce32afa3580424cd82f9 Mon Sep 17 00:00:00 2001 From: Nicholas Troast Date: Mon, 10 Oct 2016 19:27:32 -0700 Subject: [PATCH 03/11] power_supply: add PARALLEL_PERCENT property The PARALLEL_PERCENT property is used for the parallel distribution percentage. Change-Id: I7d2a7a775437770075ddbd2d1a36ce5d9e44a631 Signed-off-by: Nicholas Troast --- drivers/power/power_supply_sysfs.c | 1 + include/linux/power_supply.h | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 0619b314b7de..029ce2703dc0 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -273,6 +273,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(charger_temp), POWER_SUPPLY_ATTR(charger_temp_max), POWER_SUPPLY_ATTR(parallel_disable), + POWER_SUPPLY_ATTR(parallel_percent), /* Local extensions of type int64_t */ POWER_SUPPLY_ATTR(charge_counter_ext), /* Properties of type `const char *' */ diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 218cd875ee5a..cd559848070f 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -222,6 +222,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_CHARGER_TEMP, POWER_SUPPLY_PROP_CHARGER_TEMP_MAX, POWER_SUPPLY_PROP_PARALLEL_DISABLE, + POWER_SUPPLY_PROP_PARALLEL_PERCENT, /* Local extensions of type int64_t */ POWER_SUPPLY_PROP_CHARGE_COUNTER_EXT, /* Properties of type `const char *' */ From 257eedaa90a5c5f82cd654781f71c6cc87649bf3 Mon Sep 17 00:00:00 2001 From: Nicholas Troast Date: Wed, 21 Sep 2016 17:51:00 -0700 Subject: [PATCH 04/11] qpnp-smb2: add debugfs entries to force power supply updates It is useful when debugging to force power supply changed events. Add debugfs entries for each power supply to allow forcing power supply changed events. Change-Id: I018eb018de2a8119acc09824c82cca8ff1b52faa Signed-off-by: Nicholas Troast --- drivers/power/qcom-charger/qpnp-smb2.c | 81 ++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index 8aaeb095db3c..bb12e74fe8bd 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -10,6 +10,7 @@ * GNU General Public License for more details. */ +#include #include #include #include @@ -219,9 +220,10 @@ struct smb_dt_props { }; struct smb2 { - struct smb_charger chg; - struct smb_dt_props dt; - bool bad_part; + struct smb_charger chg; + struct dentry *dfs_root; + struct smb_dt_props dt; + bool bad_part; }; static int __debug_mask; @@ -1438,9 +1440,74 @@ static int smb2_request_interrupts(struct smb2 *chip) return rc; } -/********* - * PROBE * - *********/ +#if defined(CONFIG_DEBUG_FS) + +static int force_batt_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->batt_psy); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(force_batt_psy_update_ops, NULL, + force_batt_psy_update_write, "0x%02llx\n"); + +static int force_usb_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->usb_psy); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(force_usb_psy_update_ops, NULL, + force_usb_psy_update_write, "0x%02llx\n"); + +static int force_dc_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->dc_psy); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(force_dc_psy_update_ops, NULL, + force_dc_psy_update_write, "0x%02llx\n"); + +static void smb2_create_debugfs(struct smb2 *chip) +{ + struct dentry *file; + + chip->dfs_root = debugfs_create_dir("charger", NULL); + if (IS_ERR_OR_NULL(chip->dfs_root)) { + pr_err("Couldn't create charger debugfs rc=%ld\n", + (long)chip->dfs_root); + return; + } + + file = debugfs_create_file("force_batt_psy_update", S_IRUSR | S_IWUSR, + chip->dfs_root, chip, &force_batt_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_batt_psy_update file rc=%ld\n", + (long)file); + + file = debugfs_create_file("force_usb_psy_update", S_IRUSR | S_IWUSR, + chip->dfs_root, chip, &force_usb_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_usb_psy_update file rc=%ld\n", + (long)file); + + file = debugfs_create_file("force_dc_psy_update", S_IRUSR | S_IWUSR, + chip->dfs_root, chip, &force_dc_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_dc_psy_update file rc=%ld\n", + (long)file); +} + +#else + +static void smb2_create_debugfs(struct smb2 *chip) +{} + +#endif static int smb2_probe(struct platform_device *pdev) { @@ -1552,6 +1619,8 @@ static int smb2_probe(struct platform_device *pdev) goto cleanup; } + smb2_create_debugfs(chip); + pr_info("QPNP SMB2 probed successfully\n"); return rc; From 9fde5f583073dd842d7beff5702e0b34c6bb7920 Mon Sep 17 00:00:00 2001 From: Nicholas Troast Date: Wed, 20 Jul 2016 16:03:15 -0700 Subject: [PATCH 05/11] qcom-charger: qpnp-smb2: configure float options from DT When a float charger is detected by APSD there are configuration options that change the behavior of the charger. These options should be configurable from the device tree. Add a DT parameter "qcom,float-option" and configure it as follows: 1 - Treat as a DCP 2 - Treat as a SDP 3 - Disable charging 4 - Suspend USB input Change-Id: Ia9219bc232f2092569dfb1a14f628e788173c4ff Signed-off-by: Nicholas Troast --- .../bindings/power/qcom-charger/qpnp-smb2.txt | 10 ++++++ drivers/power/qcom-charger/qpnp-smb2.c | 36 +++++++++++++++++++ drivers/power/qcom-charger/smb-reg.h | 1 + 3 files changed, 47 insertions(+) diff --git a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt index 12ac75a8608c..25404f4f4066 100644 --- a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt +++ b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt @@ -110,6 +110,16 @@ Charger specific properties: will use io-channel-names to match IIO input names with IIO specifiers. +- qcom,float-option + Usage: optional + Value type: + Definition: Configures how the charger behaves when a float charger is + detected by APSD + 1 - Treat as a DCP + 2 - Treat as a SDP + 3 - Disable charging + 4 - Suspend USB input + ============================================= Second Level Nodes - SMB2 Charger Peripherals ============================================= diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index bb12e74fe8bd..2b2847230b9f 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -217,6 +217,7 @@ struct smb_dt_props { u32 step_soc_threshold[STEP_CHARGING_MAX_STEPS - 1]; s32 step_cc_delta[STEP_CHARGING_MAX_STEPS]; struct device_node *revid_dev_node; + int float_option; }; struct smb2 { @@ -322,6 +323,12 @@ static int smb2_parse_dt(struct smb2 *chip) } } + of_property_read_u32(node, "qcom,float-option", &chip->dt.float_option); + if (chip->dt.float_option < 0 || chip->dt.float_option > 4) { + pr_err("qcom,float-option is out of range [0, 4]\n"); + return -EINVAL; + } + return 0; } @@ -1121,6 +1128,35 @@ static int smb2_init_hw(struct smb2 *chip) return rc; } + /* configure float charger options */ + switch (chip->dt.float_option) { + case 1: + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FLOAT_OPTIONS_MASK, 0); + break; + case 2: + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FLOAT_OPTIONS_MASK, FORCE_FLOAT_SDP_CFG_BIT); + break; + case 3: + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FLOAT_OPTIONS_MASK, FLOAT_DIS_CHGING_CFG_BIT); + break; + case 4: + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FLOAT_OPTIONS_MASK, SUSPEND_FLOAT_CFG_BIT); + break; + default: + rc = 0; + break; + } + + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure float charger options rc=%d\n", + rc); + return rc; + } + return rc; } diff --git a/drivers/power/qcom-charger/smb-reg.h b/drivers/power/qcom-charger/smb-reg.h index 8a49a8fb38ba..91d9ab08c90f 100644 --- a/drivers/power/qcom-charger/smb-reg.h +++ b/drivers/power/qcom-charger/smb-reg.h @@ -585,6 +585,7 @@ enum { #define DCD_TIMEOUT_SEL_BIT BIT(5) #define OCD_CURRENT_SEL_BIT BIT(4) #define SLOW_PLUGIN_TIMER_EN_CFG_BIT BIT(3) +#define FLOAT_OPTIONS_MASK GENMASK(2, 0) #define FLOAT_DIS_CHGING_CFG_BIT BIT(2) #define SUSPEND_FLOAT_CFG_BIT BIT(1) #define FORCE_FLOAT_SDP_CFG_BIT BIT(0) From 665d020cda7c25577c52b0d979b010dc96c09867 Mon Sep 17 00:00:00 2001 From: Abhijeet Dharmapurikar Date: Mon, 10 Oct 2016 16:43:43 -0700 Subject: [PATCH 06/11] qpnp-smb2: Disable hvdcp based on user configuration Some platforms do not support HVDCP charging. Provide means to disable them. Provide a votable for disabling HVDCP in preparation to handle VBUS and CC line shorted situation. Note that when hvdcp is disabled, we only disable voltage negotiations i.e. qc 2.0 detection remains enabled since it does not need voltage to change. Change-Id: Id7eaa46f08ac451a918a550f7837efbef78ab6f6 Signed-off-by: Abhijeet Dharmapurikar --- .../bindings/power/qcom-charger/qpnp-smb2.txt | 10 +- drivers/power/qcom-charger/qpnp-smb2.c | 6 + drivers/power/qcom-charger/smb-lib.c | 125 +++++++++++++++--- drivers/power/qcom-charger/smb-lib.h | 1 + 4 files changed, 123 insertions(+), 19 deletions(-) diff --git a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt index 25404f4f4066..82386ba9b082 100644 --- a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt +++ b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-smb2.txt @@ -120,6 +120,15 @@ Charger specific properties: 3 - Disable charging 4 - Suspend USB input +- qcom,hvdcp-disable + Usage: optional + Value type: + Definition: Specifies if hvdcp charging is to be enabled or not. + If this property is not specified hvdcp will be enabled. + If this property is specified, hvdcp 2.0 detection will still + happen but the adapter won't be asked to switch to a higher + voltage point. + ============================================= Second Level Nodes - SMB2 Charger Peripherals ============================================= @@ -153,7 +162,6 @@ pmicobalt_charger: qcom,qpnp-smb2 { io-channels = <&pmic_rradc 0>; io-channel-names = "rradc_batt_id"; - qcom,suspend-input; dpdm-supply = <&qusb_phy0>; qcom,step-soc-thresholds = <60 70 80 90>; diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index 2b2847230b9f..9340b98dc883 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -218,6 +218,7 @@ struct smb_dt_props { s32 step_cc_delta[STEP_CHARGING_MAX_STEPS]; struct device_node *revid_dev_node; int float_option; + bool hvdcp_disable; }; struct smb2 { @@ -329,6 +330,9 @@ static int smb2_parse_dt(struct smb2 *chip) return -EINVAL; } + chip->dt.hvdcp_disable = of_property_read_bool(node, + "qcom,hvdcp-disable"); + return 0; } @@ -1031,6 +1035,8 @@ static int smb2_init_hw(struct smb2 *chip) DEFAULT_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, + chip->dt.hvdcp_disable, 0); /* Configure charge enable for software control; active high */ rc = smblib_masked_write(chg, CHGR_CFG2_REG, diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index d6479d62e498..b9f8202e0a9e 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -208,47 +208,97 @@ struct apsd_result { const enum power_supply_type pst; }; +enum { + UNKNOWN, + SDP, + CDP, + DCP, + OCP, + FLOAT, + HVDCP2, + HVDCP3, + MAX_TYPES +}; + static const struct apsd_result const smblib_apsd_results[] = { - {"UNKNOWN", 0, POWER_SUPPLY_TYPE_UNKNOWN}, - {"SDP", SDP_CHARGER_BIT, POWER_SUPPLY_TYPE_USB}, - {"CDP", CDP_CHARGER_BIT, POWER_SUPPLY_TYPE_USB_CDP}, - {"DCP", DCP_CHARGER_BIT, POWER_SUPPLY_TYPE_USB_DCP}, - {"OCP", OCP_CHARGER_BIT, POWER_SUPPLY_TYPE_USB_DCP}, - {"FLOAT", FLOAT_CHARGER_BIT, POWER_SUPPLY_TYPE_USB_DCP}, - {"HVDCP2", DCP_CHARGER_BIT | QC_2P0_BIT, POWER_SUPPLY_TYPE_USB_HVDCP}, - {"HVDCP3", DCP_CHARGER_BIT | QC_3P0_BIT, POWER_SUPPLY_TYPE_USB_HVDCP_3}, + [UNKNOWN] = { + .name = "UNKNOWN", + .bit = 0, + .pst = POWER_SUPPLY_TYPE_UNKNOWN + }, + [SDP] = { + .name = "SDP", + .bit = SDP_CHARGER_BIT, + .pst = POWER_SUPPLY_TYPE_USB + }, + [CDP] = { + .name = "CDP", + .bit = CDP_CHARGER_BIT, + .pst = POWER_SUPPLY_TYPE_USB_CDP + }, + [DCP] = { + .name = "DCP", + .bit = DCP_CHARGER_BIT, + .pst = POWER_SUPPLY_TYPE_USB_DCP + }, + [OCP] = { + .name = "OCP", + .bit = OCP_CHARGER_BIT, + .pst = POWER_SUPPLY_TYPE_USB_DCP + }, + [FLOAT] = { + .name = "FLOAT", + .bit = FLOAT_CHARGER_BIT, + .pst = POWER_SUPPLY_TYPE_USB_DCP + }, + [HVDCP2] = { + .name = "HVDCP2", + .bit = DCP_CHARGER_BIT | QC_2P0_BIT, + .pst = POWER_SUPPLY_TYPE_USB_HVDCP + }, + [HVDCP3] = { + .name = "HVDCP3", + .bit = DCP_CHARGER_BIT | QC_3P0_BIT, + .pst = POWER_SUPPLY_TYPE_USB_HVDCP_3, + }, }; static const struct apsd_result *smblib_get_apsd_result(struct smb_charger *chg) { int rc, i; - u8 stat; + u8 apsd_stat, stat; + const struct apsd_result *result = &smblib_apsd_results[UNKNOWN]; - rc = smblib_read(chg, APSD_STATUS_REG, &stat); + rc = smblib_read(chg, APSD_STATUS_REG, &apsd_stat); if (rc < 0) { dev_err(chg->dev, "Couldn't read APSD_STATUS rc=%d\n", rc); - return &smblib_apsd_results[0]; + return result; } - smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", stat); + smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", apsd_stat); - if (!(stat & APSD_DTC_STATUS_DONE_BIT)) - return &smblib_apsd_results[0]; + if (!(apsd_stat & APSD_DTC_STATUS_DONE_BIT)) + return result; rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat); if (rc < 0) { dev_err(chg->dev, "Couldn't read APSD_RESULT_STATUS rc=%d\n", rc); - return &smblib_apsd_results[0]; + return result; } stat &= APSD_RESULT_STATUS_MASK; for (i = 0; i < ARRAY_SIZE(smblib_apsd_results); i++) { if (smblib_apsd_results[i].bit == stat) - return &smblib_apsd_results[i]; + result = &smblib_apsd_results[i]; } - dev_err(chg->dev, "Couldn't find an APSD result for 0x%02x\n", stat); - return &smblib_apsd_results[0]; + if (apsd_stat & QC_CHARGER_BIT) { + /* since its a qc_charger, either return HVDCP3 or HVDCP2 */ + if (result != &smblib_apsd_results[HVDCP3]) + result = &smblib_apsd_results[HVDCP2]; + } + + return result; } @@ -794,6 +844,37 @@ static int smblib_pl_enable_indirect_vote_callback(struct votable *votable, return 0; } +static int smblib_hvdcp_disable_vote_callback(struct votable *votable, + void *data, + int hvdcp_disable, const char *client) +{ + struct smb_charger *chg = data; + int rc; + u8 val = HVDCP_AUTH_ALG_EN_CFG_BIT + | HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT | HVDCP_EN_BIT; + + /* + * Disable the autonomous bit and auth bit for disabling hvdcp. + * This ensures only qc 2.0 detection runs but no vbus + * negotiation happens. + */ + if (hvdcp_disable) + val = HVDCP_EN_BIT; + + rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + HVDCP_EN_BIT + | HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT + | HVDCP_AUTH_ALG_EN_CFG_BIT, + val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't %s hvdcp rc=%d\n", + hvdcp_disable ? "disable" : "enable", rc); + return rc; + } + + return 0; +} + /***************** * OTG REGULATOR * *****************/ @@ -2455,6 +2536,14 @@ static int smblib_create_votables(struct smb_charger *chg) return rc; } + chg->hvdcp_disable_votable = create_votable("HVDCP_DISABLE", + VOTE_SET_ANY, + smblib_hvdcp_disable_vote_callback, + chg); + if (IS_ERR(chg->hvdcp_disable_votable)) { + rc = PTR_ERR(chg->hvdcp_disable_votable); + return rc; + } return rc; } diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index 00975e6c1285..e2a525a525b1 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -149,6 +149,7 @@ struct smb_charger { struct votable *pl_disable_votable; struct votable *chg_disable_votable; struct votable *pl_enable_votable_indirect; + struct votable *hvdcp_disable_votable; /* work */ struct work_struct bms_update_work; From b9bcc03fb1f87e36732aaeaeb6730bc932ea8d9f Mon Sep 17 00:00:00 2001 From: Abhijeet Dharmapurikar Date: Wed, 5 Oct 2016 15:15:10 -0700 Subject: [PATCH 07/11] qcom-charger: Allow PD for hvdcp Currently the code prevents pd if an hvdcp is detected. We want to change that in that we want to prevent pd only while hvdcp detection is in progress. Once completed, we need to enable pd even if an hvdcp is detected. Enabling PD even for hvdcp allows us to detect a QC 4.0 which communicates over PD. Currently we model the pd_allowed votable as an enable votable i.e. if any one of the voters enable it, pd_allowed gets enabled. Change that to a disable votable named pd_disallowed_votable, which gets disabled when CC is detached, and hvdcp2.0 detection is not yet completed. Change-Id: I2ae261c71694695b41d8a31c4b40542d5e227c3a Signed-off-by: Abhijeet Dharmapurikar --- drivers/power/qcom-charger/qpnp-smb2.c | 4 ++ drivers/power/qcom-charger/smb-lib.c | 66 ++++++++++++++++++-------- drivers/power/qcom-charger/smb-lib.h | 4 ++ 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index 9340b98dc883..b7369a300e95 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -1037,6 +1037,10 @@ static int smb2_init_hw(struct smb2 *chip) DEFAULT_VOTER, true, chip->dt.dc_icl_ua); vote(chg->hvdcp_disable_votable, DEFAULT_VOTER, chip->dt.hvdcp_disable, 0); + vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, + true, 0); + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + true, 0); /* Configure charge enable for software control; active high */ rc = smblib_masked_write(chg, CHGR_CFG2_REG, diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index b9f8202e0a9e..3e4d39303641 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -442,7 +442,7 @@ static int smblib_update_usb_type(struct smb_charger *chg) return rc; } -static int smblib_detach_usb(struct smb_charger *chg) +static int smblib_detach_typec(struct smb_charger *chg) { int rc; @@ -469,7 +469,9 @@ static int smblib_detach_usb(struct smb_charger *chg) return rc; } - vote(chg->pd_allowed_votable, DEFAULT_VOTER, false, 0); + /* cc removed, disable pd_allowed */ + vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, true, 0); + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, true, 0); return rc; } @@ -777,6 +779,18 @@ suspend: return rc; } +static int smblib_pd_disallowed_votable_indirect_callback( + struct votable *votable, void *data, int disallowed, const char *client) +{ + struct smb_charger *chg = data; + int rc; + + rc = vote(chg->pd_allowed_votable, PD_DISALLOWED_INDIRECT_VOTER, + !disallowed, 0); + + return rc; +} + static int smblib_awake_vote_callback(struct votable *votable, void *data, int awake, const char *client) { @@ -2154,10 +2168,10 @@ static void smblib_handle_hvdcp_3p0_auth_done(struct smb_charger *chg, static void smblib_handle_hvdcp_check_timeout(struct smb_charger *chg, bool rising, bool qc_charger) { - if (rising && !qc_charger) { - vote(chg->pd_allowed_votable, DEFAULT_VOTER, true, 0); - power_supply_changed(chg->usb_psy); - } + /* Hold off PD only until hvdcp 2.0 detection timeout */ + if (rising) + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + false, 0); smblib_dbg(chg, PR_INTERRUPT, "IRQ: smblib_handle_hvdcp_check_timeout %s\n", rising ? "rising" : "falling"); @@ -2191,7 +2205,9 @@ static void smblib_handle_apsd_done(struct smb_charger *chg, bool rising) case CDP_CHARGER_BIT: case OCP_CHARGER_BIT: case FLOAT_CHARGER_BIT: - vote(chg->pd_allowed_votable, DEFAULT_VOTER, true, 0); + /* if not DCP then no hvdcp timeout happens. Enable pd here */ + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + false, 0); break; case DCP_CHARGER_BIT: if (chg->wa_flags & QC_CHARGER_DETECTION_WA_BIT) @@ -2256,7 +2272,7 @@ static void smblib_handle_typec_cc(struct smb_charger *chg, bool attached) int rc; if (!attached) { - rc = smblib_detach_usb(chg); + rc = smblib_detach_typec(chg); if (rc < 0) dev_err(chg->dev, "Couldn't detach USB rc=%d\n", rc); } @@ -2266,14 +2282,15 @@ static void smblib_handle_typec_cc(struct smb_charger *chg, bool attached) } static void smblib_handle_typec_debounce_done(struct smb_charger *chg, - bool rising, bool sink_attached) + bool attached, bool rising, bool sink_attached) { int rc; union power_supply_propval pval = {0, }; - /* allow PD for attached sinks */ - if (rising && sink_attached) - vote(chg->pd_allowed_votable, DEFAULT_VOTER, true, 0); + /* allow PD when cc is attached and debounced */ + if (attached && rising) + vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, + false, 0); rc = smblib_get_prop_typec_mode(chg, &pval); if (rc < 0) @@ -2317,6 +2334,7 @@ irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) smblib_handle_typec_cc(chg, (bool)(stat & CC_ATTACHED_BIT)); smblib_handle_typec_debounce_done(chg, + (bool)(stat & CC_ATTACHED_BIT), (bool)(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT), (bool)(stat & UFP_DFP_MODE_STATUS_BIT)); @@ -2348,14 +2366,10 @@ static void smblib_hvdcp_detect_work(struct work_struct *work) { struct smb_charger *chg = container_of(work, struct smb_charger, hvdcp_detect_work.work); - const struct apsd_result *apsd_result; - apsd_result = smblib_get_apsd_result(chg); - if (apsd_result->bit && - !(apsd_result->bit & (QC_2P0_BIT | QC_3P0_BIT))) { - vote(chg->pd_allowed_votable, DEFAULT_VOTER, true, 0); - power_supply_changed(chg->usb_psy); - } + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + false, 0); + power_supply_changed(chg->usb_psy); } static void bms_update_work(struct work_struct *work) @@ -2496,8 +2510,16 @@ static int smblib_create_votables(struct smb_charger *chg) return rc; } - chg->pd_allowed_votable = create_votable("PD_ALLOWED", VOTE_SET_ANY, - NULL, NULL); + chg->pd_disallowed_votable_indirect + = create_votable("PD_DISALLOWED_INDIRECT", VOTE_SET_ANY, + smblib_pd_disallowed_votable_indirect_callback, chg); + if (IS_ERR(chg->pd_disallowed_votable_indirect)) { + rc = PTR_ERR(chg->pd_disallowed_votable_indirect); + return rc; + } + + chg->pd_allowed_votable = create_votable("PD_ALLOWED", + VOTE_SET_ANY, NULL, NULL); if (IS_ERR(chg->pd_allowed_votable)) { rc = PTR_ERR(chg->pd_allowed_votable); return rc; @@ -2563,6 +2585,8 @@ static void smblib_destroy_votables(struct smb_charger *chg) destroy_votable(chg->usb_icl_votable); if (chg->dc_icl_votable) destroy_votable(chg->dc_icl_votable); + if (chg->pd_disallowed_votable_indirect) + destroy_votable(chg->pd_disallowed_votable_indirect); if (chg->pd_allowed_votable) destroy_votable(chg->pd_allowed_votable); if (chg->awake_votable) diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index e2a525a525b1..c6a1203f6bdb 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -37,6 +37,9 @@ enum print_reason { #define TAPER_END_VOTER "TAPER_END_VOTER" #define FCC_MAX_RESULT_VOTER "FCC_MAX_RESULT_VOTER" #define THERMAL_DAEMON_VOTER "THERMAL_DAEMON_VOTER" +#define CC_DETACHED_VOTER "CC_DETACHED_VOTER" +#define HVDCP_TIMEOUT_VOTER "HVDCP_TIMEOUT_VOTER" +#define PD_DISALLOWED_INDIRECT_VOTER "PD_DISALLOWED_INDIRECT_VOTER" enum smb_mode { PARALLEL_MASTER = 0, @@ -144,6 +147,7 @@ struct smb_charger { struct votable *fv_votable; struct votable *usb_icl_votable; struct votable *dc_icl_votable; + struct votable *pd_disallowed_votable_indirect; struct votable *pd_allowed_votable; struct votable *awake_votable; struct votable *pl_disable_votable; From f080134002472e9fb20e331e0a3dddce587458fa Mon Sep 17 00:00:00 2001 From: Abhijeet Dharmapurikar Date: Fri, 7 Oct 2016 18:46:45 -0700 Subject: [PATCH 08/11] qpnp-smb2: support exiting sink based on cc While PD is under hard reset and the device is in sink mode, it shouldn't leave sink mode because VBUS goes away. Instead it should stay in sink mode while hard reset is being conducted and exit sink mode only if CC gets detached. Configure the exit state when hard reset is in progress. Also while hard reset is in progress disable APSD else a APSD could run and disturb SDP communication after VBUS recovers in hard reset procedure. Change-Id: I7f5046d4822438e949658356e683e74acfa664f7 Signed-off-by: Abhijeet Dharmapurikar --- drivers/power/qcom-charger/qpnp-smb2.c | 6 +++ drivers/power/qcom-charger/smb-lib.c | 72 ++++++++++++++++++++++---- drivers/power/qcom-charger/smb-lib.h | 7 ++- drivers/power/qcom-charger/smb-reg.h | 2 +- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index b7369a300e95..b0edacb98edf 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -422,6 +422,9 @@ static int smb2_usb_get_prop(struct power_supply *psy, val->intval = get_client_vote(chg->pl_disable_votable, USER_VOTER); break; + case POWER_SUPPLY_PROP_PD_IN_HARD_RESET: + rc = smblib_get_prop_pd_in_hard_reset(chg, val); + break; default: pr_err("get prop %d is not supported\n", psp); rc = -EINVAL; @@ -469,6 +472,9 @@ static int smb2_usb_set_prop(struct power_supply *psy, case POWER_SUPPLY_PROP_PARALLEL_DISABLE: vote(chg->pl_disable_votable, USER_VOTER, (bool)val->intval, 0); break; + case POWER_SUPPLY_PROP_PD_IN_HARD_RESET: + rc = smblib_set_prop_pd_in_hard_reset(chg, val); + break; default: pr_err("set prop %d is not supported\n", psp); rc = -EINVAL; diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index 3e4d39303641..f5b0c6a21193 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -473,6 +473,8 @@ static int smblib_detach_typec(struct smb_charger *chg) vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, true, 0); vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, true, 0); + vote(chg->apsd_disable_votable, PD_VOTER, false, 0); + return rc; } @@ -889,6 +891,24 @@ static int smblib_hvdcp_disable_vote_callback(struct votable *votable, return 0; } +static int smblib_apsd_disable_vote_callback(struct votable *votable, + void *data, + int apsd_disable, const char *client) +{ + struct smb_charger *chg = data; + int rc; + + rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + AUTO_SRC_DETECT_BIT, + apsd_disable ? 0 : AUTO_SRC_DETECT_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't %s APSD rc=%d\n", + apsd_disable ? "disable" : "enable", rc); + return rc; + } + + return 0; +} /***************** * OTG REGULATOR * *****************/ @@ -1739,6 +1759,22 @@ int smblib_get_prop_input_current_settled(struct smb_charger *chg, return smblib_get_charge_param(chg, &chg->param.icl_stat, &val->intval); } +int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 ctrl; + + rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, &ctrl); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG rc=%d\n", + rc); + return rc; + } + val->intval = ctrl & EXIT_SNK_BASED_ON_CC_BIT; + return 0; +} + /******************* * USB PSY SETTERS * * *****************/ @@ -1838,15 +1874,7 @@ int smblib_set_prop_pd_active(struct smb_charger *chg, return -EINVAL; } - rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, - AUTO_SRC_DETECT_BIT, - val->intval ? 0 : AUTO_SRC_DETECT_BIT); - if (rc < 0) { - dev_err(chg->dev, "Couldn't %s APSD rc=%d\n", - val->intval ? "disable" : "enable", rc); - return rc; - } - + vote(chg->apsd_disable_votable, PD_VOTER, val->intval, 0); vote(chg->pd_allowed_votable, PD_VOTER, val->intval, 0); /* @@ -1888,6 +1916,20 @@ int smblib_set_prop_pd_active(struct smb_charger *chg, return rc; } +int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + EXIT_SNK_BASED_ON_CC_BIT, + (val->intval) ? EXIT_SNK_BASED_ON_CC_BIT : 0); + + vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, val->intval, 0); + + return rc; +} + /************************ * PARALLEL PSY GETTERS * ************************/ @@ -2566,6 +2608,16 @@ static int smblib_create_votables(struct smb_charger *chg) rc = PTR_ERR(chg->hvdcp_disable_votable); return rc; } + + chg->apsd_disable_votable = create_votable("APSD_DISABLE", + VOTE_SET_ANY, + smblib_apsd_disable_vote_callback, + chg); + if (IS_ERR(chg->apsd_disable_votable)) { + rc = PTR_ERR(chg->apsd_disable_votable); + return rc; + } + return rc; } @@ -2597,6 +2649,8 @@ static void smblib_destroy_votables(struct smb_charger *chg) destroy_votable(chg->chg_disable_votable); if (chg->pl_enable_votable_indirect) destroy_votable(chg->pl_enable_votable_indirect); + if (chg->apsd_disable_votable) + destroy_votable(chg->apsd_disable_votable); } static void smblib_iio_deinit(struct smb_charger *chg) diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index c6a1203f6bdb..804c5bbff013 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -40,6 +40,7 @@ enum print_reason { #define CC_DETACHED_VOTER "CC_DETACHED_VOTER" #define HVDCP_TIMEOUT_VOTER "HVDCP_TIMEOUT_VOTER" #define PD_DISALLOWED_INDIRECT_VOTER "PD_DISALLOWED_INDIRECT_VOTER" +#define PD_HARD_RESET_VOTER "PD_HARD_RESET_VOTER" enum smb_mode { PARALLEL_MASTER = 0, @@ -154,6 +155,7 @@ struct smb_charger { struct votable *chg_disable_votable; struct votable *pl_enable_votable_indirect; struct votable *hvdcp_disable_votable; + struct votable *apsd_disable_votable; /* work */ struct work_struct bms_update_work; @@ -294,6 +296,8 @@ int smblib_get_prop_pd_allowed(struct smb_charger *chg, union power_supply_propval *val); int smblib_get_prop_input_current_settled(struct smb_charger *chg, union power_supply_propval *val); +int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg, + union power_supply_propval *val); int smblib_get_prop_charger_temp(struct smb_charger *chg, union power_supply_propval *val); int smblib_get_prop_charger_temp_max(struct smb_charger *chg, @@ -308,6 +312,8 @@ int smblib_set_prop_typec_power_role(struct smb_charger *chg, const union power_supply_propval *val); int smblib_set_prop_pd_active(struct smb_charger *chg, const union power_supply_propval *val); +int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg, + const union power_supply_propval *val); int smblib_get_prop_slave_current_now(struct smb_charger *chg, union power_supply_propval *val); @@ -315,4 +321,3 @@ int smblib_get_prop_slave_current_now(struct smb_charger *chg, int smblib_init(struct smb_charger *chg); int smblib_deinit(struct smb_charger *chg); #endif /* __SMB2_CHARGER_H */ - diff --git a/drivers/power/qcom-charger/smb-reg.h b/drivers/power/qcom-charger/smb-reg.h index 91d9ab08c90f..a499d354086c 100644 --- a/drivers/power/qcom-charger/smb-reg.h +++ b/drivers/power/qcom-charger/smb-reg.h @@ -613,7 +613,7 @@ enum { #define TYPEC_VBUS_ASSERT_INT_EN_BIT BIT(0) #define TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG (USBIN_BASE + 0x68) -#define EXIT_SNK_BASED_ON_CC BIT(7) +#define EXIT_SNK_BASED_ON_CC_BIT BIT(7) #define VCONN_EN_ORIENTATION_BIT BIT(6) #define TYPEC_VCONN_OVERCURR_INT_EN_BIT BIT(5) #define VCONN_EN_SRC_BIT BIT(4) From ebdc69997401fa8f92597cbfe1ff574a8265a930 Mon Sep 17 00:00:00 2001 From: Abhijeet Dharmapurikar Date: Thu, 6 Oct 2016 17:25:01 -0700 Subject: [PATCH 09/11] qpnp-smb2: update to USB_PD type when pd is active Since the USB_PD type is no more set by the PD driver, it is expected that the type be set based on pd_active property being ACTIVE. So set the type and update the userspace when it changes. Change-Id: I0f76f092d3e8de7a916e995e4d825fe0da79bf78 Signed-off-by: Abhijeet Dharmapurikar --- drivers/power/qcom-charger/qpnp-smb2.c | 8 -------- drivers/power/qcom-charger/smb-lib.c | 15 +++++++++------ drivers/power/qcom-charger/smb-lib.h | 2 +- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/drivers/power/qcom-charger/qpnp-smb2.c b/drivers/power/qcom-charger/qpnp-smb2.c index b0edacb98edf..57ab9fe758c7 100644 --- a/drivers/power/qcom-charger/qpnp-smb2.c +++ b/drivers/power/qcom-charger/qpnp-smb2.c @@ -455,14 +455,6 @@ static int smb2_usb_set_prop(struct power_supply *psy, case POWER_SUPPLY_PROP_CURRENT_MAX: rc = smblib_set_prop_usb_current_max(chg, val); break; - case POWER_SUPPLY_PROP_TYPE: - if (chg->pd_active && val->intval == POWER_SUPPLY_TYPE_USB_PD) { - chg->usb_psy_desc.type = val->intval; - } else { - pr_err("set type %d not allowed\n", val->intval); - rc = -EINVAL; - } - break; case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE: rc = smblib_set_prop_typec_power_role(chg, val); break; diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index f5b0c6a21193..bc1b8b2bf03e 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -434,8 +434,10 @@ static int smblib_update_usb_type(struct smb_charger *chg) const struct apsd_result *apsd_result; /* if PD is active, APSD is disabled so won't have a valid result */ - if (chg->pd_active) - return rc; + if (chg->pd_active) { + chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_PD; + return 0; + } apsd_result = smblib_get_apsd_result(chg); chg->usb_psy_desc.type = apsd_result->pst; @@ -1884,11 +1886,10 @@ int smblib_set_prop_pd_active(struct smb_charger *chg, */ if (val->intval) { rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); - if (rc < 0) { - dev_err(chg->dev, - "Couldn't read TYPE_C_STATUS_4 rc=%d\n", + if (rc < 0) { + dev_err(chg->dev, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); - return rc; + return rc; } stat &= CC_ORIENTATION_BIT; @@ -1913,6 +1914,8 @@ int smblib_set_prop_pd_active(struct smb_charger *chg, chg->pd_active = (bool)val->intval; smblib_update_usb_type(chg); + power_supply_changed(chg->usb_psy); + return rc; } diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index 804c5bbff013..fc1c3ba94a15 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -169,7 +169,7 @@ struct smb_charger { /* cached status */ int voltage_min_uv; int voltage_max_uv; - bool pd_active; + int pd_active; bool vbus_present; int system_temp_level; From 9cb924f2ed8a729301291b11412a67c03a644db9 Mon Sep 17 00:00:00 2001 From: Abhijeet Dharmapurikar Date: Thu, 6 Oct 2016 12:59:04 -0700 Subject: [PATCH 10/11] qpnp-smb2: handle shorted vbus and cc lines and legacy cables There could be cables where vbus and cc lines are shorted or connected using a small resistor. Raising VBUS to higher values could cause CC line to get damaged. CC line cannot tolerate a voltage higher than 6V. So if such a cable is seen, prevent pd stack from running so there is no opportunity to increase vbus by selecting a higher voltage profile. Also disable HVDP too. Also, prevent PD_ALLOWED if a legacy cable is seen - the spec mentions that if VBUS is seen along with CC, the adapter is not pd capable. Change-Id: I7411bd541ffe704dda97dd869a3dbd4dbfc99518 Signed-off-by: Abhijeet Dharmapurikar --- drivers/power/qcom-charger/smb-lib.c | 44 ++++++++++++++++++++++++++++ drivers/power/qcom-charger/smb-lib.h | 2 ++ drivers/power/qcom-charger/smb-reg.h | 19 ++++++++++++ 3 files changed, 65 insertions(+) diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index bc1b8b2bf03e..5ed22b226162 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -474,6 +474,11 @@ static int smblib_detach_typec(struct smb_charger *chg) /* cc removed, disable pd_allowed */ 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, LEGACY_CABLE_VOTER, true, 0); + 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->apsd_disable_votable, PD_VOTER, false, 0); @@ -2361,6 +2366,43 @@ static void smblib_handle_typec_debounce_done(struct smb_charger *chg, smblib_typec_mode_name[pval.intval]); } +static void smblib_handle_legacy_cable(struct smb_charger *chg, + bool typec_debounced) +{ + int rc, rp; + u8 stat; + bool legacy_cable; + bool vbus_cc_short = false; + + if (!typec_debounced) + return; + + rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", + rc); + return; + } + + legacy_cable = stat & TYPEC_LEGACY_CABLE_STATUS_BIT; + vote(chg->pd_disallowed_votable_indirect, LEGACY_CABLE_VOTER, + legacy_cable, 0); + + if (legacy_cable) { + rp = smblib_get_prop_ufp_mode(chg); + if (rp == POWER_SUPPLY_TYPEC_SOURCE_HIGH + || rp == POWER_SUPPLY_TYPEC_NON_COMPLIANT) { + vbus_cc_short = true; + pr_err("Disabling PD and HVDCP, VBUS-CC shorted, rp = %d found\n", + rp); + } + } + + vote(chg->hvdcp_disable_votable, VBUS_CC_SHORT_VOTER, vbus_cc_short, 0); + vote(chg->pd_disallowed_votable_indirect, VBUS_CC_SHORT_VOTER, + vbus_cc_short, 0); +} + irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) { struct smb_irq_data *irq_data = data; @@ -2382,6 +2424,8 @@ irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) (bool)(stat & CC_ATTACHED_BIT), (bool)(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT), (bool)(stat & UFP_DFP_MODE_STATUS_BIT)); + smblib_handle_legacy_cable(chg, + (bool)(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT)); power_supply_changed(chg->usb_psy); diff --git a/drivers/power/qcom-charger/smb-lib.h b/drivers/power/qcom-charger/smb-lib.h index fc1c3ba94a15..910a9f7b9a30 100644 --- a/drivers/power/qcom-charger/smb-lib.h +++ b/drivers/power/qcom-charger/smb-lib.h @@ -41,6 +41,8 @@ enum print_reason { #define HVDCP_TIMEOUT_VOTER "HVDCP_TIMEOUT_VOTER" #define PD_DISALLOWED_INDIRECT_VOTER "PD_DISALLOWED_INDIRECT_VOTER" #define PD_HARD_RESET_VOTER "PD_HARD_RESET_VOTER" +#define VBUS_CC_SHORT_VOTER "VBUS_CC_SHORT_VOTER" +#define LEGACY_CABLE_VOTER "LEGACY_CABLE_VOTER" enum smb_mode { PARALLEL_MASTER = 0, diff --git a/drivers/power/qcom-charger/smb-reg.h b/drivers/power/qcom-charger/smb-reg.h index a499d354086c..c567ec2752c1 100644 --- a/drivers/power/qcom-charger/smb-reg.h +++ b/drivers/power/qcom-charger/smb-reg.h @@ -506,6 +506,15 @@ enum { #define CC_ORIENTATION_BIT BIT(1) #define CC_ATTACHED_BIT BIT(0) +#define TYPE_C_STATUS_5_REG (USBIN_BASE + 0x0F) +#define TRY_SOURCE_FAILED_BIT BIT(6) +#define TRY_SINK_FAILED_BIT BIT(5) +#define TIMER_STAGE_2_BIT BIT(4) +#define TYPEC_LEGACY_CABLE_STATUS_BIT BIT(3) +#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT BIT(2) +#define TYPEC_TRYSOURCE_DETECT_STATUS_BIT BIT(1) +#define TYPEC_TRYSINK_DETECT_STATUS_BIT BIT(0) + /* USBIN Interrupt Bits */ #define TYPE_C_CHANGE_RT_STS_BIT BIT(7) #define USBIN_ICL_CHANGE_RT_STS_BIT BIT(6) @@ -555,6 +564,16 @@ enum { #define TYPE_C_UFP_MODE_BIT BIT(1) #define EN_80UA_180UA_CUR_SOURCE_BIT BIT(0) +#define TYPE_C_CFG_3_REG (USBIN_BASE + 0x5A) +#define TVBUS_DEBOUNCE_BIT BIT(7) +#define TYPEC_LEGACY_CABLE_INT_EN_BIT BIT(6) +#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN_BIT BIT(5) +#define TYPEC_TRYSOURCE_DETECT_INT_EN_BIT BIT(4) +#define TYPEC_TRYSINK_DETECT_INT_EN_BIT BIT(3) +#define EN_TRYSINK_MODE_BIT BIT(2) +#define EN_LEGACY_CABLE_DETECTION_BIT BIT(1) +#define ALLOW_PD_DRING_UFP_TCCDB_BIT BIT(0) + #define USBIN_ADAPTER_ALLOW_CFG_REG (USBIN_BASE + 0x60) #define USBIN_ADAPTER_ALLOW_MASK GENMASK(3, 0) enum { From 2cdfc35e0b9cc3c25fad123a0581f80cae51c507 Mon Sep 17 00:00:00 2001 From: Abhijeet Dharmapurikar Date: Thu, 13 Oct 2016 09:50:41 -0700 Subject: [PATCH 11/11] smb-lib: provide insertion removal functions The current driver votes and unvotes for removal/insertion at two interrupt handlers, debounce done and the type C detach. The recommendation from hw teams is to use debounce done for both insertion and removal. Update the code. While at it, provide clean functions to vote and unvote for four situations a typec cable insertion a typec cable removal a source insertion a source removal Note that this allows us to cleanly handle PD hard resets where the typec connection remains intact but the source (or vbus) related state needs to be reset. This also helps us in cleanly handling power sole swaps where the source plugged and removed functions handle vbus related states while typec functions handle the typec related states. Change-Id: I49fccb1fcf8eaea8fea1ae186906689a628c02f8 Signed-off-by: Abhijeet Dharmapurikar --- drivers/power/qcom-charger/smb-lib.c | 184 +++++++++++++-------------- 1 file changed, 89 insertions(+), 95 deletions(-) diff --git a/drivers/power/qcom-charger/smb-lib.c b/drivers/power/qcom-charger/smb-lib.c index 5ed22b226162..dda029a3907a 100644 --- a/drivers/power/qcom-charger/smb-lib.c +++ b/drivers/power/qcom-charger/smb-lib.c @@ -444,47 +444,6 @@ static int smblib_update_usb_type(struct smb_charger *chg) return rc; } -static int smblib_detach_typec(struct smb_charger *chg) -{ - int rc; - - cancel_delayed_work_sync(&chg->hvdcp_detect_work); - chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN; - - /* 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) { - dev_err(chg->dev, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n", - rc); - return rc; - } - - chg->voltage_min_uv = MICRO_5V; - chg->voltage_max_uv = MICRO_5V; - - /* clear USB ICL vote for PD_VOTER */ - rc = vote(chg->usb_icl_votable, PD_VOTER, false, 0); - if (rc < 0) { - dev_err(chg->dev, "Couldn't vote for USB ICL rc=%d\n", - rc); - return rc; - } - - /* cc removed, disable pd_allowed */ - 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, LEGACY_CABLE_VOTER, true, 0); - 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->apsd_disable_votable, PD_VOTER, false, 0); - - return rc; -} - static int smblib_notifier_call(struct notifier_block *nb, unsigned long ev, void *v) { @@ -2317,74 +2276,83 @@ irqreturn_t smblib_handle_usb_source_change(int irq, void *data) return IRQ_HANDLED; } -static void smblib_handle_typec_cc(struct smb_charger *chg, bool attached) +static void typec_source_removal(struct smb_charger *chg) { int rc; - if (!attached) { - rc = smblib_detach_typec(chg); - if (rc < 0) - dev_err(chg->dev, "Couldn't detach USB rc=%d\n", rc); - } + vote(chg->pl_disable_votable, TYPEC_SRC_VOTER, true, 0); + /* 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); - smblib_dbg(chg, PR_INTERRUPT, "IRQ: CC %s\n", - attached ? "attached" : "detached"); + cancel_delayed_work_sync(&chg->hvdcp_detect_work); + + /* 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) + dev_err(chg->dev, "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 PD_VOTER */ + rc = vote(chg->usb_icl_votable, PD_VOTER, false, 0); + if (rc < 0) + dev_err(chg->dev, "Couldn't vote for USB ICL rc=%d\n", rc); } -static void smblib_handle_typec_debounce_done(struct smb_charger *chg, - bool attached, bool rising, bool sink_attached) +static void typec_source_insertion(struct smb_charger *chg) { - int rc; - union power_supply_propval pval = {0, }; + vote(chg->pl_disable_votable, TYPEC_SRC_VOTER, false, 0); +} - /* allow PD when cc is attached and debounced */ - if (attached && rising) - vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, - false, 0); +static void typec_sink_insertion(struct smb_charger *chg) +{ + /* when a sink is inserted we should not wait on hvdcp timeout to + * enable pd + */ + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + false, 0); +} - rc = smblib_get_prop_typec_mode(chg, &pval); - if (rc < 0) - dev_err(chg->dev, "Couldn't get prop typec mode rc=%d\n", rc); +static void smblib_handle_typec_removal(struct smb_charger *chg) +{ + 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, LEGACY_CABLE_VOTER, true, 0); + 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 to enable parallel charging if a source is attached, and disable - * otherwise + * cable could be removed during hard reset, remove its vote to + * disable apsd */ - vote(chg->pl_disable_votable, TYPEC_SRC_VOTER, - !rising || sink_attached, 0); + vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, false, 0); - if (!rising || sink_attached) { - /* 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); - } - - smblib_dbg(chg, PR_INTERRUPT, "IRQ: debounce-done %s; Type-C %s detected\n", - rising ? "rising" : "falling", - smblib_typec_mode_name[pval.intval]); + typec_source_removal(chg); } -static void smblib_handle_legacy_cable(struct smb_charger *chg, - bool typec_debounced) +static void smblib_handle_typec_insertion(struct smb_charger *chg, + bool sink_attached, bool legacy_cable) { - int rc, rp; - u8 stat; - bool legacy_cable; + int rp; bool vbus_cc_short = false; - if (!typec_debounced) - return; + vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, false, 0); - rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat); - if (rc < 0) { - dev_err(chg->dev, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", - rc); - return; + if (sink_attached) { + typec_source_removal(chg); + typec_sink_insertion(chg); + } else { + typec_source_insertion(chg); } - legacy_cable = stat & TYPEC_LEGACY_CABLE_STATUS_BIT; vote(chg->pd_disallowed_votable_indirect, LEGACY_CABLE_VOTER, legacy_cable, 0); @@ -2403,12 +2371,33 @@ static void smblib_handle_legacy_cable(struct smb_charger *chg, vbus_cc_short, 0); } +static void smblib_handle_typec_debounce_done(struct smb_charger *chg, + bool rising, bool sink_attached, bool legacy_cable) +{ + int rc; + union power_supply_propval pval = {0, }; + + if (rising) + smblib_handle_typec_insertion(chg, sink_attached, legacy_cable); + else + smblib_handle_typec_removal(chg); + + rc = smblib_get_prop_typec_mode(chg, &pval); + if (rc < 0) + dev_err(chg->dev, "Couldn't get prop typec mode rc=%d\n", rc); + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: debounce-done %s; Type-C %s detected\n", + rising ? "rising" : "falling", + smblib_typec_mode_name[pval.intval]); +} + irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) { struct smb_irq_data *irq_data = data; struct smb_charger *chg = irq_data->parent_data; int rc; u8 stat; + bool debounce_done, sink_attached, legacy_cable; rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); if (rc < 0) { @@ -2417,15 +2406,20 @@ irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) return IRQ_HANDLED; } smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat); + debounce_done = (bool)(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT); + sink_attached = (bool)(stat & UFP_DFP_MODE_STATUS_BIT); + + rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", + rc); + return IRQ_HANDLED; + } + smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_5 = 0x%02x\n", stat); + legacy_cable = (bool)(stat & TYPEC_LEGACY_CABLE_STATUS_BIT); - smblib_handle_typec_cc(chg, - (bool)(stat & CC_ATTACHED_BIT)); smblib_handle_typec_debounce_done(chg, - (bool)(stat & CC_ATTACHED_BIT), - (bool)(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT), - (bool)(stat & UFP_DFP_MODE_STATUS_BIT)); - smblib_handle_legacy_cable(chg, - (bool)(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT)); + debounce_done, sink_attached, legacy_cable); power_supply_changed(chg->usb_psy);