305 lines
7.0 KiB
C
305 lines
7.0 KiB
C
/* Copyright 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 "adc.h"
|
|
#include "board.h"
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "registers.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
#include "usb_pd.h"
|
|
#include "version.h"
|
|
|
|
#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
|
|
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
|
|
|
|
/* Acceptable margin between requested VBUS and measured value */
|
|
#define MARGIN_MV 400 /* mV */
|
|
|
|
#define PDO_FIXED_FLAGS (PDO_FIXED_DATA_SWAP | PDO_FIXED_EXTERNAL |\
|
|
PDO_FIXED_COMM_CAP)
|
|
|
|
/* Source PDOs */
|
|
const uint32_t pd_src_pdo[] = {
|
|
PDO_FIXED(5000, 3000, PDO_FIXED_FLAGS),
|
|
PDO_FIXED(12000, 3000, PDO_FIXED_FLAGS),
|
|
PDO_FIXED(20000, 3000, PDO_FIXED_FLAGS),
|
|
};
|
|
static const int pd_src_pdo_cnts[3] = {
|
|
[SRC_CAP_5V] = 1,
|
|
[SRC_CAP_12V] = 2,
|
|
[SRC_CAP_20V] = 3,
|
|
};
|
|
|
|
static int pd_src_pdo_idx;
|
|
|
|
/* Fake PDOs : we just want our pre-defined voltages */
|
|
const uint32_t pd_snk_pdo[] = {
|
|
PDO_FIXED(5000, 500, PDO_FIXED_FLAGS),
|
|
PDO_FIXED(12000, 500, PDO_FIXED_FLAGS),
|
|
PDO_FIXED(20000, 500, PDO_FIXED_FLAGS),
|
|
};
|
|
const int pd_snk_pdo_cnt = ARRAY_SIZE(pd_snk_pdo);
|
|
|
|
/* Whether alternate mode has been entered or not */
|
|
static int alt_mode;
|
|
|
|
void board_set_source_cap(enum board_src_cap cap)
|
|
{
|
|
pd_src_pdo_idx = cap;
|
|
}
|
|
|
|
int charge_manager_get_source_pdo(const uint32_t **src_pdo, const int port)
|
|
{
|
|
*src_pdo = pd_src_pdo;
|
|
return pd_src_pdo_cnts[pd_src_pdo_idx];
|
|
}
|
|
|
|
void pd_set_input_current_limit(int port, uint32_t max_ma,
|
|
uint32_t supply_voltage)
|
|
{
|
|
/* No battery, nothing to do */
|
|
return;
|
|
}
|
|
|
|
int pd_is_valid_input_voltage(int mv)
|
|
{
|
|
/* Any voltage less than the max is allowed */
|
|
return 1;
|
|
}
|
|
|
|
int pd_board_check_request(uint32_t rdo, int pdo_cnt)
|
|
{
|
|
int idx = RDO_POS(rdo);
|
|
|
|
/* Check for invalid index */
|
|
return (!idx || idx > pdo_cnt) ?
|
|
EC_ERROR_INVAL : EC_SUCCESS;
|
|
}
|
|
|
|
void pd_transition_voltage(int idx)
|
|
{
|
|
gpio_set_level(GPIO_USBC_VSEL_0, idx >= 2);
|
|
gpio_set_level(GPIO_USBC_VSEL_1, idx >= 3);
|
|
}
|
|
|
|
int pd_set_power_supply_ready(int port)
|
|
{
|
|
/* Output the correct voltage */
|
|
gpio_set_level(GPIO_VBUS_CHARGER_EN, 1);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
void pd_power_supply_reset(int port)
|
|
{
|
|
/* Kill VBUS */
|
|
gpio_set_level(GPIO_VBUS_CHARGER_EN, 0);
|
|
gpio_set_level(GPIO_USBC_VSEL_0, 0);
|
|
gpio_set_level(GPIO_USBC_VSEL_1, 0);
|
|
}
|
|
|
|
int pd_snk_is_vbus_provided(int port)
|
|
{
|
|
return gpio_get_level(GPIO_VBUS_WAKE);
|
|
}
|
|
|
|
int pd_board_checks(void)
|
|
{
|
|
static int was_connected = -1;
|
|
if (was_connected != 1 && pd_is_connected(0))
|
|
board_maybe_reset_usb_hub();
|
|
was_connected = pd_is_connected(0);
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int pd_check_power_swap(int port)
|
|
{
|
|
/* Always allow power swap */
|
|
return 1;
|
|
}
|
|
|
|
int pd_check_data_swap(int port, int data_role)
|
|
{
|
|
/* Always allow data swap */
|
|
return 1;
|
|
}
|
|
|
|
void pd_execute_data_swap(int port, int data_role)
|
|
{
|
|
/* Do nothing */
|
|
}
|
|
|
|
void pd_check_pr_role(int port, int pr_role, int flags)
|
|
{
|
|
}
|
|
|
|
void pd_check_dr_role(int port, int dr_role, int flags)
|
|
{
|
|
/* If Plankton is in USB hub mode, always act as UFP */
|
|
if (board_in_hub_mode() && dr_role == PD_ROLE_DFP &&
|
|
(flags & PD_FLAGS_PARTNER_DR_DATA))
|
|
pd_request_data_swap(port);
|
|
}
|
|
|
|
/* ----------------- Vendor Defined Messages ------------------ */
|
|
const uint32_t vdo_idh = VDO_IDH(0, /* data caps as USB host */
|
|
0, /* data caps as USB device */
|
|
IDH_PTYPE_AMA, /* Alternate mode */
|
|
1, /* supports alt modes */
|
|
USB_VID_GOOGLE);
|
|
|
|
const uint32_t vdo_product = VDO_PRODUCT(CONFIG_USB_PID, CONFIG_USB_BCD_DEV);
|
|
|
|
const uint32_t vdo_ama = VDO_AMA(CONFIG_USB_PD_IDENTITY_HW_VERS,
|
|
CONFIG_USB_PD_IDENTITY_SW_VERS,
|
|
0, 0, 0, 0, /* SS[TR][12] */
|
|
0, /* Vconn power */
|
|
0, /* Vconn power required */
|
|
1, /* Vbus power required */
|
|
AMA_USBSS_BBONLY /* USB SS support */);
|
|
|
|
static int svdm_response_identity(int port, uint32_t *payload)
|
|
{
|
|
payload[VDO_I(IDH)] = vdo_idh;
|
|
payload[VDO_I(CSTAT)] = VDO_CSTAT(0);
|
|
payload[VDO_I(PRODUCT)] = vdo_product;
|
|
payload[VDO_I(AMA)] = vdo_ama;
|
|
return VDO_I(AMA) + 1;
|
|
}
|
|
|
|
static int svdm_response_svids(int port, uint32_t *payload)
|
|
{
|
|
payload[1] = VDO_SVID(USB_SID_DISPLAYPORT, 0);
|
|
return 2;
|
|
}
|
|
|
|
/*
|
|
* Will only ever be a single mode for this UFP_D device as it has no real USB
|
|
* support making it only PIN_E configureable
|
|
*/
|
|
#define MODE_CNT 1
|
|
#define OPOS 1
|
|
|
|
const uint32_t vdo_dp_mode[MODE_CNT] = {
|
|
VDO_MODE_DP(0, /* UFP pin cfg supported : none */
|
|
MODE_DP_PIN_E, /* DFP pin cfg supported */
|
|
1, /* no usb2.0 signalling in AMode */
|
|
CABLE_PLUG, /* its a plug */
|
|
MODE_DP_V13, /* DPv1.3 Support, no Gen2 */
|
|
MODE_DP_SNK) /* Its a sink only */
|
|
};
|
|
|
|
static int svdm_response_modes(int port, uint32_t *payload)
|
|
{
|
|
if (gpio_get_level(GPIO_USBC_SS_USB_MODE))
|
|
return 0; /* nak */
|
|
|
|
if (PD_VDO_VID(payload[0]) != USB_SID_DISPLAYPORT)
|
|
return 0; /* nak */
|
|
|
|
memcpy(payload + 1, vdo_dp_mode, sizeof(vdo_dp_mode));
|
|
return MODE_CNT + 1;
|
|
}
|
|
|
|
static int dp_status(int port, uint32_t *payload)
|
|
{
|
|
int opos = PD_VDO_OPOS(payload[0]);
|
|
int hpd = gpio_get_level(GPIO_DPSRC_HPD);
|
|
if (opos != OPOS)
|
|
return 0; /* nak */
|
|
|
|
payload[1] = VDO_DP_STATUS(0, /* IRQ_HPD */
|
|
(hpd == 1), /* HPD_HI|LOW */
|
|
0, /* request exit DP */
|
|
0, /* request exit USB */
|
|
0, /* MF pref */
|
|
!gpio_get_level(GPIO_USBC_SS_USB_MODE),
|
|
0, /* power low */
|
|
0x2);
|
|
return 2;
|
|
}
|
|
|
|
static int dp_config(int port, uint32_t *payload)
|
|
{
|
|
if (PD_DP_CFG_DPON(payload[1]))
|
|
gpio_set_level(GPIO_USBC_SS_USB_MODE, 0);
|
|
return 1;
|
|
}
|
|
|
|
static int svdm_enter_mode(int port, uint32_t *payload)
|
|
{
|
|
int usb_mode = gpio_get_level(GPIO_USBC_SS_USB_MODE);
|
|
|
|
/* SID & mode request is valid */
|
|
if ((PD_VDO_VID(payload[0]) != USB_SID_DISPLAYPORT) ||
|
|
(PD_VDO_OPOS(payload[0]) != OPOS))
|
|
return 0; /* will generate NAK */
|
|
|
|
if (usb_mode) {
|
|
CPRINTS("Toggle USB_MODE if you want DP & re-connect");
|
|
return 0;
|
|
}
|
|
|
|
alt_mode = OPOS;
|
|
return 1;
|
|
}
|
|
|
|
int pd_alt_mode(int port, uint16_t svid)
|
|
{
|
|
return alt_mode;
|
|
}
|
|
|
|
static int svdm_exit_mode(int port, uint32_t *payload)
|
|
{
|
|
alt_mode = 0;
|
|
/*
|
|
* Don't actually toggle GPIO_USBC_SS_USB_MODE since its manually
|
|
* controlled by operator.
|
|
*/
|
|
return 1; /* Must return ACK */
|
|
}
|
|
|
|
static struct amode_fx dp_fx = {
|
|
.status = &dp_status,
|
|
.config = &dp_config,
|
|
};
|
|
|
|
const struct svdm_response svdm_rsp = {
|
|
.identity = &svdm_response_identity,
|
|
.svids = &svdm_response_svids,
|
|
.modes = &svdm_response_modes,
|
|
.enter_mode = &svdm_enter_mode,
|
|
.amode = &dp_fx,
|
|
.exit_mode = &svdm_exit_mode,
|
|
};
|
|
|
|
int pd_custom_vdm(int port, int cnt, uint32_t *payload, uint32_t **rpayload)
|
|
{
|
|
int cmd = PD_VDO_CMD(payload[0]);
|
|
int rsize = 1;
|
|
CPRINTF("VDM/%d [%d] %08x\n", cnt, cmd, payload[0]);
|
|
|
|
*rpayload = payload;
|
|
switch (cmd) {
|
|
case VDO_CMD_VERSION:
|
|
memcpy(payload + 1, ¤t_image_data.version, 24);
|
|
rsize = 7;
|
|
break;
|
|
default:
|
|
rsize = 0;
|
|
}
|
|
|
|
CPRINTS("DONE");
|
|
/* respond (positively) to the request */
|
|
payload[0] |= VDO_SRC_RESPONDER;
|
|
|
|
return rsize;
|
|
}
|