From 8e29d9c2e80688e9ebfa8358d2c01ecff261bc92 Mon Sep 17 00:00:00 2001 From: Alec Berg Date: Tue, 8 Jul 2014 16:48:17 -0700 Subject: pd: support for dual-role port Add support for toggling between source and sink as dual-role port. When transitioning to S0, we turn toggling on, when transitioning to S3, we turn toggling off but remain in the same PD state, and when transitioning to S5, we turn toggling off and force the PD role to a sink. Note, when toggling is off, the source disconnected state is allowed to transition to sink disconnected, but not vice versa. This means that if you go into S3 as a source, it will remain a source until the device is unplugged, at which point it will transition to a sink until the next transition to S0. The spec specifies: tDRP: 50ms - 100ms, Period a DRP shall complete a DFP to UFP and back dcDRP: 30% - 70%, Percent of time that a DRP shall advertise DFP tDRPHold: 100ms - 150ms, time to hold VBUS on after a DRP detects a UFP tDRPLock: 100ms - 150ms, time to stay in DFP after detecting loss of UFP This CL uses 40ms for time as a UFP (sink), 30ms for time as a DFP (source), and 120ms for hold and lock times. Also, if advertising as a DFP (source) and VBUS is detected, this automatically switches to a UFP (sink). BUG=chrome-os-partner:28782 BRANCH=none TEST=test on samus, make sure we are toggling between source and sink when disconnected. make sure plugging in zinger switches state machine through to sink_ready and make sure plugging in a USB switches to source_discovery. tested on a fruitpie by scoping the CC line and verifying the timing (except the hold time which I can't easily test). tested that dual role toggling is off in s3 and s5. also verified that going into s3 as a source keeps the port as a source and going into s5 switches it to a sink. Change-Id: I478634861f694164301d71359da35142ee7ebf75 Signed-off-by: Alec Berg Reviewed-on: https://chromium-review.googlesource.com/207154 Reviewed-by: Vincent Palatin --- board/samus_pd/board.c | 50 ++++++++++++++++- board/samus_pd/usb_pd_policy.c | 22 ++++++++ common/usb_pd_protocol.c | 120 +++++++++++++++++++++++++++++++++++++---- include/usb_pd.h | 17 ++++++ 4 files changed, 198 insertions(+), 11 deletions(-) diff --git a/board/samus_pd/board.c b/board/samus_pd/board.c index 7c40859fbb..cb6431d6ff 100644 --- a/board/samus_pd/board.c +++ b/board/samus_pd/board.c @@ -11,12 +11,16 @@ #include "gpio.h" #include "hooks.h" #include "i2c.h" +#include "power.h" #include "registers.h" #include "task.h" #include "usb_pd.h" #include "usb_pd_config.h" #include "util.h" +/* Chipset power state */ +static enum power_state ps; + void vbus_evt(enum gpio_signal signal) { ccprintf("VBUS %d, %d!\n", signal, gpio_get_level(signal)); @@ -30,7 +34,32 @@ void bc12_evt(enum gpio_signal signal) void pch_evt(enum gpio_signal signal) { - ccprintf("PCH change %d!\n", signal); + /* Determine new chipset state, trigger corresponding hook */ + switch (ps) { + case POWER_S5: + if (gpio_get_level(GPIO_PCH_SLP_S5_L)) { + hook_notify(HOOK_CHIPSET_STARTUP); + ps = POWER_S3; + } + break; + case POWER_S3: + if (gpio_get_level(GPIO_PCH_SLP_S3_L)) { + hook_notify(HOOK_CHIPSET_RESUME); + ps = POWER_S0; + } else if (!gpio_get_level(GPIO_PCH_SLP_S5_L)) { + hook_notify(HOOK_CHIPSET_SHUTDOWN); + ps = POWER_S5; + } + break; + case POWER_S0: + if (!gpio_get_level(GPIO_PCH_SLP_S3_L)) { + hook_notify(HOOK_CHIPSET_SUSPEND); + ps = POWER_S3; + } + break; + default: + break; + } } void board_config_pre_init(void) @@ -59,6 +88,9 @@ void board_config_pre_init(void) /* Initialize board. */ static void board_init(void) { + int slp_s5 = gpio_get_level(GPIO_PCH_SLP_S5_L); + int slp_s3 = gpio_get_level(GPIO_PCH_SLP_S3_L); + /* * Enable CC lines after all GPIO have been initialized. Note, it is * important that this is enabled after the CC_ODL lines are set low @@ -68,6 +100,22 @@ static void board_init(void) /* Enable interrupts on VBUS transitions. */ gpio_enable_interrupt(GPIO_USB_C0_VBUS_WAKE); + + /* Determine initial chipset state */ + if (slp_s5 && slp_s3) { + hook_notify(HOOK_CHIPSET_RESUME); + ps = POWER_S0; + } else if (slp_s5 && !slp_s3) { + hook_notify(HOOK_CHIPSET_STARTUP); + ps = POWER_S3; + } else { + hook_notify(HOOK_CHIPSET_SHUTDOWN); + ps = POWER_S5; + } + + /* Enable interrupts on PCH state change */ + gpio_enable_interrupt(GPIO_PCH_SLP_S3_L); + gpio_enable_interrupt(GPIO_PCH_SLP_S5_L); } DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT); diff --git a/board/samus_pd/usb_pd_policy.c b/board/samus_pd/usb_pd_policy.c index 60f6e1c21f..8e05c7014a 100644 --- a/board/samus_pd/usb_pd_policy.c +++ b/board/samus_pd/usb_pd_policy.c @@ -161,6 +161,28 @@ int pd_power_negotiation_allowed(void) return battery_ok; } +static void dual_role_on(void) +{ + pd_set_dual_role(PD_DRP_TOGGLE_ON); + CPRINTS("PCH -> S0, enable dual-role toggling"); +} +DECLARE_HOOK(HOOK_CHIPSET_RESUME, dual_role_on, HOOK_PRIO_DEFAULT); + +static void dual_role_off(void) +{ + pd_set_dual_role(PD_DRP_TOGGLE_OFF); + CPRINTS("PCH -> S3, disable dual-role toggling"); +} +DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, dual_role_off, HOOK_PRIO_DEFAULT); +DECLARE_HOOK(HOOK_CHIPSET_STARTUP, dual_role_off, HOOK_PRIO_DEFAULT); + +static void dual_role_force_sink(void) +{ + pd_set_dual_role(PD_DRP_FORCE_SINK); + CPRINTS("PCH -> S5, force dual-role port to sink"); +} +DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, dual_role_force_sink, HOOK_PRIO_DEFAULT); + static int command_ec_int(int argc, char **argv) { pd_send_ec_int(); diff --git a/common/usb_pd_protocol.c b/common/usb_pd_protocol.c index 1e37446a2c..369529b9af 100644 --- a/common/usb_pd_protocol.c +++ b/common/usb_pd_protocol.c @@ -179,6 +179,11 @@ static const uint8_t dec4b5b[] = { #define PD_T_SOURCE_ACTIVITY (45*MSEC) /* between 40ms and 50ms */ #define PD_T_SENDER_RESPONSE (30*MSEC) /* between 24ms and 30ms */ #define PD_T_PS_TRANSITION (220*MSEC) /* between 200ms and 220ms */ +#define PD_T_DRP_HOLD (120*MSEC) /* between 100ms and 150ms */ +#define PD_T_DRP_LOCK (120*MSEC) /* between 100ms and 150ms */ +/* DRP_SNK + DRP_SRC must be between 50ms and 100ms with 30%-70% duty cycle */ +#define PD_T_DRP_SNK (40*MSEC) /* toggle time for sink DRP */ +#define PD_T_DRP_SRC (30*MSEC) /* toggle time for source DRP */ /* Port role at startup */ #ifdef CONFIG_USB_PD_DUAL_ROLE @@ -194,6 +199,10 @@ static uint8_t pd_message_id; /* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */ uint8_t pd_polarity; +#ifdef CONFIG_USB_PD_DUAL_ROLE +static uint8_t pd_dual_role_toggle_on; +#endif + static enum { PD_STATE_DISABLED, #ifdef CONFIG_USB_PD_DUAL_ROLE @@ -771,6 +780,38 @@ static void execute_hard_reset(void) CPRINTF("HARD RESET!\n"); } +#ifdef CONFIG_USB_PD_DUAL_ROLE +void pd_set_dual_role(enum pd_dual_role_states dr_state) +{ + pd_dual_role_toggle_on = (dr_state == PD_DRP_TOGGLE_ON); + + /* Change to sink if in source disconnected state or if force sink */ + if (pd_role == PD_ROLE_SOURCE && + (pd_task_state == PD_STATE_SRC_DISCONNECTED || + dr_state == PD_DRP_FORCE_SINK)) { + pd_role = PD_ROLE_SINK; + pd_task_state = PD_STATE_SNK_DISCONNECTED; + pd_set_host_mode(0); + task_wake(TASK_ID_PD); + } +} +#endif + +/* Return flag for pd state is connected */ +static int pd_is_connected(void) +{ + if (pd_task_state == PD_STATE_DISABLED) + return 0; + +#ifdef CONFIG_USB_PD_DUAL_ROLE + /* Check if sink is connected */ + if (pd_role == PD_ROLE_SINK) + return pd_task_state != PD_STATE_SNK_DISCONNECTED; +#endif + /* Must be a source */ + return pd_task_state != PD_STATE_SRC_DISCONNECTED; +} + void pd_task(void) { int head; @@ -779,13 +820,20 @@ void pd_task(void) int timeout = 10*MSEC; int cc1_volt, cc2_volt; int res; +#ifdef CONFIG_USB_PD_DUAL_ROLE + uint64_t next_role_swap = PD_T_DRP_SNK; +#endif /* Ensure the power supply is in the default state */ pd_power_supply_reset(); while (1) { - /* monitor for incoming packet */ - pd_rx_enable_monitoring(); + /* monitor for incoming packet if in a connected state */ + if (pd_is_connected()) + pd_rx_enable_monitoring(); + else + pd_rx_disable_monitoring(); + /* Verify board specific health status : current, voltages... */ res = pd_board_checks(); if (res != EC_SUCCESS) { @@ -812,6 +860,8 @@ void pd_task(void) /* Nothing to do */ break; case PD_STATE_SRC_DISCONNECTED: + timeout = 10*MSEC; + /* Vnc monitoring */ cc1_volt = pd_adc_read(0); cc2_volt = pd_adc_read(1); @@ -822,8 +872,24 @@ void pd_task(void) /* Enable VBUS */ pd_set_power_supply_ready(); pd_task_state = PD_STATE_SRC_DISCOVERY; +#ifdef CONFIG_USB_PD_DUAL_ROLE + /* Keep VBUS up for the hold period */ + next_role_swap = get_time().val + PD_T_DRP_HOLD; +#endif } - timeout = 10*MSEC; +#ifdef CONFIG_USB_PD_DUAL_ROLE + /* Swap roles if time expired or VBUS is present */ + else if ((get_time().val >= next_role_swap || + pd_snk_is_vbus_provided())) { + pd_role = PD_ROLE_SINK; + pd_task_state = PD_STATE_SNK_DISCONNECTED; + pd_set_host_mode(0); + next_role_swap = get_time().val + PD_T_DRP_SNK; + + /* Swap states quickly */ + timeout = 2*MSEC; + } +#endif break; case PD_STATE_SRC_DISCOVERY: /* Query capabilites of the other side */ @@ -884,6 +950,8 @@ void pd_task(void) pd_hw_init(); break; case PD_STATE_SNK_DISCONNECTED: + timeout = 10*MSEC; + /* Source connection monitoring */ if (pd_snk_is_vbus_provided()) { cc1_volt = pd_adc_read(0); @@ -894,8 +962,18 @@ void pd_task(void) pd_select_polarity(pd_polarity); pd_task_state = PD_STATE_SNK_DISCOVERY; } + } else if (pd_dual_role_toggle_on && + get_time().val >= next_role_swap) { + /* Swap roles to source */ + pd_role = PD_ROLE_SOURCE; + pd_task_state = PD_STATE_SRC_DISCONNECTED; + pd_set_host_mode(1); + next_role_swap = get_time().val + PD_T_DRP_SRC; + + /* Swap states quickly */ + timeout = 2*MSEC; } - timeout = 50*MSEC; + break; case PD_STATE_SNK_DISCOVERY: /* Don't continue if power negotiation is not allowed */ @@ -947,11 +1025,19 @@ void pd_task(void) } /* Check for disconnection */ - if (pd_role == PD_ROLE_SOURCE && - pd_task_state != PD_STATE_SRC_DISCONNECTED) { + if (!pd_is_connected()) + continue; + if (pd_role == PD_ROLE_SOURCE) { /* Source: detect disconnect by monitoring CC */ cc1_volt = pd_adc_read(pd_polarity); +#ifdef CONFIG_USB_PD_DUAL_ROLE + if (cc1_volt > PD_SRC_VNC && + get_time().val >= next_role_swap) { + /* Stay a source port for lock period */ + next_role_swap = get_time().val + PD_T_DRP_LOCK; +#else if (cc1_volt > PD_SRC_VNC) { +#endif pd_power_supply_reset(); pd_task_state = PD_STATE_SRC_DISCONNECTED; /* Debouncing */ @@ -959,12 +1045,11 @@ void pd_task(void) } } #ifdef CONFIG_USB_PD_DUAL_ROLE - if (pd_role == PD_ROLE_SINK && - pd_task_state != PD_STATE_SNK_DISCONNECTED && - !pd_snk_is_vbus_provided()) { + if (pd_role == PD_ROLE_SINK && !pd_snk_is_vbus_provided()) { /* Sink: detect disconnect by monitoring VBUS */ pd_task_state = PD_STATE_SNK_DISCONNECTED; - timeout = 50*MSEC; + /* set timeout small to reconnect fast */ + timeout = 5*MSEC; } #endif /* CONFIG_USB_PD_DUAL_ROLE */ } @@ -1037,6 +1122,21 @@ static int command_pd(int argc, char **argv) pd_set_host_mode(1); pd_task_state = PD_STATE_SRC_READY; task_wake(TASK_ID_PD); + } else if (!strcasecmp(argv[1], "dualrole")) { + int on; + char *e; + + if (argc < 3) { + ccprintf("dual-role toggling: %d\n", + pd_dual_role_toggle_on); + } else { + on = strtoi(argv[2], &e, 10); + if (*e) + return EC_ERROR_PARAM3; + + pd_set_dual_role(on ? PD_DRP_TOGGLE_ON : + PD_DRP_FORCE_SINK); + } } else if (!strncasecmp(argv[1], "state", 5)) { const char * const state_names[] = { "DISABLED", "SUSPENDED", diff --git a/include/usb_pd.h b/include/usb_pd.h index db8d214dc7..d1045980af 100644 --- a/include/usb_pd.h +++ b/include/usb_pd.h @@ -132,6 +132,23 @@ enum pd_errors { /* USB Vendor ID assigned to Google Inc. */ #define USB_VID_GOOGLE 0x18d1 +/* --- Protocol layer functions --- */ + +#ifdef CONFIG_USB_PD_DUAL_ROLE +enum pd_dual_role_states { + PD_DRP_TOGGLE_ON, + PD_DRP_TOGGLE_OFF, + PD_DRP_FORCE_SINK +}; +/** + * Set dual role state, from among enum pd_dual_role_states + * + * @param dr_state New state of dual-role port, selected from + * enum pd_dual_role_states + */ +void pd_set_dual_role(enum pd_dual_role_states dr_state); +#endif + /* --- Policy layer functions --- */ /** -- cgit v1.2.1