diff options
Diffstat (limited to 'driver/charger/sm5803.c')
-rw-r--r-- | driver/charger/sm5803.c | 545 |
1 files changed, 341 insertions, 204 deletions
diff --git a/driver/charger/sm5803.c b/driver/charger/sm5803.c index 131c611842..b8940cf8ff 100644 --- a/driver/charger/sm5803.c +++ b/driver/charger/sm5803.c @@ -1,4 +1,4 @@ -/* Copyright 2020 The Chromium OS Authors. All rights reserved. +/* Copyright 2020 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -28,23 +28,34 @@ #error "SM5803 is a NVDC charger, please enable CONFIG_CHARGER_NARROW_VDC." #endif +#ifdef PD_MAX_VOLTAGE_MV +#if PD_MAX_VOLTAGE_MV > 15000 +/* See https://issuetracker.google.com/230712704 for details. */ +#error "VBUS >15V is forbidden for SM5803 because it can cause hardware damage" +#endif +#endif + +#ifdef CONFIG_CHARGER_SINGLE_CHIP +#define CHARGER_PRIMARY CHARGER_SOLO +#endif + /* Console output macros */ -#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args) -#define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args) +#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ##args) +#define CPRINTS(format, args...) cprints(CC_CHARGER, format, ##args) #define UNKNOWN_DEV_ID -1 static int dev_id = UNKNOWN_DEV_ID; static const struct charger_info sm5803_charger_info = { - .name = CHARGER_NAME, - .voltage_max = CHARGE_V_MAX, - .voltage_min = CHARGE_V_MIN, + .name = CHARGER_NAME, + .voltage_max = CHARGE_V_MAX, + .voltage_min = CHARGE_V_MIN, .voltage_step = CHARGE_V_STEP, - .current_max = CHARGE_I_MAX, - .current_min = CHARGE_I_MIN, + .current_max = CHARGE_I_MAX, + .current_min = CHARGE_I_MIN, .current_step = CHARGE_I_STEP, - .input_current_max = INPUT_I_MAX, - .input_current_min = INPUT_I_MIN, + .input_current_max = INPUT_I_MAX, + .input_current_min = INPUT_I_MIN, .input_current_step = INPUT_I_STEP, }; @@ -77,9 +88,8 @@ static int attempt_bfet_enable; */ static bool fast_charge_disabled; - -#define CHARGING_FAILURE_MAX_COUNT 5 -#define CHARGING_FAILURE_INTERVAL MINUTE +#define CHARGING_FAILURE_MAX_COUNT 5 +#define CHARGING_FAILURE_INTERVAL MINUTE static int sm5803_is_sourcing_otg_power(int chgnum, int port); static enum ec_error_list sm5803_get_dev_id(int chgnum, int *id); @@ -88,62 +98,105 @@ static enum ec_error_list sm5803_set_current(int chgnum, int current); static inline enum ec_error_list chg_read8(int chgnum, int offset, int *value) { return i2c_read8(chg_chips[chgnum].i2c_port, - chg_chips[chgnum].i2c_addr_flags, - offset, value); + chg_chips[chgnum].i2c_addr_flags, offset, value); } static inline enum ec_error_list chg_write8(int chgnum, int offset, int value) { return i2c_write8(chg_chips[chgnum].i2c_port, - chg_chips[chgnum].i2c_addr_flags, - offset, value); + chg_chips[chgnum].i2c_addr_flags, offset, value); } static inline enum ec_error_list meas_read8(int chgnum, int offset, int *value) { - return i2c_read8(chg_chips[chgnum].i2c_port, - SM5803_ADDR_MEAS_FLAGS, + return i2c_read8(chg_chips[chgnum].i2c_port, SM5803_ADDR_MEAS_FLAGS, offset, value); } static inline enum ec_error_list meas_write8(int chgnum, int offset, int value) { - return i2c_write8(chg_chips[chgnum].i2c_port, - SM5803_ADDR_MEAS_FLAGS, - offset, value); + return i2c_write8(chg_chips[chgnum].i2c_port, SM5803_ADDR_MEAS_FLAGS, + offset, value); } static inline enum ec_error_list main_read8(int chgnum, int offset, int *value) { - return i2c_read8(chg_chips[chgnum].i2c_port, - SM5803_ADDR_MAIN_FLAGS, + return i2c_read8(chg_chips[chgnum].i2c_port, SM5803_ADDR_MAIN_FLAGS, offset, value); } static inline enum ec_error_list main_write8(int chgnum, int offset, int value) { - return i2c_write8(chg_chips[chgnum].i2c_port, - SM5803_ADDR_MAIN_FLAGS, - offset, value); + return i2c_write8(chg_chips[chgnum].i2c_port, SM5803_ADDR_MAIN_FLAGS, + offset, value); } static inline enum ec_error_list test_write8(int chgnum, int offset, int value) { - return i2c_write8(chg_chips[chgnum].i2c_port, - SM5803_ADDR_TEST_FLAGS, - offset, value); + return i2c_write8(chg_chips[chgnum].i2c_port, SM5803_ADDR_TEST_FLAGS, + offset, value); } -static inline enum ec_error_list test_update8(int chgnum, const int offset, - const uint8_t mask, - const enum mask_update_action action) +static inline enum ec_error_list +test_update8(int chgnum, const int offset, const uint8_t mask, + const enum mask_update_action action) { - return i2c_update8(chg_chips[chgnum].i2c_port, - SM5803_ADDR_TEST_FLAGS, offset, mask, action); + return i2c_update8(chg_chips[chgnum].i2c_port, SM5803_ADDR_TEST_FLAGS, + offset, mask, action); +} + +/* + * Ensure the charger configuration is safe for operation, updating registers + * as necessary to become safe. + * + * The SM5803 runs multiple digital control loops that are important to correct + * operation. The CLOCK_SEL_LOW register reduces their speed by about 10x, which + * is dangerous when either sinking or sourcing is to be enabled because the + * control loops will respond much more slowly. Leaving clocks at low speed can + * cause incorrect operation or even hardware damage. + * + * The GPADCs are inputs to the control loops, and disabling them can also cause + * incorrect operation or hardware damage. They must be enabled for the charger + * to be safe to operate. + * + * This function is used by the functions that enable sinking or sourcing to + * ensure the current configuration is safe before enabling switching on the + * charger. + */ +static int sm5803_set_active_safe(int chgnum) +{ + int rv, val; + + /* + * Set clocks to full speed. + * + * This should occur first because enabling GPADCs with clocks slowed + * can cause spurious acquisition. + */ + rv = main_read8(chgnum, SM5803_REG_CLOCK_SEL, &val); + if (rv == 0 && val & SM5803_CLOCK_SEL_LOW) { + rv = main_write8(chgnum, SM5803_REG_CLOCK_SEL, + val & ~SM5803_CLOCK_SEL_LOW); + } + if (rv) { + goto out; + } + + /* Enable default GPADCs */ + rv = meas_write8(chgnum, SM5803_REG_GPADC_CONFIG1, + SM5803_GPADCC1_DEFAULT_ENABLE); + +out: + if (rv) { + CPRINTS("%s %d: failed to set clocks to full speed: %d", + CHARGER_NAME, chgnum, rv); + } + return rv; } -static enum ec_error_list sm5803_flow1_update(int chgnum, const uint8_t mask, - const enum mask_update_action action) +static enum ec_error_list +sm5803_flow1_update(int chgnum, const uint8_t mask, + const enum mask_update_action action) { int rv; @@ -151,8 +204,7 @@ static enum ec_error_list sm5803_flow1_update(int chgnum, const uint8_t mask, mutex_lock(&flow1_access_lock[chgnum]); rv = i2c_update8(chg_chips[chgnum].i2c_port, - chg_chips[chgnum].i2c_addr_flags, - SM5803_REG_FLOW1, + chg_chips[chgnum].i2c_addr_flags, SM5803_REG_FLOW1, mask, action); mutex_unlock(&flow1_access_lock[chgnum]); @@ -160,16 +212,16 @@ static enum ec_error_list sm5803_flow1_update(int chgnum, const uint8_t mask, return rv; } -static enum ec_error_list sm5803_flow2_update(int chgnum, const uint8_t mask, - const enum mask_update_action action) +static enum ec_error_list +sm5803_flow2_update(int chgnum, const uint8_t mask, + const enum mask_update_action action) { int rv; mutex_lock(&flow2_access_lock[chgnum]); rv = i2c_update8(chg_chips[chgnum].i2c_port, - chg_chips[chgnum].i2c_addr_flags, - SM5803_REG_FLOW2, + chg_chips[chgnum].i2c_addr_flags, SM5803_REG_FLOW2, mask, action); mutex_unlock(&flow2_access_lock[chgnum]); @@ -292,6 +344,11 @@ enum ec_error_list sm5803_vbus_sink_enable(int chgnum, int enable) return rv; if (enable) { + rv = sm5803_set_active_safe(chgnum); + if (rv) { + return rv; + } + if (chgnum == CHARGER_PRIMARY) { /* Magic for new silicon */ if (dev_id >= 3) { @@ -303,17 +360,19 @@ enum ec_error_list sm5803_vbus_sink_enable(int chgnum, int enable) * Only enable auto fast charge when a battery is * connected and out of cutoff. */ - if (battery_get_disconnect_state() == - BATTERY_NOT_DISCONNECTED) { - rv = sm5803_flow2_update(chgnum, - SM5803_FLOW2_AUTO_ENABLED, - MASK_SET); + if (IS_ENABLED(CONFIG_BATTERY) && + battery_get_disconnect_state() == + BATTERY_NOT_DISCONNECTED) { + rv = sm5803_flow2_update( + chgnum, SM5803_FLOW2_AUTO_ENABLED, + MASK_SET); fast_charge_disabled = false; } else { - rv = sm5803_flow2_update(chgnum, - SM5803_FLOW2_AUTO_TRKL_EN | + rv = sm5803_flow2_update( + chgnum, + SM5803_FLOW2_AUTO_TRKL_EN | SM5803_FLOW2_AUTO_PRECHG_EN, - MASK_SET); + MASK_SET); fast_charge_disabled = true; } } else { @@ -340,10 +399,10 @@ enum ec_error_list sm5803_vbus_sink_enable(int chgnum, int enable) rv |= sm5803_flow1_update(chgnum, CHARGER_MODE_SINK, MASK_SET); } else { if (chgnum == CHARGER_PRIMARY) - rv |= sm5803_flow2_update(chgnum, - SM5803_FLOW2_AUTO_ENABLED, - MASK_CLR); + rv |= sm5803_flow2_update( + chgnum, SM5803_FLOW2_AUTO_ENABLED, MASK_CLR); +#ifndef CONFIG_CHARGER_SINGLE_CHIP if (chgnum == CHARGER_SECONDARY) { rv |= sm5803_flow1_update(CHARGER_PRIMARY, SM5803_FLOW1_LINEAR_CHARGE_EN, @@ -355,7 +414,7 @@ enum ec_error_list sm5803_vbus_sink_enable(int chgnum, int enable) rv |= chg_write8(CHARGER_PRIMARY, SM5803_REG_FLOW3, regval); } - +#endif /* Disable sink mode, unless currently sourcing out */ if (!sm5803_is_sourcing_otg_power(chgnum, chgnum)) @@ -364,7 +423,6 @@ enum ec_error_list sm5803_vbus_sink_enable(int chgnum, int enable) } return rv; - } /* @@ -372,8 +430,8 @@ enum ec_error_list sm5803_vbus_sink_enable(int chgnum, int enable) * boot. This should prevent us from re-running inits after sysjumps. */ static bool chip_inited[CHARGER_NUM]; -#define SM5803_SYSJUMP_TAG 0x534D /* SM */ -#define SM5803_HOOK_VERSION 1 +#define SM5803_SYSJUMP_TAG 0x534D /* SM */ +#define SM5803_HOOK_VERSION 1 static void init_status_preserve(void) { @@ -387,10 +445,9 @@ static void init_status_retrieve(void) const uint8_t *tag_contents; int version, size; - tag_contents = system_get_jump_tag(SM5803_SYSJUMP_TAG, - &version, &size); + tag_contents = system_get_jump_tag(SM5803_SYSJUMP_TAG, &version, &size); if (tag_contents && (version == SM5803_HOOK_VERSION) && - (size == sizeof(chip_inited))) + (size == sizeof(chip_inited))) /* Valid init status found, restore before charger chip init */ memcpy(&chip_inited, tag_contents, size); } @@ -414,15 +471,15 @@ static void sm5803_init(int chgnum) enum ec_error_list rv; int reg; int vbus_mv; - const struct battery_info *batt_info; - int pre_term; - int cells; /* * If a charger is not currently present, disable switching per OCPC * requirements */ - rv = charger_get_vbus_voltage(chgnum, &vbus_mv); + /* Reset clocks and enable GPADCs */ + rv = sm5803_set_active_safe(chgnum); + + rv |= charger_get_vbus_voltage(chgnum, &vbus_mv); if (rv == EC_SUCCESS) { if (vbus_mv < 4000) { /* @@ -508,7 +565,7 @@ static void sm5803_init(int chgnum) rv = main_read8(chgnum, SM5803_REG_PLATFORM, &platform_id); if (rv) { CPRINTS("%s %d: Failed to read platform during init", - CHARGER_NAME, chgnum); + CHARGER_NAME, chgnum); return; } platform_id &= SM5803_PLATFORM_ID; @@ -517,10 +574,10 @@ static void sm5803_init(int chgnum) /* 3S Battery inits */ /* set 13.3V VBAT_SNSP TH GPADC THRESHOLD*/ rv |= meas_write8(chgnum, 0x26, - SM5803_VBAT_SNSP_MAXTH_3S_LEVEL); + SM5803_VBAT_SNSP_MAXTH_3S_LEVEL); /* OV_VBAT HW second level (14.1V) */ rv |= chg_write8(chgnum, 0x21, - SM5803_VBAT_PWR_MINTH_3S_LEVEL); + SM5803_VBAT_PWR_MINTH_3S_LEVEL); rv |= main_write8(chgnum, 0x30, 0xC0); rv |= main_write8(chgnum, 0x80, 0x01); rv |= main_write8(chgnum, 0x1A, 0x08); @@ -566,11 +623,11 @@ static void sm5803_init(int chgnum) * threshold for interrupt generation. */ rv |= meas_write8(chgnum, 0x26, - SM5803_VBAT_SNSP_MAXTH_2S_LEVEL); + SM5803_VBAT_SNSP_MAXTH_2S_LEVEL); /* Set OV_VBAT HW second level threshold as 9.4V */ rv |= chg_write8(chgnum, 0x21, - SM5803_VBAT_PWR_MINTH_2S_LEVEL); + SM5803_VBAT_PWR_MINTH_2S_LEVEL); rv |= main_write8(chgnum, 0x30, 0xC0); rv |= main_write8(chgnum, 0x80, 0x01); @@ -627,24 +684,6 @@ static void sm5803_init(int chgnum) reg &= ~(BIT(0) | BIT(1)); rv |= main_write8(chgnum, SM5803_REG_REFERENCE, reg); - /* Set a higher clock speed in case it was lowered for z-state */ - rv |= main_read8(chgnum, SM5803_REG_CLOCK_SEL, ®); - reg &= ~SM5803_CLOCK_SEL_LOW; - rv |= main_write8(chgnum, SM5803_REG_CLOCK_SEL, reg); - - /* - * Turn on GPADCs to default. Enable the IBAT_CHG ADC in order to - * measure battery current and calculate system resistance. - */ - reg = SM5803_GPADCC1_TINT_EN | - SM5803_GPADCC1_VSYS_EN | - SM5803_GPADCC1_VCHGPWR_EN | - SM5803_GPADCC1_VBUS_EN | - SM5803_GPADCC1_IBAT_CHG_EN | - SM5803_GPADCC1_IBAT_DIS_EN | - SM5803_GPADCC1_VBATSNSP_EN; - rv |= meas_write8(chgnum, SM5803_REG_GPADC_CONFIG1, reg); - /* Enable Psys DAC */ rv |= meas_read8(chgnum, SM5803_REG_PSYS1, ®); reg |= SM5803_PSYS1_DAC_EN; @@ -667,26 +706,26 @@ static void sm5803_init(int chgnum) rv |= chg_write8(chgnum, SM5803_REG_DPM_VL_SET_LSB, (reg & 0x7)); /* Set default input current */ - reg = SM5803_CURRENT_TO_REG(CONFIG_CHARGER_INPUT_CURRENT) - & SM5803_CHG_ILIM_RAW; + reg = SM5803_CURRENT_TO_REG(CONFIG_CHARGER_INPUT_CURRENT) & + SM5803_CHG_ILIM_RAW; rv |= chg_write8(chgnum, SM5803_REG_CHG_ILIM, reg); /* Configure charger insertion interrupts */ rv |= main_write8(chgnum, SM5803_REG_INT1_EN, SM5803_INT1_CHG); /* Enable end of charge interrupts for logging */ - rv |= main_write8(chgnum, SM5803_REG_INT4_EN, SM5803_INT4_CHG_FAIL | - SM5803_INT4_CHG_DONE | - SM5803_INT4_OTG_FAIL); + rv |= main_write8(chgnum, SM5803_REG_INT4_EN, + SM5803_INT4_CHG_FAIL | SM5803_INT4_CHG_DONE | + SM5803_INT4_OTG_FAIL); /* Set TINT interrupts for higher threshold 360 K */ rv |= meas_write8(chgnum, SM5803_REG_TINT_HIGH_TH, - SM5803_TINT_HIGH_LEVEL); + SM5803_TINT_HIGH_LEVEL); /* * Set TINT interrupts for lower threshold to 0 when not * throttled to prevent trigger interrupts continually */ rv |= meas_write8(chgnum, SM5803_REG_TINT_LOW_TH, - SM5803_TINT_MIN_LEVEL); + SM5803_TINT_MIN_LEVEL); /* * Configure VBAT_SNSP high interrupt to fire after thresholds are set. @@ -695,7 +734,6 @@ static void sm5803_init(int chgnum) reg |= SM5803_INT2_VBATSNSP; rv |= main_write8(chgnum, SM5803_REG_INT2_EN, reg); - /* Configure TINT interrupts to fire after thresholds are set */ rv |= main_write8(chgnum, SM5803_REG_INT2_EN, SM5803_INT2_TINT); @@ -706,40 +744,52 @@ static void sm5803_init(int chgnum) rv |= chg_write8(chgnum, SM5803_REG_FLOW2, SM5803_FLOW2_HOST_MODE_EN); if (chgnum == CHARGER_PRIMARY) { - int ibat_eoc_ma; + if (IS_ENABLED(CONFIG_BATTERY)) { + const struct battery_info *batt_info; + int ibat_eoc_ma; + int pre_term; + int cells; + + /* Set end of fast charge threshold */ + batt_info = battery_get_info(); + ibat_eoc_ma = batt_info->precharge_current - 50; + ibat_eoc_ma /= 100; + ibat_eoc_ma = + CLAMP(ibat_eoc_ma, 0, SM5803_CONF5_IBAT_EOC_TH); + rv |= chg_read8(chgnum, SM5803_REG_FAST_CONF5, ®); + reg &= ~SM5803_CONF5_IBAT_EOC_TH; + reg |= ibat_eoc_ma; + rv |= chg_write8(CHARGER_PRIMARY, SM5803_REG_FAST_CONF5, + reg); + + /* Setup the proper precharge thresholds. */ + cells = batt_info->voltage_max / 4; + pre_term = batt_info->voltage_min / cells; + /* Convert to decivolts. */ + pre_term /= 100; + pre_term = CLAMP(pre_term, SM5803_VBAT_PRE_TERM_MIN_DV, + SM5803_VBAT_PRE_TERM_MAX_DV); + /* Convert to regval */ + pre_term -= SM5803_VBAT_PRE_TERM_MIN_DV; + + rv |= chg_read8(chgnum, SM5803_REG_PRE_FAST_CONF_REG1, + ®); + reg &= ~SM5803_VBAT_PRE_TERM; + reg |= pre_term << SM5803_VBAT_PRE_TERM_SHIFT; + rv |= chg_write8(chgnum, SM5803_REG_PRE_FAST_CONF_REG1, + reg); - /* Set end of fast charge threshold */ - batt_info = battery_get_info(); - ibat_eoc_ma = batt_info->precharge_current - 50; - ibat_eoc_ma /= 100; - ibat_eoc_ma = CLAMP(ibat_eoc_ma, 0, SM5803_CONF5_IBAT_EOC_TH); - rv |= chg_read8(chgnum, SM5803_REG_FAST_CONF5, ®); - reg &= ~SM5803_CONF5_IBAT_EOC_TH; - reg |= ibat_eoc_ma; - rv |= chg_write8(CHARGER_PRIMARY, SM5803_REG_FAST_CONF5, reg); - - /* Setup the proper precharge thresholds. */ - cells = batt_info->voltage_max / 4; - pre_term = batt_info->voltage_min / cells; - pre_term /= 100; /* Convert to decivolts. */ - pre_term = CLAMP(pre_term, SM5803_VBAT_PRE_TERM_MIN_DV, - SM5803_VBAT_PRE_TERM_MAX_DV); - pre_term -= SM5803_VBAT_PRE_TERM_MIN_DV; /* Convert to regval */ - - rv |= chg_read8(chgnum, SM5803_REG_PRE_FAST_CONF_REG1, ®); - reg &= ~SM5803_VBAT_PRE_TERM; - reg |= pre_term << SM5803_VBAT_PRE_TERM_SHIFT; - rv |= chg_write8(chgnum, SM5803_REG_PRE_FAST_CONF_REG1, reg); - - /* - * Set up precharge current - * Note it is preferred to under-shoot the precharge current - * requested. Upper bits of this register are read/write 1 to - * clear - */ - reg = SM5803_CURRENT_TO_REG(batt_info->precharge_current); - reg = MIN(reg, SM5803_PRECHG_ICHG_PRE_SET); - rv |= chg_write8(chgnum, SM5803_REG_PRECHG, reg); + /* + * Set up precharge current + * Note it is preferred to under-shoot the precharge + * current requested. Upper bits of this register are + * read/write 1 to clear + */ + reg = SM5803_CURRENT_TO_REG( + batt_info->precharge_current); + reg = MIN(reg, SM5803_PRECHG_ICHG_PRE_SET); + rv |= chg_write8(chgnum, SM5803_REG_PRECHG, reg); + } /* * Set up BFET alerts @@ -752,7 +802,7 @@ static void sm5803_init(int chgnum) rv |= meas_write8(chgnum, SM5803_REG_BFET_PWR_MAX_TH, reg); reg = (6000 * 10) / 292; rv |= meas_write8(chgnum, SM5803_REG_BFET_PWR_HWSAFE_MAX_TH, - reg); + reg); rv |= main_read8(chgnum, SM5803_REG_INT3_EN, ®); reg |= SM5803_INT3_BFET_PWR_LIMIT | SM5803_INT3_BFET_PWR_HWSAFE_LIMIT; @@ -838,10 +888,9 @@ static void sm5803_disable_runtime_low_power_mode(void) chgnum); return; } - /* Set a higher clock speed */ - rv |= main_read8(chgnum, SM5803_REG_CLOCK_SEL, ®); - reg &= ~SM5803_CLOCK_SEL_LOW; - rv |= main_write8(chgnum, SM5803_REG_CLOCK_SEL, reg); + + /* Reset clocks and enable GPADCs */ + rv = sm5803_set_active_safe(chgnum); /* Enable ADC sigma delta */ rv |= chg_read8(chgnum, SM5803_REG_CC_CONFIG1, ®); @@ -852,10 +901,10 @@ static void sm5803_disable_runtime_low_power_mode(void) CPRINTS("%s %d: Failed to set in disable runtime LPM", CHARGER_NAME, chgnum); } -DECLARE_HOOK(HOOK_USB_PD_CONNECT, - sm5803_disable_runtime_low_power_mode, - HOOK_PRIO_FIRST); +DECLARE_HOOK(HOOK_USB_PD_CONNECT, sm5803_disable_runtime_low_power_mode, + HOOK_PRIO_FIRST); +#ifdef CONFIG_BATTERY static enum ec_error_list sm5803_enable_linear_charge(int chgnum, bool enable) { int rv; @@ -883,18 +932,15 @@ static enum ec_error_list sm5803_enable_linear_charge(int chgnum, bool enable) batt_info->precharge_current); /* Enable linear charge mode. */ - rv |= sm5803_flow1_update(chgnum, - SM5803_FLOW1_LINEAR_CHARGE_EN, - MASK_SET); + rv |= sm5803_flow1_update(chgnum, SM5803_FLOW1_LINEAR_CHARGE_EN, + MASK_SET); rv |= chg_read8(chgnum, SM5803_REG_FLOW3, ®val); regval |= BIT(6) | BIT(5) | BIT(4); rv |= chg_write8(chgnum, SM5803_REG_FLOW3, regval); } else { - rv = sm5803_flow1_update(chgnum, - SM5803_FLOW1_LINEAR_CHARGE_EN, + rv = sm5803_flow1_update(chgnum, SM5803_FLOW1_LINEAR_CHARGE_EN, MASK_CLR); - rv |= sm5803_flow2_update(chgnum, - SM5803_FLOW2_AUTO_ENABLED, + rv |= sm5803_flow2_update(chgnum, SM5803_FLOW2_AUTO_ENABLED, MASK_CLR); rv |= chg_read8(chgnum, SM5803_REG_FLOW3, ®val); regval &= ~(BIT(6) | BIT(5) | BIT(4) | @@ -907,6 +953,7 @@ static enum ec_error_list sm5803_enable_linear_charge(int chgnum, bool enable) return rv; } +#endif static void sm5803_enable_runtime_low_power_mode(void) { @@ -921,10 +968,22 @@ static void sm5803_enable_runtime_low_power_mode(void) chgnum); return; } - /* Slow the clock speed */ - rv |= main_read8(chgnum, SM5803_REG_CLOCK_SEL, ®); - reg |= SM5803_CLOCK_SEL_LOW; - rv |= main_write8(chgnum, SM5803_REG_CLOCK_SEL, reg); + + /* + * Turn off GPADCs. + * + * This is only safe to do if the charger is inactive. We ensure that + * they are enabled again in sm5803_set_active_safe() before the charger + * is enabled, and verify here that the charger is not currently active. + */ + rv |= chg_read8(chgnum, SM5803_REG_FLOW1, ®); + if (rv == 0 && (reg & SM5803_FLOW1_MODE) == CHARGER_MODE_DISABLED) { + rv |= meas_write8(chgnum, SM5803_REG_GPADC_CONFIG1, 0); + rv |= meas_write8(chgnum, SM5803_REG_GPADC_CONFIG2, 0); + } else { + CPRINTS("%s %d: FLOW1 %x is active! Not disabling GPADCs", + CHARGER_NAME, chgnum, reg); + } /* Disable ADC sigma delta */ rv |= chg_read8(chgnum, SM5803_REG_CC_CONFIG1, ®); @@ -939,13 +998,17 @@ static void sm5803_enable_runtime_low_power_mode(void) rv |= chg_write8(chgnum, SM5803_REG_PHOT1, reg); } + /* Slow the clock speed */ + rv |= main_read8(chgnum, SM5803_REG_CLOCK_SEL, ®); + reg |= SM5803_CLOCK_SEL_LOW; + rv |= main_write8(chgnum, SM5803_REG_CLOCK_SEL, reg); + if (rv) CPRINTS("%s %d: Failed to set in enable runtime LPM", CHARGER_NAME, chgnum); } -DECLARE_HOOK(HOOK_USB_PD_DISCONNECT, - sm5803_enable_runtime_low_power_mode, - HOOK_PRIO_LAST); +DECLARE_HOOK(HOOK_USB_PD_DISCONNECT, sm5803_enable_runtime_low_power_mode, + HOOK_PRIO_LAST); void sm5803_disable_low_power_mode(int chgnum) { @@ -1004,7 +1067,6 @@ void sm5803_enable_low_power_mode(int chgnum) reg |= SM5803_PHOT1_VBUS_MON_EN; rv |= chg_write8(chgnum, SM5803_REG_PHOT1, reg); - if (rv) CPRINTS("%s %d: Failed to set in enable low power mode", CHARGER_NAME, chgnum); @@ -1025,8 +1087,8 @@ void sm5803_restart_charging(void) * Enough time has passed since our last failure, * restart the timing and count from now. */ - failure_tracker[act_chg].time.val = now.val + - CHARGING_FAILURE_INTERVAL; + failure_tracker[act_chg].time.val = + now.val + CHARGING_FAILURE_INTERVAL; failure_tracker[act_chg].count = 1; sm5803_vbus_sink_enable(act_chg, 1); @@ -1052,8 +1114,7 @@ void sm5803_handle_interrupt(int chgnum) enum ec_error_list rv; int int_reg, meas_reg; static bool throttled; - struct batt_params bp; - int act_chg, val; + int act_chg; /* Note: Interrupt registers are clear on read */ rv = main_read8(chgnum, SM5803_REG_INT1_REQ, &int_reg); @@ -1089,27 +1150,27 @@ void sm5803_handle_interrupt(int chgnum) if ((meas_reg <= SM5803_TINT_LOW_LEVEL) && throttled) { throttled = false; throttle_ap(THROTTLE_OFF, THROTTLE_HARD, - THROTTLE_SRC_THERMAL); + THROTTLE_SRC_THERMAL); /* * Set back higher threshold to 360 K and set lower * threshold to 0. */ rv |= meas_write8(chgnum, SM5803_REG_TINT_LOW_TH, - SM5803_TINT_MIN_LEVEL); + SM5803_TINT_MIN_LEVEL); rv |= meas_write8(chgnum, SM5803_REG_TINT_HIGH_TH, - SM5803_TINT_HIGH_LEVEL); + SM5803_TINT_HIGH_LEVEL); } else if (meas_reg >= SM5803_TINT_HIGH_LEVEL) { throttled = true; throttle_ap(THROTTLE_ON, THROTTLE_HARD, - THROTTLE_SRC_THERMAL); + THROTTLE_SRC_THERMAL); /* * Set back lower threshold to 330 K and set higher * threshold to maximum. */ rv |= meas_write8(chgnum, SM5803_REG_TINT_HIGH_TH, - SM5803_TINT_MAX_LEVEL); + SM5803_TINT_MAX_LEVEL); rv |= meas_write8(chgnum, SM5803_REG_TINT_LOW_TH, - SM5803_TINT_LOW_LEVEL); + SM5803_TINT_LOW_LEVEL); } /* * If the interrupt came in and we're not currently throttling @@ -1125,25 +1186,23 @@ void sm5803_handle_interrupt(int chgnum) rv = main_read8(chgnum, SM5803_REG_PLATFORM, &platform_id); if (rv) { CPRINTS("%s %d: Failed to read platform in interrupt", - CHARGER_NAME, chgnum); + CHARGER_NAME, chgnum); return; } platform_id &= SM5803_PLATFORM_ID; act_chg = charge_manager_get_active_charge_port(); - rv = meas_read8(CHARGER_PRIMARY, - SM5803_REG_VBATSNSP_MEAS_MSB, - &meas_reg); + rv = meas_read8(CHARGER_PRIMARY, SM5803_REG_VBATSNSP_MEAS_MSB, + &meas_reg); if (rv) return; meas_volt = meas_reg << 2; - rv = meas_read8(CHARGER_PRIMARY, - SM5803_REG_VBATSNSP_MEAS_LSB, - &meas_reg); + rv = meas_read8(CHARGER_PRIMARY, SM5803_REG_VBATSNSP_MEAS_LSB, + &meas_reg); if (rv) return; meas_volt |= meas_reg & 0x03; - rv = meas_read8(CHARGER_PRIMARY, - SM5803_REG_VBATSNSP_MAX_TH, &meas_reg); + rv = meas_read8(CHARGER_PRIMARY, SM5803_REG_VBATSNSP_MAX_TH, + &meas_reg); if (rv) return; @@ -1152,8 +1211,7 @@ void sm5803_handle_interrupt(int chgnum) CPRINTS("%s %d : VBAT_SNSP_HIGH_TH: %d mV ! - " "VBAT %d mV", CHARGER_NAME, CHARGER_PRIMARY, - meas_reg * 408/10, - meas_volt * 102/10); + meas_reg * 408 / 10, meas_volt * 102 / 10); } if (is_platform_id_3s(platform_id)) { @@ -1161,28 +1219,27 @@ void sm5803_handle_interrupt(int chgnum) CPRINTS("%s %d : VBAT_SNSP_HIGH_TH: %d mV ! " "- VBAT %d mV", CHARGER_NAME, CHARGER_PRIMARY, - meas_reg * 616/10, - meas_volt * 154/10); + meas_reg * 616 / 10, meas_volt * 154 / 10); } /* Set Vbat Threshold to Max value to re-arm the interrupt */ - rv = meas_write8(CHARGER_PRIMARY, - SM5803_REG_VBATSNSP_MAX_TH, 0xFF); + rv = meas_write8(CHARGER_PRIMARY, SM5803_REG_VBATSNSP_MAX_TH, + 0xFF); /* Disable battery charge */ rv |= sm5803_flow1_update(chgnum, CHARGER_MODE_DISABLED, - MASK_CLR); + MASK_CLR); if (is_platform_id_2s(platform_id)) { /* 2S battery: set VBAT_SENSP TH 9V */ rv |= meas_write8(CHARGER_PRIMARY, - SM5803_REG_VBATSNSP_MAX_TH, - SM5803_VBAT_SNSP_MAXTH_2S_LEVEL); + SM5803_REG_VBATSNSP_MAX_TH, + SM5803_VBAT_SNSP_MAXTH_2S_LEVEL); } if (is_platform_id_3s(platform_id)) { /* 3S battery: set VBAT_SENSP TH 13.3V */ rv |= meas_write8(CHARGER_PRIMARY, - SM5803_REG_VBATSNSP_MAX_TH, - SM5803_VBAT_SNSP_MAXTH_3S_LEVEL); + SM5803_REG_VBATSNSP_MAX_TH, + SM5803_VBAT_SNSP_MAXTH_3S_LEVEL); } active_restart_port = act_chg; @@ -1197,13 +1254,17 @@ void sm5803_handle_interrupt(int chgnum) return; } - if ((int_reg & SM5803_INT3_BFET_PWR_LIMIT) || - (int_reg & SM5803_INT3_BFET_PWR_HWSAFE_LIMIT)) { + if (IS_ENABLED(CONFIG_BATTERY) && + ((int_reg & SM5803_INT3_BFET_PWR_LIMIT) || + (int_reg & SM5803_INT3_BFET_PWR_HWSAFE_LIMIT))) { + struct batt_params bp; + int val; + battery_get_params(&bp); act_chg = charge_manager_get_active_charge_port(); CPRINTS("%s BFET power limit reached! (%s)", CHARGER_NAME, (int_reg & SM5803_INT3_BFET_PWR_LIMIT) ? "warn" : - "FATAL"); + "FATAL"); CPRINTS("\tVbat: %dmV", bp.voltage); CPRINTS("\tIbat: %dmA", bp.current); charger_get_voltage(act_chg, &val); @@ -1240,7 +1301,7 @@ void sm5803_handle_interrupt(int chgnum) hook_call_deferred(&sm5803_restart_charging_data, 30 * SECOND); } else if ((status_reg & SM5803_STATUS_CHG_OV_VBAT) && - act_chg == CHARGER_PRIMARY) { + act_chg == CHARGER_PRIMARY) { active_restart_port = act_chg; hook_call_deferred(&sm5803_restart_charging_data, 1 * SECOND); @@ -1272,11 +1333,12 @@ void sm5803_handle_interrupt(int chgnum) * will detect us as sinking in this failure case. */ if (status_reg == 0) - rv = sm5803_flow1_update(chgnum, CHARGER_MODE_SOURCE | - SM5803_FLOW1_DIRECTCHG_SRC_EN, - MASK_CLR); + rv = sm5803_flow1_update( + chgnum, + CHARGER_MODE_SOURCE | + SM5803_FLOW1_DIRECTCHG_SRC_EN, + MASK_CLR); } - } static void sm5803_irq_deferred(void) @@ -1307,7 +1369,6 @@ static enum ec_error_list sm5803_get_dev_id(int chgnum, int *id) *id = dev_id; return rv; - } static const struct charger_info *sm5803_get_info(int chgnum) @@ -1327,7 +1388,6 @@ static enum ec_error_list sm5803_get_status(int chgnum, int *status) if (rv) return rv; - if ((reg & SM5803_FLOW1_MODE) == CHARGER_MODE_DISABLED && !(reg & SM5803_FLOW1_LINEAR_CHARGE_EN)) *status |= CHARGER_CHARGE_INHIBITED; @@ -1389,6 +1449,18 @@ static enum ec_error_list sm5803_set_current(int chgnum, int current) enum ec_error_list rv; int reg; + if (current == 0) { + /* + * Per Silicon Mitus, setting the fast charge current limit to 0 + * causes "much unstable". This normally happens when the + * battery is fully charged (so we don't expect fast charge to + * be in use): turn 0 into the minimum nonzero value so we + * avoid setting this register to 0 but still make the requested + * current as small as possible. + */ + current = SM5803_REG_TO_CURRENT(1); + } + rv = chg_read8(chgnum, SM5803_REG_FAST_CONF4, ®); if (rv) return rv; @@ -1459,9 +1531,9 @@ static enum ec_error_list sm5803_set_voltage(int chgnum, int voltage) /* Once battery is connected, set up fast charge enable */ if (fast_charge_disabled && chgnum == CHARGER_PRIMARY && + IS_ENABLED(CONFIG_BATTERY) && battery_get_disconnect_state() == BATTERY_NOT_DISCONNECTED) { - rv = sm5803_flow2_update(chgnum, - SM5803_FLOW2_AUTO_ENABLED, + rv = sm5803_flow2_update(chgnum, SM5803_FLOW2_AUTO_ENABLED, MASK_SET); fast_charge_disabled = false; } @@ -1509,12 +1581,20 @@ static enum ec_error_list sm5803_discharge_on_ac(int chgnum, int enable) } static enum ec_error_list sm5803_get_vbus_voltage(int chgnum, int port, - int *voltage) + int *voltage) { enum ec_error_list rv; int reg; int volt_bits; + rv = meas_read8(chgnum, SM5803_REG_GPADC_CONFIG1, ®); + if (rv) + return rv; + if ((reg & SM5803_GPADCC1_VBUS_EN) == 0) { + /* VBUS ADC is currently disabled */ + return EC_ERROR_NOT_POWERED; + } + rv = meas_read8(chgnum, SM5803_REG_VBUS_MEAS_MSB, ®); if (rv) return rv; @@ -1530,6 +1610,54 @@ static enum ec_error_list sm5803_get_vbus_voltage(int chgnum, int port, return rv; } +bool sm5803_check_vbus_level(int chgnum, enum vbus_level level) +{ + int rv, vbus_voltage; + + /* + * Analog reading of VBUS is more accurate and helps reliability when + * doing power role swaps, but if the charger is in LPM with the GPADCs + * disabled then the reading won't update. + * + * Digital VBUS presence (with transitions flagged by STATUS1_CHG_DET + * interrupt) still works when GPADCs are off, and shouldn't otherwise + * impact performance because the GPADCs should be enabled in any + * situation where we're doing a PRS. + */ + rv = sm5803_get_vbus_voltage(chgnum, chgnum, &vbus_voltage); + if (rv == EC_ERROR_NOT_POWERED) { + /* VBUS ADC is disabled, use digital presence */ + switch (level) { + case VBUS_PRESENT: + return sm5803_is_vbus_present(chgnum); + case VBUS_SAFE0V: + case VBUS_REMOVED: + return !sm5803_is_vbus_present(chgnum); + default: + CPRINTS("%s: unrecognized vbus_level value: %d", + __func__, level); + return false; + } + } + if (rv != EC_SUCCESS) { + /* Unhandled communication error; assume unsatisfied */ + return false; + } + + switch (level) { + case VBUS_PRESENT: + return vbus_voltage > PD_V_SAFE5V_MIN; + case VBUS_SAFE0V: + return vbus_voltage < PD_V_SAFE0V_MAX; + case VBUS_REMOVED: + return vbus_voltage < PD_V_SINK_DISCONNECT_MAX; + default: + CPRINTS("%s: unrecognized vbus_level value: %d", __func__, + level); + return false; + } +} + static enum ec_error_list sm5803_set_input_current_limit(int chgnum, int input_current) { @@ -1678,13 +1806,13 @@ static enum ec_error_list sm5803_set_otg_current_voltage(int chgnum, reg &= ~SM5803_DISCH_CONF5_CLS_LIMIT; reg |= MIN((output_current / SM5803_CLS_CURRENT_STEP), - SM5803_DISCH_CONF5_CLS_LIMIT); + SM5803_DISCH_CONF5_CLS_LIMIT); rv |= chg_write8(chgnum, SM5803_REG_DISCH_CONF5, reg); reg = SM5803_VOLTAGE_TO_REG(output_voltage); rv = chg_write8(chgnum, SM5803_REG_VPWR_MSB, (reg >> 3)); rv |= chg_write8(chgnum, SM5803_REG_DISCH_CONF2, - reg & SM5803_DISCH_CONF5_VPWR_LSB); + reg & SM5803_DISCH_CONF5_VPWR_LSB); return rv; } @@ -1697,6 +1825,11 @@ static enum ec_error_list sm5803_enable_otg_power(int chgnum, int enabled) if (enabled) { int selected_current; + rv = sm5803_set_active_safe(chgnum); + if (rv) { + return rv; + } + rv = chg_read8(chgnum, SM5803_REG_ANA_EN1, ®); if (rv) return rv; @@ -1720,7 +1853,7 @@ static enum ec_error_list sm5803_enable_otg_power(int chgnum, int enabled) return rv; selected_current = (reg & SM5803_DISCH_CONF5_CLS_LIMIT) * - SM5803_CLS_CURRENT_STEP; + SM5803_CLS_CURRENT_STEP; sm5803_set_otg_current_voltage(chgnum, selected_current, 4800); /* @@ -1728,8 +1861,9 @@ static enum ec_error_list sm5803_enable_otg_power(int chgnum, int enabled) * DIRECTCHG_SOURCE_EN - enable current loop * (for designs with no external Vbus FET) */ - rv = sm5803_flow1_update(chgnum, CHARGER_MODE_SOURCE | - SM5803_FLOW1_DIRECTCHG_SRC_EN, + rv = sm5803_flow1_update(chgnum, + CHARGER_MODE_SOURCE | + SM5803_FLOW1_DIRECTCHG_SRC_EN, MASK_SET); usleep(4000); @@ -1765,9 +1899,11 @@ static enum ec_error_list sm5803_enable_otg_power(int chgnum, int enabled) return rv; if ((reg & SM5803_FLOW1_MODE) != CHARGER_MODE_SINK || status) - rv = sm5803_flow1_update(chgnum, CHARGER_MODE_SOURCE | - SM5803_FLOW1_DIRECTCHG_SRC_EN, - MASK_CLR); + rv = sm5803_flow1_update( + chgnum, + CHARGER_MODE_SOURCE | + SM5803_FLOW1_DIRECTCHG_SRC_EN, + MASK_CLR); } return rv; @@ -1794,7 +1930,6 @@ static enum ec_error_list sm5803_set_vsys_compensation(int chgnum, int current_ma, int voltage_mv) { - int rv; int regval; int r; @@ -1928,7 +2063,9 @@ const struct charger_drv sm5803_drv = { .is_sourcing_otg_power = &sm5803_is_sourcing_otg_power, .set_vsys_compensation = &sm5803_set_vsys_compensation, .is_icl_reached = &sm5803_is_input_current_limit_reached, +#ifdef CONFIG_BATTERY .enable_linear_charge = &sm5803_enable_linear_charge, +#endif #ifdef CONFIG_CHARGE_RAMP_HW .set_hw_ramp = &sm5803_set_hw_ramp, .ramp_is_stable = &sm5803_ramp_is_stable, |