summaryrefslogtreecommitdiff
path: root/driver/tcpm/ps8xxx.c
diff options
context:
space:
mode:
Diffstat (limited to 'driver/tcpm/ps8xxx.c')
-rw-r--r--driver/tcpm/ps8xxx.c320
1 files changed, 236 insertions, 84 deletions
diff --git a/driver/tcpm/ps8xxx.c b/driver/tcpm/ps8xxx.c
index e7e561e4ae..c6e1cc671b 100644
--- a/driver/tcpm/ps8xxx.c
+++ b/driver/tcpm/ps8xxx.c
@@ -15,6 +15,7 @@
*/
#include "common.h"
+#include "console.h"
#include "ps8xxx.h"
#include "tcpci.h"
#include "tcpm.h"
@@ -38,17 +39,200 @@
#endif
+#ifdef CONFIG_USB_PD_TCPM_PS8751
+/* PS8751 cannot run with PD 3.0 (see b/148554997 for details) */
+#if defined(CONFIG_USB_PD_REV30)
+#error "PS8751 cannot run with PD 3.0. Fall back to using PD 2.0"
+#endif
+
+#endif /* CONFIG_USB_PD_TCPM_PS8751 */
+
+#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
+#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
+
+/*
+ * The product_id per ports here is expected to be set in callback function -
+ * .init of tcpm_drv by calling board_get_ps8xxx_product_id().
+ *
+ * In case of CONFIG_USB_PD_TCPM_MULTI_PS8XXX enabled, board code should
+ * override the board_get_ps8xxx_product_id() for getting the correct id.
+ */
+static uint16_t product_id[CONFIG_USB_PD_PORT_COUNT];
+
/*
* timestamp of the next possible toggle to ensure the 2-ms spacing
* between IRQ_HPD.
*/
static uint64_t hpd_deadline[CONFIG_USB_PD_PORT_COUNT];
+#if defined(CONFIG_USB_PD_TCPM_PS8705) || \
+ defined(CONFIG_USB_PD_TCPM_PS8751) || \
+ defined(CONFIG_USB_PD_TCPM_PS8755) || \
+ defined(CONFIG_USB_PD_TCPM_PS8805)
+/*
+ * DCI is enabled by default and burns about 40 mW when the port is in
+ * USB2 mode or when a C-to-A dongle is attached, so force it off.
+ */
+
+static int ps8xxx_addr_dci_disable(int port, int i2c_addr, int i2c_reg)
+{
+ int status;
+ int dci;
+
+ status = tcpc_addr_read(port, i2c_addr, i2c_reg, &dci);
+ if (status != EC_SUCCESS)
+ return status;
+ if ((dci & PS8XXX_REG_MUX_USB_DCI_CFG_MODE_MASK) !=
+ PS8XXX_REG_MUX_USB_DCI_CFG_MODE_OFF) {
+ dci &= ~PS8XXX_REG_MUX_USB_DCI_CFG_MODE_MASK;
+ dci |= PS8XXX_REG_MUX_USB_DCI_CFG_MODE_OFF;
+ if (tcpc_addr_write(port, i2c_addr, i2c_reg, dci) != EC_SUCCESS)
+ return status;
+ }
+ return EC_SUCCESS;
+}
+#endif /* CONFIG_USB_PD_TCPM_PS875[15] || CONFIG_USB_PD_TCPM_PS8[78]05 */
+
+#if defined(CONFIG_USB_PD_TCPM_PS8705) || \
+ defined(CONFIG_USB_PD_TCPM_PS8755) || \
+ defined(CONFIG_USB_PD_TCPM_PS8805)
+static int ps8705_dci_disable(int port)
+{
+ int p1_addr;
+ int p3_addr;
+ int regval;
+ int rv;
+
+ /* Enable access to debug pages. */
+ p3_addr = tcpc_config[port].i2c_info.addr_flags;
+ rv = tcpc_addr_read(port, p3_addr, PS8XXX_REG_I2C_DEBUGGING_ENABLE,
+ &regval);
+ if (rv)
+ return rv;
+
+ rv = tcpc_addr_write(port, p3_addr, PS8XXX_REG_I2C_DEBUGGING_ENABLE,
+ PS8XXX_REG_I2C_DEBUGGING_ENABLE_ON);
+
+ /* Disable Auto DCI */
+ p1_addr = PS8751_P3_TO_P1_FLAGS(p3_addr);
+ rv = ps8xxx_addr_dci_disable(port, p1_addr,
+ PS8XXX_P1_REG_MUX_USB_DCI_CFG);
+
+ /*
+ * PS8705/PS8755/PS8805 will automatically re-assert bit:0 on the
+ * PS8XXX_REG_I2C_DEBUGGING_ENABLE register.
+ */
+ return rv;
+}
+#endif /* CONFIG_USB_PD_TCPM_PS8755 || CONFIG_USB_PD_TCPM_PS8[78]05 */
+
+#ifdef CONFIG_USB_PD_TCPM_PS8751
+static int ps8751_dci_disable(int port)
+{
+ int p3_addr;
+
+ p3_addr = tcpc_config[port].i2c_info.addr_flags;
+ return ps8xxx_addr_dci_disable(port, p3_addr,
+ PS8751_REG_MUX_USB_DCI_CFG);
+}
+#endif /* CONFIG_USB_PD_TCPM_PS8751 */
+
+#ifdef CONFIG_USB_PD_TCPM_PS8815
+static int ps8815_dci_disable(int port)
+{
+ /* DCI is disabled on the ps8815 */
+ return EC_SUCCESS;
+}
+#endif /* CONFIG_USB_PD_TCPM_PS8815 */
+
+enum ps8xxx_variant_regs {
+ REG_FIRST_INDEX = 0,
+ /* NOTE: The rev will read as 0x00 if the FW has malfunctioned. */
+ REG_FW_VER = REG_FIRST_INDEX,
+ REG_MAX_COUNT,
+};
+
+struct ps8xxx_variant_map {
+ int product_id;
+ int (*dci_disable_ptr)(int port);
+ int reg_map[REG_MAX_COUNT];
+};
+
+/*
+ * variant_map here is leveraged to lookup from ps8xxx_variant_regs to i2c
+ * register and corresponding dci_disable function based on product_id.
+ */
+static struct ps8xxx_variant_map variant_map[] = {
+#ifdef CONFIG_USB_PD_TCPM_PS8705
+ {
+ PS8705_PRODUCT_ID,
+ ps8705_dci_disable,
+ {
+ [REG_FW_VER] = 0x82,
+ }
+ },
+#endif
+#ifdef CONFIG_USB_PD_TCPM_PS8751
+ {
+ PS8751_PRODUCT_ID,
+ ps8751_dci_disable,
+ {
+ [REG_FW_VER] = 0x90,
+ }
+ },
+#endif
+#ifdef CONFIG_USB_PD_TCPM_PS8755
+ {
+ PS8755_PRODUCT_ID,
+ ps8705_dci_disable,
+ {
+ [REG_FW_VER] = 0x82,
+ }
+ },
+#endif
+#ifdef CONFIG_USB_PD_TCPM_PS8805
+ {
+ PS8805_PRODUCT_ID,
+ ps8705_dci_disable,
+ {
+ [REG_FW_VER] = 0x82,
+ }
+ },
+#endif
+#ifdef CONFIG_USB_PD_TCPM_PS8815
+ {
+ PS8815_PRODUCT_ID,
+ ps8815_dci_disable,
+ {
+ [REG_FW_VER] = 0x82,
+ }
+ },
+#endif
+};
+
+static int get_reg_by_product(const int port,
+ const enum ps8xxx_variant_regs reg)
+{
+ int i;
+
+ if (reg < REG_FIRST_INDEX || reg >= REG_MAX_COUNT)
+ return INT32_MAX;
+
+ for (i = 0; i < ARRAY_SIZE(variant_map); i++) {
+ if (product_id[port] ==
+ variant_map[i].product_id) {
+ return variant_map[i].reg_map[reg];
+ }
+ }
+
+ CPRINTS("%s: failed to get register number by product_id.", __func__);
+ return INT32_MAX;
+}
+
static int dp_set_hpd(int port, int enable)
{
int reg;
int rv;
-
rv = mux_read(port, MUX_IN_HPD_ASSERTION_REG, &reg);
if (rv)
return rv;
@@ -58,13 +242,10 @@ static int dp_set_hpd(int port, int enable)
reg &= ~IN_HPD;
return mux_write(port, MUX_IN_HPD_ASSERTION_REG, reg);
}
-
static int dp_set_irq(int port, int enable)
{
-
int reg;
int rv;
-
rv = mux_read(port, MUX_IN_HPD_ASSERTION_REG, &reg);
if (rv)
return rv;
@@ -75,6 +256,33 @@ static int dp_set_irq(int port, int enable)
return mux_write(port, MUX_IN_HPD_ASSERTION_REG, reg);
}
+__overridable
+uint16_t board_get_ps8xxx_product_id(int port)
+{
+ /* Board supporting multiple chip sources in ps8xxx.c MUST override this
+ * function to judge the real chip source for this board. For example,
+ * SKU ID / strappings / provisioning in the factory can be the ways.
+ */
+
+ if (IS_ENABLED(CONFIG_USB_PD_TCPM_MULTI_PS8XXX)) {
+ CPRINTS("%s: board should override this function.", __func__);
+ return 0;
+ } else if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8705)) {
+ return PS8705_PRODUCT_ID;
+ } else if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8751)) {
+ return PS8751_PRODUCT_ID;
+ } else if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8755)) {
+ return PS8755_PRODUCT_ID;
+ } else if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8805)) {
+ return PS8805_PRODUCT_ID;
+ } else if (IS_ENABLED(CONFIG_USB_PD_TCPM_PS8815)) {
+ return PS8815_PRODUCT_ID;
+ }
+
+ CPRINTS("%s: Any new product id is not defined here?", __func__);
+ return 0;
+}
+
void ps8xxx_tcpc_update_hpd_status(int port, int hpd_lvl, int hpd_irq)
{
dp_set_hpd(port, hpd_lvl);
@@ -127,8 +335,9 @@ static int ps8xxx_tcpm_release(int port)
{
int version;
int status;
+ int reg = get_reg_by_product(port, REG_FW_VER);
- status = tcpc_read(port, FW_VER_REG, &version);
+ status = tcpc_read(port, reg, &version);
if (status != 0) {
/* wait for chip to wake up */
msleep(10);
@@ -141,26 +350,35 @@ static int ps8xxx_get_chip_info(int port, int live,
struct ec_response_pd_chip_info_v1 **chip_info)
{
int val;
+ int reg;
int rv = tcpci_get_chip_info(port, live, chip_info);
if (rv)
return rv;
if (!live) {
+ product_id[port] = board_get_ps8xxx_product_id(port);
(*chip_info)->vendor_id = PS8XXX_VENDOR_ID;
- (*chip_info)->product_id = PS8XXX_PRODUCT_ID;
+ (*chip_info)->product_id = product_id[port];
}
if ((*chip_info)->fw_version_number == 0 ||
(*chip_info)->fw_version_number == -1 || live) {
- rv = tcpc_read(port, FW_VER_REG, &val);
-
- if (rv)
+ reg = get_reg_by_product(port, REG_FW_VER);
+ rv = tcpc_read(port, reg, &val);
+ if (rv != EC_SUCCESS)
return rv;
(*chip_info)->fw_version_number = val;
}
+ /* Treat unexpected values as error (FW not initiated from reset) */
+ if (live && (
+ (*chip_info)->vendor_id != PS8XXX_VENDOR_ID ||
+ (*chip_info)->product_id != board_get_ps8xxx_product_id(port) ||
+ (*chip_info)->fw_version_number == 0))
+ return EC_ERROR_UNKNOWN;
+
#if defined(CONFIG_USB_PD_TCPM_PS8751) && \
defined(CONFIG_USB_PD_VBUS_DETECT_TCPC)
/*
@@ -173,91 +391,25 @@ static int ps8xxx_get_chip_info(int port, int live,
return rv;
}
-
-#if defined(CONFIG_USB_PD_TCPM_PS8705) || \
- defined(CONFIG_USB_PD_TCPM_PS8751) || \
- defined(CONFIG_USB_PD_TCPM_PS8755) || \
- defined(CONFIG_USB_PD_TCPM_PS8805)
-/*
- * DCI is enabled by default and burns about 40 mW when the port is in
- * USB2 mode or when a C-to-A dongle is attached, so force it off.
- */
-
-static int ps8xxx_addr_dci_disable(int port, int i2c_addr, int i2c_reg)
-{
- int status;
- int dci;
-
- status = tcpc_addr_read(port, i2c_addr, i2c_reg, &dci);
- if (status != EC_SUCCESS)
- return status;
- if ((dci & PS8XXX_REG_MUX_USB_DCI_CFG_MODE_MASK) !=
- PS8XXX_REG_MUX_USB_DCI_CFG_MODE_OFF) {
- dci &= ~PS8XXX_REG_MUX_USB_DCI_CFG_MODE_MASK;
- dci |= PS8XXX_REG_MUX_USB_DCI_CFG_MODE_OFF;
- if (tcpc_addr_write(port, i2c_addr, i2c_reg, dci) != EC_SUCCESS)
- return status;
- }
- return EC_SUCCESS;
-}
-#endif /* CONFIG_USB_PD_TCPM_PS875[15] || CONFIG_USB_PD_TCPM_PS8[78]05 */
-
-#if defined(CONFIG_USB_PD_TCPM_PS8705) || \
- defined(CONFIG_USB_PD_TCPM_PS8755) || \
- defined(CONFIG_USB_PD_TCPM_PS8805)
-static int ps8xxx_dci_disable(int port)
-{
- int p1_addr;
- int p3_addr;
- int regval;
- int rv;
-
- /* Enable access to debug pages. */
- p3_addr = tcpc_config[port].i2c_info.addr_flags;
- rv = tcpc_addr_read(port, p3_addr, PS8XXX_REG_I2C_DEBUGGING_ENABLE,
- &regval);
- if (rv)
- return rv;
-
- rv = tcpc_addr_write(port, p3_addr, PS8XXX_REG_I2C_DEBUGGING_ENABLE,
- PS8XXX_REG_I2C_DEBUGGING_ENABLE_ON);
-
- /* Disable Auto DCI */
- p1_addr = PS8751_P3_TO_P1_FLAGS(p3_addr);
- rv = ps8xxx_addr_dci_disable(port, p1_addr,
- PS8XXX_P1_REG_MUX_USB_DCI_CFG);
-
- /*
- * PS8705/PS8755/PS8805 will automatically re-assert bit:0 on the
- * PS8XXX_REG_I2C_DEBUGGING_ENABLE register.
- */
- return rv;
-}
-#endif /* CONFIG_USB_PD_TCPM_PS8755 || CONFIG_USB_PD_TCPM_PS8[78]05 */
-
-#ifdef CONFIG_USB_PD_TCPM_PS8751
static int ps8xxx_dci_disable(int port)
{
- int p3_addr;
+ int i;
- p3_addr = tcpc_config[port].i2c_info.addr_flags;
- return ps8xxx_addr_dci_disable(port, p3_addr,
- PS8751_REG_MUX_USB_DCI_CFG);
-}
-#endif /* CONFIG_USB_PD_TCPM_PS8751 */
+ for (i = 0; i < ARRAY_SIZE(variant_map); i++) {
+ if (product_id[port] == variant_map[i].product_id)
+ return variant_map[i].dci_disable_ptr(port);
+ }
-#ifdef CONFIG_USB_PD_TCPM_PS8815
-static int ps8xxx_dci_disable(int port)
-{
- /* DCI is disabled on the ps8815 */
- return EC_SUCCESS;
+ CPRINTS("%s: failed to get dci_disable function pointers.", __func__);
+ return EC_ERROR_INVAL;
}
-#endif /* CONFIG_USB_PD_TCPM_PS8815 */
static int ps8xxx_tcpm_init(int port)
{
int status;
+ product_id[port] = board_get_ps8xxx_product_id(port);
+
status = tcpci_tcpm_init(port);
if (status != EC_SUCCESS)
return status;