/* Copyright 2016 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. * * ROHM BD9995X battery charger driver. */ #include "battery.h" #include "battery_smart.h" #include "bd9995x.h" #include "charge_manager.h" #include "charge_state.h" #include "charger.h" #include "console.h" #include "ec_commands.h" #include "hooks.h" #include "i2c.h" #include "task.h" #include "time.h" #include "util.h" #include "usb_charge.h" #include "usb_pd.h" #define OTPROM_LOAD_WAIT_RETRY 3 #define BD9995X_CHARGE_PORT_COUNT 2 /* * BC1.2 detection starts 100ms after VBUS/VCC attach and typically * completes 312ms after VBUS/VCC attach. */ #define BC12_DETECT_US (312*MSEC) #define BD9995X_VSYS_PRECHARGE_OFFSET_MV 200 /* Console output macros */ #define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args) #ifdef CONFIG_BD9995X_DELAY_INPUT_PORT_SELECT /* * Used in a check to determine if VBUS is within the * range of some VOLTAGE +/- VBUS_DELTA, where voltage * is measured in mV. */ #define VBUS_DELTA 1000 /* VBUS is debounced if it's stable for this length of time */ #define VBUS_MSEC (100*MSEC) /* VBUS debouncing sample interval */ #define VBUS_CHECK_MSEC (10*MSEC) /* Time to wait before VBUS debouncing begins */ #define STABLE_TIMEOUT (500*MSEC) /* Maximum time to wait until VBUS is debounced */ #define DEBOUNCE_TIMEOUT (500*MSEC) enum vstate {START, STABLE, DEBOUNCE}; static enum vstate vbus_state; static int vbus_voltage; static uint64_t debounce_time; static uint64_t vbus_timeout; static int port_update; static int select_update; static int select_input_port_update; #endif /* Charger parameters */ static const struct charger_info bd9995x_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, }; /* Charge command code map */ static enum bd9995x_command charger_map_cmd = BD9995X_INVALID_COMMAND; /* Mutex for active register set control. */ static struct mutex bd9995x_map_mutex; /* Tracks the state of VSYS_PRIORITY */ static int vsys_priority; /* Mutex for VIN_CTRL_SET register */ static struct mutex bd9995x_vin_mutex; #ifdef HAS_TASK_USB_CHG /* USB switch */ static enum usb_switch usb_switch_state[BD9995X_CHARGE_PORT_COUNT] = { USB_SWITCH_DISCONNECT, USB_SWITCH_DISCONNECT, }; static int bd9995x_get_bc12_ilim(int charge_supplier) { switch (charge_supplier) { case CHARGE_SUPPLIER_BC12_CDP: return 1500; case CHARGE_SUPPLIER_BC12_DCP: return 2000; case CHARGE_SUPPLIER_BC12_SDP: return 900; case CHARGE_SUPPLIER_OTHER: #ifdef CONFIG_CHARGE_RAMP_SW return 2400; #else /* * Setting the higher limit of current may result in an * anti-collapse hence limiting the current to 1A. */ return 1000; #endif default: return 500; } } #endif /* HAS_TASK_USB_CHG */ static inline int ch_raw_read16(int cmd, int *param, enum bd9995x_command map_cmd) { int rv; /* Map the Charge command code to appropriate region */ mutex_lock(&bd9995x_map_mutex); if (charger_map_cmd != map_cmd) { rv = i2c_write16(I2C_PORT_CHARGER, I2C_ADDR_CHARGER_FLAGS, BD9995X_CMD_MAP_SET, map_cmd); if (rv) { charger_map_cmd = BD9995X_INVALID_COMMAND; goto bd9995x_read_cleanup; } charger_map_cmd = map_cmd; } rv = i2c_read16(I2C_PORT_CHARGER, I2C_ADDR_CHARGER_FLAGS, cmd, param); bd9995x_read_cleanup: mutex_unlock(&bd9995x_map_mutex); return rv; } static inline int ch_raw_write16(int cmd, int param, enum bd9995x_command map_cmd) { int rv; /* Map the Charge command code to appropriate region */ mutex_lock(&bd9995x_map_mutex); if (charger_map_cmd != map_cmd) { rv = i2c_write16(I2C_PORT_CHARGER, I2C_ADDR_CHARGER_FLAGS, BD9995X_CMD_MAP_SET, map_cmd); if (rv) { charger_map_cmd = BD9995X_INVALID_COMMAND; goto bd9995x_write_cleanup; } charger_map_cmd = map_cmd; } rv = i2c_write16(I2C_PORT_CHARGER, I2C_ADDR_CHARGER_FLAGS, cmd, param); bd9995x_write_cleanup: mutex_unlock(&bd9995x_map_mutex); return rv; } /* BD9995X local interfaces */ static int bd9995x_set_vfastchg(int voltage) { int rv; /* Fast Charge Voltage Regulation Settings for fast charging. */ rv = ch_raw_write16(BD9995X_CMD_VFASTCHG_REG_SET1, voltage & 0x7FF0, BD9995X_EXTENDED_COMMAND); if (rv) return rv; #ifndef CONFIG_CHARGER_BATTERY_TSENSE /* * If TSENSE is not connected set all the VFASTCHG_REG_SETx * to same voltage. */ rv = ch_raw_write16(BD9995X_CMD_VFASTCHG_REG_SET2, voltage & 0x7FF0, BD9995X_EXTENDED_COMMAND); if (rv) return rv; rv = ch_raw_write16(BD9995X_CMD_VFASTCHG_REG_SET3, voltage & 0x7FF0, BD9995X_EXTENDED_COMMAND); #endif return rv; } static int bd9995x_set_vsysreg(int voltage) { /* VSYS Regulation voltage is in 64mV steps. */ voltage &= ~0x3F; return ch_raw_write16(BD9995X_CMD_VSYSREG_SET, voltage, BD9995X_EXTENDED_COMMAND); } static int bd9995x_is_discharging_on_ac(void) { int reg; if (ch_raw_read16(BD9995X_CMD_CHGOP_SET2, ®, BD9995X_EXTENDED_COMMAND)) return 0; return !!(reg & BD9995X_CMD_CHGOP_SET2_BATT_LEARN); } static int bd9995x_charger_enable(int enable) { int rv, reg; static int prev_chg_enable = -1; const struct battery_info *bi = battery_get_info(); #ifdef CONFIG_CHARGER_BD9995X_CHGEN /* * If the battery is not yet initialized, dont turn-off the BGATE so * that voltage from the AC is applied to the battery PACK. */ if (!enable && !board_battery_initialized()) return EC_SUCCESS; #endif /* Nothing to change */ if (enable == prev_chg_enable) return EC_SUCCESS; prev_chg_enable = enable; if (enable) { /* * BGATE capacitor max : 0.1uF + 20% * Charge MOSFET threshold max : 2.8V * BGATE charge pump current min : 3uA * T = C * V / I so, Tmax = 112ms */ msleep(115); /* * Set VSYSREG_SET <= VBAT so that the charger is in Fast-Charge * state when charging. */ rv = bd9995x_set_vsysreg(bi->voltage_min); } else { /* * Set VSYSREG_SET > VBAT so that the charger is in Pre-Charge * state when not charging or discharging. */ rv = bd9995x_set_vsysreg(bi->voltage_max + BD9995X_VSYS_PRECHARGE_OFFSET_MV); /* * Allow charger in pre-charge state for 50ms before disabling * the charger which prevents inrush current while moving from * fast-charge state to pre-charge state. */ msleep(50); } if (rv) return rv; rv = ch_raw_read16(BD9995X_CMD_CHGOP_SET2, ®, BD9995X_EXTENDED_COMMAND); if (rv) return rv; if (enable) reg |= BD9995X_CMD_CHGOP_SET2_CHG_EN; else reg &= ~BD9995X_CMD_CHGOP_SET2_CHG_EN; return ch_raw_write16(BD9995X_CMD_CHGOP_SET2, reg, BD9995X_EXTENDED_COMMAND); } static int bd9995x_por_reset(void) { int rv; int reg; int i; rv = ch_raw_write16(BD9995X_CMD_SYSTEM_CTRL_SET, BD9995X_CMD_SYSTEM_CTRL_SET_OTPLD | BD9995X_CMD_SYSTEM_CTRL_SET_ALLRST, BD9995X_EXTENDED_COMMAND); if (rv) return rv; /* Wait until OTPROM loading is finished */ for (i = 0; i < OTPROM_LOAD_WAIT_RETRY; i++) { msleep(10); rv = ch_raw_read16(BD9995X_CMD_SYSTEM_STATUS, ®, BD9995X_EXTENDED_COMMAND); if (!rv && (reg & BD9995X_CMD_SYSTEM_STATUS_OTPLD_STATE) && (reg & BD9995X_CMD_SYSTEM_STATUS_ALLRST_STATE)) break; } if (rv) return rv; if (i == OTPROM_LOAD_WAIT_RETRY) return EC_ERROR_TIMEOUT; return ch_raw_write16(BD9995X_CMD_SYSTEM_CTRL_SET, 0, BD9995X_EXTENDED_COMMAND); } static int bd9995x_reset_to_zero(void) { int rv; rv = charger_set_current(0); if (rv) return rv; return charger_set_voltage(0); } static int bd9995x_get_charger_op_status(int *status) { return ch_raw_read16(BD9995X_CMD_CHGOP_STATUS, status, BD9995X_EXTENDED_COMMAND); } #ifdef HAS_TASK_USB_CHG static int bc12_detected_type[CONFIG_USB_PD_PORT_COUNT]; /* Mutex for UCD_SET regsiters, lock before read / mask / write. */ static struct mutex ucd_set_mutex[BD9995X_CHARGE_PORT_COUNT]; static int bd9995x_get_bc12_device_type(int port) { int rv; int reg; rv = ch_raw_read16((port == BD9995X_CHARGE_PORT_VBUS) ? BD9995X_CMD_VBUS_UCD_STATUS : BD9995X_CMD_VCC_UCD_STATUS, ®, BD9995X_EXTENDED_COMMAND); if (rv) return CHARGE_SUPPLIER_NONE; switch (reg & BD9995X_TYPE_MASK) { case BD9995X_TYPE_CDP: return CHARGE_SUPPLIER_BC12_CDP; case BD9995X_TYPE_DCP: return CHARGE_SUPPLIER_BC12_DCP; case BD9995X_TYPE_SDP: return CHARGE_SUPPLIER_BC12_SDP; case BD9995X_TYPE_PUP_PORT: case BD9995X_TYPE_OTHER: return CHARGE_SUPPLIER_OTHER; case BD9995X_TYPE_OPEN_PORT: case BD9995X_TYPE_VBUS_OPEN: default: return CHARGE_SUPPLIER_NONE; } } /* * Do safe read / mask / write of BD9995X_CMD_*_UCD_SET register. * The USB charger task owns all bits of this register, except for bit 0 * (BD9995X_CMD_UCD_SET_USB_SW), which is controlled by * usb_charger_set_switches(). */ static int bd9995x_update_ucd_set_reg(int port, uint16_t mask, int set) { int rv; int reg; int port_reg = (port == BD9995X_CHARGE_PORT_VBUS) ? BD9995X_CMD_VBUS_UCD_SET : BD9995X_CMD_VCC_UCD_SET; mutex_lock(&ucd_set_mutex[port]); rv = ch_raw_read16(port_reg, ®, BD9995X_EXTENDED_COMMAND); if (!rv) { if (set) reg |= mask; else reg &= ~mask; rv = ch_raw_write16(port_reg, reg, BD9995X_EXTENDED_COMMAND); } mutex_unlock(&ucd_set_mutex[port]); return rv; } static int bd9995x_bc12_check_type(int port) { int bc12_type; struct charge_port_info charge; int vbus_provided = bd9995x_is_vbus_provided(port) && !usb_charger_port_is_sourcing_vbus(port); /* * If vbus is no longer provided, then no need to continue. Return 0 so * that a wait event is not scheduled. */ if (!vbus_provided) return 0; /* get device type */ bc12_type = bd9995x_get_bc12_device_type(port); if (bc12_type == CHARGE_SUPPLIER_NONE) /* * Device type is not available, return non-zero so new wait * will be scheduled before putting the task to sleep. */ return 1; bc12_detected_type[port] = bc12_type; /* Update charge manager */ charge.voltage = USB_CHARGER_VOLTAGE_MV; charge.current = bd9995x_get_bc12_ilim(bc12_type); charge_manager_update_charge(bc12_type, port, &charge); return 0; } static void bd9995x_bc12_detach(int port, int type) { /* Update charge manager */ charge_manager_update_charge(type, port, NULL); /* Disable charging trigger by BC1.2 detection */ bd9995x_bc12_enable_charging(port, 0); } static int bd9995x_enable_vbus_detect_interrupts(int port, int enable) { int reg; int rv; int port_reg; int mask_val; /* 1st Level Interrupt Setting */ rv = ch_raw_read16(BD9995X_CMD_INT0_SET, ®, BD9995X_EXTENDED_COMMAND); if (rv) return rv; mask_val = ((port == BD9995X_CHARGE_PORT_VBUS) ? BD9995X_CMD_INT0_SET_INT1_EN : BD9995X_CMD_INT0_SET_INT2_EN) | BD9995X_CMD_INT0_SET_INT0_EN; if (enable) reg |= mask_val; else reg &= ~mask_val; rv = ch_raw_write16(BD9995X_CMD_INT0_SET, reg, BD9995X_EXTENDED_COMMAND); if (rv) return rv; /* 2nd Level Interrupt Setting */ port_reg = (port == BD9995X_CHARGE_PORT_VBUS) ? BD9995X_CMD_INT1_SET : BD9995X_CMD_INT2_SET; rv = ch_raw_read16(port_reg, ®, BD9995X_EXTENDED_COMMAND); if (rv) return rv; /* Enable threshold interrupts if we need to control discharge */ #ifdef CONFIG_USB_PD_DISCHARGE mask_val = BD9995X_CMD_INT_VBUS_DET | BD9995X_CMD_INT_VBUS_TH; #else mask_val = BD9995X_CMD_INT_VBUS_DET; #endif if (enable) reg |= mask_val; else reg &= ~mask_val; return ch_raw_write16(port_reg, reg, BD9995X_EXTENDED_COMMAND); } /* Read + clear active interrupt bits for a given port */ static int bd9995x_get_interrupts(int port) { int rv; int reg; int port_reg; port_reg = (port == BD9995X_CHARGE_PORT_VBUS) ? BD9995X_CMD_INT1_STATUS : BD9995X_CMD_INT2_STATUS; rv = ch_raw_read16(port_reg, ®, BD9995X_EXTENDED_COMMAND); if (rv) return 0; /* Clear the interrupt status bits we just read */ ch_raw_write16(port_reg, reg, BD9995X_EXTENDED_COMMAND); return reg; } /* * Set or clear registers necessary to do one-time BC1.2 detection. * Pass enable = 1 to trigger BC1.2 detection, and enable = 0 once * BC1.2 detection has completed. */ static int bd9995x_bc12_detect(int port, int enable) { return bd9995x_update_ucd_set_reg(port, BD9995X_CMD_UCD_SET_BCSRETRY | BD9995X_CMD_UCD_SET_USBDETEN | BD9995X_CMD_UCD_SET_USB_SW_EN, enable); } static int usb_charger_process(int port) { int vbus_provided = bd9995x_is_vbus_provided(port) && !usb_charger_port_is_sourcing_vbus(port); /* Inform other modules about VBUS level */ usb_charger_vbus_change(port, vbus_provided); /* * Do BC1.2 detection, if we have VBUS and our port is not known * to speak PD. */ if (vbus_provided && !pd_capable(port)) { bd9995x_bc12_detect(port, 1); /* * Need to give the charger time (~312 mSec) before the * bc12_type is available. The main task loop will schedule a * task wait event which will then call bd9995x_bc12_get_type. */ return 1; } /* Reset BC1.2 regs so we don't do auto-detection. */ bd9995x_bc12_detect(port, 0); /* * VBUS is no longer being provided, if the bc12_type had been * previously determined, then need to detach. */ if (bc12_detected_type[port] != CHARGE_SUPPLIER_NONE) { /* Charger/sink detached */ bd9995x_bc12_detach(port, bc12_detected_type[port]); bc12_detected_type[port] = CHARGE_SUPPLIER_NONE; } /* No need for the task to schedule a wait event */ return 0; } #ifdef CONFIG_CHARGE_RAMP_SW int usb_charger_ramp_allowed(int supplier) { return supplier == CHARGE_SUPPLIER_BC12_DCP || supplier == CHARGE_SUPPLIER_BC12_SDP || supplier == CHARGE_SUPPLIER_BC12_CDP || supplier == CHARGE_SUPPLIER_OTHER; } int usb_charger_ramp_max(int supplier, int sup_curr) { return bd9995x_get_bc12_ilim(supplier); } #endif /* CONFIG_CHARGE_RAMP_SW */ #endif /* HAS_TASK_USB_CHG */ /* chip specific interfaces */ int charger_set_input_current(int input_current) { int rv; /* Input current step 32 mA */ input_current &= ~0x1F; if (input_current < bd9995x_charger_info.input_current_min) input_current = bd9995x_charger_info.input_current_min; rv = ch_raw_write16(BD9995X_CMD_IBUS_LIM_SET, input_current, BD9995X_BAT_CHG_COMMAND); if (rv) return rv; return ch_raw_write16(BD9995X_CMD_ICC_LIM_SET, input_current, BD9995X_BAT_CHG_COMMAND); } int charger_get_input_current(int *input_current) { return ch_raw_read16(BD9995X_CMD_CUR_ILIM_VAL, input_current, BD9995X_EXTENDED_COMMAND); } int charger_manufacturer_id(int *id) { return EC_ERROR_UNIMPLEMENTED; } int charger_device_id(int *id) { return ch_raw_read16(BD9995X_CMD_CHIP_ID, id, BD9995X_EXTENDED_COMMAND); } int charger_get_option(int *option) { int rv; int reg; rv = ch_raw_read16(BD9995X_CMD_CHGOP_SET1, option, BD9995X_EXTENDED_COMMAND); if (rv) return rv; rv = ch_raw_read16(BD9995X_CMD_CHGOP_SET2, ®, BD9995X_EXTENDED_COMMAND); if (rv) return rv; *option |= reg << 16; return EC_SUCCESS; } int charger_set_option(int option) { int rv; rv = ch_raw_write16(BD9995X_CMD_CHGOP_SET1, option & 0xFFFF, BD9995X_EXTENDED_COMMAND); if (rv) return rv; return ch_raw_write16(BD9995X_CMD_CHGOP_SET2, (option >> 16) & 0xFFFF, BD9995X_EXTENDED_COMMAND); } /* Charger interfaces */ const struct charger_info *charger_get_info(void) { return &bd9995x_charger_info; } int charger_get_status(int *status) { int rv; int reg; int ch_status; /* charger level */ *status = CHARGER_LEVEL_2; /* charger enable/inhibit */ rv = ch_raw_read16(BD9995X_CMD_CHGOP_SET2, ®, BD9995X_EXTENDED_COMMAND); if (rv) return rv; if (!(reg & BD9995X_CMD_CHGOP_SET2_CHG_EN)) *status |= CHARGER_CHARGE_INHIBITED; /* charger alarm enable/inhibit */ rv = ch_raw_read16(BD9995X_CMD_PROCHOT_CTRL_SET, ®, BD9995X_EXTENDED_COMMAND); if (rv) return rv; if (!(reg & (BD9995X_CMD_PROCHOT_CTRL_SET_PROCHOT_EN4 | BD9995X_CMD_PROCHOT_CTRL_SET_PROCHOT_EN3 | BD9995X_CMD_PROCHOT_CTRL_SET_PROCHOT_EN2 | BD9995X_CMD_PROCHOT_CTRL_SET_PROCHOT_EN1 | BD9995X_CMD_PROCHOT_CTRL_SET_PROCHOT_EN0))) *status |= CHARGER_ALARM_INHIBITED; rv = bd9995x_get_charger_op_status(®); if (rv) return rv; /* power fail */ if (!(reg & BD9995X_CMD_CHGOP_STATUS_RBOOST_UV)) *status |= CHARGER_POWER_FAIL; /* Safety signal ranges & battery presence */ ch_status = (reg & BD9995X_BATTTEMP_MASK) >> 8; *status |= CHARGER_BATTERY_PRESENT; switch (ch_status) { case BD9995X_CMD_CHGOP_STATUS_BATTEMP_COLD1: *status |= CHARGER_RES_COLD; break; case BD9995X_CMD_CHGOP_STATUS_BATTEMP_COLD2: *status |= CHARGER_RES_COLD; *status |= CHARGER_RES_UR; break; case BD9995X_CMD_CHGOP_STATUS_BATTEMP_HOT1: case BD9995X_CMD_CHGOP_STATUS_BATTEMP_HOT2: *status |= CHARGER_RES_HOT; break; case BD9995X_CMD_CHGOP_STATUS_BATTEMP_HOT3: *status |= CHARGER_RES_HOT; *status |= CHARGER_RES_OR; break; case BD9995X_CMD_CHGOP_STATUS_BATTEMP_BATOPEN: *status &= ~CHARGER_BATTERY_PRESENT; default: break; } /* source of power */ if (bd9995x_is_vbus_provided(BD9995X_CHARGE_PORT_BOTH)) *status |= CHARGER_AC_PRESENT; return EC_SUCCESS; } int charger_set_mode(int mode) { int rv; if (mode & CHARGE_FLAG_POR_RESET) { rv = bd9995x_por_reset(); if (rv) return rv; } if (mode & CHARGE_FLAG_RESET_TO_ZERO) { rv = bd9995x_reset_to_zero(); if (rv) return rv; } return EC_SUCCESS; } int charger_get_current(int *current) { return ch_raw_read16(BD9995X_CMD_CHG_CURRENT, current, BD9995X_BAT_CHG_COMMAND); } int charger_set_current(int current) { int rv; int chg_enable = 1; /* Charge current step 64 mA */ current &= ~0x3F; if (current < BD9995X_NO_BATTERY_CHARGE_I_MIN && (battery_is_present() != BP_YES || battery_is_cut_off())) current = BD9995X_NO_BATTERY_CHARGE_I_MIN; /* * Disable charger before setting charge current to 0 or when * discharging on AC. * If charging current is set to 0mA during charging, reference of * the charge current feedback amp (VREF_CHG) is set to 0V. Hence * the DCDC stops switching (because of the EA offset). */ if (!current || bd9995x_is_discharging_on_ac()) { chg_enable = 0; rv = bd9995x_charger_enable(0); if (rv) return rv; } rv = ch_raw_write16(BD9995X_CMD_IPRECH_SET, MIN(current, BD9995X_IPRECH_MAX), BD9995X_EXTENDED_COMMAND); if (rv) return rv; rv = ch_raw_write16(BD9995X_CMD_CHG_CURRENT, current, BD9995X_BAT_CHG_COMMAND); if (rv) return rv; /* * Enable charger if charge current is non-zero or not discharging * on AC. */ return chg_enable ? bd9995x_charger_enable(1) : EC_SUCCESS; } int charger_get_voltage(int *voltage) { if (vsys_priority) { int batt_volt_measured; int reg; int rv; /* Get battery voltage as reported by charger */ batt_volt_measured = bd9995x_get_battery_voltage(); if (batt_volt_measured > (battery_get_info()->voltage_min + BD9995X_VSYS_PRECHARGE_OFFSET_MV)) { /* * Battery is not deeply discharged. Clear the * VSYS_PRIORITY bit to ensure that input current limit * is always active. */ mutex_lock(&bd9995x_vin_mutex); if (!ch_raw_read16(BD9995X_CMD_VIN_CTRL_SET, ®, BD9995X_EXTENDED_COMMAND)) { reg &= ~BD9995X_CMD_VIN_CTRL_SET_VSYS_PRIORITY; rv = ch_raw_write16(BD9995X_CMD_VIN_CTRL_SET, reg, BD9995X_EXTENDED_COMMAND); /* Mirror the state of this bit */ if (!rv) vsys_priority = 0; } mutex_unlock(&bd9995x_vin_mutex); } } return ch_raw_read16(BD9995X_CMD_CHG_VOLTAGE, voltage, BD9995X_BAT_CHG_COMMAND); } int charger_set_voltage(int voltage) { const int battery_voltage_max = battery_get_info()->voltage_max; /* * Regulate the system voltage to battery max if the battery * is not present or the battery is discharging on AC. */ if (voltage == 0 || bd9995x_is_discharging_on_ac() || battery_is_present() != BP_YES || battery_is_cut_off() || voltage > battery_voltage_max) voltage = battery_voltage_max; /* Charge voltage step 16 mV */ voltage &= ~0x0F; /* Assumes charger's voltage_min < battery's voltage_max */ if (voltage < bd9995x_charger_info.voltage_min) voltage = bd9995x_charger_info.voltage_min; return bd9995x_set_vfastchg(voltage); } static void bd9995x_battery_charging_profile_settings(void) { const struct battery_info *bi = battery_get_info(); /* Input Current Limit Setting */ charger_set_input_current(CONFIG_CHARGER_INPUT_CURRENT); /* Charge Termination Current Setting */ ch_raw_write16(BD9995X_CMD_ITERM_SET, 0, BD9995X_EXTENDED_COMMAND); /* Trickle-charge Current Setting */ ch_raw_write16(BD9995X_CMD_ITRICH_SET, bi->precharge_current & 0x07C0, BD9995X_EXTENDED_COMMAND); bd9995x_set_vfastchg(bi->voltage_max); /* Set Pre-charge Voltage Threshold for trickle charging. */ ch_raw_write16(BD9995X_CMD_VPRECHG_TH_SET, (bi->voltage_min - 1000) & 0x7FC0, BD9995X_EXTENDED_COMMAND); /* Re-charge Battery Voltage Setting */ ch_raw_write16(BD9995X_CMD_VRECHG_SET, bi->voltage_max & 0x7FF0, BD9995X_EXTENDED_COMMAND); /* Set battery OVP to 500 + maximum battery voltage */ ch_raw_write16(BD9995X_CMD_VBATOVP_SET, (bi->voltage_max + 500) & 0x7ff0, BD9995X_EXTENDED_COMMAND); /* Reverse buck boost voltage Setting */ ch_raw_write16(BD9995X_CMD_VRBOOST_SET, 0, BD9995X_EXTENDED_COMMAND); /* Disable fast/pre-charging watchdog */ ch_raw_write16(BD9995X_CMD_CHGWDT_SET, 0, BD9995X_EXTENDED_COMMAND); /* TODO(crosbug.com/p/55626): Set VSYSVAL_THH/THL appropriately */ } static void bd9995x_init(void) { int reg; /* * Disable charging trigger by BC1.2 on VCC & VBUS and * automatic limitation of the input current. */ if (ch_raw_read16(BD9995X_CMD_CHGOP_SET1, ®, BD9995X_EXTENDED_COMMAND)) return; reg |= (BD9995X_CMD_CHGOP_SET1_SDP_CHG_TRIG_EN | BD9995X_CMD_CHGOP_SET1_SDP_CHG_TRIG | BD9995X_CMD_CHGOP_SET1_VBUS_BC_DISEN | BD9995X_CMD_CHGOP_SET1_VCC_BC_DISEN | BD9995X_CMD_CHGOP_SET1_ILIM_AUTO_DISEN | BD9995X_CMD_CHGOP_SET1_SDP_500_SEL | BD9995X_CMD_CHGOP_SET1_DCP_2500_SEL); ch_raw_write16(BD9995X_CMD_CHGOP_SET1, reg, BD9995X_EXTENDED_COMMAND); /* * OTP setting for this register is 6.08V. Set VSYS to above battery max * (as is done when charger is disabled) to ensure VSYSREG_SET > VBAT so * that the charger is in Pre-Charge state and that the input current * disable setting below will be active. */ bd9995x_set_vsysreg(battery_get_info()->voltage_max + BD9995X_VSYS_PRECHARGE_OFFSET_MV); /* Enable BC1.2 USB charging and DC/DC converter @ 1200KHz */ if (ch_raw_read16(BD9995X_CMD_CHGOP_SET2, ®, BD9995X_EXTENDED_COMMAND)) return; reg &= ~(BD9995X_CMD_CHGOP_SET2_USB_SUS | BD9995X_CMD_CHGOP_SET2_DCDC_CLK_SEL); reg |= BD9995X_CMD_CHGOP_SET2_DCDC_CLK_SEL_1200; #ifdef CONFIG_CHARGER_BD9995X_CHGEN reg |= BD9995X_CMD_CHGOP_SET2_CHG_EN; #endif ch_raw_write16(BD9995X_CMD_CHGOP_SET2, reg, BD9995X_EXTENDED_COMMAND); /* * We disable IADP (here before setting IBUS_LIM_SET and ICC_LIM_SET) * to prevent voltage on IADP/RESET pin from affecting SEL_ILIM_VAL. */ if (ch_raw_read16(BD9995X_CMD_VM_CTRL_SET, ®, BD9995X_EXTENDED_COMMAND)) return; reg &= ~BD9995X_CMD_VM_CTRL_SET_EXTIADPEN; ch_raw_write16(BD9995X_CMD_VM_CTRL_SET, reg, BD9995X_EXTENDED_COMMAND); /* * Disable the input current limit when VBAT is < VSYSREG_SET. This * needs to be done before calling * bd9995x_battery_charging_profile_settings() as in that function the * input current limit is set to CONFIG_CHARGER_INPUT_CURRENT which is * 512 mA. In deeply discharged battery cases, setting the input current * limit this low can cause VSYS to collapse, which in turn can cause * the EC's brownout detector to reset the EC. */ if (ch_raw_read16(BD9995X_CMD_VIN_CTRL_SET, ®, BD9995X_EXTENDED_COMMAND)) return; reg |= BD9995X_CMD_VIN_CTRL_SET_VSYS_PRIORITY; ch_raw_write16(BD9995X_CMD_VIN_CTRL_SET, reg, BD9995X_EXTENDED_COMMAND); /* Mirror the state of this bit */ vsys_priority = 1; /* Define battery charging profile */ bd9995x_battery_charging_profile_settings(); /* Power save mode when VBUS/VCC is removed. */ #ifdef CONFIG_BD9995X_POWER_SAVE_MODE bd9995x_set_power_save_mode(CONFIG_BD9995X_POWER_SAVE_MODE); #else bd9995x_set_power_save_mode(BD9995X_PWR_SAVE_OFF); #endif #ifdef CONFIG_USB_PD_DISCHARGE /* Set VBUS / VCC detection threshold for discharge enable */ ch_raw_write16(BD9995X_CMD_VBUS_TH_SET, BD9995X_VBUS_DISCHARGE_TH, BD9995X_EXTENDED_COMMAND); ch_raw_write16(BD9995X_CMD_VCC_TH_SET, BD9995X_VBUS_DISCHARGE_TH, BD9995X_EXTENDED_COMMAND); #endif /* Unlock debug regs */ ch_raw_write16(BD9995X_CMD_PROTECT_SET, 0x3c, BD9995X_EXTENDED_COMMAND); /* Undocumented - reverse current threshold = -50mV */ ch_raw_write16(0x14, 0x0202, BD9995X_DEBUG_COMMAND); /* Undocumented - internal gain = 2x */ ch_raw_write16(0x1a, 0x80, BD9995X_DEBUG_COMMAND); /* Re-lock debug regs */ ch_raw_write16(BD9995X_CMD_PROTECT_SET, 0x0, BD9995X_EXTENDED_COMMAND); } DECLARE_HOOK(HOOK_INIT, bd9995x_init, HOOK_PRIO_INIT_EXTPOWER); int charger_post_init(void) { return EC_SUCCESS; } int charger_discharge_on_ac(int enable) { int rv; int reg; rv = ch_raw_read16(BD9995X_CMD_CHGOP_SET2, ®, BD9995X_EXTENDED_COMMAND); if (rv) return rv; /* * Suspend USB charging and DC/DC converter so that BATT_LEARN mode * doesn't auto exit if VBAT < VSYSVAL_THL_SET and also it helps to * discharge VBUS quickly when charging is not allowed and the AC * is removed. */ if (enable) reg |= BD9995X_CMD_CHGOP_SET2_BATT_LEARN | BD9995X_CMD_CHGOP_SET2_USB_SUS; else reg &= ~(BD9995X_CMD_CHGOP_SET2_BATT_LEARN | BD9995X_CMD_CHGOP_SET2_USB_SUS); return ch_raw_write16(BD9995X_CMD_CHGOP_SET2, reg, BD9995X_EXTENDED_COMMAND); } int charger_get_vbus_voltage(int port) { uint8_t read_reg; int voltage; read_reg = (port == BD9995X_CHARGE_PORT_VBUS) ? BD9995X_CMD_VBUS_VAL : BD9995X_CMD_VCC_VAL; return ch_raw_read16(read_reg, &voltage, BD9995X_EXTENDED_COMMAND) ? 0 : voltage; } /*** Non-standard interface functions ***/ int bd9995x_is_vbus_provided(enum bd9995x_charge_port port) { int reg; if (ch_raw_read16(BD9995X_CMD_VBUS_VCC_STATUS, ®, BD9995X_EXTENDED_COMMAND)) return 0; if (port == BD9995X_CHARGE_PORT_VBUS) reg &= BD9995X_CMD_VBUS_VCC_STATUS_VBUS_DETECT; else if (port == BD9995X_CHARGE_PORT_VCC) reg &= BD9995X_CMD_VBUS_VCC_STATUS_VCC_DETECT; else if (port == BD9995X_CHARGE_PORT_BOTH) { /* Check VBUS on either port */ reg &= (BD9995X_CMD_VBUS_VCC_STATUS_VCC_DETECT | BD9995X_CMD_VBUS_VCC_STATUS_VBUS_DETECT); } else reg = 0; return !!reg; } #ifdef CONFIG_BD9995X_DELAY_INPUT_PORT_SELECT static int bd9995x_select_input_port_private(enum bd9995x_charge_port port, int select) #else int bd9995x_select_input_port(enum bd9995x_charge_port port, int select) #endif { int rv; int reg; mutex_lock(&bd9995x_vin_mutex); rv = ch_raw_read16(BD9995X_CMD_VIN_CTRL_SET, ®, BD9995X_EXTENDED_COMMAND); if (rv) goto select_input_port_exit; if (select) { if (port == BD9995X_CHARGE_PORT_VBUS) { reg |= BD9995X_CMD_VIN_CTRL_SET_VBUS_EN; reg &= ~BD9995X_CMD_VIN_CTRL_SET_VCC_EN; } else if (port == BD9995X_CHARGE_PORT_VCC) { reg |= BD9995X_CMD_VIN_CTRL_SET_VCC_EN; reg &= ~BD9995X_CMD_VIN_CTRL_SET_VBUS_EN; } else if (port == BD9995X_CHARGE_PORT_BOTH) { /* Enable both the ports for PG3 */ reg |= BD9995X_CMD_VIN_CTRL_SET_VBUS_EN | BD9995X_CMD_VIN_CTRL_SET_VCC_EN; } else { /* Invalid charge port */ panic("Invalid charge port"); } } else { if (port == BD9995X_CHARGE_PORT_VBUS) reg &= ~BD9995X_CMD_VIN_CTRL_SET_VBUS_EN; else if (port == BD9995X_CHARGE_PORT_VCC) reg &= ~BD9995X_CMD_VIN_CTRL_SET_VCC_EN; else if (port == BD9995X_CHARGE_PORT_BOTH) reg &= ~(BD9995X_CMD_VIN_CTRL_SET_VBUS_EN | BD9995X_CMD_VIN_CTRL_SET_VCC_EN); else panic("Invalid charge port"); } rv = ch_raw_write16(BD9995X_CMD_VIN_CTRL_SET, reg, BD9995X_EXTENDED_COMMAND); select_input_port_exit: mutex_unlock(&bd9995x_vin_mutex); return rv; } #ifdef CONFIG_BD9995X_DELAY_INPUT_PORT_SELECT int bd9995x_select_input_port(enum bd9995x_charge_port port, int select) { port_update = port; select_update = select; vbus_state = START; select_input_port_update = 1; task_wake(TASK_ID_USB_CHG); return EC_SUCCESS; } static inline int bd9995x_vbus_test(int value, int limit) { uint32_t hi_value = limit + VBUS_DELTA; uint32_t lo_value = limit - VBUS_DELTA; return ((value > lo_value) && (value < hi_value)); } static int bd9995x_vbus_debounce(enum bd9995x_charge_port port) { int vbus_reg; int voltage; vbus_reg = (port == BD9995X_CHARGE_PORT_VBUS) ? BD9995X_CMD_VBUS_VAL : BD9995X_CMD_VCC_VAL; if (ch_raw_read16(vbus_reg, &voltage, BD9995X_EXTENDED_COMMAND)) voltage = 0; if (!bd9995x_vbus_test(voltage, vbus_voltage)) { vbus_voltage = voltage; debounce_time = get_time().val + VBUS_MSEC; } else { if (get_time().val >= debounce_time) return 1; } return 0; } #endif #ifdef CONFIG_CHARGER_BATTERY_TSENSE int bd9995x_get_battery_temp(int *temp_ptr) { int rv; rv = ch_raw_read16(BD9995X_CMD_THERM_VAL, temp_ptr, BD9995X_EXTENDED_COMMAND); if (rv) return rv; /* Degrees C = 200 - THERM_VAL, range is -55C-200C, 1C steps */ *temp_ptr = 200 - *temp_ptr; return EC_SUCCESS; } #endif void bd9995x_set_power_save_mode(int mode) { ch_raw_write16(BD9995X_CMD_SMBREG, mode, BD9995X_EXTENDED_COMMAND); } int bd9995x_get_battery_voltage(void) { int vbat_val, rv; rv = ch_raw_read16(BD9995X_CMD_VBAT_VAL, &vbat_val, BD9995X_EXTENDED_COMMAND); return rv ? 0 : vbat_val; } #ifdef HAS_TASK_USB_CHG int bd9995x_bc12_enable_charging(int port, int enable) { int rv; int reg; int mask_val; /* * For BC1.2, enable VBUS/VCC_BC_DISEN charging trigger by BC1.2 * detection and disable SDP_CHG_TRIG, SDP_CHG_TRIG_EN. Vice versa * for USB-C. */ rv = ch_raw_read16(BD9995X_CMD_CHGOP_SET1, ®, BD9995X_EXTENDED_COMMAND); if (rv) return rv; mask_val = (BD9995X_CMD_CHGOP_SET1_SDP_CHG_TRIG_EN | BD9995X_CMD_CHGOP_SET1_SDP_CHG_TRIG | ((port == BD9995X_CHARGE_PORT_VBUS) ? BD9995X_CMD_CHGOP_SET1_VBUS_BC_DISEN : BD9995X_CMD_CHGOP_SET1_VCC_BC_DISEN)); if (enable) reg &= ~mask_val; else reg |= mask_val; return ch_raw_write16(BD9995X_CMD_CHGOP_SET1, reg, BD9995X_EXTENDED_COMMAND); } void usb_charger_set_switches(int port, enum usb_switch setting) { /* If switch is not changing then return */ if (setting == usb_switch_state[port]) return; if (setting != USB_SWITCH_RESTORE) usb_switch_state[port] = setting; /* ensure we disable power saving when we are using DP/DN */ #ifdef CONFIG_BD9995X_POWER_SAVE_MODE bd9995x_set_power_save_mode( (usb_switch_state[0] == USB_SWITCH_DISCONNECT && usb_switch_state[1] == USB_SWITCH_DISCONNECT) ? CONFIG_BD9995X_POWER_SAVE_MODE : BD9995X_PWR_SAVE_OFF); #endif bd9995x_update_ucd_set_reg(port, BD9995X_CMD_UCD_SET_USB_SW, usb_switch_state[port] == USB_SWITCH_CONNECT); } void bd9995x_vbus_interrupt(enum gpio_signal signal) { task_wake(TASK_ID_USB_CHG); } void usb_charger_task(void *u) { static int initialized; int changed, port, interrupts; int sleep_usec; uint64_t bc12_det_mark[CONFIG_USB_PD_PORT_COUNT]; #ifdef CONFIG_USB_PD_DISCHARGE int vbus_reg, voltage; #endif #ifdef CONFIG_BD9995X_DELAY_INPUT_PORT_SELECT select_input_port_update = 0; vbus_voltage = 0; #endif for (port = 0; port < CONFIG_USB_PD_PORT_COUNT; port++) { bc12_detected_type[port] = CHARGE_SUPPLIER_NONE; bd9995x_enable_vbus_detect_interrupts(port, 1); bc12_det_mark[port] = 0; } while (1) { sleep_usec = -1; changed = 0; for (port = 0; port < CONFIG_USB_PD_PORT_COUNT; port++) { /* Get port interrupts */ interrupts = bd9995x_get_interrupts(port); if (interrupts & BD9995X_CMD_INT_VBUS_DET || !initialized) { /* * Detect based on current state of VBUS. If * VBUS is provided, then need to wait for * bc12_type to be available. If VBUS is not * provided, then disable wait for this port. */ bc12_det_mark[port] = usb_charger_process(port) ? get_time().val + BC12_DETECT_US : 0; changed = 1; } #ifdef CONFIG_USB_PD_DISCHARGE if (interrupts & BD9995X_CMD_INT_VBUS_TH || !initialized) { /* Get VBUS voltage */ vbus_reg = (port == BD9995X_CHARGE_PORT_VBUS) ? BD9995X_CMD_VBUS_VAL : BD9995X_CMD_VCC_VAL; if (ch_raw_read16(vbus_reg, &voltage, BD9995X_EXTENDED_COMMAND)) voltage = 0; /* Set discharge accordingly */ pd_set_vbus_discharge(port, voltage < BD9995X_VBUS_DISCHARGE_TH); changed = 1; } #endif if (bc12_det_mark[port] && (get_time().val > bc12_det_mark[port])) { /* * bc12_type result should be available. If not * available still, then function will return * 1. Set up additional 100 msec wait. Note that * if VBUS is no longer provided when this call * happens the funciton will return 0. */ bc12_det_mark[port] = bd9995x_bc12_check_type(port) ? get_time().val + 100 * MSEC : 0; /* Reset BC1.2 regs to skip auto-detection. */ bd9995x_bc12_detect(port, 0); } /* * Determine if a wait for reading bc12_type needs to be * scheduled. Use the scheduled wait for this port if * it's less than the wait needed for a previous * port. If previous port(s) don't need a wait, then * sleep_usec will be -1. */ if (bc12_det_mark[port]) { int bc12_wait_usec; bc12_wait_usec = bc12_det_mark[port] - get_time().val; if ((sleep_usec < 0) || (sleep_usec > bc12_wait_usec)) sleep_usec = bc12_wait_usec; } } initialized = 1; #ifdef CONFIG_BD9995X_DELAY_INPUT_PORT_SELECT /* * When a charge port is selected and VBUS is 5V, the inrush current on some * devices causes VBUS to droop, which could signal a sink disconnection. * * To mitigate the problem, charge port selection is delayed until VBUS * is stable or one second has passed. Hopefully PD has negotiated a VBUS * voltage of at least 9V before the one second timeout. */ if (select_input_port_update) { sleep_usec = VBUS_CHECK_MSEC; changed = 0; switch (vbus_state) { case START: vbus_timeout = get_time().val + STABLE_TIMEOUT; vbus_state = STABLE; break; case STABLE: if (get_time().val > vbus_timeout) { vbus_state = DEBOUNCE; vbus_timeout = get_time().val + DEBOUNCE_TIMEOUT; } break; case DEBOUNCE: if (bd9995x_vbus_debounce(port_update) || get_time().val > vbus_timeout) { select_input_port_update = 0; bd9995x_select_input_port_private( port_update, select_update); } break; } } #endif /* * Re-read interrupt registers immediately if we got an * interrupt. We're dealing with multiple independent * interrupt sources and the interrupt pin may have * never deasserted if both sources were not in clear * state simultaneously. */ if (!changed) task_wait_event(sleep_usec); } } #endif /* HAS_TASK_USB_CHG */ /*** Console commands ***/ #ifdef CONFIG_CMD_CHARGER_DUMP static int read_bat(uint8_t cmd) { int read = 0; ch_raw_read16(cmd, &read, BD9995X_BAT_CHG_COMMAND); return read; } static int read_ext(uint8_t cmd) { int read = 0; ch_raw_read16(cmd, &read, BD9995X_EXTENDED_COMMAND); return read; } /* Dump all readable registers on bd9995x */ static int console_bd9995x_dump_regs(int argc, char **argv) { int i; uint8_t regs[] = { 0x14, 0x15, 0x3c, 0x3d, 0x3e, 0x3f }; /* Battery group registers */ for (i = 0; i < ARRAY_SIZE(regs); ++i) ccprintf("BAT REG %4x: %4x\n", regs[i], read_bat(regs[i])); /* Extended group registers */ for (i = 0; i < 0x7f; ++i) { ccprintf("EXT REG %4x: %4x\n", i, read_ext(i)); cflush(); } return 0; } DECLARE_CONSOLE_COMMAND(charger_dump, console_bd9995x_dump_regs, NULL, "Dump all charger registers"); #endif /* CONFIG_CMD_CHARGER_DUMP */ #ifdef CONFIG_CMD_CHARGER static int console_command_bd9995x(int argc, char **argv) { int rv, reg, data, val; char rw, *e; enum bd9995x_command cmd; if (argc < 4) return EC_ERROR_PARAM_COUNT; rw = argv[1][0]; if (rw == 'w' && argc < 5) return EC_ERROR_PARAM_COUNT; else if (rw != 'w' && rw != 'r') return EC_ERROR_PARAM1; reg = strtoi(argv[2], &e, 16); if (*e || reg < 0) return EC_ERROR_PARAM2; cmd = strtoi(argv[3], &e, 0); if (*e || cmd < 0) return EC_ERROR_PARAM3; if (rw == 'r') rv = ch_raw_read16(reg, &data, cmd); else { val = strtoi(argv[4], &e, 16); if (*e || val < 0) return EC_ERROR_PARAM4; rv = ch_raw_write16(reg, val, cmd); if (rv == EC_SUCCESS) rv = ch_raw_read16(reg, &data, cmd); } if (rv == EC_SUCCESS) CPRINTS("register 0x%x [%d] = 0x%x [%d]", reg, reg, data, data); return rv; } DECLARE_CONSOLE_COMMAND(bd9995x, console_command_bd9995x, "bd9995x | ", "Read or write a charger register"); #endif /* CONFIG_CMD_CHARGER */ #ifdef CONFIG_CHARGER_PSYS_READ static int bd9995x_psys_charger_adc(void) { int i; int reg; uint64_t ipmon = 0; for (i = 0; i < BD9995X_PMON_IOUT_ADC_READ_COUNT; i++) { if (ch_raw_read16(BD9995X_CMD_PMON_DACIN_VAL, ®, BD9995X_EXTENDED_COMMAND)) return 0; /* Conversion Interval is 200us */ usleep(200); ipmon += reg; } /* * Calculate power in mW * PSYS = VACP×IACP+VBAT×IBAT = IPMON / GPMON */ return (int) ((ipmon * 1000) / (BIT(BD9995X_PSYS_GAIN_SELECT) * BD9995X_PMON_IOUT_ADC_READ_COUNT)); } static int bd9995x_enable_psys(void) { int rv; int reg; rv = ch_raw_read16(BD9995X_CMD_PMON_IOUT_CTRL_SET, ®, BD9995X_EXTENDED_COMMAND); if (rv) return rv; /* Enable PSYS & Select PSYS Gain */ reg &= ~BD9995X_CMD_PMON_IOUT_CTRL_SET_PMON_GAIN_SET_MASK; reg |= (BD9995X_CMD_PMON_IOUT_CTRL_SET_PMON_INSEL | BD9995X_CMD_PMON_IOUT_CTRL_SET_PMON_OUT_EN | BD9995X_PSYS_GAIN_SELECT); return ch_raw_write16(BD9995X_CMD_PMON_IOUT_CTRL_SET, reg, BD9995X_EXTENDED_COMMAND); } /** * Get system power. * * TODO(b:71520677): Implement charger_get_system_power, disable psys readout * when not needed (the code below leaves it enabled after the first access), * update "psys" console command to use charger_get_system_power and move it * to some common code. */ static int console_command_psys(int argc, char **argv) { int rv; rv = bd9995x_enable_psys(); if (rv) return rv; CPRINTS("PSYS from chg_adc: %d mW", bd9995x_psys_charger_adc()); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(psys, console_command_psys, NULL, "Get the system power in mW"); #endif /* CONFIG_CHARGER_PSYS_READ */ #ifdef CONFIG_CMD_CHARGER_ADC_AMON_BMON static int bd9995x_amon_bmon_chg_adc(void) { int i; int reg; int iout = 0; for (i = 0; i < BD9995X_PMON_IOUT_ADC_READ_COUNT; i++) { ch_raw_read16(BD9995X_CMD_IOUT_DACIN_VAL, ®, BD9995X_EXTENDED_COMMAND); iout += reg; /* Conversion Interval is 200us */ usleep(200); } /* * Discharge current in mA * IDCHG = iout * GIDCHG * IADP = iout * GIADP * * VIDCHG = GIDCHG * (VSRN- VSRP) = GIDCHG * IDCHG / IDCHG_RES * VIADP = GIADP * (VACP- VACN) = GIADP * IADP / IADP_RES */ return (iout * (5 << BD9995X_IOUT_GAIN_SELECT)) / (10 * BD9995X_PMON_IOUT_ADC_READ_COUNT); } static int bd9995x_amon_bmon(int amon_bmon) { int rv; int reg; int imon; int sns_res; rv = ch_raw_read16(BD9995X_CMD_PMON_IOUT_CTRL_SET, ®, BD9995X_EXTENDED_COMMAND); if (rv) return rv; /* Enable monitor */ reg &= ~BD9995X_CMD_PMON_IOUT_CTRL_SET_IOUT_GAIN_SET_MASK; reg |= (BD9995X_CMD_PMON_IOUT_CTRL_SET_IMON_INSEL | BD9995X_CMD_PMON_IOUT_CTRL_SET_IOUT_OUT_EN | (BD9995X_IOUT_GAIN_SELECT << 4)); if (amon_bmon) { reg |= BD9995X_CMD_PMON_IOUT_CTRL_SET_IOUT_SOURCE_SEL; sns_res = CONFIG_CHARGER_SENSE_RESISTOR_AC; } else { reg &= ~BD9995X_CMD_PMON_IOUT_CTRL_SET_IOUT_SOURCE_SEL; sns_res = CONFIG_CHARGER_SENSE_RESISTOR; } rv = ch_raw_write16(BD9995X_CMD_PMON_IOUT_CTRL_SET, reg, BD9995X_EXTENDED_COMMAND); if (rv) return rv; imon = bd9995x_amon_bmon_chg_adc(); CPRINTS("%cMON from chg_adc: %d uV, %d mA]", amon_bmon ? 'A' : 'B', imon * sns_res, imon); return EC_SUCCESS; } /** * Get charger AMON and BMON current. */ static int console_command_amon_bmon(int argc, char **argv) { int rv = EC_ERROR_PARAM1; /* Switch to AMON */ if (argc == 1 || (argc >= 2 && argv[1][0] == 'a')) rv = bd9995x_amon_bmon(1); /* Switch to BMON */ if (argc == 1 || (argc >= 2 && argv[1][0] == 'b')) rv = bd9995x_amon_bmon(0); return rv; } DECLARE_CONSOLE_COMMAND(amonbmon, console_command_amon_bmon, "amonbmon [a|b]", "Get charger AMON/BMON voltage diff, current"); #endif /* CONFIG_CMD_CHARGER_ADC_AMON_BMON */ #ifdef CONFIG_CMD_I2C_STRESS_TEST_CHARGER static int bd9995x_i2c_read(const int reg, int *data) { return ch_raw_read16(reg, data, BD9995X_EXTENDED_COMMAND); } static int bd9995x_i2c_write(const int reg, int data) { return ch_raw_write16(reg, data, BD9995X_EXTENDED_COMMAND); } /* BD9995X_CMD_CHIP_ID register value may vary by chip. */ struct i2c_stress_test_dev bd9995x_i2c_stress_test_dev = { .reg_info = { .read_reg = BD9995X_CMD_CHIP_ID, .read_val = BD99956_CHIP_ID, .write_reg = BD9995X_CMD_ITRICH_SET, }, .i2c_read_dev = &bd9995x_i2c_read, .i2c_write_dev = &bd9995x_i2c_write, }; #endif /* CONFIG_CMD_I2C_STRESS_TEST_CHARGER */