800 lines
20 KiB
C
800 lines
20 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.
|
||
|
*/
|
||
|
/* Plankton board configuration */
|
||
|
|
||
|
#include "adc.h"
|
||
|
#include "adc_chip.h"
|
||
|
#include "common.h"
|
||
|
#include "console.h"
|
||
|
#include "gpio.h"
|
||
|
#include "hooks.h"
|
||
|
#include "i2c.h"
|
||
|
#include "ina2xx.h"
|
||
|
#include "ioexpander_pca9534.h"
|
||
|
#include "registers.h"
|
||
|
#include "system.h"
|
||
|
#include "task.h"
|
||
|
#include "timer.h"
|
||
|
#include "usb_pd.h"
|
||
|
#include "util.h"
|
||
|
|
||
|
void button_event(enum gpio_signal signal);
|
||
|
void hpd_event(enum gpio_signal signal);
|
||
|
void vbus_event(enum gpio_signal signal);
|
||
|
#include "gpio_list.h"
|
||
|
|
||
|
static volatile uint64_t hpd_prev_ts;
|
||
|
static volatile int hpd_prev_level;
|
||
|
static volatile int hpd_possible_irq;
|
||
|
|
||
|
/* Detect the type of cable used (either single CC or double) */
|
||
|
enum typec_cable {
|
||
|
TYPEC_CABLE_NONE,
|
||
|
TYPEC_CABLE_CHECK,
|
||
|
TYPEC_CABLE_SINGLE_CC,
|
||
|
TYPEC_CABLE_DOUBLE_CC
|
||
|
};
|
||
|
static enum typec_cable cable;
|
||
|
|
||
|
static int active_cc;
|
||
|
static int host_mode;
|
||
|
static int drp_enable;
|
||
|
|
||
|
static int sn75dp130_dpcd_init(void);
|
||
|
|
||
|
/**
|
||
|
* Hotplug detect deferred task
|
||
|
*
|
||
|
* Called after level change on hpd GPIO to evaluate (and debounce) what event
|
||
|
* has occurred. There are 3 events that occur on HPD:
|
||
|
* 1. low : downstream display sink is deattached
|
||
|
* 2. high : downstream display sink is attached
|
||
|
* 3. irq : downstream display sink signalling an interrupt.
|
||
|
*
|
||
|
* The debounce times for these various events are:
|
||
|
* HPD_USTREAM_DEBOUNCE_LVL : min pulse width of level value.
|
||
|
* HPD_USTREAM_DEBOUNCE_IRQ : min pulse width of IRQ low pulse.
|
||
|
*
|
||
|
* lvl(n-2) lvl(n-1) lvl prev_delta now_delta event
|
||
|
* ----------------------------------------------------
|
||
|
* 1 0 1 <IRQ n/a low glitch (ignore)
|
||
|
* 1 0 1 >IRQ <LVL irq
|
||
|
* x 0 1 n/a >LVL high
|
||
|
* 0 1 0 <LVL n/a high glitch (ignore)
|
||
|
* x 1 0 n/a >LVL low
|
||
|
*/
|
||
|
|
||
|
void hpd_lvl_deferred(void)
|
||
|
{
|
||
|
int level = gpio_get_level(GPIO_DPSRC_HPD);
|
||
|
int dp_mode = !gpio_get_level(GPIO_USBC_SS_USB_MODE);
|
||
|
|
||
|
if (level != hpd_prev_level) {
|
||
|
/* Stable level changed. Send HPD event */
|
||
|
hpd_prev_level = level;
|
||
|
if (dp_mode)
|
||
|
pd_send_hpd(0, level ? hpd_high : hpd_low);
|
||
|
/* Configure redriver's back side */
|
||
|
if (level)
|
||
|
sn75dp130_dpcd_init();
|
||
|
|
||
|
}
|
||
|
|
||
|
/* Send queued IRQ if the cable is attached */
|
||
|
if (hpd_possible_irq && level && dp_mode)
|
||
|
pd_send_hpd(0, hpd_irq);
|
||
|
hpd_possible_irq = 0;
|
||
|
|
||
|
}
|
||
|
DECLARE_DEFERRED(hpd_lvl_deferred);
|
||
|
|
||
|
void hpd_event(enum gpio_signal signal)
|
||
|
{
|
||
|
timestamp_t now = get_time();
|
||
|
int level = gpio_get_level(signal);
|
||
|
uint64_t cur_delta = now.val - hpd_prev_ts;
|
||
|
|
||
|
/* Record low pulse */
|
||
|
if (cur_delta >= HPD_USTREAM_DEBOUNCE_IRQ && level)
|
||
|
hpd_possible_irq = 1;
|
||
|
|
||
|
/* store current time */
|
||
|
hpd_prev_ts = now.val;
|
||
|
|
||
|
/* All previous hpd level events need to be re-triggered */
|
||
|
hook_call_deferred(&hpd_lvl_deferred_data,
|
||
|
HPD_USTREAM_DEBOUNCE_LVL);
|
||
|
}
|
||
|
|
||
|
/* Debounce time for voltage buttons */
|
||
|
#define BUTTON_DEBOUNCE_US (100 * MSEC)
|
||
|
|
||
|
static enum gpio_signal button_pressed;
|
||
|
|
||
|
static int fake_pd_disconnected;
|
||
|
static int fake_pd_host_mode;
|
||
|
static int fake_pd_disconnect_duration_us;
|
||
|
|
||
|
enum usbc_action {
|
||
|
USBC_ACT_5V_TO_DUT,
|
||
|
USBC_ACT_12V_TO_DUT,
|
||
|
USBC_ACT_20V_TO_DUT,
|
||
|
USBC_ACT_DEVICE,
|
||
|
USBC_ACT_USBDP_TOGGLE,
|
||
|
USBC_ACT_USB_EN,
|
||
|
USBC_ACT_DP_EN,
|
||
|
USBC_ACT_MUX_FLIP,
|
||
|
USBC_ACT_CABLE_POLARITY0,
|
||
|
USBC_ACT_CABLE_POLARITY1,
|
||
|
USBC_ACT_CCD_EN,
|
||
|
USBC_ACT_DRP_TOGGLE,
|
||
|
|
||
|
/* Number of USBC actions */
|
||
|
USBC_ACT_COUNT
|
||
|
};
|
||
|
|
||
|
enum board_src_cap src_cap_mapping[USBC_ACT_COUNT] =
|
||
|
{
|
||
|
[USBC_ACT_5V_TO_DUT] = SRC_CAP_5V,
|
||
|
[USBC_ACT_12V_TO_DUT] = SRC_CAP_12V,
|
||
|
[USBC_ACT_20V_TO_DUT] = SRC_CAP_20V,
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Set the active CC line. The non-active CC line will be left in
|
||
|
* High-Z, and we will fake the ADC reading for it.
|
||
|
*/
|
||
|
static void set_active_cc(int cc)
|
||
|
{
|
||
|
active_cc = cc;
|
||
|
|
||
|
/*
|
||
|
* If DRP mode is enabled, then set both CC lines based
|
||
|
* on the current value of host_mode. If DRP mode is
|
||
|
* disabled then only set the active CC line.
|
||
|
*/
|
||
|
/* Pull-up on CC2 */
|
||
|
gpio_set_flags(GPIO_USBC_CC2_HOST,
|
||
|
((cc || drp_enable) && host_mode) ?
|
||
|
GPIO_OUT_HIGH : GPIO_INPUT);
|
||
|
/* Pull-down on CC2 */
|
||
|
gpio_set_flags(GPIO_USBC_CC2_DEVICE_ODL,
|
||
|
((cc || drp_enable) && !host_mode) ?
|
||
|
GPIO_OUT_LOW : GPIO_INPUT);
|
||
|
/* Pull-up on CC1 */
|
||
|
gpio_set_flags(GPIO_USBC_CC1_HOST,
|
||
|
((!cc || drp_enable) && host_mode) ?
|
||
|
GPIO_OUT_HIGH : GPIO_INPUT);
|
||
|
/* Pull-down on CC1 */
|
||
|
gpio_set_flags(GPIO_USBC_CC1_DEVICE_ODL,
|
||
|
((!cc || drp_enable) && !host_mode) ?
|
||
|
GPIO_OUT_LOW : GPIO_INPUT);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Detect type-C cable type. Toggle the active CC line until a type-C connection
|
||
|
* is detected. If a type-C connection can be made in both polarities, then we
|
||
|
* have a double CC cable, otherwise we have a single CC cable.
|
||
|
*/
|
||
|
static void detect_cc_cable(void);
|
||
|
DECLARE_DEFERRED(detect_cc_cable);
|
||
|
|
||
|
static void detect_cc_cable(void)
|
||
|
{
|
||
|
/*
|
||
|
* Delay long enough to guarantee a type-C disconnect will be seen and
|
||
|
* a new connection will be made made.
|
||
|
*/
|
||
|
hook_call_deferred(&detect_cc_cable_data,
|
||
|
PD_T_CC_DEBOUNCE + PD_T_SAFE_0V);
|
||
|
|
||
|
switch (cable) {
|
||
|
case TYPEC_CABLE_NONE:
|
||
|
/* When no cable attached, toggle active CC line */
|
||
|
if (pd_is_connected(0))
|
||
|
cable = TYPEC_CABLE_CHECK;
|
||
|
set_active_cc(!active_cc);
|
||
|
break;
|
||
|
case TYPEC_CABLE_CHECK:
|
||
|
/* If we still have a connection, we have a double CC cable */
|
||
|
cable = pd_is_connected(0) ? TYPEC_CABLE_DOUBLE_CC :
|
||
|
TYPEC_CABLE_SINGLE_CC;
|
||
|
/* Flip back to original polarity and enable PD comms */
|
||
|
set_active_cc(!active_cc);
|
||
|
pd_comm_enable(0, 1);
|
||
|
break;
|
||
|
case TYPEC_CABLE_SINGLE_CC:
|
||
|
case TYPEC_CABLE_DOUBLE_CC:
|
||
|
/* Check for disconnection and disable PD comms */
|
||
|
if (!pd_is_connected(0)) {
|
||
|
cable = TYPEC_CABLE_NONE;
|
||
|
pd_comm_enable(0, 0);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void fake_disconnect_end(void)
|
||
|
{
|
||
|
fake_pd_disconnected = 0;
|
||
|
board_pd_set_host_mode(fake_pd_host_mode);
|
||
|
|
||
|
/* Restart CC cable detection */
|
||
|
hook_call_deferred(&detect_cc_cable_data, 500*MSEC);
|
||
|
}
|
||
|
DECLARE_DEFERRED(fake_disconnect_end);
|
||
|
|
||
|
static void fake_disconnect_start(void)
|
||
|
{
|
||
|
/* Cancel detection of CC cable */
|
||
|
hook_call_deferred(&detect_cc_cable_data, -1);
|
||
|
|
||
|
/* Record the current host mode */
|
||
|
fake_pd_host_mode = !gpio_get_level(GPIO_USBC_CHARGE_EN);
|
||
|
/* Disable 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);
|
||
|
/* High Z for no pull-up or pull-down resistor on CC1 and CC2 */
|
||
|
gpio_set_flags(GPIO_USBC_CC2_HOST, GPIO_INPUT);
|
||
|
gpio_set_flags(GPIO_USBC_CC2_DEVICE_ODL, GPIO_INPUT);
|
||
|
gpio_set_flags(GPIO_USBC_CC1_HOST, GPIO_INPUT);
|
||
|
gpio_set_flags(GPIO_USBC_CC1_DEVICE_ODL, GPIO_INPUT);
|
||
|
|
||
|
fake_pd_disconnected = 1;
|
||
|
|
||
|
hook_call_deferred(&fake_disconnect_end_data,
|
||
|
fake_pd_disconnect_duration_us);
|
||
|
}
|
||
|
DECLARE_DEFERRED(fake_disconnect_start);
|
||
|
|
||
|
/**
|
||
|
* Enable or disable dualrole mode operation. By default Plankton has
|
||
|
* dualrole mode disabled and attempts to connect in a sink role. Console
|
||
|
* commands/button presses can cause it to switch to source_only/sink_only
|
||
|
* modes.
|
||
|
*/
|
||
|
static void update_usbc_dual_role(int dual_role)
|
||
|
{
|
||
|
if (dual_role == PD_DRP_TOGGLE_ON) {
|
||
|
drp_enable = 1;
|
||
|
/*
|
||
|
* Cable detect is not needed when operating in dualrole mode
|
||
|
* since both CC lines are used and SRC/SNK changes are dictated
|
||
|
* by the USB PD protocol state machine.
|
||
|
*/
|
||
|
hook_call_deferred(&detect_cc_cable_data, -1);
|
||
|
/* Need to make sure both CC lines are set for SNK or SRC. */
|
||
|
set_active_cc(host_mode);
|
||
|
/* Ensure that PD communication is enabled. */
|
||
|
pd_comm_enable(0, 1);
|
||
|
} else {
|
||
|
drp_enable = 0;
|
||
|
/*
|
||
|
* Dualrole mode is not active, resume cable detect function
|
||
|
* which controls which CC line is active.
|
||
|
*/
|
||
|
hook_call_deferred(&detect_cc_cable_data, 0);
|
||
|
}
|
||
|
/* Update dual role setting used in USB PD protocol state machine */
|
||
|
pd_set_dual_role(0, dual_role);
|
||
|
cprintf(CC_USBPD, "DRP = %d, host_mode = %d\n", drp_enable, host_mode);
|
||
|
}
|
||
|
|
||
|
static void set_usbc_action(enum usbc_action act)
|
||
|
{
|
||
|
int need_soft_reset;
|
||
|
int was_usb_mode;
|
||
|
|
||
|
switch (act) {
|
||
|
case USBC_ACT_5V_TO_DUT:
|
||
|
case USBC_ACT_12V_TO_DUT:
|
||
|
case USBC_ACT_20V_TO_DUT:
|
||
|
need_soft_reset = gpio_get_level(GPIO_VBUS_CHARGER_EN);
|
||
|
board_set_source_cap(src_cap_mapping[act]);
|
||
|
update_usbc_dual_role(PD_DRP_FORCE_SOURCE);
|
||
|
if (need_soft_reset)
|
||
|
pd_soft_reset();
|
||
|
break;
|
||
|
case USBC_ACT_DEVICE:
|
||
|
update_usbc_dual_role(PD_DRP_FORCE_SINK);
|
||
|
break;
|
||
|
case USBC_ACT_USBDP_TOGGLE:
|
||
|
was_usb_mode = gpio_get_level(GPIO_USBC_SS_USB_MODE);
|
||
|
gpio_set_level(GPIO_USBC_SS_USB_MODE, !was_usb_mode);
|
||
|
gpio_set_level(GPIO_CASE_CLOSE_EN, !was_usb_mode);
|
||
|
if (!gpio_get_level(GPIO_DPSRC_HPD))
|
||
|
break;
|
||
|
/*
|
||
|
* DP cable is connected. Send HPD event according to USB/DP
|
||
|
* mux state.
|
||
|
*/
|
||
|
if (!was_usb_mode) {
|
||
|
pd_send_hpd(0, hpd_low);
|
||
|
} else {
|
||
|
pd_send_hpd(0, hpd_high);
|
||
|
pd_send_hpd(0, hpd_irq);
|
||
|
}
|
||
|
break;
|
||
|
case USBC_ACT_USB_EN:
|
||
|
gpio_set_level(GPIO_USBC_SS_USB_MODE, 1);
|
||
|
break;
|
||
|
case USBC_ACT_DP_EN:
|
||
|
gpio_set_level(GPIO_USBC_SS_USB_MODE, 0);
|
||
|
break;
|
||
|
case USBC_ACT_MUX_FLIP:
|
||
|
/*
|
||
|
* For a single CC cable, send custom VDM to flip
|
||
|
* USB polarity only. For double CC cable, actually
|
||
|
* disconnect and reconnect with opposite polarity.
|
||
|
*/
|
||
|
if (cable == TYPEC_CABLE_SINGLE_CC) {
|
||
|
pd_send_vdm(0, USB_VID_GOOGLE, VDO_CMD_FLIP, NULL, 0);
|
||
|
gpio_set_level(GPIO_USBC_POLARITY,
|
||
|
!gpio_get_level(GPIO_USBC_POLARITY));
|
||
|
} else if (cable == TYPEC_CABLE_DOUBLE_CC) {
|
||
|
/*
|
||
|
* Fake a disconnection for long enough to guarantee
|
||
|
* that we disconnect.
|
||
|
*/
|
||
|
hook_call_deferred(&fake_disconnect_start_data, -1);
|
||
|
hook_call_deferred(&fake_disconnect_end_data, -1);
|
||
|
fake_pd_disconnect_duration_us = PD_T_SAFE_0V;
|
||
|
hook_call_deferred(&fake_disconnect_start_data, 0);
|
||
|
set_active_cc(!active_cc);
|
||
|
}
|
||
|
break;
|
||
|
case USBC_ACT_CABLE_POLARITY0:
|
||
|
gpio_set_level(GPIO_USBC_POLARITY, 0);
|
||
|
break;
|
||
|
case USBC_ACT_CABLE_POLARITY1:
|
||
|
gpio_set_level(GPIO_USBC_POLARITY, 1);
|
||
|
break;
|
||
|
case USBC_ACT_CCD_EN:
|
||
|
pd_send_vdm(0, USB_VID_GOOGLE, VDO_CMD_CCD_EN, NULL, 0);
|
||
|
/* Switch to USB mode when enable CCD. */
|
||
|
gpio_set_level(GPIO_USBC_SS_USB_MODE, 1);
|
||
|
/* Reset RFU polarity MUX */
|
||
|
gpio_set_level(GPIO_CASE_CLOSE_EN, 0);
|
||
|
gpio_set_level(GPIO_CASE_CLOSE_DFU_L, 0);
|
||
|
gpio_set_level(GPIO_CASE_CLOSE_EN, 1);
|
||
|
gpio_set_level(GPIO_CASE_CLOSE_DFU_L, 1);
|
||
|
break;
|
||
|
case USBC_ACT_DRP_TOGGLE:
|
||
|
/* Toggle dualrole mode setting. */
|
||
|
update_usbc_dual_role(drp_enable ?
|
||
|
PD_DRP_TOGGLE_OFF : PD_DRP_TOGGLE_ON);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* has Pull-up */
|
||
|
static int prev_dbg20v = 1;
|
||
|
|
||
|
static void button_dbg20v_deferred(void);
|
||
|
DECLARE_DEFERRED(button_dbg20v_deferred);
|
||
|
|
||
|
static void enable_dbg20v_poll(void)
|
||
|
{
|
||
|
hook_call_deferred(&button_dbg20v_deferred_data, 10 * MSEC);
|
||
|
}
|
||
|
|
||
|
/* Handle debounced button press */
|
||
|
static void button_deferred(void)
|
||
|
{
|
||
|
if (button_pressed == GPIO_DBG_20V_TO_DUT_L) {
|
||
|
enable_dbg20v_poll();
|
||
|
if (gpio_get_level(GPIO_DBG_20V_TO_DUT_L) == prev_dbg20v)
|
||
|
return;
|
||
|
else
|
||
|
prev_dbg20v = !prev_dbg20v;
|
||
|
}
|
||
|
/* bounce ? */
|
||
|
if (gpio_get_level(button_pressed) != 0)
|
||
|
return;
|
||
|
|
||
|
switch (button_pressed) {
|
||
|
case GPIO_DBG_5V_TO_DUT_L:
|
||
|
set_usbc_action(USBC_ACT_5V_TO_DUT);
|
||
|
break;
|
||
|
case GPIO_DBG_12V_TO_DUT_L:
|
||
|
set_usbc_action(USBC_ACT_12V_TO_DUT);
|
||
|
break;
|
||
|
case GPIO_DBG_20V_TO_DUT_L:
|
||
|
set_usbc_action(USBC_ACT_20V_TO_DUT);
|
||
|
break;
|
||
|
case GPIO_DBG_CHG_TO_DEV_L:
|
||
|
set_usbc_action(USBC_ACT_DEVICE);
|
||
|
break;
|
||
|
case GPIO_DBG_USB_TOGGLE_L:
|
||
|
set_usbc_action(USBC_ACT_USBDP_TOGGLE);
|
||
|
if (gpio_get_level(GPIO_USBC_SS_USB_MODE))
|
||
|
board_maybe_reset_usb_hub();
|
||
|
break;
|
||
|
case GPIO_DBG_MUX_FLIP_L:
|
||
|
set_usbc_action(USBC_ACT_MUX_FLIP);
|
||
|
break;
|
||
|
case GPIO_DBG_CASE_CLOSE_EN_L:
|
||
|
set_usbc_action(USBC_ACT_CCD_EN);
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
ccprintf("Button %d = %d\n",
|
||
|
button_pressed, gpio_get_level(button_pressed));
|
||
|
}
|
||
|
DECLARE_DEFERRED(button_deferred);
|
||
|
|
||
|
void button_event(enum gpio_signal signal)
|
||
|
{
|
||
|
button_pressed = signal;
|
||
|
/* reset debounce time */
|
||
|
hook_call_deferred(&button_deferred_data, BUTTON_DEBOUNCE_US);
|
||
|
}
|
||
|
|
||
|
static void button_dbg20v_deferred(void)
|
||
|
{
|
||
|
if (gpio_get_level(GPIO_DBG_20V_TO_DUT_L) == 0)
|
||
|
button_event(GPIO_DBG_20V_TO_DUT_L);
|
||
|
else
|
||
|
enable_dbg20v_poll();
|
||
|
}
|
||
|
|
||
|
void vbus_event(enum gpio_signal signal)
|
||
|
{
|
||
|
ccprintf("VBUS! =%d\n", gpio_get_level(signal));
|
||
|
task_wake(TASK_ID_PD_C0);
|
||
|
}
|
||
|
|
||
|
/* ADC channels */
|
||
|
const struct adc_t adc_channels[] = {
|
||
|
/* USB PD CC lines sensing. Converted to mV (3300mV/4096). */
|
||
|
[ADC_CH_CC1_PD] = {"CC1_PD", 3300, 4096, 0, STM32_AIN(0)},
|
||
|
[ADC_CH_CC2_PD] = {"CC2_PD", 3300, 4096, 0, STM32_AIN(4)},
|
||
|
};
|
||
|
BUILD_ASSERT(ARRAY_SIZE(adc_channels) == ADC_CH_COUNT);
|
||
|
|
||
|
/* I2C ports */
|
||
|
const struct i2c_port_t i2c_ports[] = {
|
||
|
{"master", I2C_PORT_MASTER, 100,
|
||
|
GPIO_MASTER_I2C_SCL, GPIO_MASTER_I2C_SDA},
|
||
|
};
|
||
|
const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports);
|
||
|
|
||
|
/* 8-bit address */
|
||
|
#define SN75DP130_I2C_ADDR_FLAGS 0x2e
|
||
|
/*
|
||
|
* Pin number for active-high reset from PCA9534 to CMOS pull-down to
|
||
|
* SN75DP130's RSTN (active-low)
|
||
|
*/
|
||
|
#define REDRIVER_RST_PIN 0x1
|
||
|
|
||
|
static int sn75dp130_i2c_write(uint8_t index, uint8_t value)
|
||
|
{
|
||
|
return i2c_write8(I2C_PORT_MASTER, SN75DP130_I2C_ADDR_FLAGS,
|
||
|
index, value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reset redriver.
|
||
|
*
|
||
|
* Note, MUST set SW15 to 'PD' in order to control i2c from PD-MCU. This can
|
||
|
* NOT be done via software.
|
||
|
*/
|
||
|
static int sn75dp130_reset(void)
|
||
|
{
|
||
|
int rv;
|
||
|
|
||
|
rv = pca9534_config_pin(I2C_PORT_MASTER, 0x20,
|
||
|
REDRIVER_RST_PIN, PCA9534_OUTPUT);
|
||
|
/* Assert (its active high) */
|
||
|
rv |= pca9534_set_level(I2C_PORT_MASTER, 0x20,
|
||
|
REDRIVER_RST_PIN, 1);
|
||
|
/* datasheet recommends > 100usec */
|
||
|
usleep(200);
|
||
|
|
||
|
/* De-assert */
|
||
|
rv |= pca9534_set_level(I2C_PORT_MASTER, 0x20,
|
||
|
REDRIVER_RST_PIN, 0);
|
||
|
/* datasheet recommends > 400msec */
|
||
|
usleep(450 * MSEC);
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
static int sn75dp130_dpcd_init(void)
|
||
|
{
|
||
|
int i, rv;
|
||
|
|
||
|
/* set upper & middle DPCD addr ... constant for writes below */
|
||
|
rv = sn75dp130_i2c_write(0x1c, 0x0);
|
||
|
rv |= sn75dp130_i2c_write(0x1d, 0x1);
|
||
|
|
||
|
/* link_bw_set: 5.4gbps */
|
||
|
rv |= sn75dp130_i2c_write(0x1e, 0x0);
|
||
|
rv |= sn75dp130_i2c_write(0x1f, 0x14);
|
||
|
|
||
|
/* lane_count_set: 4 */
|
||
|
rv |= sn75dp130_i2c_write(0x1e, 0x1);
|
||
|
rv |= sn75dp130_i2c_write(0x1f, 0x4);
|
||
|
|
||
|
/*
|
||
|
* Force Link voltage level & pre-emphasis by writing each of the lane's
|
||
|
* DPCD config registers 103-106h accordingly.
|
||
|
*/
|
||
|
for (i = 0x3; i < 0x7; i++) {
|
||
|
rv |= sn75dp130_i2c_write(0x1e, i);
|
||
|
rv |= sn75dp130_i2c_write(0x1f, 0x3);
|
||
|
}
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
static int sn75dp130_redriver_init(void)
|
||
|
{
|
||
|
int rv;
|
||
|
|
||
|
rv = sn75dp130_reset();
|
||
|
|
||
|
/* Disable squelch detect */
|
||
|
rv |= sn75dp130_i2c_write(0x3, 0x1a);
|
||
|
/* Disable link training on re-driver source side */
|
||
|
rv |= sn75dp130_i2c_write(0x4, 0x0);
|
||
|
|
||
|
/* Can only configure DPCD portion of redriver in presence of an HPD */
|
||
|
if (gpio_get_level(GPIO_DPSRC_HPD))
|
||
|
sn75dp130_dpcd_init();
|
||
|
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
static int cmd_usbc_action(int argc, char *argv[])
|
||
|
{
|
||
|
enum usbc_action act;
|
||
|
|
||
|
if (argc != 2)
|
||
|
return EC_ERROR_PARAM_COUNT;
|
||
|
|
||
|
if (!strcasecmp(argv[1], "5v"))
|
||
|
act = USBC_ACT_5V_TO_DUT;
|
||
|
else if (!strcasecmp(argv[1], "12v"))
|
||
|
act = USBC_ACT_12V_TO_DUT;
|
||
|
else if (!strcasecmp(argv[1], "20v"))
|
||
|
act = USBC_ACT_20V_TO_DUT;
|
||
|
else if (!strcasecmp(argv[1], "ccd"))
|
||
|
act = USBC_ACT_CCD_EN;
|
||
|
else if (!strcasecmp(argv[1], "dev"))
|
||
|
act = USBC_ACT_DEVICE;
|
||
|
else if (!strcasecmp(argv[1], "usb"))
|
||
|
act = USBC_ACT_USB_EN;
|
||
|
else if (!strcasecmp(argv[1], "dp"))
|
||
|
act = USBC_ACT_DP_EN;
|
||
|
else if (!strcasecmp(argv[1], "flip"))
|
||
|
act = USBC_ACT_MUX_FLIP;
|
||
|
else if (!strcasecmp(argv[1], "pol0"))
|
||
|
act = USBC_ACT_CABLE_POLARITY0;
|
||
|
else if (!strcasecmp(argv[1], "pol1"))
|
||
|
act = USBC_ACT_CABLE_POLARITY1;
|
||
|
else if (!strcasecmp(argv[1], "drp"))
|
||
|
act = USBC_ACT_DRP_TOGGLE;
|
||
|
else
|
||
|
return EC_ERROR_PARAM1;
|
||
|
|
||
|
set_usbc_action(act);
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
DECLARE_CONSOLE_COMMAND(usbc_action, cmd_usbc_action,
|
||
|
"<5v|12v|20v|ccd|dev|usb|dp|flip|pol0|pol1|drp>",
|
||
|
"Set Plankton type-C port state");
|
||
|
|
||
|
int board_in_hub_mode(void)
|
||
|
{
|
||
|
int ret;
|
||
|
int level;
|
||
|
|
||
|
ret = pca9534_config_pin(I2C_PORT_MASTER, 0x20,
|
||
|
6, PCA9534_INPUT);
|
||
|
if (ret)
|
||
|
return -1;
|
||
|
ret = pca9534_get_level(I2C_PORT_MASTER, 0x20,
|
||
|
6, &level);
|
||
|
if (ret)
|
||
|
return -1;
|
||
|
return level;
|
||
|
}
|
||
|
|
||
|
static int board_usb_hub_reset(void)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = pca9534_config_pin(I2C_PORT_MASTER, 0x20,
|
||
|
7, PCA9534_OUTPUT);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
ret = pca9534_set_level(I2C_PORT_MASTER, 0x20,
|
||
|
7, 0);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
usleep(100 * MSEC);
|
||
|
return pca9534_set_level(I2C_PORT_MASTER, 0x20,
|
||
|
7, 1);
|
||
|
}
|
||
|
|
||
|
void board_maybe_reset_usb_hub(void)
|
||
|
{
|
||
|
if (board_in_hub_mode() == 1)
|
||
|
board_usb_hub_reset();
|
||
|
}
|
||
|
|
||
|
static int cmd_usb_hub_reset(int argc, char *argv[])
|
||
|
{
|
||
|
return board_usb_hub_reset();
|
||
|
}
|
||
|
DECLARE_CONSOLE_COMMAND(hub_reset, cmd_usb_hub_reset,
|
||
|
NULL, "Reset USB hub");
|
||
|
|
||
|
static void board_usb_hub_reset_no_return(void)
|
||
|
{
|
||
|
board_usb_hub_reset();
|
||
|
}
|
||
|
DECLARE_DEFERRED(board_usb_hub_reset_no_return);
|
||
|
|
||
|
static int board_pd_fake_disconnected(void)
|
||
|
{
|
||
|
return fake_pd_disconnected;
|
||
|
}
|
||
|
|
||
|
int board_fake_pd_adc_read(int cc)
|
||
|
{
|
||
|
if (fake_pd_disconnected) {
|
||
|
/* Always disconnected */
|
||
|
return fake_pd_host_mode ? 3000 : 0;
|
||
|
} else {
|
||
|
if (drp_enable) {
|
||
|
/* Always read the req CC line when in drp mode */
|
||
|
return adc_read_channel(cc ? ADC_CH_CC2_PD :
|
||
|
ADC_CH_CC1_PD);
|
||
|
} else {
|
||
|
/*
|
||
|
* Only read the active CC line, fake disconnected
|
||
|
* on other CC line. */
|
||
|
if (active_cc == cc)
|
||
|
return adc_read_channel(cc ? ADC_CH_CC2_PD :
|
||
|
ADC_CH_CC1_PD);
|
||
|
else
|
||
|
return host_mode ? 3000 : 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Set fake PD pull-up/pull-down */
|
||
|
static void board_update_fake_adc_value(int host_mode)
|
||
|
{
|
||
|
fake_pd_host_mode = host_mode;
|
||
|
}
|
||
|
|
||
|
void board_pd_set_host_mode(int enable)
|
||
|
{
|
||
|
if (!drp_enable)
|
||
|
cprintf(CC_USBPD, "Host mode: %d\n", enable);
|
||
|
|
||
|
if (board_pd_fake_disconnected()) {
|
||
|
board_update_fake_adc_value(enable);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* if host mode changed, reset cable type */
|
||
|
if (host_mode != enable) {
|
||
|
host_mode = enable;
|
||
|
cable = TYPEC_CABLE_NONE;
|
||
|
}
|
||
|
|
||
|
if (enable) {
|
||
|
/* Source mode, disable charging */
|
||
|
gpio_set_level(GPIO_USBC_CHARGE_EN, 0);
|
||
|
|
||
|
/* Set CC lines */
|
||
|
set_active_cc(active_cc);
|
||
|
} else {
|
||
|
/* Device mode, disable 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);
|
||
|
|
||
|
/* Set CC lines */
|
||
|
set_active_cc(active_cc);
|
||
|
|
||
|
/* Enable charging */
|
||
|
gpio_set_level(GPIO_USBC_CHARGE_EN, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void board_init(void)
|
||
|
{
|
||
|
timestamp_t now = get_time();
|
||
|
hpd_prev_level = gpio_get_level(GPIO_DPSRC_HPD);
|
||
|
hpd_prev_ts = now.val;
|
||
|
gpio_enable_interrupt(GPIO_DPSRC_HPD);
|
||
|
|
||
|
/* Start up with dualrole mode off */
|
||
|
drp_enable = 0;
|
||
|
|
||
|
/* Enable interrupts on VBUS transitions. */
|
||
|
gpio_enable_interrupt(GPIO_VBUS_WAKE);
|
||
|
|
||
|
/* Enable button interrupts. */
|
||
|
gpio_enable_interrupt(GPIO_DBG_5V_TO_DUT_L);
|
||
|
gpio_enable_interrupt(GPIO_DBG_12V_TO_DUT_L);
|
||
|
gpio_enable_interrupt(GPIO_DBG_CHG_TO_DEV_L);
|
||
|
gpio_enable_interrupt(GPIO_DBG_USB_TOGGLE_L);
|
||
|
gpio_enable_interrupt(GPIO_DBG_MUX_FLIP_L);
|
||
|
gpio_enable_interrupt(GPIO_DBG_CASE_CLOSE_EN_L);
|
||
|
|
||
|
/* TODO(crosbug.com/33761): poll DBG_20V_TO_DUT_L */
|
||
|
enable_dbg20v_poll();
|
||
|
|
||
|
ina2xx_init(0, 0x399f, INA2XX_CALIB_1MA(10 /* mOhm */));
|
||
|
sn75dp130_redriver_init();
|
||
|
|
||
|
/* Initialize USB hub */
|
||
|
if (system_get_reset_flags() & EC_RESET_FLAG_POWER_ON)
|
||
|
hook_call_deferred(&board_usb_hub_reset_no_return_data,
|
||
|
500 * MSEC);
|
||
|
|
||
|
/* Start detecting CC cable type */
|
||
|
hook_call_deferred(&detect_cc_cable_data, SECOND);
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT);
|
||
|
|
||
|
static int cmd_fake_disconnect(int argc, char *argv[])
|
||
|
{
|
||
|
int delay_ms, duration_ms;
|
||
|
char *e;
|
||
|
|
||
|
if (argc < 3)
|
||
|
return EC_ERROR_PARAM_COUNT;
|
||
|
|
||
|
delay_ms = strtoi(argv[1], &e, 0);
|
||
|
if (*e || delay_ms < 0)
|
||
|
return EC_ERROR_PARAM1;
|
||
|
duration_ms = strtoi(argv[2], &e, 0);
|
||
|
if (*e || duration_ms < 0)
|
||
|
return EC_ERROR_PARAM2;
|
||
|
|
||
|
/* Cancel any pending function calls */
|
||
|
hook_call_deferred(&fake_disconnect_start_data, -1);
|
||
|
hook_call_deferred(&fake_disconnect_end_data, -1);
|
||
|
|
||
|
fake_pd_disconnect_duration_us = duration_ms * MSEC;
|
||
|
hook_call_deferred(&fake_disconnect_start_data, delay_ms * MSEC);
|
||
|
|
||
|
ccprintf("Fake disconnect for %d ms starting in %d ms.\n",
|
||
|
duration_ms, delay_ms);
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
DECLARE_CONSOLE_COMMAND(fakedisconnect, cmd_fake_disconnect,
|
||
|
"<delay_ms> <duration_ms>", NULL);
|
||
|
|
||
|
static void trigger_dfu_release(void)
|
||
|
{
|
||
|
gpio_set_level(GPIO_CASE_CLOSE_DFU_L, 1);
|
||
|
ccprintf("Deasserting CASE_CLOSE_DFU_L.\n");
|
||
|
}
|
||
|
DECLARE_DEFERRED(trigger_dfu_release);
|
||
|
|
||
|
static int cmd_trigger_dfu(int argc, char *argv[])
|
||
|
{
|
||
|
gpio_set_level(GPIO_CASE_CLOSE_DFU_L, 0);
|
||
|
ccprintf("Asserting CASE_CLOSE_DFU_L.\n");
|
||
|
ccprintf("If you expect to see DFU debug but it doesn't show up,\n");
|
||
|
ccprintf("try flipping the USB type-C cable.\n");
|
||
|
hook_call_deferred(&trigger_dfu_release_data, 1500 * MSEC);
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
DECLARE_CONSOLE_COMMAND(dfu, cmd_trigger_dfu, NULL, NULL);
|