diff options
Diffstat (limited to 'driver/ppc/ktu1125.c')
-rw-r--r-- | driver/ppc/ktu1125.c | 538 |
1 files changed, 538 insertions, 0 deletions
diff --git a/driver/ppc/ktu1125.c b/driver/ppc/ktu1125.c new file mode 100644 index 0000000000..72caa068da --- /dev/null +++ b/driver/ppc/ktu1125.c @@ -0,0 +1,538 @@ +/* Copyright 2021 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. + */ + +/* Kinetic KTU1125 USB-C Power Path Controller */ + +#include "common.h" +#include "console.h" +#include "ktu1125.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 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); +} + +static int set_field(const int port, const int addr, const int shift, + const int field_length, const int field_to_set) +{ + int val, rv, mask; + + mask = ((1 << field_length) - 1) << shift; + val = (field_to_set << shift) & mask; + + rv = clr_flags(port, addr, mask); + if (rv) + return rv; + + return set_flags(port, addr, val); +} + + +#ifdef CONFIG_CMD_PPC_DUMP +static int ktu1125_dump(int port) +{ + int i; + int data; + + for (i = KTU1125_ID; i <= KTU1125_INT_DATA; i++) { + read_reg(port, i, &data); + CPRINTF("REG %02Xh = 0x%02x\n", i, data); + } + + cflush(); + return EC_SUCCESS; +} +#endif /* defined(CONFIG_CMD_PPC_DUMP) */ + +/* helper */ +static int ktu1125_power_path_control(int port, int enable) +{ + int status = enable ? clr_flags(port, KTU1125_CTRL_SW_CFG, + KTU1125_SW_AB_EN) + : set_flags(port, KTU1125_CTRL_SW_CFG, + KTU1125_SW_AB_EN); + + if (status) { + CPRINTS("ppc p%d: Failed to %s power path", + port, enable ? "enable" : "disable"); + } + + return status; +} + +static int ktu1125_init(int port) +{ + int regval; + int ctrl_sw_cfg = 0; + int set_sw_cfg = 0; + int set_sw2_cfg = 0; + int sysb_clp; + int status; + + CPRINTF("KTU1125 init\n"); + + /* Read and verify KTU1125 Vendor and Chip ID */ + status = read_reg(port, KTU1125_ID, ®val); + + if (status) { + ppc_prints("Failed to read device ID!", port); + return status; + } + + if (regval != KTU1125_VENDOR_DIE_IDS) { + ppc_err_prints("KTU1125 ID mismatch!", port, regval); + return regval; + } + + + /* + * Setting control register CTRL_SW_CFG + */ + + /* Check if VBUS is present and set SW_AB_EN accordingly */ + status = read_reg(port, KTU1125_MONITOR_SNK, ®val); + if (status) { + ppc_err_prints("VBUS present error", port, status); + return 0; + } + + if (regval & KTU1125_SYSA_OK) + ctrl_sw_cfg = KTU1125_SW_AB_EN; + + status = write_reg(port, KTU1125_CTRL_SW_CFG, ctrl_sw_cfg); + if (status) { + ppc_err_prints("Failed to write CTRL_SW_CFG!", port, status); + return status; + } + + /* + * Setting control register SET_SW_CFG + */ + +#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 */ + sysb_clp = KTU1125_SYSB_ILIM_3_30; + break; + + case TYPEC_RP_1A5: + default: + /* Set current limit to ~1.5A */ + sysb_clp = KTU1125_SYSB_ILIM_1_70; + break; + } +#else /* !defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) */ + /* Default SRC current limit to ~1.5A */ + sysb_clp = KTU1125_SYSB_ILIM_1_70; +#endif /* defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) */ + + /* Set SYSB Current Limit Protection */ + set_sw_cfg |= sysb_clp << KTU1125_SYSB_CLP_SHIFT; + /* Set VCONN Current Limit Protection. + * Note: might be changed to 600mA in future + */ + set_sw_cfg |= KTU1125_VCONN_ILIM_0_40 << KTU1125_VCONN_CLP_SHIFT; + /* Disable Dead Battery resistance, because CC FETs are ON */ + set_sw_cfg |= KTU1125_RDB_DIS; + + status = write_reg(port, KTU1125_SET_SW_CFG, set_sw_cfg); + if (status) { + ppc_err_prints("Failed to write SET_SW_CFG!", port, status); + return status; + } + + /* + * Setting control register SET_SW2_CFG + */ + + /* Set T_HIC */ + set_sw2_cfg |= (KTU_T_HIC_MS_17 << KTU1125_T_HIC_SHIFT); + /* Set vbus discharge resistance */ + set_sw2_cfg |= (KTU1125_DIS_RES_1400 << KTU1125_DIS_RES_SHIFT); + /* Set Vbus OVP threshold to ~5V */ + set_sw2_cfg |= (KTU1125_SYSB_VLIM_6_00 << KTU1125_OVP_BUS_SHIFT); + + status = write_reg(port, KTU1125_SET_SW2_CFG, set_sw2_cfg); + if (status) { + ppc_err_prints("Failed to write SET_SW2_CFG!", port, status); + return status; + } + + /* + * 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; + + /* + * Enable interrupts + */ + + /* Leave SYSA_OK and FRS masked for SNK group of interrupts */ + regval = KTU1125_SNK_MASK_ALL & ~(KTU1125_SYSA_OK | KTU1125_FR_SWAP); + status = write_reg(port, KTU1125_INTMASK_SNK, regval); + if (status) { + ppc_err_prints("Failed to write INTMASK_SNK!", port, status); + return status; + } + + /* Only leave VBUS_OK masked for SRC group of interrupts */ + regval = KTU1125_SRC_MASK_ALL & ~KTU1125_VBUS_OK; + status = write_reg(port, KTU1125_INTMASK_SRC, regval); + if (status) { + ppc_err_prints("Failed to write INTMASK_SRC!", port, status); + return status; + } + + /* Unmask the entire DATA group of interrupts */ + status = write_reg(port, KTU1125_INTMASK_DATA, KTU1125_DATA_MASK_ALL); + if (status) { + ppc_err_prints("Failed to write INTMASK_DATA!", port, status); + return status; + } + + return EC_SUCCESS; +} + +#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC +static int ktu1125_is_vbus_present(int port) +{ + int regval; + int rv; + + rv = read_reg(port, KTU1125_MONITOR_SNK, ®val); + if (rv) { + ppc_err_prints("VBUS present error", port, rv); + return 0; + } + + return regval & KTU1125_SYSA_OK; +} +#endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */ + +static int ktu1125_is_sourcing_vbus(int port) +{ + int regval; + int rv; + + rv = read_reg(port, KTU1125_MONITOR_SRC, ®val); + if (rv) { + ppc_err_prints("Sourcing VBUS error", port, rv); + return 0; + } + + return regval & KTU1125_VBUS_OK; +} + +#ifdef CONFIG_USBC_PPC_POLARITY +static int ktu1125_set_polarity(int port, int polarity) +{ + if (polarity) { + /* CC2 active. */ + clr_flags(port, KTU1125_CTRL_SW_CFG, KTU1125_CC2S_VCONN); + return set_flags(port, KTU1125_CTRL_SW_CFG, + KTU1125_CC1S_VCONN); + } + + /* else CC1 active. */ + clr_flags(port, KTU1125_CTRL_SW_CFG, KTU1125_CC1S_VCONN); + return set_flags(port, KTU1125_CTRL_SW_CFG, KTU1125_CC2S_VCONN); +} +#endif + +static int ktu1125_set_vbus_src_current_limit(int port, enum tcpc_rp_value rp) +{ + int regval; + int 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 + */ + switch (rp) { + case TYPEC_RP_3A0: + regval = KTU1125_SYSB_ILIM_3_30; + break; + + case TYPEC_RP_1A5: + regval = KTU1125_SYSB_ILIM_1_70; + break; + + case TYPEC_RP_USB: + default: + regval = KTU1125_SYSB_ILIM_0_6; + break; + }; + + + status = set_field(port, KTU1125_SET_SW_CFG, KTU1125_SYSB_CLP_SHIFT, + KTU1125_SYSB_CLP_LEN, regval); + if (status) + ppc_prints("Failed to set KTU1125_SET_SW_CFG!", port); + + return status; +} + +static int ktu1125_discharge_vbus(int port, int enable) +{ + int status = enable ? set_flags(port, KTU1125_SET_SW2_CFG, + KTU1125_VBUS_DIS_EN) + : clr_flags(port, KTU1125_SET_SW2_CFG, + KTU1125_VBUS_DIS_EN); + + if (status) { + CPRINTS("ppc p%d: Failed to %s vbus discharge", + port, enable ? "enable" : "disable"); + return status; + } + + return EC_SUCCESS; +} + +#ifdef CONFIG_USBC_PPC_VCONN +static int ktu1125_set_vconn(int port, int enable) +{ + int status = enable ? set_flags(port, KTU1125_CTRL_SW_CFG, + KTU1125_VCONN_EN) + : clr_flags(port, KTU1125_CTRL_SW_CFG, + KTU1125_VCONN_EN); + + return status; +} +#endif + +#ifdef CONFIG_USB_PD_FRS_PPC +static int ktu1125_set_frs_enable(int port, int enable) +{ + /* Enable/Disable FR_SWAP Interrupt */ + int status = enable ? clr_flags(port, KTU1125_INTMASK_SNK, + KTU1125_FR_SWAP) + : set_flags(port, KTU1125_INTMASK_SNK, + KTU1125_FR_SWAP); + + if (status) { + ppc_prints("Failed to write KTU1125_INTMASK_SNK!", port); + return status; + } + + /* Set the FRS_EN bit */ + status = enable ? set_flags(port, KTU1125_CTRL_SW_CFG, + KTU1125_FRS_EN) + : clr_flags(port, KTU1125_CTRL_SW_CFG, + KTU1125_FRS_EN); + + return status; +} +#endif + +static int ktu1125_vbus_sink_enable(int port, int enable) +{ + /* Select active sink */ + int rv = clr_flags(port, KTU1125_CTRL_SW_CFG, KTU1125_POW_MODE); + + if (rv) { + ppc_err_prints("Could not select SNK path", port, rv); + return rv; + } + + return ktu1125_power_path_control(port, enable); +} + +static int ktu1125_vbus_source_enable(int port, int enable) +{ + /* Select active source */ + int rv = set_flags(port, KTU1125_CTRL_SW_CFG, KTU1125_POW_MODE); + + if (rv) { + ppc_err_prints("Could not select SRC path", port, rv); + return rv; + } + + return ktu1125_power_path_control(port, enable); +} + +#ifdef CONFIG_USBC_PPC_SBU +static int ktu1125_set_sbu(int port, int enable) +{ + int status = enable ? clr_flags(port, KTU1125_CTRL_SW_CFG, + KTU1125_SBU_SHUT) + : set_flags(port, KTU1125_CTRL_SW_CFG, + KTU1125_SBU_SHUT); + + if (status) { + CPRINTS("ppc p%d: Failed to %s sbu", + port, enable ? "enable" : "disable"); + } + + return status; +} +#endif /* CONFIG_USBC_PPC_SBU */ + +static void ktu1125_handle_interrupt(int port) +{ + int attempt = 0; + + /* + * KTU1135'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 ovp_int_count = 0; + int snk = 0; + int src = 0; + int data = 0; + + attempt++; + if (attempt > 1) + ppc_prints("Could not clear interrupts on first " + "try, retrying", port); + + /* Clear the interrupt by reading all 3 registers */ + read_reg(port, KTU1125_INT_SNK, &snk); + read_reg(port, KTU1125_INT_SRC, &src); + read_reg(port, KTU1125_INT_DATA, &data); + + CPRINTS("ppc p%d: INTERRUPT snk=%02X src=%02X data=%02X", + port, snk, src, data); + + if (snk & KTU1125_FR_SWAP) + pd_got_frs_signal(port); + + if (snk & (KTU1125_SYSA_SCP | + KTU1125_SYSA_OCP | + KTU1125_VBUS_OVP)) { + /* Log and PD reset */ + pd_handle_overcurrent(port); + } + + if (src & (KTU1125_SYSB_CLP | + KTU1125_SYSB_OCP | + KTU1125_SYSB_SCP | + KTU1125_VCONN_CLP | + KTU1125_VCONN_SCP)) { + /* Log and PD reset */ + pd_handle_overcurrent(port); + } + + if (data & (KTU1125_SBU2_OVP | KTU1125_SBU1_OVP)) { + /* Log and PD reset */ + pd_handle_overcurrent(port); + } + + if (data & (KTU1125_CC1_OVP | KTU1125_CC2_OVP)) { + ppc_prints("CC Over Voltage!", port); + /* + * Bug on ktu1125 Rev A: + * OVP interrupts are falsely triggered + * after IC reset (RST_L 0-> 1) + */ + if (ovp_int_count++) + pd_handle_cc_overvoltage(port); + } + } +} + +static void ktu1125_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) + ktu1125_handle_interrupt(i); +} +DECLARE_DEFERRED(ktu1125_irq_deferred); + +void ktu1125_interrupt(int port) +{ + atomic_or(&irq_pending, BIT(port)); + hook_call_deferred(&ktu1125_irq_deferred_data, 0); +} + +const struct ppc_drv ktu1125_drv = { + .init = &ktu1125_init, + .is_sourcing_vbus = &ktu1125_is_sourcing_vbus, + .vbus_sink_enable = &ktu1125_vbus_sink_enable, + .vbus_source_enable = &ktu1125_vbus_source_enable, +#ifdef CONFIG_USBC_PPC_POLARITY + .set_polarity = &ktu1125_set_polarity, +#endif + .set_vbus_source_current_limit = &ktu1125_set_vbus_src_current_limit, + .discharge_vbus = &ktu1125_discharge_vbus, +#ifdef CONFIG_USBC_PPC_SBU + .set_sbu = &ktu1125_set_sbu, +#endif +#ifdef CONFIG_USBC_PPC_VCONN + .set_vconn = &ktu1125_set_vconn, +#endif +#ifdef CONFIG_USB_PD_FRS_PPC + .set_frs_enable = &ktu1125_set_frs_enable, +#endif +#ifdef CONFIG_CMD_PPC_DUMP + .reg_dump = &ktu1125_dump, +#endif +#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC + .is_vbus_present = &ktu1125_is_vbus_present, +#endif + .interrupt = &ktu1125_interrupt, +}; |