summaryrefslogtreecommitdiff
path: root/driver/usb_switch_pi3usb9281.c
diff options
context:
space:
mode:
Diffstat (limited to 'driver/usb_switch_pi3usb9281.c')
-rw-r--r--driver/usb_switch_pi3usb9281.c216
1 files changed, 196 insertions, 20 deletions
diff --git a/driver/usb_switch_pi3usb9281.c b/driver/usb_switch_pi3usb9281.c
index 063d7d3983..569438b761 100644
--- a/driver/usb_switch_pi3usb9281.c
+++ b/driver/usb_switch_pi3usb9281.c
@@ -5,13 +5,18 @@
* Pericom PI3USB3281 USB port switch driver.
*/
+#include "charge_manager.h"
+#include "common.h"
#include "console.h"
+#include "ec_commands.h"
#include "gpio.h"
#include "hooks.h"
#include "i2c.h"
+#include "pi3usb9281.h"
#include "task.h"
#include "timer.h"
-#include "pi3usb9281.h"
+#include "usb_charge.h"
+#include "usb_pd.h"
#include "util.h"
/* Console output macros */
@@ -24,6 +29,18 @@
/* Delay values */
#define PI3USB9281_SW_RESET_DELAY 20
+/* Wait after a charger is detected to debounce pin contact order */
+#define PI3USB9281_DETECT_DEBOUNCE_DELAY_MS 1000
+/*
+ * Wait after reset, before re-enabling attach interrupt, so that the
+ * spurious attach interrupt from certain ports is ignored.
+ */
+#define PI3USB9281_RESET_DEBOUNCE_DELAY_MS 100
+
+/* Store the state of our USB data switches so that they can be restored. */
+static int usb_switch_state[CONFIG_USB_PD_PORT_COUNT];
+static struct mutex usb_switch_lock[CONFIG_USB_PD_PORT_COUNT];
+
static void select_chip(int port)
{
struct pi3usb9281_config *chip = &pi3usb9281_chips[port];
@@ -81,6 +98,11 @@ static int pi3usb9281_write_ctrl(int port, uint8_t ctrl)
PI3USB9281_CTRL_RSVD_1);
}
+static int pi3usb9281_set_interrupt_mask(int port, uint8_t mask)
+{
+ return pi3usb9281_write(port, PI3USB9281_REG_INT_MASK, ~mask);
+}
+
void pi3usb9281_init(int port)
{
uint8_t dev_id;
@@ -118,11 +140,6 @@ int pi3usb9281_disable_interrupts(int port)
return rv;
}
-int pi3usb9281_set_interrupt_mask(int port, uint8_t mask)
-{
- return pi3usb9281_write(port, PI3USB9281_REG_INT_MASK, ~mask);
-}
-
int pi3usb9281_get_interrupts(int port)
{
return pi3usb9281_read(port, PI3USB9281_REG_INT);
@@ -138,7 +155,7 @@ int pi3usb9281_get_charger_status(int port)
return pi3usb9281_read(port, PI3USB9281_REG_CHG_STATUS) & 0x1f;
}
-int pi3usb9281_get_ilim(int device_type, int charger_status)
+static int pi3usb9281_get_ilim(int device_type, int charger_status)
{
/* Limit USB port current. 500mA for not listed types. */
int current_limit_ma = 500;
@@ -160,16 +177,7 @@ int pi3usb9281_get_ilim(int device_type, int charger_status)
return current_limit_ma;
}
-int pi3usb9281_get_vbus(int port)
-{
- int vbus = pi3usb9281_read(port, PI3USB9281_REG_VBUS);
- if (vbus == 0xee)
- return EC_ERROR_UNKNOWN;
-
- return !!(vbus & 0x2);
-}
-
-int pi3usb9281_reset(int port)
+static int pi3usb9281_reset(int port)
{
int rv = pi3usb9281_write(port, PI3USB9281_REG_RESET, 0x1);
@@ -180,7 +188,7 @@ int pi3usb9281_reset(int port)
return rv;
}
-int pi3usb9281_set_switch_manual(int port, int val)
+static int pi3usb9281_set_switch_manual(int port, int val)
{
uint8_t ctrl = pi3usb9281_read(port, PI3USB9281_REG_CONTROL);
@@ -195,12 +203,12 @@ int pi3usb9281_set_switch_manual(int port, int val)
return pi3usb9281_write_ctrl(port, ctrl);
}
-int pi3usb9281_set_pins(int port, uint8_t val)
+static int pi3usb9281_set_pins(int port, uint8_t val)
{
return pi3usb9281_write(port, PI3USB9281_REG_MANUAL, val);
}
-int pi3usb9281_set_switches(int port, int open)
+static int pi3usb9281_set_switches(int port, int open)
{
uint8_t ctrl = pi3usb9281_read(port, PI3USB9281_REG_CONTROL);
@@ -214,3 +222,171 @@ int pi3usb9281_set_switches(int port, int open)
return pi3usb9281_write_ctrl(port, ctrl);
}
+
+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]);
+}
+
+static void 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(PI3USB9281_DETECT_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(PI3USB9281_RESET_DEBOUNCE_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);
+
+ bc12_detect(port);
+
+ while (1) {
+ /* Wait for interrupt */
+ evt = task_wait_event(-1);
+
+ /* Interrupt from the Pericom chip, determine charger type */
+ if (evt & USB_CHG_EVENT_BC12) {
+ /* Read interrupt register to clear on chip */
+ pi3usb9281_get_interrupts(port);
+ bc12_detect(port);
+ } else if (evt & USB_CHG_EVENT_INTR) {
+ /* Check the interrupt register, and clear on chip */
+ interrupt = pi3usb9281_get_interrupts(port);
+ if (interrupt & attach_mask)
+ 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
+ }
+ }
+}