| /* Copyright 2015 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. |
| */ |
| |
| /* |
| * USB charger / BC1.2 task. This code assumes that CONFIG_CHARGE_MANAGER |
| * is defined and implemented. PI3USB9281 is the only charger detector |
| * currently supported. |
| */ |
| |
| #include "charge_manager.h" |
| #include "common.h" |
| #include "console.h" |
| #include "ec_commands.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "pi3usb9281.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "usb_charge.h" |
| #include "usb_pd.h" |
| |
| #define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ## args) |
| |
| /* Wait after a charger is detected to debounce pin contact order */ |
| #define USB_CHG_DEBOUNCE_DELAY_MS 1000 |
| /* |
| * Wait after reset, before re-enabling attach interrupt, so that the |
| * spurious attach interrupt from certain ports is ignored. |
| */ |
| #define USB_CHG_RESET_DELAY_MS 100 |
| |
| /* |
| * Store the state of our USB data switches so that they can be restored |
| * after pericom reset. |
| */ |
| static int usb_switch_state[CONFIG_USB_PD_PORT_COUNT]; |
| static struct mutex usb_switch_lock[CONFIG_USB_PD_PORT_COUNT]; |
| |
| static void update_vbus_supplier(int port, int vbus_level) |
| { |
| struct charge_port_info charge; |
| |
| /* |
| * If VBUS is low, or VBUS is high and we are not outputting VBUS |
| * ourselves, then update the VBUS supplier. |
| */ |
| if (!vbus_level || !usb_charger_port_is_sourcing_vbus(port)) { |
| charge.voltage = USB_CHARGER_VOLTAGE_MV; |
| charge.current = vbus_level ? USB_CHARGER_MIN_CURR_MA : 0; |
| charge_manager_update_charge(CHARGE_SUPPLIER_VBUS, |
| port, |
| &charge); |
| } |
| } |
| |
| int usb_charger_port_is_sourcing_vbus(int port) |
| { |
| if (port == 0) |
| return gpio_get_level(GPIO_USB_C0_5V_EN); |
| #if CONFIG_USB_PD_PORT_COUNT >= 2 |
| else if (port == 1) |
| return gpio_get_level(GPIO_USB_C1_5V_EN); |
| #endif |
| /* Not a valid port */ |
| return 0; |
| } |
| |
| 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; |
| |
| mutex_lock(&usb_switch_lock[port]); |
| if (setting != USB_SWITCH_RESTORE) |
| usb_switch_state[port] = setting; |
| pi3usb9281_set_switches(port, usb_switch_state[port]); |
| mutex_unlock(&usb_switch_lock[port]); |
| } |
| |
| void usb_charger_vbus_change(int port, int vbus_level) |
| { |
| /* If VBUS has transitioned low, notify PD module directly */ |
| pd_vbus_low(port); |
| /* Update VBUS supplier and signal VBUS change to USB_CHG task */ |
| update_vbus_supplier(port, vbus_level); |
| #if CONFIG_USB_PD_PORT_COUNT == 2 |
| task_set_event(port ? TASK_ID_USB_CHG_P1 : TASK_ID_USB_CHG_P0, |
| USB_CHG_EVENT_VBUS, 0); |
| #else |
| task_set_event(TASK_ID_USB_CHG_P0, USB_CHG_EVENT_VBUS, 0); |
| #endif |
| } |
| |
| static void usb_charger_bc12_detect(int port) |
| { |
| int device_type, charger_status; |
| struct charge_port_info charge; |
| int type; |
| |
| charge.voltage = USB_CHARGER_VOLTAGE_MV; |
| |
| if (usb_charger_port_is_sourcing_vbus(port)) { |
| /* If we're sourcing VBUS then we're not charging */ |
| device_type = charger_status = 0; |
| } else { |
| /* Set device type */ |
| device_type = pi3usb9281_get_device_type(port); |
| charger_status = pi3usb9281_get_charger_status(port); |
| } |
| |
| /* Debounce pin plug order if we detect a charger */ |
| if (device_type || PI3USB9281_CHG_STATUS_ANY(charger_status)) { |
| /* next operation might trigger a detach interrupt */ |
| pi3usb9281_disable_interrupts(port); |
| /* |
| * Ensure D+/D- are open before resetting |
| * Note: we can't simply call pi3usb9281_set_switches() because |
| * another task might override it and set the switches closed. |
| */ |
| pi3usb9281_set_switch_manual(port, 1); |
| pi3usb9281_set_pins(port, 0); |
| |
| /* Delay to debounce pin attach order */ |
| msleep(USB_CHG_DEBOUNCE_DELAY_MS); |
| |
| /* |
| * Trigger chip reset to refresh detection registers. |
| * WARNING: This reset is acceptable for samus_pd, |
| * but may not be acceptable for devices that have |
| * an OTG / device mode, as we may be interrupting |
| * the connection. |
| */ |
| pi3usb9281_reset(port); |
| /* |
| * Restore data switch settings - switches return to |
| * closed on reset until restored. |
| */ |
| usb_charger_set_switches(port, USB_SWITCH_RESTORE); |
| /* Clear possible disconnect interrupt */ |
| pi3usb9281_get_interrupts(port); |
| /* Mask attach interrupt */ |
| pi3usb9281_set_interrupt_mask(port, |
| 0xff & |
| ~PI3USB9281_INT_ATTACH); |
| /* Re-enable interrupts */ |
| pi3usb9281_enable_interrupts(port); |
| msleep(USB_CHG_RESET_DELAY_MS); |
| |
| /* Clear possible attach interrupt */ |
| pi3usb9281_get_interrupts(port); |
| /* Re-enable attach interrupt */ |
| pi3usb9281_set_interrupt_mask(port, 0xff); |
| |
| /* Re-read ID registers */ |
| device_type = pi3usb9281_get_device_type(port); |
| charger_status = pi3usb9281_get_charger_status(port); |
| } |
| |
| /* Attachment: decode + update available charge */ |
| if (device_type || PI3USB9281_CHG_STATUS_ANY(charger_status)) { |
| if (PI3USB9281_CHG_STATUS_ANY(charger_status)) |
| type = CHARGE_SUPPLIER_PROPRIETARY; |
| else if (device_type & PI3USB9281_TYPE_CDP) |
| type = CHARGE_SUPPLIER_BC12_CDP; |
| else if (device_type & PI3USB9281_TYPE_DCP) |
| type = CHARGE_SUPPLIER_BC12_DCP; |
| else if (device_type & PI3USB9281_TYPE_SDP) |
| type = CHARGE_SUPPLIER_BC12_SDP; |
| else |
| type = CHARGE_SUPPLIER_OTHER; |
| |
| charge.current = pi3usb9281_get_ilim(device_type, |
| charger_status); |
| charge_manager_update_charge(type, port, &charge); |
| } else { /* Detachment: update available charge to 0 */ |
| charge.current = 0; |
| charge_manager_update_charge( |
| CHARGE_SUPPLIER_PROPRIETARY, |
| port, |
| &charge); |
| charge_manager_update_charge( |
| CHARGE_SUPPLIER_BC12_CDP, |
| port, |
| &charge); |
| charge_manager_update_charge( |
| CHARGE_SUPPLIER_BC12_DCP, |
| port, |
| &charge); |
| charge_manager_update_charge( |
| CHARGE_SUPPLIER_BC12_SDP, |
| port, |
| &charge); |
| charge_manager_update_charge( |
| CHARGE_SUPPLIER_OTHER, |
| port, |
| &charge); |
| } |
| |
| /* notify host of power info change */ |
| pd_send_host_event(PD_EVENT_POWER_CHANGE); |
| } |
| |
| void usb_charger_task(void) |
| { |
| const int attach_mask = PI3USB9281_INT_ATTACH | PI3USB9281_INT_DETACH; |
| int port = (task_get_current() == TASK_ID_USB_CHG_P0 ? 0 : 1); |
| int interrupt; |
| uint32_t evt; |
| |
| /* Initialize chip and enable interrupts */ |
| pi3usb9281_init(port); |
| |
| usb_charger_bc12_detect(port); |
| |
| while (1) { |
| /* Wait for interrupt */ |
| evt = task_wait_event(-1); |
| /* Read interrupt register to clear on chip */ |
| interrupt = pi3usb9281_get_interrupts(port); |
| |
| /* Interrupt from the Pericom chip, determine interrupt type */ |
| if (evt & USB_CHG_EVENT_INTR) { |
| if (interrupt & attach_mask) |
| usb_charger_bc12_detect(port); |
| } |
| |
| /* Ignore interrupt status and determine charger type */ |
| if (evt & USB_CHG_EVENT_BC12) |
| usb_charger_bc12_detect(port); |
| |
| /* |
| * Re-enable interrupts on pericom charger detector since the |
| * chip may periodically reset itself, and come back up with |
| * registers in default state. TODO(crosbug.com/p/33823): Fix |
| * these unwanted resets. |
| */ |
| if (evt & USB_CHG_EVENT_VBUS) { |
| pi3usb9281_enable_interrupts(port); |
| #ifndef CONFIG_USB_PD_TCPM_VBUS |
| CPRINTS("VBUS p%d %d", port, |
| pd_snk_is_vbus_provided(port)); |
| #endif |
| } |
| } |
| } |
| |
| static void usb_charger_init(void) |
| { |
| int i; |
| struct charge_port_info charge_none; |
| |
| /* Initialize all pericom charge suppliers to 0 */ |
| charge_none.voltage = USB_CHARGER_VOLTAGE_MV; |
| charge_none.current = 0; |
| for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) { |
| charge_manager_update_charge(CHARGE_SUPPLIER_PROPRIETARY, |
| i, |
| &charge_none); |
| charge_manager_update_charge(CHARGE_SUPPLIER_BC12_CDP, |
| i, |
| &charge_none); |
| charge_manager_update_charge(CHARGE_SUPPLIER_BC12_DCP, |
| i, |
| &charge_none); |
| charge_manager_update_charge(CHARGE_SUPPLIER_BC12_SDP, |
| i, |
| &charge_none); |
| charge_manager_update_charge(CHARGE_SUPPLIER_OTHER, |
| i, |
| &charge_none); |
| |
| #ifndef CONFIG_USB_PD_TCPM_VBUS |
| /* Initialize VBUS supplier based on whether VBUS is present */ |
| update_vbus_supplier(i, pd_snk_is_vbus_provided(i)); |
| #endif |
| } |
| } |
| DECLARE_HOOK(HOOK_INIT, usb_charger_init, HOOK_PRIO_DEFAULT); |