| /* 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 "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_MAX_COUNT]; |
| |
| /* |
| * The USB Type-C specification limits the maximum amount of current from BC 1.2 |
| * suppliers to 1.5A. Technically, proprietary methods are not allowed, but we |
| * will continue to allow those. |
| */ |
| static const struct bc12_status bc12_chg_limits[] = { |
| [CHG_OTHER] = {CHARGE_SUPPLIER_OTHER, 500}, |
| [CHG_2_4A] = {CHARGE_SUPPLIER_PROPRIETARY, USB_CHARGER_MAX_CURR_MA}, |
| [CHG_2_0A] = {CHARGE_SUPPLIER_PROPRIETARY, USB_CHARGER_MAX_CURR_MA}, |
| [CHG_1_0A] = {CHARGE_SUPPLIER_PROPRIETARY, 1000}, |
| [CHG_RESERVED] = {CHARGE_SUPPLIER_NONE, 0}, |
| [CHG_CDP] = {CHARGE_SUPPLIER_BC12_CDP, USB_CHARGER_MAX_CURR_MA}, |
| [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 or 1.5A (whichever is lower). |
| * Although, the BC 1.2 specification allows DCP suppliers to ramp to |
| * much higher currents, the USB Type-C specification limits the |
| * maximum current allowed for BC 1.2 suppliers to 1.5A. |
| */ |
| [CHG_DCP] = {CHARGE_SUPPLIER_BC12_DCP, USB_CHARGER_MAX_CURR_MA}, |
| #else |
| [CHG_DCP] = {CHARGE_SUPPLIER_BC12_DCP, 500}, |
| #endif |
| }; |
| |
| static inline int raw_read8(int port, int offset, int *value) |
| { |
| return i2c_read8(pi3usb9201_bc12_chips[port].i2c_port, |
| pi3usb9201_bc12_chips[port].i2c_addr_flags, |
| offset, value); |
| } |
| |
| static int pi3usb9201_raw(int port, int reg, int mask, int val) |
| { |
| /* Clear mask and then set val in i2c reg value */ |
| return i2c_field_update8(pi3usb9201_bc12_chips[port].i2c_port, |
| pi3usb9201_bc12_chips[port].i2c_addr_flags, |
| reg, mask, 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); |
| |
| /* There's nothing else to do if the part is always powered. */ |
| if (pi3usb9201_bc12_chips[port].flags & PI3USB9201_ALWAYS_POWERED) |
| return; |
| |
| #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 (IS_ENABLED(CONFIG_POWER_PP5000_CONTROL) && |
| IS_ENABLED(HAS_TASK_CHIPSET) && |
| !(pi3usb9201_bc12_chips[port].flags & PI3USB9201_ALWAYS_POWERED)) { |
| /* 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); |
| } |
| |
| pi3usb9201_interrupt_mask(port, 1); |
| } |
| |
| static void pi3usb9201_usb_charger_task(const int port) |
| { |
| uint32_t evt; |
| int i; |
| |
| /* |
| * Set most recent bc1.2 detection supplier result to |
| * CHARGE_SUPPLIER_NONE for all ports. |
| */ |
| for (i = 0; i < board_get_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 host; |
| int rv; |
| |
| rv = pi3usb9201_get_status(port, &client, &host); |
| if (!rv && client) |
| /* |
| * Any bit set in client status register |
| * indicates that BC1.2 detection has |
| * completed. |
| */ |
| bc12_update_charge_manager(port, client); |
| if (!rv && host) { |
| /* |
| * Switch to SDP after device is plugged in to |
| * avoid noise (pulse on D-) causing USB |
| * disconnect (b/156014140). |
| */ |
| if (host & PI3USB9201_REG_HOST_STS_DEV_PLUG) |
| pi3usb9201_set_mode(port, |
| PI3USB9201_SDP_HOST_MODE); |
| /* |
| * Switch to CDP after device is unplugged so |
| * we advertise higher power available for next |
| * device. |
| */ |
| if (host & PI3USB9201_REG_HOST_STS_DEV_UNPLUG) |
| pi3usb9201_set_mode(port, |
| PI3USB9201_CDP_HOST_MODE); |
| } |
| /* |
| * TODO(b/124061702): Use host status to allocate power |
| * more intelligently. |
| */ |
| } |
| |
| #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); |
| } |
| } |
| |
| 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)) { |
| CPRINTS("pi3usb9201[p%d]: CDP_HOST mode", port); |
| /* |
| * Read both status registers to ensure that all |
| * interrupt indications are cleared prior to |
| * starting DFP CDP host mode. |
| */ |
| pi3usb9201_get_status(port, NULL, NULL); |
| pi3usb9201_set_mode(port, |
| PI3USB9201_CDP_HOST_MODE); |
| /* |
| * Unmask interrupt to wake task when host |
| * status changes. |
| */ |
| pi3usb9201_interrupt_mask(port, 0); |
| } |
| } |
| |
| if (evt & USB_CHG_EVENT_CC_OPEN) |
| bc12_power_down(port); |
| } |
| } |
| |
| #if defined(CONFIG_CHARGE_RAMP_SW) || defined(CONFIG_CHARGE_RAMP_HW) |
| static int pi3usb9201_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); |
| } |
| |
| static int pi3usb9201_ramp_max(int supplier, int sup_curr) |
| { |
| /* |
| * Use the level from the bc12_chg_limits table above except for |
| * proprietary or 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 USB_CHARGER_MAX_CURR_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 */ |
| |
| const struct bc12_drv pi3usb9201_drv = { |
| .usb_charger_task = pi3usb9201_usb_charger_task, |
| #if defined(CONFIG_CHARGE_RAMP_SW) || defined(CONFIG_CHARGE_RAMP_HW) |
| .ramp_allowed = pi3usb9201_ramp_allowed, |
| .ramp_max = pi3usb9201_ramp_max, |
| #endif /* CONFIG_CHARGE_RAMP_SW || CONFIG_CHARGE_RAMP_HW */ |
| }; |
| |
| #ifdef CONFIG_BC12_SINGLE_DRIVER |
| /* provide a default bc12_ports[] for backward compatibility */ |
| struct bc12_config bc12_ports[CHARGE_PORT_COUNT] = { |
| [0 ... (CHARGE_PORT_COUNT - 1)] = { |
| .drv = &pi3usb9201_drv, |
| } |
| }; |
| #endif /* CONFIG_BC12_SINGLE_DRIVER */ |