/* Copyright 2020 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * Silicon Mitus SM5803 Buck-Boost Charger */ #include "atomic.h" #include "battery.h" #include "battery_smart.h" #include "charge_state_v2.h" #include "charger.h" #include "extpower.h" #include "gpio.h" #include "hooks.h" #include "i2c.h" #include "sm5803.h" #include "system.h" #include "stdbool.h" #include "throttle_ap.h" #include "timer.h" #include "usb_charge.h" #include "usb_pd.h" #include "usbc_ocp.h" #include "util.h" #include "watchdog.h" #ifndef CONFIG_CHARGER_NARROW_VDC #error "SM5803 is a NVDC charger, please enable CONFIG_CHARGER_NARROW_VDC." #endif /* Console output macros */ #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, .voltage_step = CHARGE_V_STEP, .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_step = INPUT_I_STEP, }; static uint32_t irq_pending; /* Bitmask of chips with interrupts pending */ static struct mutex flow1_access_lock[CHARGER_NUM]; static struct mutex flow2_access_lock[CHARGER_NUM]; static int charger_vbus[CHARGER_NUM]; /* Tracker for charging failures per port */ struct { int count; timestamp_t time; } failure_tracker[CHARGER_NUM] = {}; /* Port to restart charging on */ static int active_restart_port = CHARGE_PORT_NONE; /* * If powered from the sub board port, we need to attempt to enable the BFET * before proceeding with charging. */ static int attempt_bfet_enable; /* * Note if auto fast charge for the primary port is disabled due to a * disconnected battery, at re-enable auto fast charge later when the battery * has connected. */ static bool fast_charge_disabled; #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); 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); } 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); } 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, 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); } 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, 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); } 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); } 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); } static enum ec_error_list sm5803_flow1_update(int chgnum, const uint8_t mask, const enum mask_update_action action) { int rv; /* Safety checks done, onto the actual register update */ mutex_lock(&flow1_access_lock[chgnum]); rv = i2c_update8(chg_chips[chgnum].i2c_port, chg_chips[chgnum].i2c_addr_flags, SM5803_REG_FLOW1, mask, action); mutex_unlock(&flow1_access_lock[chgnum]); return rv; } 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, mask, action); mutex_unlock(&flow2_access_lock[chgnum]); return rv; } static bool is_platform_id_2s(uint32_t platform_id) { return platform_id >= 0x06 && platform_id <= 0x0D; } static bool is_platform_id_3s(uint32_t platform_id) { return platform_id >= 0x0E && platform_id <= 0x16; } int sm5803_is_vbus_present(int chgnum) { return charger_vbus[chgnum]; } enum ec_error_list sm5803_configure_gpio0(int chgnum, enum sm5803_gpio0_modes mode, int od) { enum ec_error_list rv; int reg; rv = main_read8(chgnum, SM5803_REG_GPIO0_CTRL, ®); if (rv) return rv; reg &= ~SM5803_GPIO0_MODE_MASK; reg |= mode << 1; if (od) reg |= SM5803_GPIO0_OPEN_DRAIN_EN; else reg &= ~SM5803_GPIO0_OPEN_DRAIN_EN; rv = main_write8(chgnum, SM5803_REG_GPIO0_CTRL, reg); return rv; } enum ec_error_list sm5803_set_gpio0_level(int chgnum, int level) { enum ec_error_list rv; int reg; rv = main_read8(chgnum, SM5803_REG_GPIO0_CTRL, ®); if (rv) return rv; if (level) reg |= SM5803_GPIO0_VAL; else reg &= ~SM5803_GPIO0_VAL; rv = main_write8(chgnum, SM5803_REG_GPIO0_CTRL, reg); return rv; } enum ec_error_list sm5803_configure_chg_det_od(int chgnum, int enable) { enum ec_error_list rv; int reg; rv = main_read8(chgnum, SM5803_REG_GPIO0_CTRL, ®); if (rv) return rv; if (enable) reg |= SM5803_CHG_DET_OPEN_DRAIN_EN; else reg &= ~SM5803_CHG_DET_OPEN_DRAIN_EN; rv = main_write8(chgnum, SM5803_REG_GPIO0_CTRL, reg); return rv; } enum ec_error_list sm5803_get_chg_det(int chgnum, int *chg_det) { enum ec_error_list rv; int reg; rv = main_read8(chgnum, SM5803_REG_STATUS1, ®); if (rv) return rv; *chg_det = (reg & SM5803_STATUS1_CHG_DET) != 0; return EC_SUCCESS; } enum ec_error_list sm5803_set_vbus_disch(int chgnum, int enable) { enum ec_error_list rv; int reg; rv = main_read8(chgnum, SM5803_REG_PORTS_CTRL, ®); if (rv) return rv; if (enable) reg |= SM5803_PORTS_VBUS_DISCH; else reg &= ~SM5803_PORTS_VBUS_DISCH; rv = main_write8(chgnum, SM5803_REG_PORTS_CTRL, reg); return rv; } enum ec_error_list sm5803_vbus_sink_enable(int chgnum, int enable) { enum ec_error_list rv; int regval; rv = sm5803_get_dev_id(chgnum, &dev_id); if (rv) return rv; if (enable) { if (chgnum == CHARGER_PRIMARY) { /* Magic for new silicon */ if (dev_id >= 3) { rv |= main_write8(chgnum, 0x1F, 0x1); rv |= test_write8(chgnum, 0x44, 0x2); rv |= main_write8(chgnum, 0x1F, 0); } /* * 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); fast_charge_disabled = false; } else { rv = sm5803_flow2_update(chgnum, SM5803_FLOW2_AUTO_TRKL_EN | SM5803_FLOW2_AUTO_PRECHG_EN, MASK_SET); fast_charge_disabled = true; } } else { if (dev_id >= 3) { /* Touch of magic on the primary charger */ rv |= main_write8(CHARGER_PRIMARY, 0x1F, 0x1); rv |= test_write8(CHARGER_PRIMARY, 0x44, 0x20); rv |= main_write8(CHARGER_PRIMARY, 0x1F, 0x0); /* * Disable linear, pre-charge, and linear fast * charge for primary charger. */ rv = chg_read8(CHARGER_PRIMARY, SM5803_REG_FLOW3, ®val); regval &= ~(BIT(6) | BIT(5) | BIT(4)); rv |= chg_write8(CHARGER_PRIMARY, SM5803_REG_FLOW3, regval); } } /* Last but not least, enable sinking */ 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); if (chgnum == CHARGER_SECONDARY) { rv |= sm5803_flow1_update(CHARGER_PRIMARY, SM5803_FLOW1_LINEAR_CHARGE_EN, MASK_CLR); rv = chg_read8(CHARGER_PRIMARY, SM5803_REG_FLOW3, ®val); regval &= ~(BIT(6) | BIT(5) | BIT(4)); rv |= chg_write8(CHARGER_PRIMARY, SM5803_REG_FLOW3, regval); } /* Disable sink mode, unless currently sourcing out */ if (!sm5803_is_sourcing_otg_power(chgnum, chgnum)) rv |= sm5803_flow1_update(chgnum, CHARGER_MODE_SINK, MASK_CLR); } return rv; } /* * Track and store whether we've initialized the charger chips already on this * 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 static void init_status_preserve(void) { system_add_jump_tag(SM5803_SYSJUMP_TAG, SM5803_HOOK_VERSION, sizeof(chip_inited), &chip_inited); } DECLARE_HOOK(HOOK_SYSJUMP, init_status_preserve, HOOK_PRIO_DEFAULT); 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); if (tag_contents && (version == SM5803_HOOK_VERSION) && (size == sizeof(chip_inited))) /* Valid init status found, restore before charger chip init */ memcpy(&chip_inited, tag_contents, size); } DECLARE_HOOK(HOOK_INIT, init_status_retrieve, HOOK_PRIO_FIRST); 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); if (rv == EC_SUCCESS) { if (vbus_mv < 4000) { /* * No charger connected, disable CHG_EN * (note other bits default to 0) */ rv = chg_write8(chgnum, SM5803_REG_FLOW1, 0); } else if (!sm5803_is_sourcing_otg_power(chgnum, chgnum)) { charger_vbus[chgnum] = 1; } } else { CPRINTS("%s %d: Failed to read VBUS voltage during init", CHARGER_NAME, chgnum); return; } /* * A previous boot already ran inits, safe to return now that we've * checked i2c communication to the chip and cached Vbus presence */ if (chip_inited[chgnum]) { CPRINTS("%s %d: Already initialized", CHARGER_NAME, chgnum); return; } rv |= charger_device_id(®); if (reg == 0x02) { /* --- Special register init --- * For early silicon (ID 2) with 3S batteries */ rv |= main_write8(chgnum, 0x20, 0x08); rv |= main_write8(chgnum, 0x30, 0xC0); rv |= main_write8(chgnum, 0x80, 0x01); rv |= meas_write8(chgnum, 0x08, 0xC2); rv |= chg_write8(chgnum, 0x1D, 0x40); rv |= chg_write8(chgnum, 0x1F, 0x09); rv |= chg_write8(chgnum, 0x22, 0xB3); rv |= chg_write8(chgnum, 0x23, 0x81); rv |= chg_write8(chgnum, 0x28, 0xB7); rv |= chg_write8(chgnum, 0x4A, 0x82); rv |= chg_write8(chgnum, 0x4B, 0xA3); rv |= chg_write8(chgnum, 0x4C, 0xA8); rv |= chg_write8(chgnum, 0x4D, 0xCA); rv |= chg_write8(chgnum, 0x4E, 0x07); rv |= chg_write8(chgnum, 0x4F, 0xFF); rv |= chg_write8(chgnum, 0x50, 0x98); rv |= chg_write8(chgnum, 0x51, 0x00); rv |= chg_write8(chgnum, 0x52, 0x77); rv |= chg_write8(chgnum, 0x53, 0xD2); rv |= chg_write8(chgnum, 0x54, 0x02); rv |= chg_write8(chgnum, 0x55, 0xD1); rv |= chg_write8(chgnum, 0x56, 0x7F); rv |= chg_write8(chgnum, 0x57, 0x02); rv |= chg_write8(chgnum, 0x58, 0xD1); rv |= chg_write8(chgnum, 0x59, 0x7F); rv |= chg_write8(chgnum, 0x5A, 0x13); rv |= chg_write8(chgnum, 0x5B, 0x50); rv |= chg_write8(chgnum, 0x5C, 0x5B); rv |= chg_write8(chgnum, 0x5D, 0xB0); rv |= chg_write8(chgnum, 0x5E, 0x3C); rv |= chg_write8(chgnum, 0x5F, 0x3C); rv |= chg_write8(chgnum, 0x60, 0x44); rv |= chg_write8(chgnum, 0x61, 0x20); rv |= chg_write8(chgnum, 0x65, 0x35); rv |= chg_write8(chgnum, 0x66, 0x29); rv |= chg_write8(chgnum, 0x67, 0x64); rv |= chg_write8(chgnum, 0x68, 0x88); rv |= chg_write8(chgnum, 0x69, 0xC7); /* Inits to access page 0x37 and enable trickle charging */ rv |= main_write8(chgnum, 0x1F, 0x01); rv |= test_update8(chgnum, 0x8E, BIT(5), MASK_SET); rv |= main_write8(chgnum, 0x1F, 0x00); } else if (reg == 0x03) { uint32_t platform_id; rv = main_read8(chgnum, SM5803_REG_PLATFORM, &platform_id); if (rv) { CPRINTS("%s %d: Failed to read platform during init", CHARGER_NAME, chgnum); return; } platform_id &= SM5803_PLATFORM_ID; if (is_platform_id_3s(platform_id)) { /* 3S Battery inits */ /* set 13.3V VBAT_SNSP TH GPADC THRESHOLD*/ rv |= meas_write8(chgnum, 0x26, SM5803_VBAT_SNSP_MAXTH_3S_LEVEL); /* OV_VBAT HW second level (14.1V) */ rv |= chg_write8(chgnum, 0x21, SM5803_VBAT_PWR_MINTH_3S_LEVEL); rv |= main_write8(chgnum, 0x30, 0xC0); rv |= main_write8(chgnum, 0x80, 0x01); rv |= main_write8(chgnum, 0x1A, 0x08); rv |= meas_write8(chgnum, 0x08, 0xC2); rv |= chg_write8(chgnum, 0x1D, 0x40); rv |= chg_write8(chgnum, 0x22, 0xB3); rv |= chg_write8(chgnum, 0x3E, 0x3C); rv |= chg_write8(chgnum, 0x4B, 0xA6); rv |= chg_write8(chgnum, 0x4F, 0xBF); rv |= chg_write8(chgnum, 0x52, 0x77); rv |= chg_write8(chgnum, 0x53, 0xD2); rv |= chg_write8(chgnum, 0x54, 0x02); rv |= chg_write8(chgnum, 0x55, 0xD1); rv |= chg_write8(chgnum, 0x56, 0x7F); rv |= chg_write8(chgnum, 0x57, 0x01); rv |= chg_write8(chgnum, 0x58, 0x50); rv |= chg_write8(chgnum, 0x59, 0x7F); rv |= chg_write8(chgnum, 0x5A, 0x13); rv |= chg_write8(chgnum, 0x5B, 0x50); rv |= chg_write8(chgnum, 0x5D, 0xB0); rv |= chg_write8(chgnum, 0x60, 0x44); rv |= chg_write8(chgnum, 0x65, 0x35); rv |= chg_write8(chgnum, 0x66, 0x29); rv |= chg_write8(chgnum, 0x7D, 0x67); rv |= chg_write8(chgnum, 0x7E, 0x04); rv |= chg_write8(chgnum, 0x33, 0x3C); rv |= chg_write8(chgnum, 0x5C, 0x7A); } else if (is_platform_id_2s(platform_id)) { /* 2S Battery inits */ /* * Set 9V as higher threshold for VBATSNSP_MAX_TH GPADC * threshold for interrupt generation. */ rv |= meas_write8(chgnum, 0x26, 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); rv |= main_write8(chgnum, 0x30, 0xC0); rv |= main_write8(chgnum, 0x80, 0x01); rv |= main_write8(chgnum, 0x1A, 0x08); rv |= meas_write8(chgnum, 0x08, 0xC2); rv |= chg_write8(chgnum, 0x1D, 0x40); rv |= chg_write8(chgnum, 0x22, 0xB3); rv |= chg_write8(chgnum, 0x3E, 0x3C); rv |= chg_write8(chgnum, 0x4F, 0xBF); rv |= chg_write8(chgnum, 0x52, 0x77); rv |= chg_write8(chgnum, 0x53, 0xD2); rv |= chg_write8(chgnum, 0x54, 0x02); rv |= chg_write8(chgnum, 0x55, 0xD1); rv |= chg_write8(chgnum, 0x56, 0x7F); rv |= chg_write8(chgnum, 0x57, 0x01); rv |= chg_write8(chgnum, 0x58, 0x50); rv |= chg_write8(chgnum, 0x59, 0x7F); rv |= chg_write8(chgnum, 0x5A, 0x13); rv |= chg_write8(chgnum, 0x5B, 0x52); rv |= chg_write8(chgnum, 0x5D, 0xD0); rv |= chg_write8(chgnum, 0x60, 0x44); rv |= chg_write8(chgnum, 0x65, 0x35); rv |= chg_write8(chgnum, 0x66, 0x29); rv |= chg_write8(chgnum, 0x7D, 0x97); rv |= chg_write8(chgnum, 0x7E, 0x07); rv |= chg_write8(chgnum, 0x33, 0x3C); rv |= chg_write8(chgnum, 0x5C, 0x7A); } rv |= chg_write8(chgnum, 0x73, 0x22); rv |= chg_write8(chgnum, 0x50, 0x88); rv |= chg_read8(chgnum, 0x34, ®); reg |= BIT(7); rv |= chg_write8(chgnum, 0x34, reg); rv |= main_write8(chgnum, 0x1F, 0x1); rv |= test_write8(chgnum, 0x43, 0x10); rv |= test_write8(chgnum, 0x47, 0x10); rv |= test_write8(chgnum, 0x48, 0x04); rv |= main_write8(chgnum, 0x1F, 0x0); } /* Enable LDO bits */ rv |= main_read8(chgnum, SM5803_REG_REFERENCE, ®); 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; rv |= meas_write8(chgnum, SM5803_REG_PSYS1, reg); /* Enable ADC sigma delta */ rv |= chg_read8(chgnum, SM5803_REG_CC_CONFIG1, ®); reg |= SM5803_CC_CONFIG1_SD_PWRUP; rv |= chg_write8(chgnum, SM5803_REG_CC_CONFIG1, reg); /* Enable PROCHOT comparators except Ibus */ rv |= chg_read8(chgnum, SM5803_REG_PHOT1, ®); reg |= SM5803_PHOT1_COMPARATOR_EN; reg &= ~SM5803_PHOT1_IBUS_PHOT_COMP_EN; rv |= chg_write8(chgnum, SM5803_REG_PHOT1, reg); /* Set DPM Voltage to 4200 mv, see b:172173517 */ reg = SM5803_VOLTAGE_TO_REG(4200); rv = chg_write8(chgnum, SM5803_REG_DPM_VL_SET_MSB, (reg >> 3)); 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; 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); /* Set TINT interrupts for higher threshold 360 K */ rv |= meas_write8(chgnum, SM5803_REG_TINT_HIGH_TH, 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); /* * Configure VBAT_SNSP high interrupt to fire after thresholds are set. */ rv |= main_read8(chgnum, SM5803_REG_INT2_EN, ®); 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); /* * Configure CHG_ENABLE to only be set through I2C by setting * HOST_MODE_EN bit (all other register bits are 0 by default) */ rv |= chg_write8(chgnum, SM5803_REG_FLOW2, SM5803_FLOW2_HOST_MODE_EN); if (chgnum == CHARGER_PRIMARY) { int ibat_eoc_ma; /* 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 BFET alerts * * We'll set the soft limit at 1.5W and the hard limit at 6W. * * The register is 29.2 mW per bit. */ reg = (1500 * 10) / 292; 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); rv |= main_read8(chgnum, SM5803_REG_INT3_EN, ®); reg |= SM5803_INT3_BFET_PWR_LIMIT | SM5803_INT3_BFET_PWR_HWSAFE_LIMIT; rv |= main_write8(chgnum, SM5803_REG_INT3_EN, reg); rv |= chg_read8(chgnum, SM5803_REG_FLOW3, ®); reg &= ~SM5803_FLOW3_SWITCH_BCK_BST; rv |= chg_write8(chgnum, SM5803_REG_FLOW3, reg); rv |= chg_read8(chgnum, SM5803_REG_SWITCHER_CONF, ®); reg |= SM5803_SW_BCK_BST_CONF_AUTO; rv |= chg_write8(chgnum, SM5803_REG_SWITCHER_CONF, reg); } if (rv) CPRINTS("%s %d: Failed initialization", CHARGER_NAME, chgnum); else chip_inited[chgnum] = true; } static enum ec_error_list sm5803_post_init(int chgnum) { /* Nothing to do, charger is always powered */ return EC_SUCCESS; } void sm5803_hibernate(int chgnum) { enum ec_error_list rv; int reg; rv = main_read8(chgnum, SM5803_REG_REFERENCE, ®); if (rv) { CPRINTS("%s %d: Failed to read REFERENCE reg", CHARGER_NAME, chgnum); return; } /* Disable LDO bits - note the primary LDO should not be disabled */ if (chgnum != CHARGER_PRIMARY) { reg |= (BIT(0) | BIT(1)); rv |= main_write8(chgnum, SM5803_REG_REFERENCE, 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); /* Turn off GPADCs */ rv |= meas_write8(chgnum, SM5803_REG_GPADC_CONFIG1, 0); rv |= meas_write8(chgnum, SM5803_REG_GPADC_CONFIG2, 0); /* Disable Psys DAC */ rv |= meas_read8(chgnum, SM5803_REG_PSYS1, ®); reg &= ~SM5803_PSYS1_DAC_EN; rv |= meas_write8(chgnum, SM5803_REG_PSYS1, reg); /* Disable ADC sigma delta */ rv |= chg_read8(chgnum, SM5803_REG_CC_CONFIG1, ®); reg &= ~SM5803_CC_CONFIG1_SD_PWRUP; rv |= chg_write8(chgnum, SM5803_REG_CC_CONFIG1, reg); /* Disable PROCHOT comparators */ rv |= chg_read8(chgnum, SM5803_REG_PHOT1, ®); reg &= ~SM5803_PHOT1_COMPARATOR_EN; rv |= chg_write8(chgnum, SM5803_REG_PHOT1, reg); if (rv) CPRINTS("%s %d: Failed to set hibernate", CHARGER_NAME, chgnum); } static void sm5803_disable_runtime_low_power_mode(void) { enum ec_error_list rv; int reg; int chgnum = TASK_ID_TO_PD_PORT(task_get_current()); CPRINTS("%s %d: disable runtime low power mode", CHARGER_NAME, chgnum); rv = main_read8(chgnum, SM5803_REG_REFERENCE, ®); if (rv) { CPRINTS("%s %d: Failed to read REFERENCE reg", CHARGER_NAME, 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); /* Enable ADC sigma delta */ rv |= chg_read8(chgnum, SM5803_REG_CC_CONFIG1, ®); reg |= SM5803_CC_CONFIG1_SD_PWRUP; rv |= chg_write8(chgnum, SM5803_REG_CC_CONFIG1, reg); if (rv) 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); static enum ec_error_list sm5803_enable_linear_charge(int chgnum, bool enable) { int rv; int regval; const struct battery_info *batt_info; if (enable) { /* * We need to wait for the BFET enable attempt to complete, * otherwise we may end up disabling linear charge. */ if (!attempt_bfet_enable) return EC_ERROR_TRY_AGAIN; rv = main_write8(chgnum, 0x1F, 0x1); rv |= test_write8(chgnum, 0x44, 0x20); rv |= main_write8(chgnum, 0x1F, 0); /* * Precharge thresholds have already been set up as a part of * init, however set fast charge current equal to the precharge * current in case the battery moves beyond that threshold. */ batt_info = battery_get_info(); rv |= sm5803_set_current(CHARGER_PRIMARY, batt_info->precharge_current); /* Enable linear charge mode. */ 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, MASK_CLR); 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) | SM5803_FLOW3_SWITCH_BCK_BST); rv |= chg_write8(chgnum, SM5803_REG_FLOW3, regval); rv |= chg_read8(chgnum, SM5803_REG_SWITCHER_CONF, ®val); regval |= SM5803_SW_BCK_BST_CONF_AUTO; rv |= chg_write8(chgnum, SM5803_REG_SWITCHER_CONF, regval); } return rv; } static void sm5803_enable_runtime_low_power_mode(void) { enum ec_error_list rv; int reg; int chgnum = TASK_ID_TO_PD_PORT(task_get_current()); CPRINTS("%s %d: enable runtime low power mode", CHARGER_NAME, chgnum); rv = main_read8(chgnum, SM5803_REG_REFERENCE, ®); if (rv) { CPRINTS("%s %d: Failed to read REFERENCE reg", CHARGER_NAME, 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); /* Disable ADC sigma delta */ rv |= chg_read8(chgnum, SM5803_REG_CC_CONFIG1, ®); reg &= ~SM5803_CC_CONFIG1_SD_PWRUP; rv |= chg_write8(chgnum, SM5803_REG_CC_CONFIG1, reg); /* If the system is off, all PROCHOT comparators may be turned off */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF | CHIPSET_STATE_ANY_SUSPEND)) { rv |= chg_read8(chgnum, SM5803_REG_PHOT1, ®); reg &= ~SM5803_PHOT1_COMPARATOR_EN; rv |= chg_write8(chgnum, SM5803_REG_PHOT1, 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); void sm5803_disable_low_power_mode(int chgnum) { enum ec_error_list rv; int reg; CPRINTS("%s %d: disable low power mode", CHARGER_NAME, chgnum); rv = main_read8(chgnum, SM5803_REG_REFERENCE, ®); if (rv) { CPRINTS("%s %d: Failed to read REFERENCE reg", CHARGER_NAME, chgnum); return; } /* Enable Psys DAC */ rv |= meas_read8(chgnum, SM5803_REG_PSYS1, ®); reg |= SM5803_PSYS1_DAC_EN; rv |= meas_write8(chgnum, SM5803_REG_PSYS1, reg); /* Enable PROCHOT comparators except Ibus */ rv |= chg_read8(chgnum, SM5803_REG_PHOT1, ®); reg |= SM5803_PHOT1_COMPARATOR_EN; reg &= ~SM5803_PHOT1_IBUS_PHOT_COMP_EN; rv |= chg_write8(chgnum, SM5803_REG_PHOT1, reg); if (rv) CPRINTS("%s %d: Failed to set in disable low power mode", CHARGER_NAME, chgnum); } void sm5803_enable_low_power_mode(int chgnum) { enum ec_error_list rv; int reg; CPRINTS("%s %d: enable low power mode", CHARGER_NAME, chgnum); rv = main_read8(chgnum, SM5803_REG_REFERENCE, ®); if (rv) { CPRINTS("%s %d: Failed to read REFERENCE reg", CHARGER_NAME, chgnum); return; } /* Disable Psys DAC */ rv |= meas_read8(chgnum, SM5803_REG_PSYS1, ®); reg &= ~SM5803_PSYS1_DAC_EN; rv |= meas_write8(chgnum, SM5803_REG_PSYS1, reg); /* * Disable all PROCHOT comparators only if port is inactive. Vbus * sourcing requires that the Vbus comparator be enabled, and it * cannot be enabled from HOOK_USB_PD_CONNECT since that is * called after Vbus has turned on. */ rv |= chg_read8(chgnum, SM5803_REG_PHOT1, ®); reg &= ~SM5803_PHOT1_COMPARATOR_EN; if (pd_is_connected(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); } /* * Restart charging on the active port, if it's still active and it hasn't * exceeded our maximum number of restarts. */ void sm5803_restart_charging(void) { int act_chg = charge_manager_get_active_charge_port(); timestamp_t now = get_time(); if (act_chg == active_restart_port) { if (timestamp_expired(failure_tracker[act_chg].time, &now)) { /* * 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].count = 1; sm5803_vbus_sink_enable(act_chg, 1); } else if (++failure_tracker[act_chg].count > CHARGING_FAILURE_MAX_COUNT) { CPRINTS("%s %d: Exceeded charging failure retries", CHARGER_NAME, act_chg); } else { sm5803_vbus_sink_enable(act_chg, 1); } } active_restart_port = CHARGE_PORT_NONE; } DECLARE_DEFERRED(sm5803_restart_charging); /* * Process interrupt registers and report any Vbus changes. Alert the AP if the * charger has become too hot. */ 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; /* Note: Interrupt registers are clear on read */ rv = main_read8(chgnum, SM5803_REG_INT1_REQ, &int_reg); if (rv) { CPRINTS("%s %d: Failed read int1 register", CHARGER_NAME, chgnum); return; } if (int_reg & SM5803_INT1_CHG) { rv = main_read8(chgnum, SM5803_REG_STATUS1, &meas_reg); if (!(meas_reg & SM5803_STATUS1_CHG_DET)) { charger_vbus[chgnum] = 0; if (IS_ENABLED(CONFIG_USB_CHARGER)) usb_charger_vbus_change(chgnum, 0); } else { charger_vbus[chgnum] = 1; if (IS_ENABLED(CONFIG_USB_CHARGER)) usb_charger_vbus_change(chgnum, 1); } board_check_extpower(); } rv = main_read8(chgnum, SM5803_REG_INT2_REQ, &int_reg); if (rv) { CPRINTS("%s %d: Failed read int2 register", CHARGER_NAME, chgnum); return; } if (int_reg & SM5803_INT2_TINT) { rv = meas_read8(chgnum, SM5803_REG_TINT_MEAS_MSB, &meas_reg); if ((meas_reg <= SM5803_TINT_LOW_LEVEL) && throttled) { throttled = false; throttle_ap(THROTTLE_OFF, THROTTLE_HARD, 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); rv |= meas_write8(chgnum, SM5803_REG_TINT_HIGH_TH, SM5803_TINT_HIGH_LEVEL); } else if (meas_reg >= SM5803_TINT_HIGH_LEVEL) { throttled = true; throttle_ap(THROTTLE_ON, THROTTLE_HARD, 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); rv |= meas_write8(chgnum, SM5803_REG_TINT_LOW_TH, SM5803_TINT_LOW_LEVEL); } /* * If the interrupt came in and we're not currently throttling * or the level is below the upper threshold, it can likely be * ignored. */ } if (int_reg & SM5803_INT2_VBATSNSP) { int meas_volt; uint32_t platform_id; rv = main_read8(chgnum, SM5803_REG_PLATFORM, &platform_id); if (rv) { CPRINTS("%s %d: Failed to read platform in interrupt", 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); if (rv) return; meas_volt = meas_reg << 2; 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); if (rv) return; if (is_platform_id_2s(platform_id)) { /* 2S Battery */ CPRINTS("%s %d : VBAT_SNSP_HIGH_TH: %d mV ! - " "VBAT %d mV", CHARGER_NAME, CHARGER_PRIMARY, meas_reg * 408/10, meas_volt * 102/10); } if (is_platform_id_3s(platform_id)) { /* 3S Battery */ CPRINTS("%s %d : VBAT_SNSP_HIGH_TH: %d mV ! " "- VBAT %d mV", CHARGER_NAME, CHARGER_PRIMARY, 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); /* Disable battery charge */ rv |= sm5803_flow1_update(chgnum, CHARGER_MODE_DISABLED, 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); } 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); } active_restart_port = act_chg; hook_call_deferred(&sm5803_restart_charging_data, 1 * SECOND); } /* TODO(b/159376384): Take action on fatal BFET power alert. */ rv = main_read8(chgnum, SM5803_REG_INT3_REQ, &int_reg); if (rv) { CPRINTS("%s %d: Failed to read int3 register", CHARGER_NAME, chgnum); return; } if ((int_reg & SM5803_INT3_BFET_PWR_LIMIT) || (int_reg & SM5803_INT3_BFET_PWR_HWSAFE_LIMIT)) { 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"); CPRINTS("\tVbat: %dmV", bp.voltage); CPRINTS("\tIbat: %dmA", bp.current); charger_get_voltage(act_chg, &val); CPRINTS("\tVsys(aux): %dmV", val); charger_get_current(act_chg, &val); CPRINTS("\tIsys: %dmA", val); cflush(); } rv = main_read8(chgnum, SM5803_REG_INT4_REQ, &int_reg); if (rv) { CPRINTS("%s %d: Failed to read int4 register", CHARGER_NAME, chgnum); return; } if (int_reg & SM5803_INT4_CHG_FAIL) { int status_reg; act_chg = charge_manager_get_active_charge_port(); chg_read8(chgnum, SM5803_REG_STATUS_CHG_REG, &status_reg); CPRINTS("%s %d: CHG_FAIL_INT fired. Status 0x%02x", CHARGER_NAME, chgnum, status_reg); /* Write 1 to clear status interrupts */ chg_write8(chgnum, SM5803_REG_STATUS_CHG_REG, status_reg); /* * If a survivable fault happened, re-start sinking on the * active charger after an appropriate delay. */ if (status_reg & SM5803_STATUS_CHG_OV_ITEMP) { active_restart_port = act_chg; hook_call_deferred(&sm5803_restart_charging_data, 30 * SECOND); } else if ((status_reg & SM5803_STATUS_CHG_OV_VBAT) && act_chg == CHARGER_PRIMARY) { active_restart_port = act_chg; hook_call_deferred(&sm5803_restart_charging_data, 1 * SECOND); } } if (int_reg & SM5803_INT4_CHG_DONE) CPRINTS("%s %d: CHG_DONE_INT fired!!!", CHARGER_NAME, chgnum); if (int_reg & SM5803_INT4_OTG_FAIL) { int status_reg; /* * Gather status to detect if this was overcurrent * * Note: a status of 0 with this interrupt also indicates an * overcurrent (see b/170517117) */ chg_read8(chgnum, SM5803_REG_STATUS_DISCHG, &status_reg); CPRINTS("%s %d: OTG_FAIL_INT fired. Status 0x%02x", CHARGER_NAME, chgnum, status_reg); if ((status_reg == 0) || (status_reg == SM5803_STATUS_DISCHG_VBUS_SHORT)) { pd_handle_overcurrent(chgnum); } /* * Clear source mode here when status is 0, since OTG disable * 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); } } static void sm5803_irq_deferred(void) { int i; uint32_t pending = atomic_clear(&irq_pending); for (i = 0; i < CHARGER_NUM; i++) if (BIT(i) & pending) sm5803_handle_interrupt(i); } DECLARE_DEFERRED(sm5803_irq_deferred); void sm5803_interrupt(int chgnum) { atomic_or(&irq_pending, BIT(chgnum)); hook_call_deferred(&sm5803_irq_deferred_data, 0); } static enum ec_error_list sm5803_get_dev_id(int chgnum, int *id) { int rv = EC_SUCCESS; if (dev_id == UNKNOWN_DEV_ID) rv = main_read8(chgnum, SM5803_REG_CHIP_ID, &dev_id); if (!rv) *id = dev_id; return rv; } static const struct charger_info *sm5803_get_info(int chgnum) { return &sm5803_charger_info; } static enum ec_error_list sm5803_get_status(int chgnum, int *status) { enum ec_error_list rv; int reg; /* Charger obeys smart battery requests - making it level 2 */ *status = CHARGER_LEVEL_2; rv = chg_read8(chgnum, SM5803_REG_FLOW1, ®); if (rv) return rv; if ((reg & SM5803_FLOW1_MODE) == CHARGER_MODE_DISABLED && !(reg & SM5803_FLOW1_LINEAR_CHARGE_EN)) *status |= CHARGER_CHARGE_INHIBITED; return EC_SUCCESS; } static enum ec_error_list sm5803_set_mode(int chgnum, int mode) { enum ec_error_list rv = EC_SUCCESS; if (mode & CHARGE_FLAG_INHIBIT_CHARGE) { rv = sm5803_flow1_update(chgnum, 0xFF, MASK_CLR); rv |= sm5803_flow2_update(chgnum, SM5803_FLOW2_AUTO_ENABLED, MASK_CLR); } return rv; } static enum ec_error_list sm5803_get_actual_current(int chgnum, int *current) { enum ec_error_list rv; int reg; int curr; rv = meas_read8(chgnum, SM5803_REG_IBAT_CHG_AVG_MEAS_MSB, ®); if (rv) return rv; curr = reg << 2; rv = meas_read8(chgnum, SM5803_REG_IBAT_CHG_AVG_MEAS_LSB, ®); if (rv) return rv; curr |= reg & SM5803_IBAT_CHG_MEAS_LSB; /* The LSB is 7.32mA */ *current = curr * 732 / 100; return EC_SUCCESS; } static enum ec_error_list sm5803_get_current(int chgnum, int *current) { enum ec_error_list rv; int reg; rv = chg_read8(chgnum, SM5803_REG_FAST_CONF4, ®); if (rv) return rv; reg &= SM5803_CONF4_ICHG_FAST; *current = SM5803_REG_TO_CURRENT(reg); return EC_SUCCESS; } static enum ec_error_list sm5803_set_current(int chgnum, int current) { enum ec_error_list rv; int reg; rv = chg_read8(chgnum, SM5803_REG_FAST_CONF4, ®); if (rv) return rv; reg &= ~SM5803_CONF4_ICHG_FAST; reg |= SM5803_CURRENT_TO_REG(current); rv = chg_write8(chgnum, SM5803_REG_FAST_CONF4, reg); return rv; } static enum ec_error_list sm5803_get_actual_voltage(int chgnum, int *voltage) { enum ec_error_list rv; int reg; int volt_bits; rv = meas_read8(chgnum, SM5803_REG_VSYS_AVG_MEAS_MSB, ®); if (rv) return rv; volt_bits = reg << 2; rv = meas_read8(chgnum, SM5803_REG_VSYS_AVG_MEAS_LSB, ®); if (rv) return rv; volt_bits |= reg & 0x3; /* The LSB is 23.4mV */ *voltage = volt_bits * 234 / 10; return EC_SUCCESS; } static enum ec_error_list sm5803_get_voltage(int chgnum, int *voltage) { enum ec_error_list rv; int regval; int v; rv = chg_read8(chgnum, SM5803_REG_VBAT_FAST_MSB, ®val); v = regval << 3; rv |= chg_read8(chgnum, SM5803_REG_VBAT_FAST_LSB, ®val); v |= (regval & 0x3); *voltage = SM5803_REG_TO_VOLTAGE(v); if (rv) return EC_ERROR_UNKNOWN; return EC_SUCCESS; } static enum ec_error_list sm5803_set_voltage(int chgnum, int voltage) { enum ec_error_list rv; int regval; regval = SM5803_VOLTAGE_TO_REG(voltage); /* * Note: Set both voltages on both chargers. Vbat will only be used on * primary, which enables charging. */ rv = chg_write8(chgnum, SM5803_REG_VSYS_PREREG_MSB, (regval >> 3)); rv |= chg_write8(chgnum, SM5803_REG_VSYS_PREREG_LSB, (regval & 0x7)); rv |= chg_write8(chgnum, SM5803_REG_VBAT_FAST_MSB, (regval >> 3)); rv |= chg_write8(chgnum, SM5803_REG_VBAT_FAST_LSB, (regval & 0x7)); /* Once battery is connected, set up fast charge enable */ if (fast_charge_disabled && chgnum == CHARGER_PRIMARY && battery_get_disconnect_state() == BATTERY_NOT_DISCONNECTED) { rv = sm5803_flow2_update(chgnum, SM5803_FLOW2_AUTO_ENABLED, MASK_SET); fast_charge_disabled = false; } if (IS_ENABLED(CONFIG_OCPC) && chgnum != CHARGER_PRIMARY) { /* * Check to see if the BFET is enabled. If not, enable it by * toggling linear mode on the primary charger. The BFET can be * disabled if the system is powered up from an auxiliary charge * port and the battery is dead. */ rv |= chg_read8(CHARGER_PRIMARY, SM5803_REG_LOG1, ®val); if (!(regval & SM5803_BATFET_ON) && !attempt_bfet_enable) { CPRINTS("SM5803: Attempting to turn on BFET"); cflush(); rv |= sm5803_flow1_update(CHARGER_PRIMARY, SM5803_FLOW1_LINEAR_CHARGE_EN, MASK_SET); rv |= sm5803_flow1_update(CHARGER_PRIMARY, SM5803_FLOW1_LINEAR_CHARGE_EN, MASK_CLR); attempt_bfet_enable = 1; sm5803_vbus_sink_enable(chgnum, 1); } /* There's no need to attempt it if the BFET's already on. */ if (regval & SM5803_BATFET_ON) attempt_bfet_enable = 1; } return rv; } static enum ec_error_list sm5803_discharge_on_ac(int chgnum, int enable) { enum ec_error_list rv = EC_SUCCESS; if (enable) { rv = sm5803_vbus_sink_enable(chgnum, 0); } else { if (chgnum == charge_manager_get_active_charge_port()) rv = sm5803_vbus_sink_enable(chgnum, 1); } return rv; } static enum ec_error_list sm5803_get_vbus_voltage(int chgnum, int port, int *voltage) { enum ec_error_list rv; int reg; int volt_bits; rv = meas_read8(chgnum, SM5803_REG_VBUS_MEAS_MSB, ®); if (rv) return rv; volt_bits = reg << 2; rv = meas_read8(chgnum, SM5803_REG_VBUS_MEAS_LSB, ®); volt_bits |= reg & SM5803_VBUS_MEAS_LSB; /* Vbus ADC is in 23.4 mV steps */ *voltage = (volt_bits * 234) / 10; return rv; } static enum ec_error_list sm5803_set_input_current_limit(int chgnum, int input_current) { int reg; reg = SM5803_CURRENT_TO_REG(input_current) & SM5803_CHG_ILIM_RAW; return chg_write8(chgnum, SM5803_REG_CHG_ILIM, reg); } static enum ec_error_list sm5803_get_input_current_limit(int chgnum, int *input_current) { int rv; int val; rv = chg_read8(chgnum, SM5803_REG_CHG_ILIM, &val); if (rv) return rv; *input_current = SM5803_REG_TO_CURRENT(val & SM5803_CHG_ILIM_RAW); return rv; } static enum ec_error_list sm5803_get_input_current(int chgnum, int *input_current) { enum ec_error_list rv; int reg; int curr; rv = meas_read8(chgnum, SM5803_REG_IBUS_CHG_MEAS_MSB, ®); if (rv) return rv; curr = reg << 2; rv = meas_read8(chgnum, SM5803_REG_IBUS_CHG_MEAS_LSB, ®); if (rv) return rv; curr |= reg & 0x3; /* The LSB is 7.32mA */ *input_current = curr * 732 / 100; return EC_SUCCESS; } static enum ec_error_list sm5803_get_option(int chgnum, int *option) { enum ec_error_list rv; uint32_t control; int reg; rv = chg_read8(chgnum, SM5803_REG_FLOW1, ®); control = reg; rv |= chg_read8(chgnum, SM5803_REG_FLOW2, ®); control |= reg << 8; rv |= chg_read8(chgnum, SM5803_REG_FLOW3, ®); control |= reg << 16; return rv; } enum ec_error_list sm5803_is_acok(int chgnum, bool *acok) { int rv; int reg, vbus_mv; rv = main_read8(chgnum, SM5803_REG_STATUS1, ®); if (rv) return rv; /* If we're not sinking, then AC can't be OK. */ if (!(reg & SM5803_STATUS1_CHG_DET)) { *acok = false; return EC_SUCCESS; } /* * Okay, we're sinking. Check that VBUS has some voltage. This * should indicate that the path is good. */ rv = charger_get_vbus_voltage(chgnum, &vbus_mv); if (rv) return rv; /* Assume that ACOK would be asserted if VBUS is higher than ~4V. */ *acok = vbus_mv >= 4000; return EC_SUCCESS; } static enum ec_error_list sm5803_is_input_current_limit_reached(int chgnum, bool *reached) { enum ec_error_list rv; int reg; rv = chg_read8(chgnum, SM5803_REG_LOG2, ®); if (rv) return rv; *reached = (reg & SM5803_ISOLOOP_ON) ? true : false; return EC_SUCCESS; } static enum ec_error_list sm5803_set_option(int chgnum, int option) { enum ec_error_list rv; int reg; mutex_lock(&flow1_access_lock[chgnum]); reg = option & 0xFF; rv = chg_write8(chgnum, SM5803_REG_FLOW1, reg); mutex_unlock(&flow1_access_lock[chgnum]); if (rv) return rv; reg = (option >> 8) & 0xFF; rv = chg_write8(chgnum, SM5803_REG_FLOW2, reg); if (rv) return rv; reg = (option >> 16) & 0xFF; rv = chg_write8(chgnum, SM5803_REG_FLOW3, reg); return rv; } static enum ec_error_list sm5803_set_otg_current_voltage(int chgnum, int output_current, int output_voltage) { enum ec_error_list rv; int reg; rv = chg_read8(chgnum, SM5803_REG_DISCH_CONF5, ®); if (rv) return rv; reg &= ~SM5803_DISCH_CONF5_CLS_LIMIT; reg |= MIN((output_current / SM5803_CLS_CURRENT_STEP), 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); return rv; } static enum ec_error_list sm5803_enable_otg_power(int chgnum, int enabled) { enum ec_error_list rv; int reg, status; if (enabled) { int selected_current; rv = chg_read8(chgnum, SM5803_REG_ANA_EN1, ®); if (rv) return rv; /* Enable current limit */ reg &= ~SM5803_ANA_EN1_CLS_DISABLE; rv = chg_write8(chgnum, SM5803_REG_ANA_EN1, reg); /* Disable ramps on current set in discharge */ rv |= chg_read8(chgnum, SM5803_REG_DISCH_CONF6, ®); reg |= SM5803_DISCH_CONF6_RAMPS_DIS; rv |= chg_write8(chgnum, SM5803_REG_DISCH_CONF6, reg); /* * In order to ensure the Vbus output doesn't overshoot too * much, turn the starting voltage down to 4.8 V and ramp up * after 4 ms */ rv = chg_read8(chgnum, SM5803_REG_DISCH_CONF5, ®); if (rv) return rv; selected_current = (reg & SM5803_DISCH_CONF5_CLS_LIMIT) * SM5803_CLS_CURRENT_STEP; sm5803_set_otg_current_voltage(chgnum, selected_current, 4800); /* * Enable: SOURCE_MODE - enable sourcing out * 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, MASK_SET); usleep(4000); sm5803_set_otg_current_voltage(chgnum, selected_current, 5000); } else { /* Always clear out discharge status before clearing FLOW1 */ rv = chg_read8(chgnum, SM5803_REG_STATUS_DISCHG, &status); if (rv) return rv; if (status) CPRINTS("%s %d: Discharge failure 0x%02x", CHARGER_NAME, chgnum, status); rv |= chg_write8(chgnum, SM5803_REG_STATUS_DISCHG, status); /* Re-enable ramps on current set in discharge */ rv |= chg_read8(chgnum, SM5803_REG_DISCH_CONF6, ®); reg &= ~SM5803_DISCH_CONF6_RAMPS_DIS; rv |= chg_write8(chgnum, SM5803_REG_DISCH_CONF6, reg); /* * PD tasks will always turn off previous sourcing on init. * Protect ourselves from brown out on init by checking if we're * sinking right now. The init process should only leave sink * mode enabled if a charger is plugged in; otherwise it's * expected to be 0. * * Always clear out sourcing if the previous source-out failed. */ rv |= chg_read8(chgnum, SM5803_REG_FLOW1, ®); if (rv) 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); } return rv; } static int sm5803_is_sourcing_otg_power(int chgnum, int port) { enum ec_error_list rv; int reg; rv = chg_read8(chgnum, SM5803_REG_FLOW1, ®); if (rv) return 0; /* * Note: In linear mode, MB charger will read a reserved mode when * sourcing, so bit 1 is the most reliable way to detect sourcing. */ return (reg & BIT(1)); } static enum ec_error_list sm5803_set_vsys_compensation(int chgnum, struct ocpc_data *ocpc, int current_ma, int voltage_mv) { int rv; int regval; int r; /* Set IR drop compensation */ r = ocpc->combined_rsys_rbatt_mo * 100 / 167; /* 1.67mOhm steps */ r = MAX(0, r); rv = chg_write8(chgnum, SM5803_REG_IR_COMP2, r & 0x7F); rv |= chg_read8(chgnum, SM5803_REG_IR_COMP1, ®val); regval &= ~SM5803_IR_COMP_RES_SET_MSB; r = r >> 8; /* Bits 9:8 */ regval |= (r & 0x3) << SM5803_IR_COMP_RES_SET_MSB_SHIFT; regval |= SM5803_IR_COMP_EN; rv |= chg_write8(chgnum, SM5803_REG_IR_COMP1, regval); if (rv) return EC_ERROR_UNKNOWN; return EC_ERROR_UNIMPLEMENTED; } /* Hardware current ramping (aka DPM: Dynamic Power Management) */ #ifdef CONFIG_CHARGE_RAMP_HW static enum ec_error_list sm5803_set_hw_ramp(int chgnum, int enable) { enum ec_error_list rv; int reg; rv = chg_read8(chgnum, SM5803_REG_CHG_MON_REG, ®); if (enable) reg |= SM5803_DPM_LOOP_EN; else reg &= ~SM5803_DPM_LOOP_EN; rv |= chg_write8(chgnum, SM5803_REG_CHG_MON_REG, reg); return rv; } static int sm5803_ramp_is_stable(int chgnum) { /* * There is no way to read current limit that the ramp has * settled on with sm5803, so we don't consider the ramp stable, * because we never know what the stable limit is. */ return 0; } static int sm5803_ramp_is_detected(int chgnum) { return 1; } static int sm5803_ramp_get_current_limit(int chgnum) { int rv; int input_current = 0; rv = sm5803_get_input_current_limit(chgnum, &input_current); return rv ? -1 : input_current; } #endif /* CONFIG_CHARGE_RAMP_HW */ #ifdef CONFIG_CMD_CHARGER_DUMP static int command_sm5803_dump(int argc, char **argv) { int reg; int regval; int chgnum = 0; if (argc > 1) chgnum = atoi(argv[1]); /* Dump base regs */ ccprintf("BASE regs\n"); for (reg = 0x01; reg <= 0x30; reg++) { if (!main_read8(chgnum, reg, ®val)) ccprintf("[0x%02X] = 0x%02x\n", reg, regval); if (reg & 0xf) { cflush(); /* Flush periodically */ watchdog_reload(); } } /* Dump measure regs */ ccprintf("MEAS regs\n"); for (reg = 0x01; reg <= 0xED; reg++) { if (!meas_read8(chgnum, reg, ®val)) ccprintf("[0x%02X] = 0x%02x\n", reg, regval); if (reg & 0xf) { cflush(); /* Flush periodically */ watchdog_reload(); } } /* Dump Charger regs from 0x1C to 0x7F */ ccprintf("CHG regs\n"); for (reg = 0x1C; reg <= 0x7F; reg++) { if (!chg_read8(chgnum, reg, ®val)) ccprintf("[0x%02X] = 0x%02x\n", reg, regval); if (reg & 0xf) { cflush(); /* Flush periodically */ watchdog_reload(); } } return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(charger_dump, command_sm5803_dump, "charger_dump [chgnum]", "Dumps SM5803 registers"); #endif /* CONFIG_CMD_CHARGER_DUMP */ const struct charger_drv sm5803_drv = { .init = &sm5803_init, .post_init = &sm5803_post_init, .get_info = &sm5803_get_info, .get_status = &sm5803_get_status, .set_mode = &sm5803_set_mode, .get_actual_current = &sm5803_get_actual_current, .get_current = &sm5803_get_current, .set_current = &sm5803_set_current, .get_actual_voltage = &sm5803_get_actual_voltage, .get_voltage = &sm5803_get_voltage, .set_voltage = &sm5803_set_voltage, .discharge_on_ac = &sm5803_discharge_on_ac, .get_vbus_voltage = &sm5803_get_vbus_voltage, .set_input_current_limit = &sm5803_set_input_current_limit, .get_input_current_limit = &sm5803_get_input_current_limit, .get_input_current = &sm5803_get_input_current, .device_id = &sm5803_get_dev_id, .get_option = &sm5803_get_option, .set_option = &sm5803_set_option, .set_otg_current_voltage = &sm5803_set_otg_current_voltage, .enable_otg_power = &sm5803_enable_otg_power, .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, .enable_linear_charge = &sm5803_enable_linear_charge, #ifdef CONFIG_CHARGE_RAMP_HW .set_hw_ramp = &sm5803_set_hw_ramp, .ramp_is_stable = &sm5803_ramp_is_stable, .ramp_is_detected = &sm5803_ramp_is_detected, .ramp_get_current_limit = &sm5803_ramp_get_current_limit, #endif };