From: Josep Orga Date: Thu, 2 Sep 2021 07:14:41 +0000 (+0200) Subject: imx8mm-somdevices: Add somdevices board necessary files based on freescale/common. X-Git-Tag: rel_imx_5.10.35_2.0.0-somdevices.0~44 X-Git-Url: https://git.somdevices.com/?a=commitdiff_plain;h=0d6ac701b768db2495a14d3d4bfa984c82949bea;p=u-boot.git imx8mm-somdevices: Add somdevices board necessary files based on freescale/common. Signed-off-by: Josep Orga --- diff --git a/board/somdevices/common/Kconfig b/board/somdevices/common/Kconfig new file mode 100644 index 0000000000..9ed58cd4ba --- /dev/null +++ b/board/somdevices/common/Kconfig @@ -0,0 +1,63 @@ +config CHAIN_OF_TRUST + depends on !FIT_SIGNATURE && NXP_ESBC + imply CMD_BLOB + imply CMD_HASH if ARM + select FSL_CAAM + select SPL_BOARD_INIT if (ARM && SPL) + select SHA_HW_ACCEL + select SHA_PROG_HW_ACCEL + select ENV_IS_NOWHERE + select CMD_EXT4 if ARM + select CMD_EXT4_WRITE if ARM + bool + default y + +config CMD_ESBC_VALIDATE + bool "Enable the 'esbc_validate' and 'esbc_halt' commands" + default y if CHAIN_OF_TRUST + help + This option enables two commands used for secure booting: + + esbc_validate - validate signature using RSA verification + esbc_halt - put the core in spin loop (Secure Boot Only) + +config VID + depends on DM_I2C + bool "Enable Freescale VID" + help + This option enables setting core voltage based on individual + values saved in SoC fuses. + +config VOL_MONITOR_LTC3882_READ + depends on VID + bool "Enable the LTC3882 voltage monitor read" + help + This option enables LTC3882 voltage monitor read + functionality. It is used by the common VID driver. + +config VOL_MONITOR_LTC3882_SET + depends on VID + bool "Enable the LTC3882 voltage monitor set" + help + This option enables LTC3882 voltage monitor set + functionality. It is used by the common VID driver. + +config VOL_MONITOR_ISL68233_READ + depends on VID + bool "Enable the ISL68233 voltage monitor read" + help + This option enables ISL68233 voltage monitor read + functionality. It is used by the common VID driver. + +config VOL_MONITOR_ISL68233_SET + depends on VID + bool "Enable the ISL68233 voltage monitor set" + help + This option enables ISL68233 voltage monitor set + functionality. It is used by the common VID driver. + +config USB_TCPC + bool "USB Typec port controller simple driver" + default n + help + Enable USB type-c port controller (TCPC) driver diff --git a/board/somdevices/common/Makefile b/board/somdevices/common/Makefile new file mode 100644 index 0000000000..abc3a6660b --- /dev/null +++ b/board/somdevices/common/Makefile @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2006 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. + +MINIMAL= + +ifdef CONFIG_SPL_BUILD +ifdef CONFIG_SPL_INIT_MINIMAL +MINIMAL=y +endif +endif + +ifdef MINIMAL +# necessary to create built-in.o +obj- := __dummy__.o +else +obj-$(CONFIG_FSL_CADMUS) += cadmus.o +obj-$(CONFIG_FSL_VIA) += cds_via.o +obj-$(CONFIG_FMAN_ENET) += fman.o +obj-$(CONFIG_FSL_PIXIS) += pixis.o +ifndef CONFIG_SPL_BUILD +obj-$(CONFIG_FSL_NGPIXIS) += ngpixis.o +endif +obj-$(CONFIG_VID) += vid.o +obj-$(CONFIG_FSL_QIXIS) += qixis.o +obj-$(CONFIG_PQ_MDS_PIB) += pq-mds-pib.o +ifndef CONFIG_SPL_BUILD +obj-$(CONFIG_ID_EEPROM) += sys_eeprom.o +endif +obj-$(CONFIG_FSL_SGMII_RISER) += sgmii_riser.o +ifndef CONFIG_RAMBOOT_PBL +obj-$(CONFIG_FSL_FIXED_MMC_LOCATION) += sdhc_boot.o +endif + +obj-$(CONFIG_FSL_DIU_CH7301) += diu_ch7301.o + +ifdef CONFIG_ARM +obj-$(CONFIG_DEEP_SLEEP) += arm_sleep.o +else +obj-$(CONFIG_DEEP_SLEEP) += mpc85xx_sleep.o +endif + +obj-$(CONFIG_FSL_DCU_SII9022A) += dcu_sii9022a.o + +obj-$(CONFIG_TARGET_MPC8541CDS) += cds_pci_ft.o +obj-$(CONFIG_TARGET_MPC8548CDS) += cds_pci_ft.o +obj-$(CONFIG_TARGET_MPC8555CDS) += cds_pci_ft.o + +obj-$(CONFIG_TARGET_MPC8536DS) += ics307_clk.o +obj-$(CONFIG_TARGET_P1022DS) += ics307_clk.o +obj-$(CONFIG_P2020DS) += ics307_clk.o +obj-$(CONFIG_TARGET_P3041DS) += ics307_clk.o +obj-$(CONFIG_TARGET_P4080DS) += ics307_clk.o +obj-$(CONFIG_TARGET_P5040DS) += ics307_clk.o +obj-$(CONFIG_VSC_CROSSBAR) += vsc3316_3308.o +obj-$(CONFIG_IDT8T49N222A) += idt8t49n222a_serdes_clk.o +obj-$(CONFIG_ZM7300) += zm7300.o +obj-$(CONFIG_POWER_PFUZE100) += pfuze.o +obj-$(CONFIG_DM_PMIC_PFUZE100) += pfuze.o +obj-$(CONFIG_POWER_MC34VR500) += mc34vr500.o +obj-$(CONFIG_MXC_EPDC) += epdc_setup.o +ifneq (,$(filter $(SOC), mx25 mx31 mx35 mx5 mx6 mx7 mx7ulp imx8 imx8m vf610)) +obj-y += mmc.o +endif +ifdef CONFIG_FSL_FASTBOOT +obj-${CONFIG_ANDROID_RECOVERY} += recovery_keypad.o +endif + +obj-$(CONFIG_LS102XA_STREAM_ID) += ls102xa_stream_id.o + +obj-$(CONFIG_EMC2305) += emc2305.o +obj-$(CONFIG_QSFP_EEPROM) += qsfp_eeprom.o + +# deal with common files for P-series corenet based devices +obj-$(CONFIG_TARGET_P2041RDB) += p_corenet/ +obj-$(CONFIG_TARGET_P3041DS) += p_corenet/ +obj-$(CONFIG_TARGET_P4080DS) += p_corenet/ +obj-$(CONFIG_TARGET_P5020DS) += p_corenet/ +obj-$(CONFIG_TARGET_P5040DS) += p_corenet/ + +obj-$(CONFIG_LAYERSCAPE_NS_ACCESS) += ns_access.o + +ifdef CONFIG_NXP_ESBC +obj-$(CONFIG_CMD_ESBC_VALIDATE) += fsl_validate.o cmd_esbc_validate.o +endif +obj-$(CONFIG_CHAIN_OF_TRUST) += fsl_chain_of_trust.o + +ifndef CONFIG_SPL_BUILD +obj-$(CONFIG_USB_TCPC) += tcpc.o +endif + +endif diff --git a/board/somdevices/common/mmc.c b/board/somdevices/common/mmc.c new file mode 100644 index 0000000000..ab1652d697 --- /dev/null +++ b/board/somdevices/common/mmc.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2018 NXP + */ +#include +#include +#include +#include +#include +#include +#include +#include + +static int check_mmc_autodetect(void) +{ + char *autodetect_str = env_get("mmcautodetect"); + + if ((autodetect_str != NULL) && + (strcmp(autodetect_str, "yes") == 0)) { + return 1; + } + + return 0; +} + +/* This should be defined for each board */ +__weak int mmc_map_to_kernel_blk(int dev_no) +{ + return dev_no; +} + +void board_late_mmc_env_init(void) +{ + char cmd[32]; + char mmcblk[32]; + u32 dev_no = mmc_get_env_dev(); + + if (!check_mmc_autodetect()) + return; + + env_set_ulong("mmcdev", dev_no); + + /* Set mmcblk env */ + sprintf(mmcblk, "/dev/mmcblk%dp2 rootwait rw", + mmc_map_to_kernel_blk(dev_no)); + env_set("mmcroot", mmcblk); + + sprintf(cmd, "mmc dev %d", dev_no); + run_command(cmd, 0); +} diff --git a/board/somdevices/common/tcpc.c b/board/somdevices/common/tcpc.c new file mode 100644 index 0000000000..f7d6ea66d3 --- /dev/null +++ b/board/somdevices/common/tcpc.c @@ -0,0 +1,1036 @@ +/* + * Copyright 2017,2019 NXP + * + * SPDX-License-Identifier: GPL-2.0+ + */ +#include +#include +#include +#include +#include "tcpc.h" + +#ifdef DEBUG +#define tcpc_debug_log(port, fmt, args...) tcpc_log(port, fmt, ##args) +#else +#define tcpc_debug_log(port, fmt, args...) +#endif + +static int tcpc_log(struct tcpc_port *port, const char *fmt, ...) +{ + va_list args; + int i; + + va_start(args, fmt); + i = vscnprintf(port->log_p, port->log_size, fmt, args); + va_end(args); + + port->log_size -= i; + port->log_p += i; + + return i; +} + +int tcpc_set_cc_to_source(struct tcpc_port *port) +{ + uint8_t valb; + int err; + + if (port == NULL) + return -EINVAL; + + valb = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) | + (TCPC_ROLE_CTRL_RP_VAL_DEF << + TCPC_ROLE_CTRL_RP_VAL_SHIFT) | TCPC_ROLE_CTRL_DRP; + + err = dm_i2c_write(port->i2c_dev, TCPC_ROLE_CTRL, &valb, 1); + if (err) + tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err); + return err; +} + +int tcpc_set_cc_to_sink(struct tcpc_port *port) +{ + uint8_t valb; + int err; + + if (port == NULL) + return -EINVAL; + + valb = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) | + (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT) | TCPC_ROLE_CTRL_DRP; + + err = dm_i2c_write(port->i2c_dev, TCPC_ROLE_CTRL, &valb, 1); + if (err) + tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err); + return err; +} + + +int tcpc_set_plug_orientation(struct tcpc_port *port, enum typec_cc_polarity polarity) +{ + uint8_t valb; + int err; + + if (port == NULL) + return -EINVAL; + + err = dm_i2c_read(port->i2c_dev, TCPC_TCPC_CTRL, &valb, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err); + return -EIO; + } + + if (polarity == TYPEC_POLARITY_CC2) + valb |= TCPC_TCPC_CTRL_ORIENTATION; + else + valb &= ~TCPC_TCPC_CTRL_ORIENTATION; + + err = dm_i2c_write(port->i2c_dev, TCPC_TCPC_CTRL, &valb, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err); + return -EIO; + } + + return 0; +} + +int tcpc_get_cc_status(struct tcpc_port *port, enum typec_cc_polarity *polarity, enum typec_cc_state *state) +{ + + uint8_t valb_cc, cc2, cc1; + int err; + + if (port == NULL || polarity == NULL || state == NULL) + return -EINVAL; + + err = dm_i2c_read(port->i2c_dev, TCPC_CC_STATUS, (uint8_t *)&valb_cc, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err); + return -EIO; + } + + tcpc_debug_log(port, "cc status 0x%x\n", valb_cc); + + cc2 = (valb_cc >> TCPC_CC_STATUS_CC2_SHIFT) & TCPC_CC_STATUS_CC2_MASK; + cc1 = (valb_cc >> TCPC_CC_STATUS_CC1_SHIFT) & TCPC_CC_STATUS_CC1_MASK; + + if (valb_cc & TCPC_CC_STATUS_LOOK4CONN) + return -EFAULT; + + *state = TYPEC_STATE_OPEN; + + if (valb_cc & TCPC_CC_STATUS_TERM) { + if (cc2) { + *polarity = TYPEC_POLARITY_CC2; + + switch (cc2) { + case 0x1: + *state = TYPEC_STATE_SNK_DEFAULT; + tcpc_log(port, "SNK.Default on CC2\n"); + break; + case 0x2: + *state = TYPEC_STATE_SNK_POWER15; + tcpc_log(port, "SNK.Power1.5 on CC2\n"); + break; + case 0x3: + *state = TYPEC_STATE_SNK_POWER30; + tcpc_log(port, "SNK.Power3.0 on CC2\n"); + break; + } + } else if (cc1) { + *polarity = TYPEC_POLARITY_CC1; + + switch (cc1) { + case 0x1: + *state = TYPEC_STATE_SNK_DEFAULT; + tcpc_log(port, "SNK.Default on CC1\n"); + break; + case 0x2: + *state = TYPEC_STATE_SNK_POWER15; + tcpc_log(port, "SNK.Power1.5 on CC1\n"); + break; + case 0x3: + *state = TYPEC_STATE_SNK_POWER30; + tcpc_log(port, "SNK.Power3.0 on CC1\n"); + break; + } + } else { + *state = TYPEC_STATE_OPEN; + return -EPERM; + } + + } else { + if (cc2) { + *polarity = TYPEC_POLARITY_CC2; + + switch (cc2) { + case 0x1: + if (cc1 == 0x1) { + *state = TYPEC_STATE_SRC_BOTH_RA; + tcpc_log(port, "SRC.Ra on both CC1 and CC2\n"); + } else if (cc1 == 0x2) { + *state = TYPEC_STATE_SRC_RD_RA; + tcpc_log(port, "SRC.Ra on CC2, SRC.Rd on CC1\n"); + } else if (cc1 == 0x0) { + tcpc_log(port, "SRC.Ra only on CC2\n"); + return -EFAULT; + } else + return -EFAULT; + break; + case 0x2: + if (cc1 == 0x1) { + *state = TYPEC_STATE_SRC_RD_RA; + tcpc_log(port, "SRC.Ra on CC1, SRC.Rd on CC2\n"); + } else if (cc1 == 0x0) { + *state = TYPEC_STATE_SRC_RD; + tcpc_log(port, "SRC.Rd on CC2\n"); + } else + return -EFAULT; + break; + case 0x3: + *state = TYPEC_STATE_SRC_RESERVED; + return -EFAULT; + } + } else if (cc1) { + *polarity = TYPEC_POLARITY_CC1; + + switch (cc1) { + case 0x1: + tcpc_log(port, "SRC.Ra only on CC1\n"); + return -EFAULT; + case 0x2: + *state = TYPEC_STATE_SRC_RD; + tcpc_log(port, "SRC.Rd on CC1\n"); + break; + case 0x3: + *state = TYPEC_STATE_SRC_RESERVED; + return -EFAULT; + } + } else { + *state = TYPEC_STATE_OPEN; + return -EPERM; + } + } + + return 0; +} + +int tcpc_clear_alert(struct tcpc_port *port, uint16_t clear_mask) +{ + int err; + + if (port == NULL) + return -EINVAL; + + err = dm_i2c_write(port->i2c_dev, TCPC_ALERT, (const uint8_t *)&clear_mask, 2); + if (err) { + tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err); + return -EIO; + } + + return 0; +} + +int tcpc_send_command(struct tcpc_port *port, uint8_t command) +{ + int err; + + if (port == NULL) + return -EINVAL; + + err = dm_i2c_write(port->i2c_dev, TCPC_COMMAND, (const uint8_t *)&command, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err); + return -EIO; + } + + return 0; +} + +int tcpc_polling_reg(struct tcpc_port *port, uint8_t reg, + uint8_t reg_width, uint16_t mask, uint16_t value, ulong timeout_ms) +{ + uint16_t val = 0; + int err; + ulong start; + + if (port == NULL) + return -EINVAL; + + tcpc_debug_log(port, "%s reg 0x%x, mask 0x%x, value 0x%x\n", __func__, reg, mask, value); + + /* TCPC registers is 8 bits or 16 bits */ + if (reg_width != 1 && reg_width != 2) + return -EINVAL; + + start = get_timer(0); /* Get current timestamp */ + do { + err = dm_i2c_read(port->i2c_dev, reg, (uint8_t *)&val, reg_width); + if (err) + return -EIO; + + if ((val & mask) == value) + return 0; + } while (get_timer(0) < (start + timeout_ms)); + + return -ETIME; +} + +void tcpc_print_log(struct tcpc_port *port) +{ + if (port == NULL) + return; + + if (port->log_print == port->log_p) /*nothing to output*/ + return; + + printf("%s", port->log_print); + + port->log_print = port->log_p; +} + +int tcpc_setup_dfp_mode(struct tcpc_port *port) +{ + enum typec_cc_polarity pol; + enum typec_cc_state state; + int ret; + + if ((port == NULL) || (port->i2c_dev == NULL)) + return -EINVAL; + + if (tcpc_pd_sink_check_charging(port)) { + tcpc_log(port, "%s: Can't apply DFP mode when PD is charging\n", + __func__); + return -EPERM; + } + + tcpc_set_cc_to_source(port); + + ret = tcpc_send_command(port, TCPC_CMD_LOOK4CONNECTION); + if (ret) + return ret; + + /* At least wait tCcStatusDelay + tTCPCFilter + tCcTCPCSampleRate (max) = 200us + 500us + ?ms + * PTN5110 datasheet does not contain the sample rate value, according other productions, + * the sample rate is at ms level, about 2 ms -10ms. So wait 100ms should be enough. + */ + mdelay(100); + + ret = tcpc_polling_reg(port, TCPC_ALERT, 2, TCPC_ALERT_CC_STATUS, TCPC_ALERT_CC_STATUS, 100); + if (ret) { + tcpc_log(port, "%s: Polling ALERT register, TCPC_ALERT_CC_STATUS bit failed, ret = %d\n", + __func__, ret); + return ret; + } + + ret = tcpc_get_cc_status(port, &pol, &state); + tcpc_clear_alert(port, TCPC_ALERT_CC_STATUS); + + if (!ret) { + /* If presenting as Rd/audio mode/open, return */ + if (state != TYPEC_STATE_SRC_RD_RA && state != TYPEC_STATE_SRC_RD) + return -EPERM; + + if (pol == TYPEC_POLARITY_CC1) + tcpc_debug_log(port, "polarity cc1\n"); + else + tcpc_debug_log(port, "polarity cc2\n"); + + if (port->ss_sel_func) + port->ss_sel_func(pol); + + ret = tcpc_set_plug_orientation(port, pol); + if (ret) + return ret; + + /* Enable source vbus default voltage */ + ret = tcpc_send_command(port, TCPC_CMD_SRC_VBUS_DEFAULT); + if (ret) + return ret; + + /* The max vbus on time is 200ms, we add margin 100ms */ + mdelay(300); + + } + + return 0; +} + +int tcpc_setup_ufp_mode(struct tcpc_port *port) +{ + enum typec_cc_polarity pol; + enum typec_cc_state state; + int ret; + + if ((port == NULL) || (port->i2c_dev == NULL)) + return -EINVAL; + + /* Check if the PD charge is working. If not, need to configure CC role for UFP */ + if (!tcpc_pd_sink_check_charging(port)) { + + /* Disable the source vbus once it is enabled by DFP mode */ + tcpc_disable_src_vbus(port); + + tcpc_set_cc_to_sink(port); + + ret = tcpc_send_command(port, TCPC_CMD_LOOK4CONNECTION); + if (ret) + return ret; + + /* At least wait tCcStatusDelay + tTCPCFilter + tCcTCPCSampleRate (max) = 200us + 500us + ?ms + * PTN5110 datasheet does not contain the sample rate value, according other productions, + * the sample rate is at ms level, about 2 ms -10ms. So wait 100ms should be enough. + */ + mdelay(100); + + ret = tcpc_polling_reg(port, TCPC_ALERT, 2, TCPC_ALERT_CC_STATUS, TCPC_ALERT_CC_STATUS, 100); + if (ret) { + tcpc_log(port, "%s: Polling ALERT register, TCPC_ALERT_CC_STATUS bit failed, ret = %d\n", + __func__, ret); + return ret; + } + + ret = tcpc_get_cc_status(port, &pol, &state); + tcpc_clear_alert(port, TCPC_ALERT_CC_STATUS); + + } else { + ret = tcpc_get_cc_status(port, &pol, &state); + } + + if (!ret) { + /* If presenting not as sink, then return */ + if (state != TYPEC_STATE_SNK_DEFAULT && state != TYPEC_STATE_SNK_POWER15 && + state != TYPEC_STATE_SNK_POWER30) + return -EPERM; + + if (pol == TYPEC_POLARITY_CC1) + tcpc_debug_log(port, "polarity cc1\n"); + else + tcpc_debug_log(port, "polarity cc2\n"); + + if (port->ss_sel_func) + port->ss_sel_func(pol); + + ret = tcpc_set_plug_orientation(port, pol); + if (ret) + return ret; + } + + return 0; +} + +int tcpc_disable_src_vbus(struct tcpc_port *port) +{ + int ret; + + if (port == NULL) + return -EINVAL; + + /* Disable VBUS*/ + ret = tcpc_send_command(port, TCPC_CMD_DISABLE_SRC_VBUS); + if (ret) + return ret; + + /* The max vbus off time is 0.5ms, we add margin 0.5 ms */ + mdelay(1); + + return 0; +} + +int tcpc_disable_sink_vbus(struct tcpc_port *port) +{ + int ret; + + if (port == NULL) + return -EINVAL; + + /* Disable SINK VBUS*/ + ret = tcpc_send_command(port, TCPC_CMD_DISABLE_SINK_VBUS); + if (ret) + return ret; + + /* The max vbus off time is 0.5ms, we add margin 0.5 ms */ + mdelay(1); + + return 0; +} + + +static int tcpc_pd_receive_message(struct tcpc_port *port, struct pd_message *msg) +{ + int ret; + uint8_t cnt; + uint16_t val; + + if (port == NULL) + return -EINVAL; + + /* Generally the max tSenderResponse is 30ms, max tTypeCSendSourceCap is 200ms, we set the timeout to 500ms */ + ret = tcpc_polling_reg(port, TCPC_ALERT, 2, TCPC_ALERT_RX_STATUS, TCPC_ALERT_RX_STATUS, 500); + if (ret) { + tcpc_log(port, "%s: Polling ALERT register, TCPC_ALERT_RX_STATUS bit failed, ret = %d\n", + __func__, ret); + return ret; + } + + cnt = 0; + ret = dm_i2c_read(port->i2c_dev, TCPC_RX_BYTE_CNT, (uint8_t *)&cnt, 1); + if (ret) + return -EIO; + + if (cnt > 0) { + ret = dm_i2c_read(port->i2c_dev, TCPC_RX_BUF_FRAME_TYPE, (uint8_t *)msg, cnt); + if (ret) + return -EIO; + + /* Clear RX status alert bit */ + val = TCPC_ALERT_RX_STATUS; + ret = dm_i2c_write(port->i2c_dev, TCPC_ALERT, (const uint8_t *)&val, 2); + if (ret) + return -EIO; + } + + return cnt; +} + +static int tcpc_pd_transmit_message(struct tcpc_port *port, struct pd_message *msg_p, uint8_t bytes) +{ + int ret; + uint8_t valb; + uint16_t val = 0; + + if (port == NULL) + return -EINVAL; + + if (msg_p == NULL || bytes <= 0) + return -EINVAL; + + ret = dm_i2c_write(port->i2c_dev, TCPC_TX_BYTE_CNT, (const uint8_t *)&bytes, 1); + if (ret) + return -EIO; + + ret = dm_i2c_write(port->i2c_dev, TCPC_TX_HDR, (const uint8_t *)&(msg_p->header), bytes); + if (ret) + return -EIO; + + valb = (3 << TCPC_TRANSMIT_RETRY_SHIFT) | (TCPC_TX_SOP << TCPC_TRANSMIT_TYPE_SHIFT); + ret = dm_i2c_write(port->i2c_dev, TCPC_TRANSMIT, (const uint8_t *)&valb, 1); + if (ret) + return -EIO; + + /* Max tReceive is 1.1ms, we set to 5ms timeout */ + ret = tcpc_polling_reg(port, TCPC_ALERT, 2, TCPC_ALERT_TX_SUCCESS, TCPC_ALERT_TX_SUCCESS, 5); + if (ret) { + if (ret == -ETIME) { + ret = dm_i2c_read(port->i2c_dev, TCPC_ALERT, (uint8_t *)&val, 2); + if (ret) + return -EIO; + + if (val & TCPC_ALERT_TX_FAILED) + tcpc_log(port, "%s: PD TX FAILED, ALERT = 0x%x\n", __func__, val); + + if (val & TCPC_ALERT_TX_DISCARDED) + tcpc_log(port, "%s: PD TX DISCARDED, ALERT = 0x%x\n", __func__, val); + + } else { + tcpc_log(port, "%s: Polling ALERT register, TCPC_ALERT_TX_SUCCESS bit failed, ret = %d\n", + __func__, ret); + } + } else { + port->tx_msg_id = (port->tx_msg_id + 1) & PD_HEADER_ID_MASK; + } + + /* Clear ALERT status */ + val &= (TCPC_ALERT_TX_FAILED | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_TX_SUCCESS); + ret = dm_i2c_write(port->i2c_dev, TCPC_ALERT, (const uint8_t *)&val, 2); + if (ret) + return -EIO; + + return ret; +} + +static void tcpc_log_source_caps(struct tcpc_port *port, struct pd_message *msg, unsigned int capcount) +{ + int i; + + for (i = 0; i < capcount; i++) { + u32 pdo = msg->payload[i]; + enum pd_pdo_type type = pdo_type(pdo); + + tcpc_log(port, "PDO %d: type %d, ", + i, type); + + switch (type) { + case PDO_TYPE_FIXED: + tcpc_log(port, "%u mV, %u mA [%s%s%s%s%s%s]\n", + pdo_fixed_voltage(pdo), + pdo_max_current(pdo), + (pdo & PDO_FIXED_DUAL_ROLE) ? + "R" : "", + (pdo & PDO_FIXED_SUSPEND) ? + "S" : "", + (pdo & PDO_FIXED_HIGHER_CAP) ? + "H" : "", + (pdo & PDO_FIXED_USB_COMM) ? + "U" : "", + (pdo & PDO_FIXED_DATA_SWAP) ? + "D" : "", + (pdo & PDO_FIXED_EXTPOWER) ? + "E" : ""); + break; + case PDO_TYPE_VAR: + tcpc_log(port, "%u-%u mV, %u mA\n", + pdo_min_voltage(pdo), + pdo_max_voltage(pdo), + pdo_max_current(pdo)); + break; + case PDO_TYPE_BATT: + tcpc_log(port, "%u-%u mV, %u mW\n", + pdo_min_voltage(pdo), + pdo_max_voltage(pdo), + pdo_max_power(pdo)); + break; + default: + tcpc_log(port, "undefined\n"); + break; + } + } +} + +static int tcpc_pd_select_pdo(struct pd_message *msg, uint32_t capcount, uint32_t max_snk_mv, uint32_t max_snk_ma) +{ + unsigned int i, max_mw = 0, max_mv = 0; + int ret = -EINVAL; + + /* + * Select the source PDO providing the most power while staying within + * the board's voltage limits. Prefer PDO providing exp + */ + for (i = 0; i < capcount; i++) { + u32 pdo = msg->payload[i]; + enum pd_pdo_type type = pdo_type(pdo); + unsigned int mv, ma, mw; + + if (type == PDO_TYPE_FIXED) + mv = pdo_fixed_voltage(pdo); + else + mv = pdo_min_voltage(pdo); + + if (type == PDO_TYPE_BATT) { + mw = pdo_max_power(pdo); + } else { + ma = min(pdo_max_current(pdo), + max_snk_ma); + mw = ma * mv / 1000; + } + + /* Perfer higher voltages if available */ + if ((mw > max_mw || (mw == max_mw && mv > max_mv)) && + mv <= max_snk_mv) { + ret = i; + max_mw = mw; + max_mv = mv; + } + } + + return ret; +} + +static int tcpc_pd_build_request(struct tcpc_port *port, + struct pd_message *msg, + uint32_t capcount, + uint32_t max_snk_mv, + uint32_t max_snk_ma, + uint32_t max_snk_mw, + uint32_t operating_snk_mw, + uint32_t *rdo) +{ + unsigned int mv, ma, mw, flags; + unsigned int max_ma, max_mw; + enum pd_pdo_type type; + int index; + u32 pdo; + + index = tcpc_pd_select_pdo(msg, capcount, max_snk_mv, max_snk_ma); + if (index < 0) + return -EINVAL; + + pdo = msg->payload[index]; + type = pdo_type(pdo); + + if (type == PDO_TYPE_FIXED) + mv = pdo_fixed_voltage(pdo); + else + mv = pdo_min_voltage(pdo); + + /* Select maximum available current within the board's power limit */ + if (type == PDO_TYPE_BATT) { + mw = pdo_max_power(pdo); + ma = 1000 * min(mw, max_snk_mw) / mv; + } else { + ma = min(pdo_max_current(pdo), + 1000 * max_snk_mw / mv); + } + ma = min(ma, max_snk_ma); + + /* XXX: Any other flags need to be set? */ + flags = 0; + + /* Set mismatch bit if offered power is less than operating power */ + mw = ma * mv / 1000; + max_ma = ma; + max_mw = mw; + if (mw < operating_snk_mw) { + flags |= RDO_CAP_MISMATCH; + max_mw = operating_snk_mw; + max_ma = max_mw * 1000 / mv; + } + + if (type == PDO_TYPE_BATT) { + *rdo = RDO_BATT(index + 1, mw, max_mw, flags); + + tcpc_log(port, "Requesting PDO %d: %u mV, %u mW%s\n", + index, mv, mw, + flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); + } else { + *rdo = RDO_FIXED(index + 1, ma, max_ma, flags); + + tcpc_log(port, "Requesting PDO %d: %u mV, %u mA%s\n", + index, mv, ma, + flags & RDO_CAP_MISMATCH ? " [mismatch]" : ""); + } + + return 0; +} + +static void tcpc_pd_sink_process(struct tcpc_port *port) +{ + int ret; + uint8_t msgtype; + uint32_t objcnt; + struct pd_message msg; + enum pd_sink_state pd_state = WAIT_SOURCE_CAP; + + while (tcpc_pd_receive_message(port, &msg) > 0) { + + msgtype = pd_header_type(msg.header); + objcnt = pd_header_cnt_le(msg.header); + + tcpc_debug_log(port, "get msg, type %d, cnt %d\n", msgtype, objcnt); + + switch (pd_state) { + case WAIT_SOURCE_CAP: + case SINK_READY: + if (msgtype != PD_DATA_SOURCE_CAP) + continue; + + uint32_t rdo = 0; + + tcpc_log_source_caps(port, &msg, objcnt); + + tcpc_pd_build_request(port, &msg, objcnt, + port->cfg.max_snk_mv, port->cfg.max_snk_ma, + port->cfg.max_snk_mw, port->cfg.op_snk_mv, + &rdo); + + memset(&msg, 0, sizeof(msg)); + msg.header = PD_HEADER(PD_DATA_REQUEST, 0, 0, port->tx_msg_id, 1); /* power sink, data device, id 0, len 1 */ + msg.payload[0] = rdo; + + ret = tcpc_pd_transmit_message(port, &msg, 6); + if (ret) + tcpc_log(port, "send request failed\n"); + else + pd_state = WAIT_SOURCE_ACCEPT; + + break; + case WAIT_SOURCE_ACCEPT: + if (objcnt > 0) /* Should be ctrl message */ + continue; + + if (msgtype == PD_CTRL_ACCEPT) { + pd_state = WAIT_SOURCE_READY; + tcpc_log(port, "Source accept request\n"); + } else if (msgtype == PD_CTRL_REJECT) { + tcpc_log(port, "Source reject request\n"); + return; + } + + break; + case WAIT_SOURCE_READY: + if (objcnt > 0) /* Should be ctrl message */ + continue; + + if (msgtype == PD_CTRL_PS_RDY) { + tcpc_log(port, "PD source ready!\n"); + pd_state = SINK_READY; + } + + break; + default: + tcpc_log(port, "unexpect status: %u\n", pd_state); + break; + } + } +} + +bool tcpc_pd_sink_check_charging(struct tcpc_port *port) +{ + uint8_t valb; + int err; + enum typec_cc_polarity pol; + enum typec_cc_state state; + + if (port == NULL) + return false; + + /* Check the CC status, must be sink */ + err = tcpc_get_cc_status(port, &pol, &state); + if (err || (state != TYPEC_STATE_SNK_POWER15 + && state != TYPEC_STATE_SNK_POWER30 + && state != TYPEC_STATE_SNK_DEFAULT)) { + tcpc_debug_log(port, "TCPC wrong state for PD charging, err = %d, CC = 0x%x\n", + err, state); + return false; + } + + /* Check the VBUS PRES and SINK VBUS for dead battery */ + err = dm_i2c_read(port->i2c_dev, TCPC_POWER_STATUS, &valb, 1); + if (err) { + tcpc_debug_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err); + return false; + } + + if (!(valb & TCPC_POWER_STATUS_VBUS_PRES)) { + tcpc_debug_log(port, "VBUS NOT PRES \n"); + return false; + } + + if (!(valb & TCPC_POWER_STATUS_SINKING_VBUS)) { + tcpc_debug_log(port, "SINK VBUS is not enabled for dead battery\n"); + return false; + } + + return true; +} + +static int tcpc_pd_sink_disable(struct tcpc_port *port) +{ + uint8_t valb; + int err; + + if (port == NULL) + return -EINVAL; + + port->pd_state = UNATTACH; + + /* Check the VBUS PRES and SINK VBUS for dead battery */ + err = dm_i2c_read(port->i2c_dev, TCPC_POWER_STATUS, &valb, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err); + return -EIO; + } + + if ((valb & TCPC_POWER_STATUS_VBUS_PRES) && (valb & TCPC_POWER_STATUS_SINKING_VBUS)) { + err = dm_i2c_read(port->i2c_dev, TCPC_POWER_CTRL, (uint8_t *)&valb, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err); + return -EIO; + } + + valb &= ~TCPC_POWER_CTRL_AUTO_DISCH_DISCO; /* disable AutoDischargeDisconnect */ + err = dm_i2c_write(port->i2c_dev, TCPC_POWER_CTRL, (const uint8_t *)&valb, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err); + return -EIO; + } + + tcpc_disable_sink_vbus(port); + } + + if (port->cfg.switch_setup_func) + port->cfg.switch_setup_func(port); + + return 0; +} + +static int tcpc_pd_sink_init(struct tcpc_port *port) +{ + uint8_t valb; + uint16_t val; + int err; + enum typec_cc_polarity pol; + enum typec_cc_state state; + + if (port == NULL) + return -EINVAL; + + port->pd_state = UNATTACH; + + /* Check the VBUS PRES and SINK VBUS for dead battery */ + err = dm_i2c_read(port->i2c_dev, TCPC_POWER_STATUS, &valb, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err); + return -EIO; + } + + if (!(valb & TCPC_POWER_STATUS_VBUS_PRES)) { + tcpc_debug_log(port, "VBUS NOT PRES \n"); + return -EPERM; + } + + if (!(valb & TCPC_POWER_STATUS_SINKING_VBUS)) { + tcpc_debug_log(port, "SINK VBUS is not enabled for dead battery\n"); + return -EPERM; + } + + err = dm_i2c_read(port->i2c_dev, TCPC_ALERT, (uint8_t *)&val, 2); + if (err) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err); + return -EIO; + } + + if (!(val & TCPC_ALERT_CC_STATUS)) { + tcpc_debug_log(port, "CC STATUS not detected for dead battery\n"); + return -EPERM; + } + + err = tcpc_get_cc_status(port, &pol, &state); + if (err || (state != TYPEC_STATE_SNK_POWER15 + && state != TYPEC_STATE_SNK_POWER30 + && state != TYPEC_STATE_SNK_DEFAULT)) { + tcpc_log(port, "TCPC wrong state for dead battery, err = %d, CC = 0x%x\n", + err, state); + return -EPERM; + } else { + err = tcpc_set_plug_orientation(port, pol); + if (err) { + tcpc_log(port, "TCPC set plug orientation failed, err = %d\n", err); + return err; + } + port->pd_state = ATTACHED; + } + + err = dm_i2c_read(port->i2c_dev, TCPC_POWER_CTRL, (uint8_t *)&valb, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err); + return -EIO; + } + + valb &= ~TCPC_POWER_CTRL_AUTO_DISCH_DISCO; /* disable AutoDischargeDisconnect */ + err = dm_i2c_write(port->i2c_dev, TCPC_POWER_CTRL, (const uint8_t *)&valb, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_write failed, err %d\n", __func__, err); + return -EIO; + } + + if (port->cfg.switch_setup_func) + port->cfg.switch_setup_func(port); + + /* As sink role */ + valb = 0x00; + err = dm_i2c_write(port->i2c_dev, TCPC_MSG_HDR_INFO, (const uint8_t *)&valb, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err); + return -EIO; + } + + /* Enable rx */ + valb = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET; + err = dm_i2c_write(port->i2c_dev, TCPC_RX_DETECT, (const uint8_t *)&valb, 1); + if (err) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, err); + return -EIO; + } + + tcpc_pd_sink_process(port); + + return 0; +} + +int tcpc_init(struct tcpc_port *port, struct tcpc_port_config config, ss_mux_sel ss_sel_func) +{ + int ret; + uint8_t valb; + uint16_t vid, pid; + struct udevice *bus; + struct udevice *i2c_dev = NULL; + + memset(port, 0, sizeof(struct tcpc_port)); + + if (port == NULL) + return -EINVAL; + + port->cfg = config; + port->tx_msg_id = 0; + port->ss_sel_func = ss_sel_func; + port->log_p = (char *)&(port->logbuffer); + port->log_size = TCPC_LOG_BUFFER_SIZE; + port->log_print = port->log_p; + memset(&(port->logbuffer), 0, TCPC_LOG_BUFFER_SIZE); + + ret = uclass_get_device_by_seq(UCLASS_I2C, port->cfg.i2c_bus, &bus); + if (ret) { + printf("%s: Can't find bus\n", __func__); + return -EINVAL; + } + + ret = dm_i2c_probe(bus, port->cfg.addr, 0, &i2c_dev); + if (ret) { + printf("%s: Can't find device id=0x%x\n", + __func__, config.addr); + return -ENODEV; + } + + port->i2c_dev = i2c_dev; + + /* Check the Initialization Status bit in 1s */ + ret = tcpc_polling_reg(port, TCPC_POWER_STATUS, 1, TCPC_POWER_STATUS_UNINIT, 0, 1000); + if (ret) { + tcpc_log(port, "%s: Polling TCPC POWER STATUS Initialization Status bit failed, ret = %d\n", + __func__, ret); + return ret; + } + + dm_i2c_read(port->i2c_dev, TCPC_POWER_STATUS, &valb, 1); + tcpc_debug_log(port, "POWER STATUS: 0x%x\n", valb); + + /* Clear AllRegistersResetToDefault */ + valb = 0x80; + ret = dm_i2c_write(port->i2c_dev, TCPC_FAULT_STATUS, (const uint8_t *)&valb, 1); + if (ret) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, ret); + return -EIO; + } + + /* Read Vendor ID and Product ID */ + ret = dm_i2c_read(port->i2c_dev, TCPC_VENDOR_ID, (uint8_t *)&vid, 2); + if (ret) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, ret); + return -EIO; + } + + ret = dm_i2c_read(port->i2c_dev, TCPC_PRODUCT_ID, (uint8_t *)&pid, 2); + if (ret) { + tcpc_log(port, "%s dm_i2c_read failed, err %d\n", __func__, ret); + return -EIO; + } + + tcpc_log(port, "TCPC: Vendor ID [0x%x], Product ID [0x%x], Addr [I2C%u 0x%x]\n", + vid, pid, port->cfg.i2c_bus, port->cfg.addr); + + if (!port->cfg.disable_pd) { + if (port->cfg.port_type == TYPEC_PORT_UFP + || port->cfg.port_type == TYPEC_PORT_DRP) + tcpc_pd_sink_init(port); + } else { + tcpc_pd_sink_disable(port); + } + + tcpc_clear_alert(port, 0xffff); + + tcpc_print_log(port); + + return 0; +} diff --git a/board/somdevices/common/tcpc.h b/board/somdevices/common/tcpc.h new file mode 100644 index 0000000000..ff749518a8 --- /dev/null +++ b/board/somdevices/common/tcpc.h @@ -0,0 +1,469 @@ +/* + * Copyright 2017 NXP + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __TCPCI_H +#define __TCPCI_H + +#include + +#define TCPC_VENDOR_ID 0x0 +#define TCPC_PRODUCT_ID 0x2 + +#define TCPC_ALERT 0x10 +#define TCPC_ALERT_VBUS_DISCNCT BIT(11) +#define TCPC_ALERT_RX_BUF_OVF BIT(10) +#define TCPC_ALERT_FAULT BIT(9) +#define TCPC_ALERT_V_ALARM_LO BIT(8) +#define TCPC_ALERT_V_ALARM_HI BIT(7) +#define TCPC_ALERT_TX_SUCCESS BIT(6) +#define TCPC_ALERT_TX_DISCARDED BIT(5) +#define TCPC_ALERT_TX_FAILED BIT(4) +#define TCPC_ALERT_RX_HARD_RST BIT(3) +#define TCPC_ALERT_RX_STATUS BIT(2) +#define TCPC_ALERT_POWER_STATUS BIT(1) +#define TCPC_ALERT_CC_STATUS BIT(0) + +#define TCPC_TCPC_CTRL 0x19 +#define TCPC_TCPC_CTRL_BIST_MODE BIT(1) +#define TCPC_TCPC_CTRL_ORIENTATION BIT(0) + +#define TCPC_ROLE_CTRL 0x1a +#define TCPC_ROLE_CTRL_DRP BIT(6) +#define TCPC_ROLE_CTRL_RP_VAL_SHIFT 4 +#define TCPC_ROLE_CTRL_RP_VAL_MASK 0x3 +#define TCPC_ROLE_CTRL_RP_VAL_DEF 0x0 +#define TCPC_ROLE_CTRL_RP_VAL_1_5 0x1 +#define TCPC_ROLE_CTRL_RP_VAL_3_0 0x2 +#define TCPC_ROLE_CTRL_CC2_SHIFT 2 +#define TCPC_ROLE_CTRL_CC2_MASK 0x3 +#define TCPC_ROLE_CTRL_CC1_SHIFT 0 +#define TCPC_ROLE_CTRL_CC1_MASK 0x3 +#define TCPC_ROLE_CTRL_CC_RA 0x0 +#define TCPC_ROLE_CTRL_CC_RP 0x1 +#define TCPC_ROLE_CTRL_CC_RD 0x2 +#define TCPC_ROLE_CTRL_CC_OPEN 0x3 + +#define TCPC_POWER_CTRL 0x1c +#define TCPC_POWER_CTRL_EN_VCONN BIT(0) +#define TCPC_POWER_CTRL_VCONN_POWER BIT(1) +#define TCPC_POWER_CTRL_FORCE_DISCH BIT(2) +#define TCPC_POWER_CTRL_EN_BLEED_CH BIT(3) +#define TCPC_POWER_CTRL_AUTO_DISCH_DISCO BIT(4) +#define TCPC_POWER_CTRL_DIS_V_ALARMS BIT(5) +#define TCPC_POWER_CTRL_VBUS_V_MONITOR BIT(6) + +#define TCPC_CC_STATUS 0x1d +#define TCPC_CC_STATUS_LOOK4CONN BIT(5) +#define TCPC_CC_STATUS_TERM BIT(4) +#define TCPC_CC_STATUS_CC2_SHIFT 2 +#define TCPC_CC_STATUS_CC2_MASK 0x3 +#define TCPC_CC_STATUS_CC1_SHIFT 0 +#define TCPC_CC_STATUS_CC1_MASK 0x3 + +#define TCPC_POWER_STATUS 0x1e +#define TCPC_POWER_STATUS_UNINIT BIT(6) +#define TCPC_POWER_STATUS_VBUS_DET BIT(3) +#define TCPC_POWER_STATUS_VBUS_PRES BIT(2) +#define TCPC_POWER_STATUS_SINKING_VBUS BIT(0) + +#define TCPC_FAULT_STATUS 0x1f + +#define TCPC_COMMAND 0x23 +#define TCPC_CMD_WAKE_I2C 0x11 +#define TCPC_CMD_DISABLE_VBUS_DETECT 0x22 +#define TCPC_CMD_ENABLE_VBUS_DETECT 0x33 +#define TCPC_CMD_DISABLE_SINK_VBUS 0x44 +#define TCPC_CMD_SINK_VBUS 0x55 +#define TCPC_CMD_DISABLE_SRC_VBUS 0x66 +#define TCPC_CMD_SRC_VBUS_DEFAULT 0x77 +#define TCPC_CMD_SRC_VBUS_HIGH 0x88 +#define TCPC_CMD_LOOK4CONNECTION 0x99 +#define TCPC_CMD_RXONEMORE 0xAA +#define TCPC_CMD_I2C_IDLE 0xFF + +#define TCPC_DEV_CAP_1 0x24 +#define TCPC_DEV_CAP_2 0x26 +#define TCPC_STD_INPUT_CAP 0x28 +#define TCPC_STD_OUTPUT_CAP 0x29 + +#define TCPC_MSG_HDR_INFO 0x2e +#define TCPC_MSG_HDR_INFO_DATA_ROLE BIT(3) +#define TCPC_MSG_HDR_INFO_PWR_ROLE BIT(0) +#define TCPC_MSG_HDR_INFO_REV_SHIFT 1 +#define TCPC_MSG_HDR_INFO_REV_MASK 0x3 + +#define TCPC_RX_DETECT 0x2f +#define TCPC_RX_DETECT_HARD_RESET BIT(5) +#define TCPC_RX_DETECT_SOP BIT(0) + +#define TCPC_RX_BYTE_CNT 0x30 +#define TCPC_RX_BUF_FRAME_TYPE 0x31 +#define TCPC_RX_HDR 0x32 +#define TCPC_RX_DATA 0x34 /* through 0x4f */ + +#define TCPC_TRANSMIT 0x50 +#define TCPC_TRANSMIT_RETRY_SHIFT 4 +#define TCPC_TRANSMIT_RETRY_MASK 0x3 +#define TCPC_TRANSMIT_TYPE_SHIFT 0 +#define TCPC_TRANSMIT_TYPE_MASK 0x7 + +#define TCPC_TX_BYTE_CNT 0x51 +#define TCPC_TX_HDR 0x52 +#define TCPC_TX_DATA 0x54 /* through 0x6f */ + +#define TCPC_VBUS_VOLTAGE 0x70 +#define TCPC_VBUS_VOL_MASK 0x3ff +#define TCPC_VBUS_VOL_SCALE_FACTOR_MASK 0xc00 +#define TCPC_VBUS_VOL_SCALE_FACTOR_SHIFT 10 +#define TCPC_VBUS_VOL_MV_UNIT 25 + +#define TCPC_VBUS_SINK_DISCONNECT_THRESH 0x72 +#define TCPC_VBUS_STOP_DISCHARGE_THRESH 0x74 +#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76 +#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78 + +enum typec_role { + TYPEC_SINK, + TYPEC_SOURCE, + TYPEC_ROLE_UNKNOWN, +}; + +enum typec_data_role { + TYPEC_DEVICE, + TYPEC_HOST, +}; + +enum typec_cc_polarity { + TYPEC_POLARITY_CC1, + TYPEC_POLARITY_CC2, +}; + +enum typec_cc_state { + TYPEC_STATE_OPEN, + TYPEC_STATE_SRC_BOTH_RA, + TYPEC_STATE_SRC_RD_RA, + TYPEC_STATE_SRC_RD, + TYPEC_STATE_SRC_RESERVED, + TYPEC_STATE_SNK_DEFAULT, + TYPEC_STATE_SNK_POWER15, + TYPEC_STATE_SNK_POWER30, +}; + + +/* USB PD Messages */ +enum pd_ctrl_msg_type { + /* 0 Reserved */ + PD_CTRL_GOOD_CRC = 1, + PD_CTRL_GOTO_MIN = 2, + PD_CTRL_ACCEPT = 3, + PD_CTRL_REJECT = 4, + PD_CTRL_PING = 5, + PD_CTRL_PS_RDY = 6, + PD_CTRL_GET_SOURCE_CAP = 7, + PD_CTRL_GET_SINK_CAP = 8, + PD_CTRL_DR_SWAP = 9, + PD_CTRL_PR_SWAP = 10, + PD_CTRL_VCONN_SWAP = 11, + PD_CTRL_WAIT = 12, + PD_CTRL_SOFT_RESET = 13, + /* 14-15 Reserved */ +}; + +enum pd_data_msg_type { + /* 0 Reserved */ + PD_DATA_SOURCE_CAP = 1, + PD_DATA_REQUEST = 2, + PD_DATA_BIST = 3, + PD_DATA_SINK_CAP = 4, + /* 5-14 Reserved */ + PD_DATA_VENDOR_DEF = 15, +}; + +enum tcpc_transmit_type { + TCPC_TX_SOP = 0, + TCPC_TX_SOP_PRIME = 1, + TCPC_TX_SOP_PRIME_PRIME = 2, + TCPC_TX_SOP_DEBUG_PRIME = 3, + TCPC_TX_SOP_DEBUG_PRIME_PRIME = 4, + TCPC_TX_HARD_RESET = 5, + TCPC_TX_CABLE_RESET = 6, + TCPC_TX_BIST_MODE_2 = 7 +}; + +enum pd_sink_state{ + UNATTACH = 0, + ATTACHED, + WAIT_SOURCE_CAP, + WAIT_SOURCE_ACCEPT, + WAIT_SOURCE_READY, + SINK_READY, +}; + + +#define PD_REV10 0x0 +#define PD_REV20 0x1 + +#define PD_HEADER_CNT_SHIFT 12 +#define PD_HEADER_CNT_MASK 0x7 +#define PD_HEADER_ID_SHIFT 9 +#define PD_HEADER_ID_MASK 0x7 +#define PD_HEADER_PWR_ROLE BIT(8) +#define PD_HEADER_REV_SHIFT 6 +#define PD_HEADER_REV_MASK 0x3 +#define PD_HEADER_DATA_ROLE BIT(5) +#define PD_HEADER_TYPE_SHIFT 0 +#define PD_HEADER_TYPE_MASK 0xf + +#define PD_HEADER(type, pwr, data, id, cnt) \ + ((((type) & PD_HEADER_TYPE_MASK) << PD_HEADER_TYPE_SHIFT) | \ + ((pwr) == TYPEC_SOURCE ? PD_HEADER_PWR_ROLE : 0) | \ + ((data) == TYPEC_HOST ? PD_HEADER_DATA_ROLE : 0) | \ + (PD_REV20 << PD_HEADER_REV_SHIFT) | \ + (((id) & PD_HEADER_ID_MASK) << PD_HEADER_ID_SHIFT) | \ + (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT)) + + +static inline unsigned int pd_header_cnt(uint16_t header) +{ + return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK; +} + +static inline unsigned int pd_header_cnt_le(__le16 header) +{ + return pd_header_cnt(le16_to_cpu(header)); +} + +static inline unsigned int pd_header_type(uint16_t header) +{ + return (header >> PD_HEADER_TYPE_SHIFT) & PD_HEADER_TYPE_MASK; +} + +static inline unsigned int pd_header_type_le(__le16 header) +{ + return pd_header_type(le16_to_cpu(header)); +} + +#define PD_MAX_PAYLOAD 7 + +struct pd_message { + uint8_t frametype; + uint16_t header; + uint32_t payload[PD_MAX_PAYLOAD]; +} __packed; + +enum pd_pdo_type { + PDO_TYPE_FIXED = 0, + PDO_TYPE_BATT = 1, + PDO_TYPE_VAR = 2, +}; + + +#define PDO_TYPE_SHIFT 30 +#define PDO_TYPE_MASK 0x3 + +#define PDO_TYPE(t) ((t) << PDO_TYPE_SHIFT) + +#define PDO_VOLT_MASK 0x3ff +#define PDO_CURR_MASK 0x3ff +#define PDO_PWR_MASK 0x3ff + +#define PDO_FIXED_DUAL_ROLE BIT(29) /* Power role swap supported */ +#define PDO_FIXED_SUSPEND BIT(28) /* USB Suspend supported (Source) */ +#define PDO_FIXED_HIGHER_CAP BIT(28) /* Requires more than vSafe5V (Sink) */ +#define PDO_FIXED_EXTPOWER BIT(27) /* Externally powered */ +#define PDO_FIXED_USB_COMM BIT(26) /* USB communications capable */ +#define PDO_FIXED_DATA_SWAP BIT(25) /* Data role swap supported */ +#define PDO_FIXED_VOLT_SHIFT 10 /* 50mV units */ +#define PDO_FIXED_CURR_SHIFT 0 /* 10mA units */ + +#define PDO_FIXED_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_FIXED_VOLT_SHIFT) +#define PDO_FIXED_CURR(ma) ((((ma) / 10) & PDO_CURR_MASK) << PDO_FIXED_CURR_SHIFT) + +#define PDO_FIXED(mv, ma, flags) \ + (PDO_TYPE(PDO_TYPE_FIXED) | (flags) | \ + PDO_FIXED_VOLT(mv) | PDO_FIXED_CURR(ma)) + +#define PDO_BATT_MAX_VOLT_SHIFT 20 /* 50mV units */ +#define PDO_BATT_MIN_VOLT_SHIFT 10 /* 50mV units */ +#define PDO_BATT_MAX_PWR_SHIFT 0 /* 250mW units */ + +#define PDO_BATT_MIN_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_BATT_MIN_VOLT_SHIFT) +#define PDO_BATT_MAX_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_BATT_MAX_VOLT_SHIFT) +#define PDO_BATT_MAX_POWER(mw) ((((mw) / 250) & PDO_PWR_MASK) << PDO_BATT_MAX_PWR_SHIFT) + +#define PDO_BATT(min_mv, max_mv, max_mw) \ + (PDO_TYPE(PDO_TYPE_BATT) | PDO_BATT_MIN_VOLT(min_mv) | \ + PDO_BATT_MAX_VOLT(max_mv) | PDO_BATT_MAX_POWER(max_mw)) + +#define PDO_VAR_MAX_VOLT_SHIFT 20 /* 50mV units */ +#define PDO_VAR_MIN_VOLT_SHIFT 10 /* 50mV units */ +#define PDO_VAR_MAX_CURR_SHIFT 0 /* 10mA units */ + +#define PDO_VAR_MIN_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_VAR_MIN_VOLT_SHIFT) +#define PDO_VAR_MAX_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_VAR_MAX_VOLT_SHIFT) +#define PDO_VAR_MAX_CURR(ma) ((((ma) / 10) & PDO_CURR_MASK) << PDO_VAR_MAX_CURR_SHIFT) + +#define PDO_VAR(min_mv, max_mv, max_ma) \ + (PDO_TYPE(PDO_TYPE_VAR) | PDO_VAR_MIN_VOLT(min_mv) | \ + PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma)) + +static inline enum pd_pdo_type pdo_type(uint32_t pdo) +{ + return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK; +} + +static inline unsigned int pdo_fixed_voltage(uint32_t pdo) +{ + return ((pdo >> PDO_FIXED_VOLT_SHIFT) & PDO_VOLT_MASK) * 50; +} + +static inline unsigned int pdo_min_voltage(uint32_t pdo) +{ + return ((pdo >> PDO_VAR_MIN_VOLT_SHIFT) & PDO_VOLT_MASK) * 50; +} + +static inline unsigned int pdo_max_voltage(uint32_t pdo) +{ + return ((pdo >> PDO_VAR_MAX_VOLT_SHIFT) & PDO_VOLT_MASK) * 50; +} + +static inline unsigned int pdo_max_current(uint32_t pdo) +{ + return ((pdo >> PDO_VAR_MAX_CURR_SHIFT) & PDO_CURR_MASK) * 10; +} + +static inline unsigned int pdo_max_power(uint32_t pdo) +{ + return ((pdo >> PDO_BATT_MAX_PWR_SHIFT) & PDO_PWR_MASK) * 250; +} + +/* RDO: Request Data Object */ +#define RDO_OBJ_POS_SHIFT 28 +#define RDO_OBJ_POS_MASK 0x7 +#define RDO_GIVE_BACK BIT(27) /* Supports reduced operating current */ +#define RDO_CAP_MISMATCH BIT(26) /* Not satisfied by source caps */ +#define RDO_USB_COMM BIT(25) /* USB communications capable */ +#define RDO_NO_SUSPEND BIT(24) /* USB Suspend not supported */ + +#define RDO_PWR_MASK 0x3ff +#define RDO_CURR_MASK 0x3ff + +#define RDO_FIXED_OP_CURR_SHIFT 10 +#define RDO_FIXED_MAX_CURR_SHIFT 0 + +#define RDO_OBJ(idx) (((idx) & RDO_OBJ_POS_MASK) << RDO_OBJ_POS_SHIFT) + +#define PDO_FIXED_OP_CURR(ma) ((((ma) / 10) & RDO_CURR_MASK) << RDO_FIXED_OP_CURR_SHIFT) +#define PDO_FIXED_MAX_CURR(ma) ((((ma) / 10) & RDO_CURR_MASK) << RDO_FIXED_MAX_CURR_SHIFT) + +#define RDO_FIXED(idx, op_ma, max_ma, flags) \ + (RDO_OBJ(idx) | (flags) | \ + PDO_FIXED_OP_CURR(op_ma) | PDO_FIXED_MAX_CURR(max_ma)) + +#define RDO_BATT_OP_PWR_SHIFT 10 /* 250mW units */ +#define RDO_BATT_MAX_PWR_SHIFT 0 /* 250mW units */ + +#define RDO_BATT_OP_PWR(mw) ((((mw) / 250) & RDO_PWR_MASK) << RDO_BATT_OP_PWR_SHIFT) +#define RDO_BATT_MAX_PWR(mw) ((((mw) / 250) & RDO_PWR_MASK) << RDO_BATT_MAX_PWR_SHIFT) + +#define RDO_BATT(idx, op_mw, max_mw, flags) \ + (RDO_OBJ(idx) | (flags) | \ + RDO_BATT_OP_PWR(op_mw) | RDO_BATT_MAX_PWR(max_mw)) + +static inline unsigned int rdo_index(u32 rdo) +{ + return (rdo >> RDO_OBJ_POS_SHIFT) & RDO_OBJ_POS_MASK; +} + +static inline unsigned int rdo_op_current(u32 rdo) +{ + return ((rdo >> RDO_FIXED_OP_CURR_SHIFT) & RDO_CURR_MASK) * 10; +} + +static inline unsigned int rdo_max_current(u32 rdo) +{ + return ((rdo >> RDO_FIXED_MAX_CURR_SHIFT) & + RDO_CURR_MASK) * 10; +} + +static inline unsigned int rdo_op_power(u32 rdo) +{ + return ((rdo >> RDO_BATT_OP_PWR_SHIFT) & RDO_PWR_MASK) * 250; +} + +static inline unsigned int rdo_max_power(u32 rdo) +{ + return ((rdo >> RDO_BATT_MAX_PWR_SHIFT) & RDO_PWR_MASK) * 250; +} + +#define TCPC_LOG_BUFFER_SIZE 1024 + +struct tcpc_port; + +typedef void (*ss_mux_sel)(enum typec_cc_polarity pol); +typedef int (*ext_pd_switch_setup)(struct tcpc_port *port_p); + +enum tcpc_port_type { + TYPEC_PORT_DFP, + TYPEC_PORT_UFP, + TYPEC_PORT_DRP, +}; + +struct tcpc_port_config { + uint8_t i2c_bus; + uint8_t addr; + enum tcpc_port_type port_type; + uint32_t max_snk_mv; + uint32_t max_snk_ma; + uint32_t max_snk_mw; + uint32_t op_snk_mv; + bool disable_pd; + ext_pd_switch_setup switch_setup_func; +}; + +struct tcpc_port { + struct tcpc_port_config cfg; + struct udevice *i2c_dev; + ss_mux_sel ss_sel_func; + enum pd_sink_state pd_state; + uint32_t tx_msg_id; + uint32_t log_size; + char logbuffer[TCPC_LOG_BUFFER_SIZE]; + char *log_p; + char *log_print; +}; + +int tcpc_set_cc_to_source(struct tcpc_port *port); +int tcpc_set_cc_to_sink(struct tcpc_port *port); +int tcpc_set_plug_orientation(struct tcpc_port *port, enum typec_cc_polarity polarity); +int tcpc_get_cc_status(struct tcpc_port *port, enum typec_cc_polarity *polarity, enum typec_cc_state *state); +int tcpc_clear_alert(struct tcpc_port *port, uint16_t clear_mask); +int tcpc_send_command(struct tcpc_port *port, uint8_t command); +int tcpc_polling_reg(struct tcpc_port *port, uint8_t reg, + uint8_t reg_width, uint16_t mask, uint16_t value, ulong timeout_ms); +int tcpc_setup_dfp_mode(struct tcpc_port *port); +int tcpc_setup_ufp_mode(struct tcpc_port *port); +int tcpc_disable_src_vbus(struct tcpc_port *port); +int tcpc_init(struct tcpc_port *port, struct tcpc_port_config config, ss_mux_sel ss_sel_func); +bool tcpc_pd_sink_check_charging(struct tcpc_port *port); +void tcpc_print_log(struct tcpc_port *port); + +#ifdef CONFIG_SPL_BUILD +int tcpc_setup_ufp_mode(struct tcpc_port *port) +{ + return 0; +} +int tcpc_setup_dfp_mode(struct tcpc_port *port) +{ + return 0; +} + +int tcpc_disable_src_vbus(struct tcpc_port *port) +{ + return 0; +} +#endif +#endif /* __TCPCI_H */