/* Copyright 2019 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. */ /* PI3USB9201 USB BC 1.2 Charger Detector driver. */ #include "pi3usb9201.h" #include "charge_manager.h" #include "chipset.h" #include "common.h" #include "console.h" #include "gpio.h" #include "power.h" #include "task.h" #include "tcpm.h" #include "timer.h" #include "usb_charge.h" #include "usb_pd.h" #include "util.h" #define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ## args) enum pi3usb9201_client_sts { CHG_OTHER = 0, CHG_2_4A, CHG_2_0A, CHG_1_0A, CHG_RESERVED, CHG_CDP, CHG_SDP, CHG_DCP, }; struct bc12_status { enum charge_supplier supplier; int current_limit; }; /* Used to store last BC1.2 detection result */ static enum charge_supplier bc12_supplier[CONFIG_USB_PD_PORT_COUNT]; static const struct bc12_status bc12_chg_limits[] = { [CHG_OTHER] = {CHARGE_SUPPLIER_OTHER, 500}, [CHG_2_4A] = {CHARGE_SUPPLIER_PROPRIETARY, 2400}, [CHG_2_0A] = {CHARGE_SUPPLIER_PROPRIETARY, 2000}, [CHG_1_0A] = {CHARGE_SUPPLIER_PROPRIETARY, 1000}, [CHG_RESERVED] = {CHARGE_SUPPLIER_NONE, 0}, [CHG_CDP] = {CHARGE_SUPPLIER_BC12_CDP, 1500}, [CHG_SDP] = {CHARGE_SUPPLIER_BC12_SDP, 500}, #if defined(CONFIG_CHARGE_RAMP_SW) || defined(CONFIG_CHARGE_RAMP_HW) /* * If ramping is supported, then for DCP set the current limit to be the * max supported for the port by the board. This because for DCP the * charger is allowed to set its own max up to 5A. */ [CHG_DCP] = {CHARGE_SUPPLIER_BC12_DCP, PD_MAX_CURRENT_MA}, #else [CHG_DCP] = {CHARGE_SUPPLIER_BC12_DCP, 500}, #endif }; static inline int raw_read8(int port, int offset, int *value) { return i2c_read8__7bf(pi3usb2901_bc12_chips[port].i2c_port, pi3usb2901_bc12_chips[port].i2c_addr__7bf, offset, value); } static inline int raw_write8(int port, int offset, int value) { return i2c_write8__7bf(pi3usb2901_bc12_chips[port].i2c_port, pi3usb2901_bc12_chips[port].i2c_addr__7bf, offset, value); } static int pi3usb9201_raw(int port, int reg, int mask, int val) { int rv; int reg_val; rv = raw_read8(port, reg, ®_val); if (rv) return rv; reg_val &= ~mask; reg_val |= val; return raw_write8(port, reg, reg_val); } static int pi3usb9201_interrupt_mask(int port, int enable) { return pi3usb9201_raw(port, PI3USB9201_REG_CTRL_1, PI3USB9201_REG_CTRL_1_INT_MASK, enable); } static int pi3usb9201_bc12_detect_ctrl(int port, int enable) { return pi3usb9201_raw(port, PI3USB9201_REG_CTRL_2, PI3USB9201_REG_CTRL_2_START_DET, enable ? PI3USB9201_REG_CTRL_2_START_DET : 0); } static int pi3usb9201_set_mode(int port, int desired_mode) { return pi3usb9201_raw(port, PI3USB9201_REG_CTRL_1, PI3USB9201_REG_CTRL_1_MODE_MASK, desired_mode << PI3USB9201_REG_CTRL_1_MODE_SHIFT); } static int pi3usb9201_get_mode(int port, int *mode) { int rv; rv = raw_read8(port, PI3USB9201_REG_CTRL_1, mode); if (rv) return rv; *mode &= PI3USB9201_REG_CTRL_1_MODE_MASK; *mode >>= PI3USB9201_REG_CTRL_1_MODE_SHIFT; return EC_SUCCESS; } static int pi3usb9201_get_status(int port, int *client, int *host) { int rv; int status; rv = raw_read8(port, PI3USB9201_REG_CLIENT_STS, &status); if (client) *client = status; rv |= raw_read8(port, PI3USB9201_REG_HOST_STS, &status); if (host) *host = status; return rv; } static void bc12_update_supplier(enum charge_supplier supplier, int port, struct charge_port_info *new_chg) { /* * If most recent supplier type is not CHARGE_SUPPLIER_NONE, then the * charge manager table entry for that supplier type needs to be cleared * out. */ if (bc12_supplier[port] != CHARGE_SUPPLIER_NONE) charge_manager_update_charge(bc12_supplier[port], port, NULL); /* Now update the current supplier type */ bc12_supplier[port] = supplier; /* If new supplier type != NONE, then notify charge manager */ if (supplier != CHARGE_SUPPLIER_NONE) charge_manager_update_charge(supplier, port, new_chg); } static void bc12_update_charge_manager(int port, int client_status) { struct charge_port_info new_chg; enum charge_supplier supplier; int bit_pos; /* Set charge voltage to 5V */ new_chg.voltage = USB_CHARGER_VOLTAGE_MV; /* * Find set bit position. Note that this funciton is only called if a * bit was set in client_status, so bit_pos won't be negative. */ bit_pos = __builtin_ffs(client_status) - 1; new_chg.current = bc12_chg_limits[bit_pos].current_limit; supplier = bc12_chg_limits[bit_pos].supplier; CPRINTS("pi3usb9201[p%d]: sts = 0x%x, lim = %d mA, supplier = %d", port, client_status, new_chg.current, supplier); /* bc1.2 is complete and start bit does not auto clear */ pi3usb9201_bc12_detect_ctrl(port, 0); /* Inform charge manager of new supplier type and current limit */ bc12_update_supplier(supplier, port, &new_chg); } static int bc12_detect_start(int port) { int rv; /* * Read both status registers to ensure that all interrupt indications * are cleared prior to starting bc1.2 detection. */ pi3usb9201_get_status(port, NULL, NULL); /* Put pi3usb9201 into client mode */ rv = pi3usb9201_set_mode(port, PI3USB9201_CLIENT_MODE); if (rv) return rv; /* Have pi3usb9201 start bc1.2 detection */ rv = pi3usb9201_bc12_detect_ctrl(port, 1); if (rv) return rv; /* Unmask interrupt to wake task when detection completes */ return pi3usb9201_interrupt_mask(port, 0); } static void bc12_power_down(int port) { /* Put pi3usb9201 into its power down mode */ pi3usb9201_set_mode(port, PI3USB9201_POWER_DOWN); /* The start bc1.2 bit does not auto clear */ pi3usb9201_bc12_detect_ctrl(port, 0); /* Mask interrupts unitl next bc1.2 detection event */ pi3usb9201_interrupt_mask(port, 1); /* * Let charge manager know there's no more charge available for the * supplier type that was most recently detected. */ bc12_update_supplier(CHARGE_SUPPLIER_NONE, port, NULL); #if defined(CONFIG_POWER_PP5000_CONTROL) && defined(HAS_TASK_CHIPSET) /* Indicate PP5000_A rail is not required by USB_CHG task. */ power_5v_enable(task_get_current(), 0); #endif } static void bc12_power_up(int port) { #if defined(CONFIG_POWER_PP5000_CONTROL) && defined(HAS_TASK_CHIPSET) /* Turn on the 5V rail to allow the chip to be powered. */ power_5v_enable(task_get_current(), 1); /* Give the pi3usb9201 time so it's ready to receive i2c messages */ msleep(1); #endif pi3usb9201_interrupt_mask(port, 1); } void usb_charger_task(void *u) { int port = (task_get_current() == TASK_ID_USB_CHG_P0 ? 0 : 1); uint32_t evt; int i; /* * Set most recent bc1.2 detection supplier result to * CHARGE_SUPPLIER_NONE for all ports. */ for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) bc12_supplier[port] = CHARGE_SUPPLIER_NONE; /* * The is no specific initialization required for the pi3usb9201 other * than enabling the interrupt mask. */ pi3usb9201_interrupt_mask(port, 1); while (1) { /* Wait for interrupt */ evt = task_wait_event(-1); /* Interrupt from the Pericom chip, determine charger type */ if (evt & USB_CHG_EVENT_BC12) { int client; int rv; rv = pi3usb9201_get_status(port, &client, NULL); if (!rv && client) /* * Any bit set in client status register * indicates that BC1.2 detection has * completed. */ bc12_update_charge_manager(port, client); } #ifndef CONFIG_USB_PD_VBUS_DETECT_TCPC if (evt & USB_CHG_EVENT_VBUS) CPRINTS("VBUS p%d %d", port, pd_snk_is_vbus_provided(port)); #endif if (evt & USB_CHG_EVENT_DR_UFP) { bc12_power_up(port); if (bc12_detect_start(port)) { struct charge_port_info new_chg; /* * VBUS is present, but starting bc1.2 detection * failed for some reason. So limit charge * current to default 500 mA for this case. */ new_chg.voltage = USB_CHARGER_VOLTAGE_MV; new_chg.current = USB_CHARGER_MIN_CURR_MA; /* Save supplier type and notify chg manager */ bc12_update_supplier(CHARGE_SUPPLIER_OTHER, port, &new_chg); CPRINTS("pi3usb9201[p%d]: bc1.2 failed use " "defaults", port); } } /* * TODO(b/124061702): For host mode, currently only setting it * to host CDP mode. However, there are 3 host status bits to * know things such as an adapter connected, but no USB device * present, or bc1.2 activity detected. */ if (evt & USB_CHG_EVENT_DR_DFP) { int mode; int rv; /* * Update the charge manager if bc1.2 client mode is * currently active. */ bc12_update_supplier(CHARGE_SUPPLIER_NONE, port, NULL); /* * If the port is in DFP mode, then need to set mode to * CDP_HOST which will auto close D+/D- switches. */ bc12_power_up(port); rv = pi3usb9201_get_mode(port, &mode); if (!rv && (mode != PI3USB9201_CDP_HOST_MODE)) pi3usb9201_set_mode(port, PI3USB9201_CDP_HOST_MODE); } if (evt & USB_CHG_EVENT_CC_OPEN) bc12_power_down(port); } } void usb_charger_set_switches(int port, enum usb_switch setting) { /* * Switches are controlled automatically based on whether the port is * acting as a source or as sink and the result of BC1.2 detection. */ } #if defined(CONFIG_CHARGE_RAMP_SW) || defined(CONFIG_CHARGE_RAMP_HW) int usb_charger_ramp_allowed(int supplier) { /* Don't allow ramp if charge supplier is OTHER, SDP, or NONE */ return !(supplier == CHARGE_SUPPLIER_OTHER || supplier == CHARGE_SUPPLIER_BC12_SDP || supplier == CHARGE_SUPPLIER_NONE); } int usb_charger_ramp_max(int supplier, int sup_curr) { /* * Use the level from the bc12_chg_limits table above except for * proprietary of CDP and in those cases the charge current from the * charge manager is already set at the max determined by bc1.2 * detection. */ switch (supplier) { case CHARGE_SUPPLIER_BC12_DCP: return PD_MAX_CURRENT_MA; case CHARGE_SUPPLIER_BC12_CDP: case CHARGE_SUPPLIER_PROPRIETARY: return sup_curr; case CHARGE_SUPPLIER_BC12_SDP: default: return 500; } } #endif /* CONFIG_CHARGE_RAMP_SW || CONFIG_CHARGE_RAMP_HW */