summaryrefslogtreecommitdiff
path: root/zephyr/projects/corsola/src/usb_pd_policy.c
diff options
context:
space:
mode:
Diffstat (limited to 'zephyr/projects/corsola/src/usb_pd_policy.c')
-rw-r--r--zephyr/projects/corsola/src/usb_pd_policy.c226
1 files changed, 226 insertions, 0 deletions
diff --git a/zephyr/projects/corsola/src/usb_pd_policy.c b/zephyr/projects/corsola/src/usb_pd_policy.c
new file mode 100644
index 0000000000..a885362c61
--- /dev/null
+++ b/zephyr/projects/corsola/src/usb_pd_policy.c
@@ -0,0 +1,226 @@
+/* Copyright 2021 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "atomic.h"
+#include "console.h"
+#include "chipset.h"
+#include "hooks.h"
+#include "timer.h"
+#include "typec_control.h"
+#include "usb_dp_alt_mode.h"
+#include "usb_mux.h"
+#include "usb_pd.h"
+#include "usbc_ppc.h"
+
+#include "baseboard_usbc_config.h"
+
+#define CPRINTS(format, args...) cprints(CC_USBPD, format, ##args)
+#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ##args)
+
+static int active_aux_port = -1;
+
+int pd_check_vconn_swap(int port)
+{
+ /* Allow Vconn swap if AP is on. */
+ return chipset_in_state(CHIPSET_STATE_SUSPEND | CHIPSET_STATE_ON);
+}
+
+static void set_dp_aux_path_sel(int port)
+{
+ gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(dp_aux_path_sel), port);
+ CPRINTS("Set DP_AUX_PATH_SEL: %d", port);
+}
+
+int svdm_get_hpd_gpio(int port)
+{
+ /* HPD is low active, inverse the result */
+ return !gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(ec_ap_dp_hpd_odl));
+}
+
+static void reset_aux_deferred(void)
+{
+ if (active_aux_port == -1)
+ /* reset to 1 for lower power consumption. */
+ set_dp_aux_path_sel(1);
+}
+DECLARE_DEFERRED(reset_aux_deferred);
+
+void svdm_set_hpd_gpio(int port, int en)
+{
+ /*
+ * HPD is low active, inverse the en.
+ *
+ * Implement FCFS policy:
+ * 1) Enable hpd if no active port.
+ * 2) Disable hpd if active port is the given port.
+ */
+ if (en && active_aux_port < 0) {
+ gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(ec_ap_dp_hpd_odl), 0);
+ active_aux_port = port;
+ hook_call_deferred(&reset_aux_deferred_data, -1);
+ }
+
+ if (!en && active_aux_port == port) {
+ gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(ec_ap_dp_hpd_odl), 1);
+ active_aux_port = -1;
+ /*
+ * This might be a HPD debounce to send a HPD IRQ (500us), so
+ * do not reset the aux path immediately. Defer this call and
+ * re-check if this is a real disable.
+ */
+ hook_call_deferred(&reset_aux_deferred_data, 1 * MSEC);
+ }
+}
+
+__override int svdm_dp_config(int port, uint32_t *payload)
+{
+ int opos = pd_alt_mode(port, TCPCI_MSG_SOP, USB_SID_DISPLAYPORT);
+ uint8_t pin_mode = get_dp_pin_mode(port);
+ mux_state_t mux_mode = svdm_dp_get_mux_mode(port);
+ int mf_pref = PD_VDO_DPSTS_MF_PREF(dp_status[port]);
+
+ if (!pin_mode) {
+ return 0;
+ }
+
+ CPRINTS("pin_mode: %x, mf: %d, mux: %d", pin_mode, mf_pref, mux_mode);
+ /*
+ * Defer setting the usb_mux until HPD goes high, svdm_dp_attention().
+ * The AP only supports one DP phy. An external DP mux switches between
+ * the two ports. Should switch those muxes when it is really used,
+ * i.e. HPD high; otherwise, the real use case is preempted, like:
+ * (1) plug a dongle without monitor connected to port-0,
+ * (2) plug a dongle without monitor connected to port-1,
+ * (3) plug a monitor to the port-1 dongle.
+ */
+
+ payload[0] =
+ VDO(USB_SID_DISPLAYPORT, 1, CMD_DP_CONFIG | VDO_OPOS(opos));
+ payload[1] = VDO_DP_CFG(pin_mode, /* pin mode */
+ 1, /* DPv1.3 signaling */
+ 2); /* UFP connected */
+ return 2;
+};
+
+__override void svdm_dp_post_config(int port)
+{
+ mux_state_t mux_mode = svdm_dp_get_mux_mode(port);
+
+ typec_set_sbu(port, true);
+
+ /*
+ * Prior to post-config, the mux will be reset to safe mode, and this
+ * will break mux config and aux path config we did in the first DP
+ * status command. Only enable this if the port is the current aux-port.
+ */
+ if (port == active_aux_port) {
+ usb_mux_set(port, mux_mode, USB_SWITCH_CONNECT,
+ polarity_rm_dts(pd_get_polarity(port)));
+ usb_mux_hpd_update(port, USB_PD_MUX_HPD_LVL |
+ USB_PD_MUX_HPD_IRQ_DEASSERTED);
+ }
+
+ dp_flags[port] |= DP_FLAGS_DP_ON;
+}
+
+int corsola_is_dp_muxable(int port)
+{
+ int i;
+
+ for (i = 0; i < board_get_usb_pd_port_count(); i++) {
+ if (i != port) {
+ if (usb_mux_get(i) & USB_PD_MUX_DP_ENABLED) {
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+__override int svdm_dp_attention(int port, uint32_t *payload)
+{
+ int lvl = PD_VDO_DPSTS_HPD_LVL(payload[1]);
+ int irq = PD_VDO_DPSTS_HPD_IRQ(payload[1]);
+#ifdef CONFIG_USB_PD_DP_HPD_GPIO
+ int cur_lvl = svdm_get_hpd_gpio(port);
+#endif /* CONFIG_USB_PD_DP_HPD_GPIO */
+ mux_state_t mux_state;
+
+ dp_status[port] = payload[1];
+
+ if (!corsola_is_dp_muxable(port)) {
+ /* TODO(waihong): Info user? */
+ CPRINTS("p%d: The other port is already muxed.", port);
+ return 0; /* nak */
+ }
+
+ if (lvl) {
+ set_dp_aux_path_sel(port);
+
+ usb_mux_set(port, USB_PD_MUX_DOCK, USB_SWITCH_CONNECT,
+ polarity_rm_dts(pd_get_polarity(port)));
+ } else {
+ usb_mux_set(port, USB_PD_MUX_USB_ENABLED, USB_SWITCH_CONNECT,
+ polarity_rm_dts(pd_get_polarity(port)));
+ }
+
+ if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND) && (irq || lvl)) {
+ /*
+ * Wake up the AP. IRQ or level high indicates a DP sink is now
+ * present.
+ */
+ if (IS_ENABLED(CONFIG_MKBP_EVENT)) {
+ pd_notify_dp_alt_mode_entry(port);
+ }
+ }
+
+#ifdef CONFIG_USB_PD_DP_HPD_GPIO
+ if (irq && !lvl) {
+ /*
+ * IRQ can only be generated when the level is high, because
+ * the IRQ is signaled by a short low pulse from the high level.
+ */
+ CPRINTF("ERR:HPD:IRQ&LOW\n");
+ return 0; /* nak */
+ }
+
+ if (irq && cur_lvl) {
+ uint64_t now = get_time().val;
+ /* wait for the minimum spacing between IRQ_HPD if needed */
+ if (now < svdm_hpd_deadline[port]) {
+ usleep(svdm_hpd_deadline[port] - now);
+ }
+
+ /* generate IRQ_HPD pulse */
+ svdm_set_hpd_gpio(port, 0);
+ /*
+ * b/171172053#comment14: since the HPD_DSTREAM_DEBOUNCE_IRQ is
+ * very short (500us), we can use udelay instead of usleep for
+ * more stable pulse period.
+ */
+ udelay(HPD_DSTREAM_DEBOUNCE_IRQ);
+ svdm_set_hpd_gpio(port, 1);
+ } else {
+ svdm_set_hpd_gpio(port, lvl);
+ }
+
+ /* set the minimum time delay (2ms) for the next HPD IRQ */
+ svdm_hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL;
+#endif /* CONFIG_USB_PD_DP_HPD_GPIO */
+
+ mux_state = (lvl ? USB_PD_MUX_HPD_LVL : USB_PD_MUX_HPD_LVL_DEASSERTED) |
+ (irq ? USB_PD_MUX_HPD_IRQ : USB_PD_MUX_HPD_IRQ_DEASSERTED);
+ usb_mux_hpd_update(port, mux_state);
+
+#ifdef USB_PD_PORT_TCPC_MST
+ if (port == USB_PD_PORT_TCPC_MST) {
+ baseboard_mst_enable_control(port, lvl);
+ }
+#endif
+
+ /* ack */
+ return 1;
+}