383 lines
9.4 KiB
C
383 lines
9.4 KiB
C
/* Copyright 2020 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.
|
|
*/
|
|
|
|
/* Waddledoo board-specific configuration */
|
|
|
|
#include "adc_chip.h"
|
|
#include "button.h"
|
|
#include "charge_manager.h"
|
|
#include "charge_state_v2.h"
|
|
#include "charger.h"
|
|
#include "common.h"
|
|
#include "compile_time_macros.h"
|
|
#include "driver/accel_bma2x2.h"
|
|
#include "driver/accelgyro_bmi160.h"
|
|
#include "driver/bc12/pi3usb9201.h"
|
|
#include "driver/sync.h"
|
|
#include "driver/tcpm/raa489000.h"
|
|
#include "driver/tcpm/tcpci.h"
|
|
#include "driver/usb_mux/pi3usb3x532.h"
|
|
#include "extpower.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "i2c.h"
|
|
#include "keyboard_scan.h"
|
|
#include "lid_switch.h"
|
|
#include "motion_sense.h"
|
|
#include "power.h"
|
|
#include "power_button.h"
|
|
#include "stdbool.h"
|
|
#include "switch.h"
|
|
#include "tablet_mode.h"
|
|
#include "task.h"
|
|
#include "usb_mux.h"
|
|
#include "usb_pd.h"
|
|
#include "usb_pd_tcpm.h"
|
|
|
|
#define CPRINTS(format, args...) cprints(CC_USBCHARGE, format, ## args)
|
|
#define CPRINTF(format, args...) cprintf(CC_USBCHARGE, format, ## args)
|
|
|
|
static void tcpc_alert_event(enum gpio_signal s)
|
|
{
|
|
int port = (s == GPIO_USB_C0_INT_ODL) ? 0 : 1;
|
|
|
|
schedule_deferred_pd_interrupt(port);
|
|
}
|
|
|
|
static void usb_c0_interrupt(enum gpio_signal s)
|
|
{
|
|
/*
|
|
* The interrupt line is shared between the TCPC and BC 1.2 detection
|
|
* chip. Therefore we'll need to check both ICs.
|
|
*/
|
|
tcpc_alert_event(s);
|
|
task_set_event(TASK_ID_USB_CHG_P0, USB_CHG_EVENT_BC12, 0);
|
|
}
|
|
|
|
static void sub_usb_c1_interrupt(enum gpio_signal s)
|
|
{
|
|
/*
|
|
* The interrupt line is shared between the TCPC and BC 1.2 detection
|
|
* chip. Therefore we'll need to check both ICs.
|
|
*/
|
|
tcpc_alert_event(s);
|
|
task_set_event(TASK_ID_USB_CHG_P1, USB_CHG_EVENT_BC12, 0);
|
|
}
|
|
|
|
#include "gpio_list.h"
|
|
|
|
void board_init(void)
|
|
{
|
|
gpio_enable_interrupt(GPIO_USB_C0_INT_ODL);
|
|
gpio_enable_interrupt(GPIO_SUB_USB_C1_INT_ODL);
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT);
|
|
|
|
void board_reset_pd_mcu(void)
|
|
{
|
|
/*
|
|
* TODO(b:147316511): Here we could issue a digital reset to the IC,
|
|
* unsure if we actually want to do that or not yet.
|
|
*/
|
|
}
|
|
|
|
int board_is_sourcing_vbus(int port)
|
|
{
|
|
int regval;
|
|
|
|
tcpc_read(port, TCPC_REG_POWER_STATUS, ®val);
|
|
return !!(regval & TCPC_REG_POWER_STATUS_SOURCING_VBUS);
|
|
|
|
}
|
|
|
|
int board_set_active_charge_port(int port)
|
|
{
|
|
int is_real_port = (port >= 0 &&
|
|
port < CONFIG_USB_PD_PORT_MAX_COUNT);
|
|
int i;
|
|
int old_port;
|
|
|
|
if (!is_real_port && port != CHARGE_PORT_NONE)
|
|
return EC_ERROR_INVAL;
|
|
|
|
old_port = charge_manager_get_active_charge_port();
|
|
|
|
CPRINTS("New chg p%d", port);
|
|
|
|
/* Disable all ports. */
|
|
if (port == CHARGE_PORT_NONE) {
|
|
for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++)
|
|
tcpc_write(i, TCPC_REG_COMMAND,
|
|
TCPC_REG_COMMAND_SNK_CTRL_LOW);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/* Check if port is sourcing VBUS. */
|
|
if (board_is_sourcing_vbus(port)) {
|
|
CPRINTS("Skip enable p%d", port);
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
|
|
/*
|
|
* Turn off the other ports' sink path FETs, before enabling the
|
|
* requested charge port.
|
|
*/
|
|
for (i = 0; i < CONFIG_USB_PD_PORT_MAX_COUNT; i++) {
|
|
if (i == port)
|
|
continue;
|
|
|
|
if (tcpc_write(i, TCPC_REG_COMMAND,
|
|
TCPC_REG_COMMAND_SNK_CTRL_LOW))
|
|
CPRINTS("p%d: sink path disable failed.", i);
|
|
}
|
|
|
|
/*
|
|
* Stop the charger IC from switching while changing ports. Otherwise,
|
|
* we can overcurrent the adapter we're switching to. (crbug.com/926056)
|
|
*/
|
|
if (old_port != CHARGE_PORT_NONE)
|
|
charger_discharge_on_ac(1);
|
|
|
|
/* Enable requested charge port. */
|
|
if (tcpc_write(port, TCPC_REG_COMMAND,
|
|
TCPC_REG_COMMAND_SNK_CTRL_HIGH)) {
|
|
CPRINTS("p%d: sink path enable failed.", port);
|
|
charger_discharge_on_ac(0);
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/* Allow the charger IC to begin/continue switching. */
|
|
charger_discharge_on_ac(0);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
void board_set_charge_limit(int port, int supplier, int charge_ma,
|
|
int max_ma, int charge_mv)
|
|
{
|
|
int icl = MAX(charge_ma, CONFIG_CHARGER_INPUT_CURRENT);
|
|
|
|
/*
|
|
* TODO(b:147463641): Characterize the input current limit in case that
|
|
* a scaling needs to be applied here.
|
|
*/
|
|
charge_set_input_current_limit(icl, charge_mv);
|
|
}
|
|
|
|
/* Sensors */
|
|
static struct mutex g_lid_mutex;
|
|
static struct mutex g_base_mutex;
|
|
|
|
static struct accelgyro_saved_data_t g_bma253_data;
|
|
static struct bmi160_drv_data_t g_bmi160_data;
|
|
|
|
struct motion_sensor_t motion_sensors[] = {
|
|
[LID_ACCEL] = {
|
|
.name = "Lid Accel",
|
|
.active_mask = SENSOR_ACTIVE_S0_S3,
|
|
.chip = MOTIONSENSE_CHIP_BMA255,
|
|
.type = MOTIONSENSE_TYPE_ACCEL,
|
|
.location = MOTIONSENSE_LOC_LID,
|
|
.drv = &bma2x2_accel_drv,
|
|
.mutex = &g_lid_mutex,
|
|
.drv_data = &g_bma253_data,
|
|
.port = I2C_PORT_SENSOR,
|
|
.i2c_spi_addr_flags = BMA2x2_I2C_ADDR1_FLAGS,
|
|
.rot_standard_ref = NULL,
|
|
.default_range = 2,
|
|
.min_frequency = BMA255_ACCEL_MIN_FREQ,
|
|
.max_frequency = BMA255_ACCEL_MAX_FREQ,
|
|
.config = {
|
|
[SENSOR_CONFIG_EC_S0] = {
|
|
.odr = 10000 | ROUND_UP_FLAG,
|
|
},
|
|
[SENSOR_CONFIG_EC_S3] = {
|
|
.odr = 10000 | ROUND_UP_FLAG,
|
|
},
|
|
},
|
|
},
|
|
[BASE_ACCEL] = {
|
|
.name = "Base Accel",
|
|
.active_mask = SENSOR_ACTIVE_S0_S3,
|
|
.chip = MOTIONSENSE_CHIP_BMI160,
|
|
.type = MOTIONSENSE_TYPE_ACCEL,
|
|
.location = MOTIONSENSE_LOC_BASE,
|
|
.drv = &bmi160_drv,
|
|
.mutex = &g_base_mutex,
|
|
.drv_data = &g_bmi160_data,
|
|
.port = I2C_PORT_SENSOR,
|
|
.i2c_spi_addr_flags = BMI160_ADDR0_FLAGS,
|
|
.rot_standard_ref = NULL,
|
|
.default_range = 4,
|
|
.min_frequency = BMI160_ACCEL_MIN_FREQ,
|
|
.max_frequency = BMI160_ACCEL_MAX_FREQ,
|
|
.config = {
|
|
[SENSOR_CONFIG_EC_S0] = {
|
|
.odr = 13000 | ROUND_UP_FLAG,
|
|
.ec_rate = 100 * MSEC,
|
|
},
|
|
[SENSOR_CONFIG_EC_S3] = {
|
|
.odr = 10000 | ROUND_UP_FLAG,
|
|
.ec_rate = 100 * MSEC,
|
|
},
|
|
},
|
|
},
|
|
[BASE_GYRO] = {
|
|
.name = "Base Gyro",
|
|
.active_mask = SENSOR_ACTIVE_S0_S3,
|
|
.chip = MOTIONSENSE_CHIP_BMI160,
|
|
.type = MOTIONSENSE_TYPE_GYRO,
|
|
.location = MOTIONSENSE_LOC_BASE,
|
|
.drv = &bmi160_drv,
|
|
.mutex = &g_base_mutex,
|
|
.drv_data = &g_bmi160_data,
|
|
.port = I2C_PORT_SENSOR,
|
|
.i2c_spi_addr_flags = BMI160_ADDR0_FLAGS,
|
|
.default_range = 1000, /* dps */
|
|
.rot_standard_ref = NULL,
|
|
.min_frequency = BMI160_GYRO_MIN_FREQ,
|
|
.max_frequency = BMI160_GYRO_MAX_FREQ,
|
|
},
|
|
[VSYNC] = {
|
|
.name = "Camera VSYNC",
|
|
.active_mask = SENSOR_ACTIVE_S0,
|
|
.chip = MOTIONSENSE_CHIP_GPIO,
|
|
.type = MOTIONSENSE_TYPE_SYNC,
|
|
.location = MOTIONSENSE_LOC_CAMERA,
|
|
.drv = &sync_drv,
|
|
.default_range = 0,
|
|
.min_frequency = 0,
|
|
.max_frequency = 1,
|
|
},
|
|
};
|
|
|
|
const unsigned int motion_sensor_count = ARRAY_SIZE(motion_sensors);
|
|
|
|
int extpower_is_present(void)
|
|
{
|
|
/*
|
|
* TODO(b:146651593) We can likely use the charger IC to determine VBUS
|
|
* presence.
|
|
*/
|
|
return pd_snk_is_vbus_provided(0) || pd_snk_is_vbus_provided(1);
|
|
}
|
|
|
|
int pd_snk_is_vbus_provided(int port)
|
|
{
|
|
int regval = 0;
|
|
|
|
tcpc_read(port, TCPC_REG_POWER_STATUS, ®val);
|
|
return regval & TCPC_REG_POWER_STATUS_VBUS_PRES;
|
|
}
|
|
|
|
const struct pi3usb9201_config_t pi3usb9201_bc12_chips[] = {
|
|
{
|
|
.i2c_port = I2C_PORT_USB_C0,
|
|
.i2c_addr_flags = PI3USB9201_I2C_ADDR_3_FLAGS,
|
|
},
|
|
|
|
{
|
|
.i2c_port = I2C_PORT_SUB_USB_C1,
|
|
.i2c_addr_flags = PI3USB9201_I2C_ADDR_3_FLAGS,
|
|
},
|
|
};
|
|
|
|
const struct tcpc_config_t tcpc_config[CONFIG_USB_PD_PORT_MAX_COUNT] = {
|
|
{
|
|
.bus_type = EC_BUS_TYPE_I2C,
|
|
.i2c_info = {
|
|
.port = I2C_PORT_USB_C0,
|
|
.addr_flags = RAA489000_TCPC0_I2C_FLAGS,
|
|
},
|
|
.flags = TCPC_FLAGS_TCPCI_V2_0,
|
|
.drv = &raa489000_tcpm_drv,
|
|
},
|
|
|
|
{
|
|
.bus_type = EC_BUS_TYPE_I2C,
|
|
.i2c_info = {
|
|
.port = I2C_PORT_SUB_USB_C1,
|
|
.addr_flags = RAA489000_TCPC0_I2C_FLAGS,
|
|
},
|
|
.flags = TCPC_FLAGS_TCPCI_V2_0,
|
|
.drv = &raa489000_tcpm_drv,
|
|
},
|
|
};
|
|
|
|
struct usb_mux usb_muxes[CONFIG_USB_PD_PORT_MAX_COUNT] = {
|
|
{
|
|
.port_addr = MUX_PORT_AND_ADDR(I2C_PORT_USB_C0, 0x54),
|
|
.driver = &pi3usb3x532_usb_mux_driver,
|
|
},
|
|
{
|
|
.port_addr = MUX_PORT_AND_ADDR(I2C_PORT_SUB_USB_C1, 0x54),
|
|
.driver = &pi3usb3x532_usb_mux_driver,
|
|
}
|
|
};
|
|
|
|
uint16_t tcpc_get_alert_status(void)
|
|
{
|
|
uint16_t status = 0;
|
|
int regval;
|
|
|
|
/*
|
|
* The interrupt line is shared between the TCPC and BC1.2 detector IC.
|
|
* Therefore, go out and actually read the alert registers to report the
|
|
* alert status.
|
|
*/
|
|
if (!gpio_get_level(GPIO_USB_C0_INT_ODL)) {
|
|
if (!tcpc_read16(0, TCPC_REG_ALERT, ®val)) {
|
|
/* The TCPCI v1.0 spec says to ignore bits 14:12. */
|
|
if (!(tcpc_config[0].flags & TCPC_FLAGS_TCPCI_V2_0))
|
|
regval &= ~((1 << 14) | (1 << 13) | (1 << 12));
|
|
|
|
if (regval)
|
|
status |= PD_STATUS_TCPC_ALERT_0;
|
|
}
|
|
}
|
|
|
|
if (!gpio_get_level(GPIO_SUB_USB_C1_INT_ODL)) {
|
|
if (!tcpc_read16(1, TCPC_REG_ALERT, ®val)) {
|
|
/* TCPCI spec v1.0 says to ignore bits 14:12. */
|
|
if (!(tcpc_config[1].flags & TCPC_FLAGS_TCPCI_V2_0))
|
|
regval &= ~((1 << 14) | (1 << 13) | (1 << 12));
|
|
|
|
if (regval)
|
|
status |= PD_STATUS_TCPC_ALERT_1;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
#ifndef TEST_BUILD
|
|
/* This callback disables keyboard when convertibles are fully open */
|
|
void lid_angle_peripheral_enable(int enable)
|
|
{
|
|
int chipset_in_s0 = chipset_in_state(CHIPSET_STATE_ON);
|
|
|
|
/*
|
|
* If the lid is in tablet position via other sensors,
|
|
* ignore the lid angle, which might be faulty then
|
|
* disable keyboard.
|
|
*/
|
|
if (tablet_get_mode())
|
|
enable = 0;
|
|
|
|
if (enable) {
|
|
keyboard_scan_enable(1, KB_SCAN_DISABLE_LID_ANGLE);
|
|
} else {
|
|
/*
|
|
* Ensure that the chipset is off before disabling the keyboard.
|
|
* When the chipset is on, the EC keeps the keyboard enabled and
|
|
* the AP decides whether to ignore input devices or not.
|
|
*/
|
|
if (!chipset_in_s0)
|
|
keyboard_scan_enable(0, KB_SCAN_DISABLE_LID_ANGLE);
|
|
}
|
|
}
|
|
#endif
|