Files
OpenCellular/board/ryu/usb_pd_policy.c
Sam Hurst c5bd6d98b9 charger: Send host event after charge info is updated.
When the charger is detached, the host event would sometimes be
sent before the charge info was updated, resulting in the host
thinking that the charger was still connected.

BUG=chrome-os-partner:55584
BRANCH=none
TEST=Connected charger to kevin 15 times and verified that the
icon was removed in 2-seconds or less.
Change-Id: I1a4e4e0f7cc23010210570fc261da8308d8e8070
Reviewed-on: https://chromium-review.googlesource.com/367809
Commit-Ready: Wonjoon Lee <woojoo.lee@samsung.com>
Tested-by: Sam Hurst <shurst@google.com>
Reviewed-by: Shawn N <shawnn@chromium.org>
2016-08-24 17:40:38 -07:00

396 lines
9.2 KiB
C

/* Copyright (c) 2014 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 "config.h"
#include "case_closed_debug.h"
#include "charge_manager.h"
#include "charger.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)
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(4500, 14000, 10000),
PDO_VAR(4500, 14000, 3000),
};
const int pd_snk_pdo_cnt = ARRAY_SIZE(pd_snk_pdo);
void pd_set_input_current_limit(int port, uint32_t max_ma,
uint32_t supply_voltage)
{
struct charge_port_info charge;
charge.current = max_ma;
charge.voltage = supply_voltage;
charge_manager_update_charge(CHARGE_SUPPLIER_PD, port, &charge);
}
void typec_set_input_current_limit(int port, uint32_t max_ma,
uint32_t supply_voltage)
{
struct charge_port_info charge;
charge.current = max_ma;
charge.voltage = supply_voltage;
charge_manager_update_charge(CHARGE_SUPPLIER_TYPEC, port, &charge);
}
int pd_is_valid_input_voltage(int mv)
{
/* Any voltage less than the max is allowed */
return 1;
}
void pd_transition_voltage(int idx)
{
/* No-operation: we are always 5V */
}
int pd_set_power_supply_ready(int port)
{
/* provide VBUS */
gpio_set_level(GPIO_CHGR_OTG, 1);
charger_enable_otg_power(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)
{
/* Kill VBUS */
charger_enable_otg_power(0);
gpio_set_level(GPIO_CHGR_OTG, 0);
/* notify host of power info change */
pd_send_host_event(PD_EVENT_POWER_CHANGE);
}
int pd_snk_is_vbus_provided(int port)
{
return gpio_get_level(GPIO_CHGR_ACOK);
}
int pd_board_checks(void)
{
return EC_SUCCESS;
}
int pd_check_power_swap(int port)
{
/* TODO: use battery level to decide to accept/reject power swap */
/*
* 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)
{
/* Always allow data swap: we can be DFP or UFP for USB */
return 1;
}
int pd_check_vconn_swap(int port)
{
/*
* VCONN is provided directly by the battery(PPVAR_SYS)
* but use the same rules as power swap
*/
return pd_get_dual_role() == PD_DRP_TOGGLE_ON ? 1 : 0;
}
void pd_execute_data_swap(int port, int data_role)
{
/* inform the host controller to change role */
pd_send_host_event(PD_EVENT_DATA_SWAP);
}
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 source and partner is externally powered,
* swap to become a sink.
*/
if ((flags & PD_FLAGS_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 the partner is a DRP (e.g. laptop), try to switch to UFP */
if ((flags & PD_FLAGS_PARTNER_DR_DATA) && dr_role == PD_ROLE_DFP)
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, is_latest;
/* 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]);
is_latest = pd_dev_store_rw_hash(port,
dev_id,
payload + 1,
is_rw ?
SYSTEM_IMAGE_RW :
SYSTEM_IMAGE_RO);
/*
* Send update host event unless our RW hash is
* already known to be the latest update RW.
*/
if (!is_rw || !is_latest)
pd_send_host_event(PD_EVENT_UPDATE_DEVICE);
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 */
#ifdef CONFIG_CASE_CLOSED_DEBUG
case VDO_CMD_CCD_EN:
ccd_set_mode(system_is_locked() ? CCD_MODE_PARTIAL
: CCD_MODE_ENABLED);
break;
#endif
}
return 0;
}
static int dp_flags;
/* DP Status VDM as returned by UFP */
static uint32_t dp_status;
static void svdm_safe_dp_mode(int port)
{
/* make DP interface safe until configure */
usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_CONNECT, 0);
dp_flags = 0;
dp_status = 0;
}
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 & DP_FLAGS_DP_ON)),
0, /* power low? ... no */
(!!(dp_flags & 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);
int pin_mode = pd_dfp_dp_get_pin_mode(port, dp_status);
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_U connected as UFP_D */
return 2;
};
static void svdm_dp_post_config(int port)
{
dp_flags |= DP_FLAGS_DP_ON;
if (!(dp_flags & DP_FLAGS_HPD_HI_PENDING))
return;
gpio_set_level(GPIO_USBC_DP_HPD, 1);
}
static void hpd_irq_deferred(void)
{
gpio_set_level(GPIO_USBC_DP_HPD, 1);
}
DECLARE_DEFERRED(hpd_irq_deferred);
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]);
cur_lvl = gpio_get_level(GPIO_USBC_DP_HPD);
dp_status = payload[1];
/* Its initial DP status message prior to config */
if (!(dp_flags & DP_FLAGS_DP_ON)) {
if (lvl)
dp_flags |= DP_FLAGS_HPD_HI_PENDING;
return 1;
}
if (irq & cur_lvl) {
gpio_set_level(GPIO_USBC_DP_HPD, 0);
hook_call_deferred(&hpd_irq_deferred_data,
HPD_DSTREAM_DEBOUNCE_IRQ);
} else if (irq & !cur_lvl) {
CPRINTF("ERR:HPD:IRQ&LOW\n");
return 0; /* nak */
} else {
gpio_set_level(GPIO_USBC_DP_HPD, lvl);
}
/* ack */
return 1;
}
static void svdm_exit_dp_mode(int port)
{
svdm_safe_dp_mode(port);
gpio_set_level(GPIO_USBC_DP_HPD, 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);