406 lines
9.0 KiB
C
406 lines
9.0 KiB
C
|
/* Copyright 2019 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.
|
||
|
*/
|
||
|
|
||
|
/* Fluffy configuration */
|
||
|
|
||
|
#include "adc.h"
|
||
|
#include "adc_chip.h"
|
||
|
#include "common.h"
|
||
|
#include "console.h"
|
||
|
#include "ec_version.h"
|
||
|
#include "hooks.h"
|
||
|
#include "i2c.h"
|
||
|
#include "usb_descriptor.h"
|
||
|
#include "registers.h"
|
||
|
#include "timer.h"
|
||
|
#include "usb_pd.h"
|
||
|
#include "util.h"
|
||
|
|
||
|
#include "gpio_list.h"
|
||
|
|
||
|
#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)
|
||
|
#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ## args)
|
||
|
|
||
|
/******************************************************************************
|
||
|
* Define the strings used in our USB descriptors.
|
||
|
*/
|
||
|
|
||
|
const void *const usb_strings[] = {
|
||
|
[USB_STR_DESC] = usb_string_desc,
|
||
|
[USB_STR_VENDOR] = USB_STRING_DESC("Google Inc."),
|
||
|
[USB_STR_PRODUCT] = USB_STRING_DESC("Fluffy"),
|
||
|
/* This gets filled in at runtime. */
|
||
|
[USB_STR_SERIALNO] = USB_STRING_DESC(""),
|
||
|
[USB_STR_VERSION] = USB_STRING_DESC(CROS_EC_VERSION32),
|
||
|
[USB_STR_CONSOLE_NAME] = USB_STRING_DESC("Fluffy Shell"),
|
||
|
};
|
||
|
|
||
|
BUILD_ASSERT(ARRAY_SIZE(usb_strings) == USB_STR_COUNT);
|
||
|
|
||
|
/* ADC channels */
|
||
|
const struct adc_t adc_channels[] = {
|
||
|
/* Sensing the VBUS voltage at the DUT side. Converted to mV. */
|
||
|
[ADC_PPVAR_VBUS_DUT] = {
|
||
|
.name = "PPVAR_VBUS_DUT",
|
||
|
.factor_mul = 3300,
|
||
|
.factor_div = 4096,
|
||
|
.shift = 0,
|
||
|
.channel = STM32_AIN(0),
|
||
|
},
|
||
|
};
|
||
|
BUILD_ASSERT(ARRAY_SIZE(adc_channels) == ADC_CH_COUNT);
|
||
|
|
||
|
/* I2C ports */
|
||
|
const struct i2c_port_t i2c_ports[] = {
|
||
|
{
|
||
|
.name = "master",
|
||
|
.port = 1,
|
||
|
.kbps = 400,
|
||
|
.scl = GPIO_I2C_SCL,
|
||
|
.sda = GPIO_I2C_SDA,
|
||
|
},
|
||
|
};
|
||
|
const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports);
|
||
|
|
||
|
static enum gpio_signal enabled_port = GPIO_EN_C0;
|
||
|
static uint8_t output_en;
|
||
|
|
||
|
static void print_port_status(void)
|
||
|
{
|
||
|
if (!output_en)
|
||
|
CPRINTS("No ports enabled. zZZ");
|
||
|
else
|
||
|
CPRINTS("Port %d is ON", enabled_port - GPIO_EN_C0);
|
||
|
|
||
|
CPRINTS("CC Flip: %s", gpio_get_level(GPIO_EN_CC_FLIP) ? "YES" : "NO");
|
||
|
CPRINTS("USB MUX: %s", gpio_get_level(GPIO_EN_USB_MUX2) ? "ON" : "OFF");
|
||
|
}
|
||
|
|
||
|
static int command_cc_flip(int argc, char *argv[])
|
||
|
{
|
||
|
int enable;
|
||
|
|
||
|
if (argc != 2)
|
||
|
return EC_ERROR_PARAM_COUNT;
|
||
|
|
||
|
if (!parse_bool(argv[1], &enable))
|
||
|
return EC_ERROR_INVAL;
|
||
|
|
||
|
if (output_en) {
|
||
|
gpio_set_level(enabled_port, 0);
|
||
|
gpio_set_level(GPIO_EN_USB_MUX2, 0);
|
||
|
/* Wait long enough for CC to discharge. */
|
||
|
usleep(500 * MSEC);
|
||
|
}
|
||
|
|
||
|
gpio_set_level(GPIO_EN_CC_FLIP, enable);
|
||
|
/* Allow some time for new CC configuration to settle. */
|
||
|
usleep(500 * MSEC);
|
||
|
|
||
|
if (output_en) {
|
||
|
gpio_set_level(enabled_port, 1);
|
||
|
gpio_set_level(GPIO_EN_USB_MUX2, 1);
|
||
|
}
|
||
|
|
||
|
print_port_status();
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
DECLARE_CONSOLE_COMMAND(ccflip, command_cc_flip,
|
||
|
"<enable/disable>",
|
||
|
"enable or disable flipping CC orientation");
|
||
|
/*
|
||
|
* Support tca6416 I2C ioexpander.
|
||
|
*/
|
||
|
#define GPIOX_I2C_ADDR_FLAGS 0x20
|
||
|
#define GPIOX_IN_PORT_A 0x0
|
||
|
#define GPIOX_IN_PORT_B 0x1
|
||
|
#define GPIOX_OUT_PORT_A 0x2
|
||
|
#define GPIOX_OUT_PORT_B 0x3
|
||
|
#define GPIOX_DIR_PORT_A 0x6
|
||
|
#define GPIOX_DIR_PORT_B 0x7
|
||
|
#define I2C_PORT_MASTER 1
|
||
|
|
||
|
static void i2c_expander_init(void)
|
||
|
{
|
||
|
gpio_set_level(GPIO_XP_RESET_L, 1);
|
||
|
|
||
|
/*
|
||
|
* Setup P00, P02, P04, P10, and P12 on the I/O expander as an output.
|
||
|
*/
|
||
|
i2c_write8(I2C_PORT_MASTER, GPIOX_I2C_ADDR_FLAGS,
|
||
|
GPIOX_DIR_PORT_A, 0xea);
|
||
|
i2c_write8(I2C_PORT_MASTER, GPIOX_I2C_ADDR_FLAGS,
|
||
|
GPIOX_DIR_PORT_B, 0xfa);
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_INIT, i2c_expander_init, HOOK_PRIO_INIT_I2C+1);
|
||
|
|
||
|
/* Write to a GPIO register on the tca6416 I2C ioexpander. */
|
||
|
static void write_ioexpander(int bank, int gpio, int reg, int val)
|
||
|
{
|
||
|
int tmp;
|
||
|
|
||
|
/* Read output port register */
|
||
|
i2c_read8(I2C_PORT_MASTER, GPIOX_I2C_ADDR_FLAGS,
|
||
|
reg + bank, &tmp);
|
||
|
if (val)
|
||
|
tmp |= BIT(gpio);
|
||
|
else
|
||
|
tmp &= ~BIT(gpio);
|
||
|
/* Write back modified output port register */
|
||
|
i2c_write8(I2C_PORT_MASTER, GPIOX_I2C_ADDR_FLAGS,
|
||
|
reg + bank, tmp);
|
||
|
}
|
||
|
|
||
|
enum led_ch {
|
||
|
LED_5V = 0,
|
||
|
LED_9V,
|
||
|
LED_12V,
|
||
|
LED_15V,
|
||
|
LED_20V,
|
||
|
LED_COUNT,
|
||
|
};
|
||
|
|
||
|
static void set_led(enum led_ch led, int enable)
|
||
|
{
|
||
|
int bank;
|
||
|
int gpio;
|
||
|
|
||
|
switch (led) {
|
||
|
case LED_5V:
|
||
|
bank = 0;
|
||
|
gpio = 0;
|
||
|
break;
|
||
|
|
||
|
case LED_9V:
|
||
|
bank = 0;
|
||
|
gpio = 2;
|
||
|
break;
|
||
|
|
||
|
case LED_12V:
|
||
|
bank = 0;
|
||
|
gpio = 4;
|
||
|
break;
|
||
|
|
||
|
case LED_15V:
|
||
|
bank = 1;
|
||
|
gpio = 0;
|
||
|
break;
|
||
|
|
||
|
case LED_20V:
|
||
|
bank = 1;
|
||
|
gpio = 2;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Setup the LED as an output if enabled, otherwise as an input to keep
|
||
|
* the LEDs off.
|
||
|
*/
|
||
|
write_ioexpander(bank, gpio, GPIOX_DIR_PORT_A, !enable);
|
||
|
|
||
|
/* The LEDs are active low. */
|
||
|
if (enable)
|
||
|
write_ioexpander(bank, gpio, GPIOX_OUT_PORT_A, 0);
|
||
|
}
|
||
|
|
||
|
void show_output_voltage_on_leds(void);
|
||
|
DECLARE_DEFERRED(show_output_voltage_on_leds);
|
||
|
|
||
|
static void board_init(void)
|
||
|
{
|
||
|
/* Do a sweeping LED dance. */
|
||
|
for (enum led_ch led = 0; led < LED_COUNT; led++) {
|
||
|
set_led(led, 1);
|
||
|
msleep(100);
|
||
|
}
|
||
|
|
||
|
msleep(500);
|
||
|
|
||
|
for (enum led_ch led = 0; led < LED_COUNT; led++)
|
||
|
set_led(led, 0);
|
||
|
|
||
|
show_output_voltage_on_leds();
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT);
|
||
|
|
||
|
|
||
|
enum usb_mux {
|
||
|
USB_MUX0 = 0,
|
||
|
USB_MUX1,
|
||
|
USB_MUX2,
|
||
|
USB_MUX_COUNT,
|
||
|
};
|
||
|
|
||
|
static void set_mux(enum usb_mux mux, uint8_t val)
|
||
|
{
|
||
|
enum gpio_signal c0;
|
||
|
enum gpio_signal c1;
|
||
|
enum gpio_signal c2;
|
||
|
|
||
|
switch (mux) {
|
||
|
case USB_MUX0:
|
||
|
c0 = GPIO_USB_MUX0_C0;
|
||
|
c1 = GPIO_USB_MUX0_C1;
|
||
|
c2 = GPIO_USB_MUX0_C2;
|
||
|
break;
|
||
|
|
||
|
case USB_MUX1:
|
||
|
c0 = GPIO_USB_MUX1_C0;
|
||
|
c1 = GPIO_USB_MUX1_C1;
|
||
|
c2 = GPIO_USB_MUX1_C2;
|
||
|
break;
|
||
|
|
||
|
case USB_MUX2:
|
||
|
c0 = GPIO_USB_MUX2_C0;
|
||
|
c1 = GPIO_USB_MUX2_C1;
|
||
|
c2 = GPIO_USB_MUX2_C2;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
val &= 0x7;
|
||
|
|
||
|
gpio_set_level(c0, val & BIT(0));
|
||
|
gpio_set_level(c1, val & BIT(1));
|
||
|
gpio_set_level(c2, val & BIT(2));
|
||
|
}
|
||
|
|
||
|
/* This function assumes only 1 port works at a time. */
|
||
|
static int command_portctl(int argc, char **argv)
|
||
|
{
|
||
|
int port;
|
||
|
int enable;
|
||
|
|
||
|
if (argc < 2)
|
||
|
return EC_ERROR_PARAM_COUNT;
|
||
|
|
||
|
port = atoi(argv[1]);
|
||
|
if ((port < 0) || (port > 19) || !parse_bool(argv[2], &enable))
|
||
|
return EC_ERROR_INVAL;
|
||
|
|
||
|
gpio_set_level(GPIO_EN_USB_MUX2, 0);
|
||
|
|
||
|
/*
|
||
|
* For each port, we must configure the USB 2.0 muxes and make sure that
|
||
|
* the power enables are configured as desired.
|
||
|
*/
|
||
|
|
||
|
gpio_set_level(enabled_port, 0);
|
||
|
if (enabled_port != GPIO_EN_C0 + port)
|
||
|
CPRINTS("Port %d: disabled", enabled_port-GPIO_EN_C0);
|
||
|
|
||
|
/* Allow time for an "unplug" to allow VBUS and CC to fall. */
|
||
|
usleep(1 * SECOND);
|
||
|
|
||
|
/*
|
||
|
* The USB 2.0 lines are arranged using 3x 8:1 muxes. Ports 0-7 are
|
||
|
* handled by the first mux, ports 8-15 are handled by the 2nd mux, then
|
||
|
* the outputs of those muxes are fed into the third mux along with
|
||
|
* ports 16-19. The schematic contains the truth table.
|
||
|
*/
|
||
|
if (enable) {
|
||
|
enabled_port = GPIO_EN_C0 + port;
|
||
|
gpio_set_level(enabled_port, 1);
|
||
|
|
||
|
if (port < 8) {
|
||
|
set_mux(USB_MUX0, 7-port);
|
||
|
set_mux(USB_MUX2, 3);
|
||
|
} else if (port < 16) {
|
||
|
if (port < 14)
|
||
|
set_mux(USB_MUX1, 5-(port-8));
|
||
|
else
|
||
|
set_mux(USB_MUX1, 7-(port-14));
|
||
|
|
||
|
set_mux(USB_MUX2, 1);
|
||
|
} else {
|
||
|
set_mux(USB_MUX2, 7-(port-16));
|
||
|
}
|
||
|
|
||
|
gpio_set_level(GPIO_EN_USB_MUX2, 1);
|
||
|
output_en = 1;
|
||
|
} else {
|
||
|
gpio_set_level(enabled_port, 0);
|
||
|
output_en = 0;
|
||
|
}
|
||
|
|
||
|
print_port_status();
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
DECLARE_CONSOLE_COMMAND(portctl, command_portctl,
|
||
|
"<port# 0-19> <enable/disable>",
|
||
|
"enable or disable a port");
|
||
|
|
||
|
static int command_status(int argc, char **argv)
|
||
|
{
|
||
|
int vbus_mv = adc_read_channel(ADC_PPVAR_VBUS_DUT);
|
||
|
|
||
|
CPRINTS("PPVAR_VBUS_DUT: %dmV (raw: %d)", vbus_mv*7692/1000,
|
||
|
vbus_mv);
|
||
|
print_port_status();
|
||
|
|
||
|
return EC_SUCCESS;
|
||
|
}
|
||
|
DECLARE_CONSOLE_COMMAND(status, command_status, NULL, "show current status");
|
||
|
|
||
|
/*
|
||
|
* According to the USB PD Spec, the minimum voltage for a fixed source is 95%
|
||
|
* of the new source voltage with an additional 500mV drop.
|
||
|
*
|
||
|
* vSrcNew | min | vSrcNew(min) + vSrcValid
|
||
|
* 5V | 4.75V | 4.25V | 553mV
|
||
|
* 9V | 8.55V | 8.05V | 1047mV
|
||
|
* 12V | 11.4V | 10.9V | 1417mV
|
||
|
* 15V | 14.25V | 13.75V | 1788mV
|
||
|
* 20V | 19V | 18.5V | 2405mV
|
||
|
*
|
||
|
* With the resistor divider that fluffy has, the ADC is only seeing 0.13 of the
|
||
|
* actual voltage.
|
||
|
*/
|
||
|
void show_output_voltage_on_leds(void)
|
||
|
{
|
||
|
int vbus_mv = adc_read_channel(ADC_PPVAR_VBUS_DUT);
|
||
|
static int prev_vbus_mv;
|
||
|
int i;
|
||
|
int act;
|
||
|
enum led_ch max_on_exclusive = LED_5V;
|
||
|
|
||
|
if (vbus_mv != ADC_READ_ERROR) {
|
||
|
if (vbus_mv >= 2405)
|
||
|
max_on_exclusive = LED_COUNT;
|
||
|
else if (vbus_mv >= 1788)
|
||
|
max_on_exclusive = LED_20V;
|
||
|
else if (vbus_mv >= 1417)
|
||
|
max_on_exclusive = LED_15V;
|
||
|
else if (vbus_mv >= 1047)
|
||
|
max_on_exclusive = LED_12V;
|
||
|
else if (vbus_mv >= 553)
|
||
|
max_on_exclusive = LED_9V;
|
||
|
|
||
|
for (i = 0; i < LED_COUNT; i++)
|
||
|
set_led(i, i < max_on_exclusive);
|
||
|
|
||
|
act = (vbus_mv * 7692) / 1000;
|
||
|
if ((vbus_mv > prev_vbus_mv+2) || (vbus_mv < prev_vbus_mv-2)) {
|
||
|
CPRINTS("PPVAR_VBUS_DUT: %d mV (raw: %d)", act,
|
||
|
vbus_mv);
|
||
|
prev_vbus_mv = vbus_mv;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The reason we reschedule this ourselves as opposed to declaring it as
|
||
|
* a hook with a HOOK_TICK period is to allow the LED sweep sequence
|
||
|
* when the board boots up.
|
||
|
*/
|
||
|
hook_call_deferred(&show_output_voltage_on_leds_data,
|
||
|
500 * MSEC);
|
||
|
}
|