/* Copyright 2017 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. */ /* TI SN5S330 USB-C Power Path Controller */ /* * PP1 : Sourcing power path. * PP2 : Sinking power path. */ #include "common.h" #include "console.h" #include "sn5s330.h" #include "hooks.h" #include "i2c.h" #include "system.h" #include "timer.h" #include "usb_charge.h" #include "usb_pd_tcpm.h" #include "usb_pd.h" #include "usbc_ppc.h" #include "util.h" #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) #define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) static uint32_t irq_pending; /* Bitmask of ports signaling an interrupt. */ static int source_enabled[CONFIG_USB_PD_PORT_MAX_COUNT]; static int read_reg(uint8_t port, int reg, int *regval) { return i2c_read8(ppc_chips[port].i2c_port, ppc_chips[port].i2c_addr_flags, reg, regval); } static int write_reg(uint8_t port, int reg, int regval) { return i2c_write8(ppc_chips[port].i2c_port, ppc_chips[port].i2c_addr_flags, reg, regval); } static int set_flags(const int port, const int addr, const int flags_to_set) { int val, rv; rv = read_reg(port, addr, &val); if (rv) return rv; val |= flags_to_set; return write_reg(port, addr, val); } static int clr_flags(const int port, const int addr, const int flags_to_clear) { int val, rv; rv = read_reg(port, addr, &val); if (rv) return rv; val &= ~flags_to_clear; return write_reg(port, addr, val); } #ifdef CONFIG_CMD_PPC_DUMP static int sn5s330_dump(int port) { int i; int data; const int i2c_port = ppc_chips[port].i2c_port; const uint16_t i2c_addr_flags = ppc_chips[port].i2c_addr_flags; /* Flush after every set otherwise console buffer may get full. */ for (i = SN5S330_FUNC_SET1; i <= SN5S330_FUNC_SET12; i++) { i2c_read8(i2c_port, i2c_addr_flags, i, &data); ccprintf("FUNC_SET%d [%02Xh] = 0x%02x\n", i - SN5S330_FUNC_SET1 + 1, i, data); } cflush(); for (i = SN5S330_INT_STATUS_REG1; i <= SN5S330_INT_STATUS_REG4; i++) { i2c_read8(i2c_port, i2c_addr_flags, i, &data); ccprintf("INT_STATUS_REG%d [%02Xh] = 0x%02x\n", i - SN5S330_INT_STATUS_REG1 + 1, i, data); } cflush(); for (i = SN5S330_INT_TRIP_RISE_REG1; i <= SN5S330_INT_TRIP_RISE_REG3; i++) { i2c_read8(i2c_port, i2c_addr_flags, i, &data); ccprintf("INT_TRIP_RISE_REG%d [%02Xh] = 0x%02x\n", i - SN5S330_INT_TRIP_RISE_REG1 + 1, i, data); } cflush(); for (i = SN5S330_INT_TRIP_FALL_REG1; i <= SN5S330_INT_TRIP_FALL_REG3; i++) { i2c_read8(i2c_port, i2c_addr_flags, i, &data); ccprintf("INT_TRIP_FALL_REG%d [%02Xh] = 0x%02x\n", i - SN5S330_INT_TRIP_FALL_REG1 + 1, i, data); } cflush(); for (i = SN5S330_INT_MASK_RISE_REG1; i <= SN5S330_INT_MASK_RISE_REG3; i++) { i2c_read8(i2c_port, i2c_addr_flags, i, &data); ccprintf("INT_MASK_RISE_REG%d [%02Xh] = 0x%02x\n", i - SN5S330_INT_MASK_RISE_REG1 + 1, i, data); } cflush(); for (i = SN5S330_INT_MASK_FALL_REG1; i <= SN5S330_INT_MASK_FALL_REG3; i++) { i2c_read8(i2c_port, i2c_addr_flags, i, &data); ccprintf("INT_MASK_FALL_REG%d [%02Xh] = 0x%02x\n", i - SN5S330_INT_MASK_FALL_REG1 + 1, i, data); } cflush(); return EC_SUCCESS; } #endif /* defined(CONFIG_CMD_PPC_DUMP) */ static int sn5s330_pp_fet_enable(uint8_t port, enum sn5s330_pp_idx pp, int enable) { int status; int pp_bit; if (pp == SN5S330_PP1) pp_bit = SN5S330_PP1_EN; else if (pp == SN5S330_PP2) pp_bit = SN5S330_PP2_EN; else return EC_ERROR_INVAL; status = enable ? set_flags(port, SN5S330_FUNC_SET3, pp_bit) : clr_flags(port, SN5S330_FUNC_SET3, pp_bit); if (status) { ppc_prints("Failed to set FUNC_SET3!", port); return status; } if (pp == SN5S330_PP1) source_enabled[port] = enable; return EC_SUCCESS; } static int sn5s330_init(int port) { int regval; int status; int retries; int reg; const int i2c_port = ppc_chips[port].i2c_port; const uint16_t i2c_addr_flags = ppc_chips[port].i2c_addr_flags; #ifdef CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT /* Set the sourcing current limit value. */ switch (CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) { case TYPEC_RP_3A0: /* Set current limit to ~3A. */ regval = SN5S330_ILIM_3_06; break; case TYPEC_RP_1A5: default: /* Set current limit to ~1.5A. */ regval = SN5S330_ILIM_1_62; break; } #else /* !defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) */ /* Default SRC current limit to ~1.5A. */ regval = SN5S330_ILIM_1_62; #endif /* defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) */ /* * It seems that sometimes setting the FUNC_SET1 register fails * initially. Therefore, we'll retry a couple of times. */ retries = 0; do { status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_FUNC_SET1, regval); if (status) { ppc_prints("Failed to set FUNC_SET1! Retrying..", port); retries++; msleep(1); } else { break; } } while (retries < 10); /* Set Vbus OVP threshold to ~22.325V. */ regval = 0x37; status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_FUNC_SET5, regval); if (status) { ppc_prints("Failed to set FUNC_SET5!", port); return status; } /* Set Vbus UVP threshold to ~2.75V. */ status = i2c_read8(i2c_port, i2c_addr_flags, SN5S330_FUNC_SET6, ®val); if (status) { ppc_prints("Failed to read FUNC_SET6!", port); return status; } regval &= ~0x3F; regval |= 1; status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_FUNC_SET6, regval); if (status) { ppc_prints("Failed to write FUNC_SET6!", port); return status; } /* Enable SBU Fets and set PP2 current limit to ~3A. */ regval = SN5S330_SBU_EN | 0x8; status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_FUNC_SET2, regval); if (status) { ppc_prints("Failed to set FUNC_SET2!", port); return status; } /* * Indicate we are using PP2 configuration 2 and enable OVP comparator * for CC lines. * * Also, turn off under-voltage protection for incoming Vbus as it would * prevent us from enabling SNK path before we hibernate the ec. We * need to enable the SNK path so USB power will assert ACOK and wake * the EC up went inserting USB power. We always turn off under-voltage * protection because the battery charger will boost the voltage up * to the needed battery voltage either way (and it will have its own * low voltage protection). */ regval = SN5S330_OVP_EN_CC | SN5S330_PP2_CONFIG | SN5S330_CONFIG_UVP; status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_FUNC_SET9, regval); if (status) { ppc_prints("Failed to set FUNC_SET9!", port); return status; } /* * Set analog current limit delay to 200 us for PP1, * set 1000 us for PP2 for compatibility. */ regval = (PPX_ILIM_DEGLITCH_0_US_200 << 3) | PPX_ILIM_DEGLITCH_0_US_1000; status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_FUNC_SET11, regval); if (status) { ppc_prints("Failed to set FUNC_SET11", port); return status; } #ifdef CONFIG_USBC_PPC_VCONN /* * Set the deglitch timeout on the Vconn current limit to 640us. This * improves compatibility with some USB C -> HDMI devices versus the * reset default (20 us). */ regval = 0; status = i2c_read8(i2c_port, i2c_addr_flags, SN5S330_FUNC_SET8, ®val); if (status) { ppc_prints("Failed to read FUNC_SET8!", port); return status; } regval &= ~SN5S330_VCONN_DEGLITCH_MASK; regval |= SN5S330_VCONN_DEGLITCH_640_US; status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_FUNC_SET8, regval); if (status) { ppc_prints("Failed to set FUNC_SET8!", port); return status; } #endif /* CONFIG_USBC_PPC_VCONN */ /* * Turn off dead battery resistors, turn on CC FETs, and set the higher * of the two VCONN current limits (min 0.6A). Many VCONN accessories * trip the default current limit of min 0.35A. */ status = set_flags(port, SN5S330_FUNC_SET4, SN5S330_CC_EN | SN5S330_VCONN_ILIM_SEL); if (status) { ppc_prints("Failed to set FUNC_SET4!", port); return status; } /* Set ideal diode mode for both PP1 and PP2. */ status = set_flags(port, SN5S330_FUNC_SET3, SN5S330_SET_RCP_MODE_PP1 | SN5S330_SET_RCP_MODE_PP2); if (status) { ppc_prints("Failed to set FUNC_SET3!", port); return status; } /* Turn off PP1 FET. */ status = sn5s330_pp_fet_enable(port, SN5S330_PP1, 0); if (status) { ppc_prints("Failed to turn off PP1 FET!", port); } /* * Don't proceed with the rest of initialization if we're sysjumping. * We would have already done this before. */ if (system_jumped_late()) return EC_SUCCESS; /* * Clear the digital reset bit, and mask off and clear vSafe0V * interrupts. Leave the dead battery mode bit unchanged since it * is checked below. */ regval = SN5S330_DIG_RES | SN5S330_VSAFE0V_MASK; status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_INT_STATUS_REG4, regval); if (status) { ppc_prints("Failed to write INT_STATUS_REG4!", port); return status; } /* * Before turning on the PP2 FET, mask off all unwanted interrupts and * then clear all pending interrupts. * * TODO(aaboagye): Unmask fast-role swap events once fast-role swap is * implemented in the PD stack. */ /* Enable PP1 overcurrent interrupts. */ regval = ~SN5S330_ILIM_PP1_MASK; status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_INT_MASK_RISE_REG1, regval); if (status) { ppc_prints("Failed to write INT_MASK_RISE1!", port); return status; } status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_INT_MASK_FALL_REG1, 0xFF); if (status) { ppc_prints("Failed to write INT_MASK_FALL1!", port); return status; } /* Enable VCONN overcurrent and CC1/CC2 overvoltage interrupts. */ regval = ~(SN5S330_VCONN_ILIM | SN5S330_CC1_CON | SN5S330_CC2_CON); status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_INT_MASK_RISE_REG2, regval); if (status) { ppc_prints("Failed to write INT_MASK_RISE2!", port); return status; } status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_INT_MASK_FALL_REG2, 0xFF); if (status) { ppc_prints("Failed to write INT_MASK_FALL2!", port); return status; } #if defined(CONFIG_USB_PD_VBUS_DETECT_PPC) && defined(CONFIG_USB_CHARGER) /* If PPC is being used to detect VBUS, enable VBUS interrupts. */ regval = ~SN5S330_VBUS_GOOD_MASK; #else regval = 0xFF; #endif /* CONFIG_USB_PD_VBUS_DETECT_PPC && CONFIG_USB_CHARGER */ status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_INT_MASK_RISE_REG3, regval); if (status) { ppc_prints("Failed to write INT_MASK_RISE3!", port); return status; } status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_INT_MASK_FALL_REG3, regval); if (status) { ppc_prints("Failed to write INT_MASK_FALL3!", port); return status; } /* Now clear any pending interrupts. */ for (reg = SN5S330_INT_TRIP_RISE_REG1; reg <= SN5S330_INT_TRIP_FALL_REG3; reg++) { status = i2c_write8(i2c_port, i2c_addr_flags, reg, 0xFF); if (status) { CPRINTS("ppc p%d: Failed to write reg 0x%2x!", port, reg); return status; } } /* * For PP2, check to see if we booted in dead battery mode. If we * booted in dead battery mode, the PP2 FET will already be enabled. */ status = i2c_read8(i2c_port, i2c_addr_flags, SN5S330_INT_STATUS_REG4, ®val); if (status) { ppc_prints("Failed to read INT_STATUS_REG4!", port); return status; } if (regval & SN5S330_DB_BOOT) { /* * Clear the bit by writing 1 and keep vSafe0V_MASK * unchanged. */ i2c_write8(i2c_port, i2c_addr_flags, SN5S330_INT_STATUS_REG4, regval); /* Turn on PP2 FET. */ status = sn5s330_pp_fet_enable(port, SN5S330_PP2, 1); if (status) { ppc_prints("Failed to turn on PP2 FET!", port); return status; } } return EC_SUCCESS; } #ifdef CONFIG_USB_PD_VBUS_DETECT_PPC static int sn5s330_is_vbus_present(int port) { int regval; int rv; rv = read_reg(port, SN5S330_INT_STATUS_REG3, ®val); if (rv) { ppc_err_prints("VBUS present error", port, rv); return 0; } return !!(regval & SN5S330_VBUS_GOOD); } #endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */ static int sn5s330_is_sourcing_vbus(int port) { return source_enabled[port]; } #ifdef CONFIG_USBC_PPC_POLARITY static int sn5s330_set_polarity(int port, int polarity) { if (polarity) /* CC2 active. */ return set_flags(port, SN5S330_FUNC_SET4, SN5S330_CC_POLARITY); else /* CC1 active. */ return clr_flags(port, SN5S330_FUNC_SET4, SN5S330_CC_POLARITY); } #endif static int sn5s330_set_vbus_source_current_limit(int port, enum tcpc_rp_value rp) { int regval; int status; status = read_reg(port, SN5S330_FUNC_SET1, ®val); if (status) return status; /* * Note that we chose the lowest current limit setting that is just * above indicated Rp value. This is because these are minimum values * and we must be able to provide the current that we advertise. */ regval &= ~0x1F; /* The current limit settings are 4:0. */ switch (rp) { case TYPEC_RP_3A0: regval |= SN5S330_ILIM_3_06; break; case TYPEC_RP_1A5: regval |= SN5S330_ILIM_1_62; break; case TYPEC_RP_USB: default: regval |= SN5S330_ILIM_0_63; break; }; status = write_reg(port, SN5S330_FUNC_SET1, regval); return status; } static int sn5s330_discharge_vbus(int port, int enable) { int status = enable ? set_flags(port, SN5S330_FUNC_SET3, SN5S330_VBUS_DISCH_EN) : clr_flags(port, SN5S330_FUNC_SET3, SN5S330_VBUS_DISCH_EN); if (status) { CPRINTS("ppc p%d: Failed to %s vbus discharge", port, enable ? "enable" : "disable"); return status; } return EC_SUCCESS; } static int sn5s330_enter_low_power_mode(int port) { int rv; /* Turn off both SRC and SNK FETs */ rv = clr_flags(port, SN5S330_FUNC_SET3, SN5S330_PP1_EN | SN5S330_PP2_EN); if (rv) { ppc_err_prints("Could not disable both FETS", port, rv); return rv; } /* Turn off Vconn power */ rv = clr_flags(port, SN5S330_FUNC_SET4, SN5S330_VCONN_EN); if (rv) { ppc_err_prints("Could not disable Vconn", port, rv); return rv; } /* Turn off SBU path */ rv = clr_flags(port, SN5S330_FUNC_SET2, SN5S330_SBU_EN); if (rv) { ppc_err_prints("Could not disable SBU path", port, rv); return rv; } /* * Turn off the Over Voltage Protection circuits. Needs to happen after * FETs are disabled, otherwise OVP can automatically turned back on. * Since FETs are off, any over voltage does not make it to the board * side of the PPC. */ rv = clr_flags(port, SN5S330_FUNC_SET9, SN5S330_FORCE_OVP_EN_SBU | SN5S330_FORCE_ON_VBUS_OVP | SN5S330_FORCE_ON_VBUS_UVP); if (rv) { ppc_err_prints("Could not disable OVP circuit", port, rv); return rv; } return EC_SUCCESS; } #ifdef CONFIG_USBC_PPC_VCONN static int sn5s330_set_vconn(int port, int enable) { int regval; int status; status = read_reg(port, SN5S330_FUNC_SET4, ®val); if (status) return status; if (enable) regval |= SN5S330_VCONN_EN; else regval &= ~SN5S330_VCONN_EN; return write_reg(port, SN5S330_FUNC_SET4, regval); } #endif static int sn5s330_vbus_sink_enable(int port, int enable) { return sn5s330_pp_fet_enable(port, SN5S330_PP2, !!enable); } static int sn5s330_vbus_source_enable(int port, int enable) { return sn5s330_pp_fet_enable(port, SN5S330_PP1, !!enable); } #ifdef CONFIG_USBC_PPC_SBU static int sn5s330_set_sbu(int port, int enable) { int rv; if (enable) rv = set_flags(port, SN5S330_FUNC_SET2, SN5S330_SBU_EN); else rv = clr_flags(port, SN5S330_FUNC_SET2, SN5S330_SBU_EN); return rv; } #endif /* CONFIG_USBC_PPC_SBU */ static void sn5s330_handle_interrupt(int port) { int attempt = 0; /* * SN5S330's /INT pin is level, so process interrupts until it * deasserts if the chip has a dedicated interrupt pin. */ #ifdef CONFIG_USBC_PPC_DEDICATED_INT while (ppc_get_alert_status(port)) #endif { int rise = 0; int fall = 0; attempt++; if (attempt > 1) ppc_prints("Could not clear interrupts on first " "try, retrying", port); read_reg(port, SN5S330_INT_TRIP_RISE_REG1, &rise); read_reg(port, SN5S330_INT_TRIP_FALL_REG1, &fall); /* Notify the system about the overcurrent event. */ if (rise & SN5S330_ILIM_PP1_MASK) pd_handle_overcurrent(port); /* Clear the interrupt sources. */ write_reg(port, SN5S330_INT_TRIP_RISE_REG1, rise); write_reg(port, SN5S330_INT_TRIP_FALL_REG1, fall); read_reg(port, SN5S330_INT_TRIP_RISE_REG2, &rise); read_reg(port, SN5S330_INT_TRIP_FALL_REG2, &fall); /* * VCONN may be latched off due to an overcurrent. Indicate * when the VCONN overcurrent happens. */ if (rise & SN5S330_VCONN_ILIM) ppc_prints("VCONN OC!", port); /* Notify the system about the CC overvoltage event. */ if (rise & SN5S330_CC1_CON || rise & SN5S330_CC2_CON) { ppc_prints("CC OV!", port); pd_handle_cc_overvoltage(port); } /* Clear the interrupt sources. */ write_reg(port, SN5S330_INT_TRIP_RISE_REG2, rise); write_reg(port, SN5S330_INT_TRIP_FALL_REG2, fall); #if defined(CONFIG_USB_PD_VBUS_DETECT_PPC) && defined(CONFIG_USB_CHARGER) read_reg(port, SN5S330_INT_TRIP_RISE_REG3, &rise); read_reg(port, SN5S330_INT_TRIP_FALL_REG3, &fall); /* Inform other modules about VBUS level */ if (rise & SN5S330_VBUS_GOOD_MASK || fall & SN5S330_VBUS_GOOD_MASK) usb_charger_vbus_change(port, sn5s330_is_vbus_present(port)); /* Clear the interrupt sources. */ write_reg(port, SN5S330_INT_TRIP_RISE_REG3, rise); write_reg(port, SN5S330_INT_TRIP_FALL_REG3, fall); #endif /* CONFIG_USB_PD_VBUS_DETECT_PPC && CONFIG_USB_CHARGER */ } } static void sn5s330_irq_deferred(void) { int i; uint32_t pending = atomic_clear(&irq_pending); for (i = 0; i < board_get_usb_pd_port_count(); i++) if (BIT(i) & pending) sn5s330_handle_interrupt(i); } DECLARE_DEFERRED(sn5s330_irq_deferred); void sn5s330_interrupt(int port) { atomic_or(&irq_pending, BIT(port)); hook_call_deferred(&sn5s330_irq_deferred_data, 0); } const struct ppc_drv sn5s330_drv = { .init = &sn5s330_init, .is_sourcing_vbus = &sn5s330_is_sourcing_vbus, .vbus_sink_enable = &sn5s330_vbus_sink_enable, .vbus_source_enable = &sn5s330_vbus_source_enable, .set_vbus_source_current_limit = &sn5s330_set_vbus_source_current_limit, .discharge_vbus = &sn5s330_discharge_vbus, .enter_low_power_mode = &sn5s330_enter_low_power_mode, #ifdef CONFIG_CMD_PPC_DUMP .reg_dump = &sn5s330_dump, #endif #ifdef CONFIG_USB_PD_VBUS_DETECT_PPC .is_vbus_present = &sn5s330_is_vbus_present, #endif #ifdef CONFIG_USBC_PPC_POLARITY .set_polarity = &sn5s330_set_polarity, #endif #ifdef CONFIG_USBC_PPC_SBU .set_sbu = &sn5s330_set_sbu, #endif /* defined(CONFIG_USBC_PPC_SBU) */ #ifdef CONFIG_USBC_PPC_VCONN .set_vconn = &sn5s330_set_vconn, #endif };