mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-08 16:41:55 +00:00
In order to ensure we are always meeting the deadlines for the IRQ_HPD pulse, increase the priority of the processing by moving the rising edge from the low-priority HOOK task (in a deferred function) to the caller task (which is the high-priority PD task). The downside is we are now sleeping in the PD task blocking the processing of the PD messages during this time. Changed HPD_DSTREAM_DEBOUNCE_IRQ to 500us instead of 750us. According to DP spec, the IRQ_HPD pulse width is between 500us and 1000us. Ensure there is a minimum of 2ms delay in between each IRQ_HPD as specified by the DP spec, by sleeping before sending the next pulse if needed. (in practice, this should not wait if we are not too off processing the messages) BUG=chromium:711334 BRANCH=glados strago reef oak TEST=manual, on SKL platform with kernel 3.18 and MST, verify display is functional on USB-C dock. Change-Id: Ib2e9dd608c5f1c671cc5a0fd979a5742101375ff Signed-off-by: Kevin K Wong <kevin.k.wong@intel.com> Signed-off-by: Vincent Palatin <vpalatin@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/508629 Reviewed-by: Todd Broch <tbroch@chromium.org>
403 lines
9.8 KiB
C
403 lines
9.8 KiB
C
/* 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.
|
|
*/
|
|
|
|
#include "atomic.h"
|
|
#include "charge_manager.h"
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "registers.h"
|
|
#include "system.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
#include "usb_mux.h"
|
|
#include "usb_pd.h"
|
|
|
|
#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
|
|
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
|
|
|
|
#define PDO_FIXED_FLAGS (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP |\
|
|
PDO_FIXED_COMM_CAP)
|
|
|
|
/* TODO: fill in correct source and sink capabilities */
|
|
const uint32_t pd_src_pdo[] = {
|
|
PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS),
|
|
};
|
|
const int pd_src_pdo_cnt = ARRAY_SIZE(pd_src_pdo);
|
|
|
|
const uint32_t pd_snk_pdo[] = {
|
|
PDO_FIXED(5000, 500, PDO_FIXED_FLAGS),
|
|
PDO_BATT(4750, 21000, 15000),
|
|
PDO_VAR(4750, 21000, 3000),
|
|
};
|
|
const int pd_snk_pdo_cnt = ARRAY_SIZE(pd_snk_pdo);
|
|
|
|
int pd_is_valid_input_voltage(int mv)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
void pd_transition_voltage(int idx)
|
|
{
|
|
/* No-operation: we are always 5V */
|
|
}
|
|
|
|
int pd_set_power_supply_ready(int port)
|
|
{
|
|
/* Disable charging */
|
|
gpio_set_level(port ? GPIO_USB_C1_CHARGE_EN_L :
|
|
GPIO_USB_C0_CHARGE_EN_L, 1);
|
|
/* Provide VBUS */
|
|
gpio_set_level(port ? GPIO_USB_C1_5V_EN :
|
|
GPIO_USB_C0_5V_EN, 1);
|
|
|
|
/* notify host of power info change */
|
|
pd_send_host_event(PD_EVENT_POWER_CHANGE);
|
|
|
|
return EC_SUCCESS; /* we are ready */
|
|
}
|
|
|
|
void pd_power_supply_reset(int port)
|
|
{
|
|
/* Disable VBUS */
|
|
gpio_set_level(port ? GPIO_USB_C1_5V_EN :
|
|
GPIO_USB_C0_5V_EN, 0);
|
|
|
|
/* notify host of power info change */
|
|
pd_send_host_event(PD_EVENT_POWER_CHANGE);
|
|
}
|
|
|
|
void pd_set_input_current_limit(int port, uint32_t max_ma,
|
|
uint32_t supply_voltage)
|
|
{
|
|
#ifdef CONFIG_CHARGE_MANAGER
|
|
struct charge_port_info charge;
|
|
|
|
charge.current = max_ma;
|
|
charge.voltage = supply_voltage;
|
|
charge_manager_update_charge(CHARGE_SUPPLIER_PD, port, &charge);
|
|
#endif
|
|
}
|
|
|
|
void typec_set_input_current_limit(int port, uint32_t max_ma,
|
|
uint32_t supply_voltage)
|
|
{
|
|
#ifdef CONFIG_CHARGE_MANAGER
|
|
struct charge_port_info charge;
|
|
|
|
charge.current = max_ma;
|
|
charge.voltage = supply_voltage;
|
|
charge_manager_update_charge(CHARGE_SUPPLIER_TYPEC, port, &charge);
|
|
#endif
|
|
}
|
|
|
|
int pd_snk_is_vbus_provided(int port)
|
|
{
|
|
return !gpio_get_level(port ? GPIO_USB_C1_VBUS_WAKE_L :
|
|
GPIO_USB_C0_VBUS_WAKE_L);
|
|
}
|
|
|
|
int pd_board_checks(void)
|
|
{
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int pd_check_power_swap(int port)
|
|
{
|
|
/*
|
|
* Allow power swap as long as we are acting as a dual role device,
|
|
* otherwise assume our role is fixed (not in S0 or console command
|
|
* to fix our role).
|
|
*/
|
|
return pd_get_dual_role() == PD_DRP_TOGGLE_ON ? 1 : 0;
|
|
}
|
|
|
|
int pd_check_data_swap(int port, int data_role)
|
|
{
|
|
/* Allow data swap if we are a UFP, otherwise don't allow */
|
|
return (data_role == PD_ROLE_UFP) ? 1 : 0;
|
|
}
|
|
|
|
int pd_check_vconn_swap(int port)
|
|
{
|
|
/* in G3, do not allow vconn swap since pp5000_A rail is off */
|
|
return gpio_get_level(GPIO_PMIC_SLP_SUS_L);
|
|
}
|
|
|
|
void pd_execute_data_swap(int port, int data_role)
|
|
{
|
|
/* Do nothing */
|
|
}
|
|
|
|
void pd_check_pr_role(int port, int pr_role, int flags)
|
|
{
|
|
/*
|
|
* If partner is dual-role power and dualrole toggling is on, consider
|
|
* if a power swap is necessary.
|
|
*/
|
|
if ((flags & PD_FLAGS_PARTNER_DR_POWER) &&
|
|
pd_get_dual_role() == PD_DRP_TOGGLE_ON) {
|
|
/*
|
|
* If we are a sink and partner is not externally powered, then
|
|
* swap to become a source. If we are source and partner is
|
|
* externally powered, swap to become a sink.
|
|
*/
|
|
int partner_extpower = flags & PD_FLAGS_PARTNER_EXTPOWER;
|
|
|
|
if ((!partner_extpower && pr_role == PD_ROLE_SINK) ||
|
|
(partner_extpower && pr_role == PD_ROLE_SOURCE))
|
|
pd_request_power_swap(port);
|
|
}
|
|
}
|
|
|
|
void pd_check_dr_role(int port, int dr_role, int flags)
|
|
{
|
|
/* If UFP, try to switch to DFP */
|
|
if ((flags & PD_FLAGS_PARTNER_DR_DATA) && dr_role == PD_ROLE_UFP)
|
|
pd_request_data_swap(port);
|
|
}
|
|
/* ----------------- Vendor Defined Messages ------------------ */
|
|
const struct svdm_response svdm_rsp = {
|
|
.identity = NULL,
|
|
.svids = NULL,
|
|
.modes = NULL,
|
|
};
|
|
|
|
int pd_custom_vdm(int port, int cnt, uint32_t *payload,
|
|
uint32_t **rpayload)
|
|
{
|
|
int cmd = PD_VDO_CMD(payload[0]);
|
|
uint16_t dev_id = 0;
|
|
int is_rw;
|
|
|
|
/* make sure we have some payload */
|
|
if (cnt == 0)
|
|
return 0;
|
|
|
|
switch (cmd) {
|
|
case VDO_CMD_VERSION:
|
|
/* guarantee last byte of payload is null character */
|
|
*(payload + cnt - 1) = 0;
|
|
CPRINTF("version: %s\n", (char *)(payload+1));
|
|
break;
|
|
case VDO_CMD_READ_INFO:
|
|
case VDO_CMD_SEND_INFO:
|
|
/* copy hash */
|
|
if (cnt == 7) {
|
|
dev_id = VDO_INFO_HW_DEV_ID(payload[6]);
|
|
is_rw = VDO_INFO_IS_RW(payload[6]);
|
|
|
|
CPRINTF("DevId:%d.%d SW:%d RW:%d\n",
|
|
HW_DEV_ID_MAJ(dev_id),
|
|
HW_DEV_ID_MIN(dev_id),
|
|
VDO_INFO_SW_DBG_VER(payload[6]),
|
|
is_rw);
|
|
} else if (cnt == 6) {
|
|
/* really old devices don't have last byte */
|
|
pd_dev_store_rw_hash(port, dev_id, payload + 1,
|
|
SYSTEM_IMAGE_UNKNOWN);
|
|
}
|
|
break;
|
|
case VDO_CMD_CURRENT:
|
|
CPRINTF("Current: %dmA\n", payload[1]);
|
|
break;
|
|
case VDO_CMD_FLIP:
|
|
usb_mux_flip(port);
|
|
break;
|
|
#ifdef CONFIG_USB_PD_LOGGING
|
|
case VDO_CMD_GET_LOG:
|
|
pd_log_recv_vdm(port, cnt, payload);
|
|
break;
|
|
#endif /* CONFIG_USB_PD_LOGGING */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_USB_PD_ALT_MODE_DFP
|
|
static int dp_flags[CONFIG_USB_PD_PORT_COUNT];
|
|
/* DP Status VDM as returned by UFP */
|
|
static uint32_t dp_status[CONFIG_USB_PD_PORT_COUNT];
|
|
|
|
static void svdm_safe_dp_mode(int port)
|
|
{
|
|
/* make DP interface safe until configure */
|
|
dp_flags[port] = 0;
|
|
dp_status[port] = 0;
|
|
usb_mux_set(port, TYPEC_MUX_NONE,
|
|
USB_SWITCH_CONNECT, pd_get_polarity(port));
|
|
}
|
|
|
|
static int svdm_enter_dp_mode(int port, uint32_t mode_caps)
|
|
{
|
|
/* Only enter mode if device is DFP_D capable */
|
|
if (mode_caps & MODE_DP_SNK) {
|
|
svdm_safe_dp_mode(port);
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int svdm_dp_status(int port, uint32_t *payload)
|
|
{
|
|
int opos = pd_alt_mode(port, USB_SID_DISPLAYPORT);
|
|
|
|
payload[0] = VDO(USB_SID_DISPLAYPORT, 1,
|
|
CMD_DP_STATUS | VDO_OPOS(opos));
|
|
payload[1] = VDO_DP_STATUS(0, /* HPD IRQ ... not applicable */
|
|
0, /* HPD level ... not applicable */
|
|
0, /* exit DP? ... no */
|
|
0, /* usb mode? ... no */
|
|
0, /* multi-function ... no */
|
|
(!!(dp_flags[port] & DP_FLAGS_DP_ON)),
|
|
0, /* power low? ... no */
|
|
(!!(dp_flags[port] & DP_FLAGS_DP_ON)));
|
|
return 2;
|
|
};
|
|
|
|
static int svdm_dp_config(int port, uint32_t *payload)
|
|
{
|
|
int opos = pd_alt_mode(port, USB_SID_DISPLAYPORT);
|
|
int mf_pref = PD_VDO_DPSTS_MF_PREF(dp_status[port]);
|
|
int pin_mode = pd_dfp_dp_get_pin_mode(port, dp_status[port]);
|
|
|
|
if (!pin_mode)
|
|
return 0;
|
|
|
|
usb_mux_set(port, mf_pref ? TYPEC_MUX_DOCK : TYPEC_MUX_DP,
|
|
USB_SWITCH_CONNECT, pd_get_polarity(port));
|
|
|
|
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;
|
|
};
|
|
|
|
/*
|
|
* timestamp of the next possible toggle to ensure the 2-ms spacing
|
|
* between IRQ_HPD.
|
|
*/
|
|
static uint64_t hpd_deadline[CONFIG_USB_PD_PORT_COUNT];
|
|
|
|
#define PORT_TO_HPD(port) ((port) ? GPIO_USB_C1_DP_HPD : GPIO_USB_C0_DP_HPD)
|
|
static void svdm_dp_post_config(int port)
|
|
{
|
|
dp_flags[port] |= DP_FLAGS_DP_ON;
|
|
if (!(dp_flags[port] & DP_FLAGS_HPD_HI_PENDING))
|
|
return;
|
|
|
|
gpio_set_level(PORT_TO_HPD(port), 1);
|
|
|
|
/* set the minimum time delay (2ms) for the next HPD IRQ */
|
|
hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL;
|
|
}
|
|
|
|
static int svdm_dp_attention(int port, uint32_t *payload)
|
|
{
|
|
int cur_lvl;
|
|
int lvl = PD_VDO_DPSTS_HPD_LVL(payload[1]);
|
|
int irq = PD_VDO_DPSTS_HPD_IRQ(payload[1]);
|
|
enum gpio_signal hpd = PORT_TO_HPD(port);
|
|
|
|
cur_lvl = gpio_get_level(hpd);
|
|
dp_status[port] = payload[1];
|
|
|
|
/* Its initial DP status message prior to config */
|
|
if (!(dp_flags[port] & DP_FLAGS_DP_ON)) {
|
|
if (lvl)
|
|
dp_flags[port] |= DP_FLAGS_HPD_HI_PENDING;
|
|
return 1;
|
|
}
|
|
|
|
if (irq & cur_lvl) {
|
|
uint64_t now = get_time().val;
|
|
/* wait for the minimum spacing between IRQ_HPD if needed */
|
|
if (now < hpd_deadline[port])
|
|
usleep(hpd_deadline[port] - now);
|
|
|
|
/* generate IRQ_HPD pulse */
|
|
gpio_set_level(hpd, 0);
|
|
usleep(HPD_DSTREAM_DEBOUNCE_IRQ);
|
|
gpio_set_level(hpd, 1);
|
|
|
|
/* set the minimum time delay (2ms) for the next HPD IRQ */
|
|
hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL;
|
|
} else if (irq & !cur_lvl) {
|
|
CPRINTF("ERR:HPD:IRQ&LOW\n");
|
|
return 0; /* nak */
|
|
} else {
|
|
gpio_set_level(hpd, lvl);
|
|
/* set the minimum time delay (2ms) for the next HPD IRQ */
|
|
hpd_deadline[port] = get_time().val + HPD_USTREAM_DEBOUNCE_LVL;
|
|
}
|
|
/* ack */
|
|
return 1;
|
|
}
|
|
|
|
static void svdm_exit_dp_mode(int port)
|
|
{
|
|
svdm_safe_dp_mode(port);
|
|
gpio_set_level(PORT_TO_HPD(port), 0);
|
|
}
|
|
|
|
static int svdm_enter_gfu_mode(int port, uint32_t mode_caps)
|
|
{
|
|
/* Always enter GFU mode */
|
|
return 0;
|
|
}
|
|
|
|
static void svdm_exit_gfu_mode(int port)
|
|
{
|
|
}
|
|
|
|
static int svdm_gfu_status(int port, uint32_t *payload)
|
|
{
|
|
/*
|
|
* This is called after enter mode is successful, send unstructured
|
|
* VDM to read info.
|
|
*/
|
|
pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_READ_INFO, NULL, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int svdm_gfu_config(int port, uint32_t *payload)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int svdm_gfu_attention(int port, uint32_t *payload)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const struct svdm_amode_fx supported_modes[] = {
|
|
{
|
|
.svid = USB_SID_DISPLAYPORT,
|
|
.enter = &svdm_enter_dp_mode,
|
|
.status = &svdm_dp_status,
|
|
.config = &svdm_dp_config,
|
|
.post_config = &svdm_dp_post_config,
|
|
.attention = &svdm_dp_attention,
|
|
.exit = &svdm_exit_dp_mode,
|
|
},
|
|
{
|
|
.svid = USB_VID_GOOGLE,
|
|
.enter = &svdm_enter_gfu_mode,
|
|
.status = &svdm_gfu_status,
|
|
.config = &svdm_gfu_config,
|
|
.attention = &svdm_gfu_attention,
|
|
.exit = &svdm_exit_gfu_mode,
|
|
}
|
|
};
|
|
const int supported_modes_cnt = ARRAY_SIZE(supported_modes);
|
|
#endif /* CONFIG_USB_PD_ALT_MODE_DFP */
|
|
|