blob: 31bac3b276a49fbcc9c98d0da69fe510cd6c3e87 [file] [log] [blame]
/* 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.
*/
/* Trembyle board configuration */
#include "adc.h"
#include "adc_chip.h"
#include "button.h"
#include "charger.h"
#include "cbi_ec_fw_config.h"
#include "driver/accelgyro_bmi_common.h"
#include "driver/accel_kionix.h"
#include "driver/accel_kx022.h"
#include "driver/retimer/pi3dpx1207.h"
#include "driver/retimer/pi3hdx1204.h"
#include "driver/retimer/ps8811.h"
#include "driver/temp_sensor/sb_tsi.h"
#include "driver/usb_mux/amd_fp5.h"
#include "extpower.h"
#include "fan.h"
#include "fan_chip.h"
#include "gpio.h"
#include "hooks.h"
#include "lid_switch.h"
#include "power.h"
#include "power_button.h"
#include "pwm.h"
#include "pwm_chip.h"
#include "switch.h"
#include "system.h"
#include "task.h"
#include "thermistor.h"
#include "temp_sensor.h"
#include "usb_charge.h"
#include "usb_mux.h"
#include "gpio_list.h"
#define CPRINTSUSB(format, args...) cprints(CC_USBCHARGE, format, ## args)
#define CPRINTFUSB(format, args...) cprintf(CC_USBCHARGE, format, ## args)
#ifdef HAS_TASK_MOTIONSENSE
/* Motion sensors */
static struct mutex g_lid_mutex;
static struct mutex g_base_mutex;
/* sensor private data */
static struct kionix_accel_data g_kx022_data;
static struct bmi_drv_data_t g_bmi160_data;
/* TODO(gcc >= 5.0) Remove the casts to const pointer at rot_standard_ref */
struct motion_sensor_t motion_sensors[] = {
[LID_ACCEL] = {
.name = "Lid Accel",
.active_mask = SENSOR_ACTIVE_S0_S3,
.chip = MOTIONSENSE_CHIP_KX022,
.type = MOTIONSENSE_TYPE_ACCEL,
.location = MOTIONSENSE_LOC_LID,
.drv = &kionix_accel_drv,
.mutex = &g_lid_mutex,
.drv_data = &g_kx022_data,
.port = I2C_PORT_SENSOR,
.i2c_spi_addr_flags = KX022_ADDR1_FLAGS,
.rot_standard_ref = NULL,
.default_range = 2, /* g, enough for laptop. */
.min_frequency = KX022_ACCEL_MIN_FREQ,
.max_frequency = KX022_ACCEL_MAX_FREQ,
.config = {
/* EC use accel for angle detection */
[SENSOR_CONFIG_EC_S0] = {
.odr = 10000 | ROUND_UP_FLAG,
.ec_rate = 100,
},
/* EC use accel for angle detection */
[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,
.default_range = 2, /* g, enough for laptop */
.rot_standard_ref = NULL,
.min_frequency = BMI_ACCEL_MIN_FREQ,
.max_frequency = BMI_ACCEL_MAX_FREQ,
.config = {
/* EC use accel for angle detection */
[SENSOR_CONFIG_EC_S0] = {
.odr = 10000 | ROUND_UP_FLAG,
.ec_rate = 100,
},
/* EC use accel for angle detection */
[SENSOR_CONFIG_EC_S3] = {
.odr = 10000 | ROUND_UP_FLAG,
},
},
},
[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 = BMI_GYRO_MIN_FREQ,
.max_frequency = BMI_GYRO_MAX_FREQ,
},
};
unsigned int motion_sensor_count = ARRAY_SIZE(motion_sensors);
#endif /* HAS_TASK_MOTIONSENSE */
const struct power_signal_info power_signal_list[] = {
[X86_SLP_S3_N] = {
.gpio = GPIO_PCH_SLP_S3_L,
.flags = POWER_SIGNAL_ACTIVE_HIGH,
.name = "SLP_S3_DEASSERTED",
},
[X86_SLP_S5_N] = {
.gpio = GPIO_PCH_SLP_S5_L,
.flags = POWER_SIGNAL_ACTIVE_HIGH,
.name = "SLP_S5_DEASSERTED",
},
[X86_S0_PGOOD] = {
.gpio = GPIO_S0_PGOOD,
.flags = POWER_SIGNAL_ACTIVE_HIGH,
.name = "S0_PGOOD",
},
[X86_S5_PGOOD] = {
.gpio = GPIO_S5_PGOOD,
.flags = POWER_SIGNAL_ACTIVE_HIGH,
.name = "S5_PGOOD",
},
};
BUILD_ASSERT(ARRAY_SIZE(power_signal_list) == POWER_SIGNAL_COUNT);
const struct pwm_t pwm_channels[] = {
[PWM_CH_KBLIGHT] = {
.channel = 3,
.flags = PWM_CONFIG_DSLEEP,
.freq = 100,
},
[PWM_CH_FAN] = {
.channel = 2,
.flags = PWM_CONFIG_OPEN_DRAIN,
.freq = 25000,
},
};
BUILD_ASSERT(ARRAY_SIZE(pwm_channels) == PWM_CH_COUNT);
/* MFT channels. These are logically separate from pwm_channels. */
const struct mft_t mft_channels[] = {
[MFT_CH_0] = {
.module = NPCX_MFT_MODULE_1,
.clk_src = TCKC_LFCLK,
.pwm_id = PWM_CH_FAN,
},
};
BUILD_ASSERT(ARRAY_SIZE(mft_channels) == MFT_CH_COUNT);
const int usb_port_enable[USBA_PORT_COUNT] = {
IOEX_EN_USB_A0_5V,
IOEX_EN_USB_A1_5V_DB,
};
const struct pi3hdx1204_tuning pi3hdx1204_tuning = {
.eq_ch0_ch1_offset = PI3HDX1204_EQ_DB710,
.eq_ch2_ch3_offset = PI3HDX1204_EQ_DB710,
.vod_offset = PI3HDX1204_VOD_130_ALL_CHANNELS,
.de_offset = PI3HDX1204_DE_DB_MINUS5,
};
/*****************************************************************************
* Board suspend / resume
*/
#define PS8811_ACCESS_RETRIES 2
static void board_chipset_resume(void)
{
int rv;
int retry;
/* Turn on the retimers */
ioex_set_level(IOEX_USB_A0_RETIMER_EN, 1);
ioex_set_level(IOEX_USB_A1_RETIMER_EN, 1);
/* USB-A0 can run with default settings */
for (retry = 0; retry < PS8811_ACCESS_RETRIES; ++retry) {
int val;
rv = i2c_read8(I2C_PORT_USBA0,
PS8811_I2C_ADDR_FLAGS3 + PS8811_REG_PAGE1,
PS8811_REG1_USB_BEQ_LEVEL, &val);
if (!rv)
break;
}
if (rv) {
ioex_set_level(IOEX_USB_A0_RETIMER_EN, 0);
CPRINTSUSB("A0: PS8811 not detected");
}
/* USB-A1 needs to increase gain to get over MB/DB connector */
for (retry = 0; retry < PS8811_ACCESS_RETRIES; ++retry) {
rv = i2c_write8(I2C_PORT_USBA1,
PS8811_I2C_ADDR_FLAGS3 + PS8811_REG_PAGE1,
PS8811_REG1_USB_BEQ_LEVEL,
PS8811_BEQ_I2C_LEVEL_UP_13DB |
PS8811_BEQ_PIN_LEVEL_UP_18DB);
if (!rv)
break;
}
if (rv) {
ioex_set_level(IOEX_USB_A1_RETIMER_EN, 0);
CPRINTSUSB("A1: PS8811 not detected");
}
if (ec_config_has_hdmi_retimer_pi3hdx1204()) {
pi3hdx1204_enable(I2C_PORT_TCPC1,
PI3HDX1204_I2C_ADDR_FLAGS,
1);
}
}
DECLARE_HOOK(HOOK_CHIPSET_RESUME, board_chipset_resume, HOOK_PRIO_DEFAULT);
static void board_chipset_suspend(void)
{
ioex_set_level(IOEX_USB_A0_RETIMER_EN, 0);
ioex_set_level(IOEX_USB_A1_RETIMER_EN, 0);
if (ec_config_has_hdmi_retimer_pi3hdx1204()) {
pi3hdx1204_enable(I2C_PORT_TCPC1,
PI3HDX1204_I2C_ADDR_FLAGS,
0);
}
}
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, board_chipset_suspend, HOOK_PRIO_DEFAULT);
/*****************************************************************************
* USB-C MUX/Retimer dynamic configuration
*/
static void setup_mux(void)
{
if (ec_config_has_usbc1_retimer_ps8802()) {
/*
* Main MUX is PS8802, secondary MUX is modified FP5
*
* Replace usb_muxes[USBC_PORT_C1] with the PS8802
* table entry.
*/
memcpy(&usb_muxes[USBC_PORT_C1],
&usbc1_ps8802,
sizeof(struct usb_mux));
/* Set the AMD FP5 as the secondary MUX */
usb_muxes[USBC_PORT_C1].next_mux = &usbc1_amd_fp5_usb_mux;
/* Don't have the AMD FP5 flip */
usbc1_amd_fp5_usb_mux.flags = USB_MUX_FLAG_SET_WITHOUT_FLIP;
} else if (ec_config_has_usbc1_retimer_ps8818()) {
/*
* Main MUX is FP5, secondary MUX is PS8818
*
* Replace usb_muxes[USBC_PORT_C1] with the AMD FP5
* table entry.
*/
memcpy(&usb_muxes[USBC_PORT_C1],
&usbc1_amd_fp5_usb_mux,
sizeof(struct usb_mux));
/* Set the PS8818 as the secondary MUX */
usb_muxes[USBC_PORT_C1].next_mux = &usbc1_ps8818;
}
}
const struct pi3dpx1207_usb_control pi3dpx1207_controls[] = {
[USBC_PORT_C0] = {
.enable_gpio = IOEX_USB_C0_DATA_EN,
.dp_enable_gpio = GPIO_USB_C0_IN_HPD,
},
[USBC_PORT_C1] = {
},
};
BUILD_ASSERT(ARRAY_SIZE(pi3dpx1207_controls) == USBC_PORT_COUNT);
const struct usb_mux usbc0_pi3dpx1207_usb_retimer = {
.usb_port = USBC_PORT_C0,
.i2c_port = I2C_PORT_TCPC0,
.i2c_addr_flags = PI3DPX1207_I2C_ADDR_FLAGS,
.driver = &pi3dpx1207_usb_retimer,
};
struct usb_mux usb_muxes[] = {
[USBC_PORT_C0] = {
.usb_port = USBC_PORT_C0,
.i2c_port = I2C_PORT_USB_AP_MUX,
.i2c_addr_flags = AMD_FP5_MUX_I2C_ADDR_FLAGS,
.driver = &amd_fp5_usb_mux_driver,
.next_mux = &usbc0_pi3dpx1207_usb_retimer,
},
[USBC_PORT_C1] = {
/* Filled in dynamically at startup */
},
};
BUILD_ASSERT(ARRAY_SIZE(usb_muxes) == USBC_PORT_COUNT);
/*****************************************************************************
* Use FW_CONFIG to set correct configuration.
*/
int board_usbc1_retimer_inhpd = IOEX_USB_C1_HPD_IN_DB;
static void setup_v0_charger(void)
{
chg_chips[0].i2c_port = I2C_PORT_CHARGER_V0;
}
/*
* Use HOOK_PRIO_INIT_I2C so we re-map before charger_chips_init()
* talks to the charger.
*/
DECLARE_HOOK(HOOK_INIT, setup_v0_charger, HOOK_PRIO_INIT_I2C);
static void setup_fw_config(void)
{
/* Enable Gyro interrupts */
gpio_enable_interrupt(GPIO_6AXIS_INT_L);
setup_mux();
if (ec_config_has_mst_hub_rtd2141b())
ioex_enable_interrupt(IOEX_MST_HPD_OUT);
if (ec_config_has_hdmi_conn_hpd())
ioex_enable_interrupt(IOEX_HDMI_CONN_HPD_3V3_DB);
}
/* Use HOOK_PRIO_INIT_I2C + 2 to be after ioex_init(). */
DECLARE_HOOK(HOOK_INIT, setup_fw_config, HOOK_PRIO_INIT_I2C + 2);
/*****************************************************************************
* Fan
*/
/* Physical fans. These are logically separate from pwm_channels. */
const struct fan_conf fan_conf_0 = {
.flags = FAN_USE_RPM_MODE,
.ch = MFT_CH_0, /* Use MFT id to control fan */
.pgood_gpio = -1,
.enable_gpio = -1,
};
const struct fan_rpm fan_rpm_0 = {
.rpm_min = 3000,
.rpm_start = 3000,
.rpm_max = 4900,
};
const struct fan_t fans[] = {
[FAN_CH_0] = {
.conf = &fan_conf_0,
.rpm = &fan_rpm_0,
},
};
BUILD_ASSERT(ARRAY_SIZE(fans) == FAN_CH_COUNT);
int board_get_temp(int idx, int *temp_k)
{
int mv;
int temp_c;
enum adc_channel channel;
/* idx is the sensor index set in board temp_sensors[] */
switch (idx) {
case TEMP_SENSOR_CHARGER:
channel = ADC_TEMP_SENSOR_CHARGER;
break;
case TEMP_SENSOR_SOC:
/* thermistor is not powered in G3 */
if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
return EC_ERROR_NOT_POWERED;
channel = ADC_TEMP_SENSOR_SOC;
break;
default:
return EC_ERROR_INVAL;
}
mv = adc_read_channel(channel);
if (mv < 0)
return EC_ERROR_INVAL;
temp_c = thermistor_linear_interpolate(mv, &thermistor_info);
*temp_k = C_TO_K(temp_c);
return EC_SUCCESS;
}
const struct adc_t adc_channels[] = {
[ADC_TEMP_SENSOR_CHARGER] = {
.name = "CHARGER",
.input_ch = NPCX_ADC_CH2,
.factor_mul = ADC_MAX_VOLT,
.factor_div = ADC_READ_MAX + 1,
.shift = 0,
},
[ADC_TEMP_SENSOR_SOC] = {
.name = "SOC",
.input_ch = NPCX_ADC_CH3,
.factor_mul = ADC_MAX_VOLT,
.factor_div = ADC_READ_MAX + 1,
.shift = 0,
},
};
BUILD_ASSERT(ARRAY_SIZE(adc_channels) == ADC_CH_COUNT);
const struct temp_sensor_t temp_sensors[] = {
[TEMP_SENSOR_CHARGER] = {
.name = "Charger",
.type = TEMP_SENSOR_TYPE_BOARD,
.read = board_get_temp,
.idx = TEMP_SENSOR_CHARGER,
},
[TEMP_SENSOR_SOC] = {
.name = "SOC",
.type = TEMP_SENSOR_TYPE_BOARD,
.read = board_get_temp,
.idx = TEMP_SENSOR_SOC,
},
[TEMP_SENSOR_CPU] = {
.name = "CPU",
.type = TEMP_SENSOR_TYPE_CPU,
.read = sb_tsi_get_val,
.idx = 0,
},
};
BUILD_ASSERT(ARRAY_SIZE(temp_sensors) == TEMP_SENSOR_COUNT);
const static struct ec_thermal_config thermal_thermistor = {
.temp_host = {
[EC_TEMP_THRESH_HIGH] = C_TO_K(90),
[EC_TEMP_THRESH_HALT] = C_TO_K(92),
},
.temp_host_release = {
[EC_TEMP_THRESH_HIGH] = C_TO_K(80),
},
.temp_fan_off = C_TO_K(25),
.temp_fan_max = C_TO_K(58),
};
const static struct ec_thermal_config thermal_cpu = {
.temp_host = {
[EC_TEMP_THRESH_HIGH] = C_TO_K(90),
[EC_TEMP_THRESH_HALT] = C_TO_K(92),
},
.temp_host_release = {
[EC_TEMP_THRESH_HIGH] = C_TO_K(80),
},
.temp_fan_off = C_TO_K(25),
.temp_fan_max = C_TO_K(58),
};
struct ec_thermal_config thermal_params[TEMP_SENSOR_COUNT];
static void setup_fans(void)
{
thermal_params[TEMP_SENSOR_CHARGER] = thermal_thermistor;
thermal_params[TEMP_SENSOR_SOC] = thermal_thermistor;
thermal_params[TEMP_SENSOR_CPU] = thermal_cpu;
}
DECLARE_HOOK(HOOK_INIT, setup_fans, HOOK_PRIO_DEFAULT);
/*****************************************************************************
* MST hub
*/
static void mst_hpd_handler(void)
{
int hpd = 0;
/*
* Ensure level on GPIO_DP1_HPD matches IOEX_MST_HPD_OUT, in case
* we got out of sync.
*/
ioex_get_level(IOEX_MST_HPD_OUT, &hpd);
gpio_set_level(GPIO_DP1_HPD, hpd);
ccprints("MST HPD %d", hpd);
}
DECLARE_DEFERRED(mst_hpd_handler);
void mst_hpd_interrupt(enum ioex_signal signal)
{
/*
* Goal is to pass HPD through from DB OPT3 MST hub to AP's DP1.
* Immediately invert GPIO_DP1_HPD, to pass through the edge on
* IOEX_MST_HPD_OUT. Then check level after 2 msec debounce.
*/
int hpd = !gpio_get_level(GPIO_DP1_HPD);
gpio_set_level(GPIO_DP1_HPD, hpd);
hook_call_deferred(&mst_hpd_handler_data, (2 * MSEC));
}
static void hdmi_hpd_handler(void)
{
int hpd = 0;
/* Pass HPD through from DB OPT1 HDMI connector to AP's DP1. */
ioex_get_level(IOEX_HDMI_CONN_HPD_3V3_DB, &hpd);
gpio_set_level(GPIO_DP1_HPD, hpd);
ccprints("HDMI HPD %d", hpd);
}
DECLARE_DEFERRED(hdmi_hpd_handler);
void hdmi_hpd_interrupt(enum ioex_signal signal)
{
/* Debounce for 2 msec. */
hook_call_deferred(&hdmi_hpd_handler_data, (2 * MSEC));
}