From df3820165f8dd8bfb4b0a30ae1ac14372610cae8 Mon Sep 17 00:00:00 2001 From: Aseda Aboagye Date: Mon, 16 Oct 2017 14:36:42 -0700 Subject: driver: ppc: Add support for TI SN5S330. This commit adds a basic driver for the TI SN5S330. This driver just sets up the IC and provides an API to turn on or off the PP2 FET. BUG=b:67663166, b:67663124 BRANCH=None TEST=Enable code for zoombini; Flash a board which has the SN5S330 stuffed; Verify that we're able to perform PD negotiation and negotiate all the way up to 20V. TEST=Boot only on AC. sysjump to RW, verify that board does not brownout. Change-Id: I9c147ee8465eed878843cf902db301d62e8f627e Signed-off-by: Aseda Aboagye Reviewed-on: https://chromium-review.googlesource.com/722104 Commit-Ready: Aseda Aboagye Tested-by: Aseda Aboagye Reviewed-by: Shawn N --- driver/ppc/sn5s330.c | 305 +++++++++++++++++++++++++++++++++++++++++++++++++++ driver/ppc/sn5s330.h | 117 ++++++++++++++++++++ 2 files changed, 422 insertions(+) create mode 100644 driver/ppc/sn5s330.c create mode 100644 driver/ppc/sn5s330.h (limited to 'driver/ppc') diff --git a/driver/ppc/sn5s330.c b/driver/ppc/sn5s330.c new file mode 100644 index 0000000000..81efb290b8 --- /dev/null +++ b/driver/ppc/sn5s330.c @@ -0,0 +1,305 @@ +/* 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 "driver/ppc/sn5s330.h" +#include "hooks.h" +#include "i2c.h" +#include "system.h" +#include "timer.h" +#include "util.h" + +#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) +#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) + +#ifdef CONFIG_CMD_PPC_DUMP +static int command_sn5s330_dump(int argc, char **argv) +{ + int i; + int data; + int chip_idx; + int port; + int addr; + + if (argc < 2) + return EC_ERROR_PARAM_COUNT; + + chip_idx = atoi(argv[1]); + if (chip_idx >= sn5s330_cnt) + return EC_ERROR_PARAM1; + + port = sn5s330_chips[chip_idx].i2c_port; + addr = sn5s330_chips[chip_idx].i2c_addr; + + for (i = SN5S330_FUNC_SET1; i <= SN5S330_FUNC_SET12; i++) { + i2c_read8(port, addr, i, &data); + ccprintf("FUNC_SET%d [%02Xh] = 0x%02x\n", + i - SN5S330_FUNC_SET1 + 1, + i, + data); + } + + for (i = SN5S330_INT_STATUS_REG1; i <= SN5S330_INT_STATUS_REG4; i++) { + i2c_read8(port, addr, i, &data); + ccprintf("INT_STATUS_REG%d [%02Xh] = 0x%02x\n", + i - SN5S330_INT_STATUS_REG1 + 1, + i, + data); + } + + for (i = SN5S330_INT_TRIP_RISE_REG1; i <= SN5S330_INT_TRIP_RISE_REG3; + i++) { + i2c_read8(port, addr, i, &data); + ccprintf("INT_TRIP_RISE_REG%d [%02Xh] = 0x%02x\n", + i - SN5S330_INT_TRIP_RISE_REG1 + 1, + i, + data); + } + + for (i = SN5S330_INT_TRIP_FALL_REG1; i <= SN5S330_INT_TRIP_FALL_REG3; + i++) { + i2c_read8(port, addr, i, &data); + ccprintf("INT_TRIP_FALL_REG%d [%02Xh] = 0x%02x\n", + i - SN5S330_INT_TRIP_FALL_REG1 + 1, + i, + data); + } + + for (i = SN5S330_INT_MASK_RISE_REG1; i <= SN5S330_INT_MASK_RISE_REG3; + i++) { + i2c_read8(port, addr, i, &data); + ccprintf("INT_MASK_RISE_REG%d [%02Xh] = 0x%02x\n", + i - SN5S330_INT_MASK_RISE_REG1 + 1, + i, + data); + } + + for (i = SN5S330_INT_MASK_FALL_REG1; i <= SN5S330_INT_MASK_FALL_REG3; + i++) { + i2c_read8(port, addr, i, &data); + ccprintf("INT_MASK_FALL_REG%d [%02Xh] = 0x%02x\n", + i - SN5S330_INT_MASK_FALL_REG1 + 1, + i, + data); + } + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(ppc_dump, command_sn5s330_dump, + "", "dump the SN5S330 regs"); +#endif /* defined(CONFIG_CMD_PPC_DUMP) */ + +int sn5s330_pp_fet_enable(uint8_t chip_idx, enum sn5s330_pp_idx pp, int enable) +{ + int regval; + int status; + int pp_bit; + int port; + int addr; + + if (pp == SN5S330_PP1) { + pp_bit = SN5S330_PP1_EN; + } else if (pp == SN5S330_PP2) { + pp_bit = SN5S330_PP2_EN; + } else { + CPRINTF("bad PP idx(%d)!", pp); + return EC_ERROR_INVAL; + } + + port = sn5s330_chips[chip_idx].i2c_port; + addr = sn5s330_chips[chip_idx].i2c_addr; + + status = i2c_read8(port, addr, SN5S330_FUNC_SET3, ®val); + if (status) { + CPRINTS("Failed to read FUNC_SET3!"); + return status; + } + + if (enable) + regval |= pp_bit; + else + regval &= ~pp_bit; + + status = i2c_write8(port, addr, SN5S330_FUNC_SET3, regval); + if (status) { + CPRINTS("Failed to set FUNC_SET3!"); + return status; + } + + return EC_SUCCESS; +} + +static int init_sn5s330(int idx) +{ + int regval; + int status; + int retries; + int i2c_port; + int i2c_addr; + + i2c_port = sn5s330_chips[idx].i2c_port; + i2c_addr = sn5s330_chips[idx].i2c_addr; + + /* Set the sourcing current limit value. */ +#if defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) && \ + (CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT == TYPEC_RP_3A0) + /* Set current limit to ~3A. */ + regval = SN5S330_ILIM_3_06; +#else + /* Set current limit to ~1.5A. */ + regval = SN5S330_ILIM_1_62; +#endif + + /* + * 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, SN5S330_FUNC_SET1, + regval); + if (status) { + CPRINTS("Failed to set FUNC_SET1! Retrying..."); + retries++; + msleep(1); + } else { + break; + } + } while (retries < 10); + + /* Set Vbus OVP threshold to ~22.325V. */ + regval = 0x37; + status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET5, regval); + if (status) { + CPRINTS("Failed to set FUNC_SET5!"); + return status; + } + + /* Set Vbus UVP threshold to ~2.75V. */ + status = i2c_read8(i2c_port, i2c_addr, SN5S330_FUNC_SET6, ®val); + if (status) { + CPRINTS("Failed to read FUNC_SET6!"); + return status; + } + regval &= ~0x3F; + regval |= 1; + status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET6, regval); + if (status) { + CPRINTS("Failed to write FUNC_SET6!"); + return status; + } + + /* Enable SBU Fets and set PP2 current limit to ~3A. */ + regval = SN5S330_SBU_EN | 0xf; + status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET2, regval); + if (status) { + CPRINTS("Failed to set FUNC_SET2!"); + return status; + } + + /* TODO(aaboagye): What about Vconn */ + + /* + * Indicate we are using PP2 configuration 2 and enable OVP comparator + * for CC lines. + */ + regval = SN5S330_OVP_EN_CC | SN5S330_PP2_CONFIG; + status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET9, regval); + if (status) { + CPRINTS("Failed to set FUNC_SET9!"); + return status; + } + + /* Set analog current limit delay to 200 us for both PP1 & PP2. */ + regval = (PPX_ILIM_DEGLITCH_0_US_200 << 3) | PPX_ILIM_DEGLITCH_0_US_200; + status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET11, + regval); + if (status) { + CPRINTS("Failed to set FUNC_SET11"); + return status; + } + + /* Turn off dead battery resistors and turn on CC FETs. */ + status = i2c_read8(i2c_port, i2c_addr, SN5S330_FUNC_SET4, ®val); + if (status) { + CPRINTS("Failed to read FUNC_SET4!"); + return status; + } + regval |= SN5S330_CC_EN; + status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET4, regval); + if (status) { + CPRINTS("Failed to set FUNC_SET4!"); + return status; + } + + /* Set ideal diode mode for both PP1 and PP2. */ + status = i2c_read8(i2c_port, i2c_addr, SN5S330_FUNC_SET3, ®val); + if (status) { + CPRINTS("Failed to read FUNC_SET3!"); + return status; + } + regval |= SN5S330_SET_RCP_MODE_PP1 | SN5S330_SET_RCP_MODE_PP2; + status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET3, regval); + if (status) { + CPRINTS("Failed to set FUNC_SET3!"); + return status; + } + + /* Turn off PP1 FET. */ + status = sn5s330_pp_fet_enable(idx, SN5S330_PP1, 0); + if (status) { + CPRINTS("Failed to turn off PP1 FET!"); + } + + /* Don't touch the PP2 FET yet if we're sysjumping. */ + if (system_jumped_to_this_image()) + return EC_SUCCESS; + + /* For PP2, check to see if we booted in dead battery mode. */ + status = i2c_read8(i2c_port, i2c_addr, SN5S330_INT_STATUS_REG4, + ®val); + if (status) { + CPRINTS("Failed to read INT_STATUS_REG4!"); + return status; + } + + if (regval & SN5S330_DB_BOOT) { + /* Clear the bit. */ + i2c_write8(i2c_port, i2c_addr, SN5S330_INT_STATUS_REG4, + SN5S330_DB_BOOT); + + /* Turn on PP2 FET. */ + status = sn5s330_pp_fet_enable(idx, SN5S330_PP2, 1); + if (status) { + CPRINTS("Failed to turn on PP2 FET!"); + return status; + } + } + + return EC_SUCCESS; +} + +static void sn5s330_init(void) +{ + int i; + int rv; + + for (i = 0; i < sn5s330_cnt; i++) { + rv = init_sn5s330(i); + if (!rv) + CPRINTS("C%d: SN5S330 init done.", i); + else + CPRINTS("C%d: SN5S330 init failed! (%d)", i, rv); + } +} +DECLARE_HOOK(HOOK_INIT, sn5s330_init, HOOK_PRIO_LAST); diff --git a/driver/ppc/sn5s330.h b/driver/ppc/sn5s330.h new file mode 100644 index 0000000000..c4aa5fb81b --- /dev/null +++ b/driver/ppc/sn5s330.h @@ -0,0 +1,117 @@ +/* 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 Type-C Power Path Controller */ + +#ifndef __CROS_EC_SN5S330_H +#define __CROS_EC_SN5S330_H + +#include "common.h" + +struct sn5s330_config { + uint8_t i2c_port; + uint8_t i2c_addr; +}; + +extern const struct sn5s330_config sn5s330_chips[]; +extern const unsigned int sn5s330_cnt; + +/* Power Path Indices */ +enum sn5s330_pp_idx { + SN5S330_PP1, + SN5S330_PP2, + SN5S330_PP_COUNT, +}; + +#define SN5S330_ADDR0 0x80 +#define SN5S330_ADDR1 0x82 +#define SN5S330_ADDR2 0x84 +#define SN5S330_ADDR3 0x86 + +#define SN5S330_FUNC_SET1 0x50 +#define SN5S330_FUNC_SET2 0x51 +#define SN5S330_FUNC_SET3 0x52 +#define SN5S330_FUNC_SET4 0x53 +#define SN5S330_FUNC_SET5 0x54 +#define SN5S330_FUNC_SET6 0x55 +#define SN5S330_FUNC_SET7 0x56 +#define SN5S330_FUNC_SET8 0x57 +#define SN5S330_FUNC_SET9 0x58 +#define SN5S330_FUNC_SET10 0x59 +#define SN5S330_FUNC_SET11 0x5A +#define SN5S330_FUNC_SET12 0x5B + +#define SN5S330_INT_STATUS_REG1 0x2F +#define SN5S330_INT_STATUS_REG2 0x30 +#define SN5S330_INT_STATUS_REG3 0x31 +#define SN5S330_INT_STATUS_REG4 0x32 + +#define SN5S330_INT_TRIP_RISE_REG1 0x20 +#define SN5S330_INT_TRIP_RISE_REG2 0x21 +#define SN5S330_INT_TRIP_RISE_REG3 0x22 +#define SN5S330_INT_TRIP_FALL_REG1 0x23 +#define SN5S330_INT_TRIP_FALL_REG2 0x24 +#define SN5S330_INT_TRIP_FALL_REG3 0x25 + +#define SN5S330_INT_MASK_RISE_REG1 0x26 +#define SN5S330_INT_MASK_RISE_REG2 0x27 +#define SN5S330_INT_MASK_RISE_REG3 0x28 +#define SN5S330_INT_MASK_FALL_REG1 0x29 +#define SN5S330_INT_MASK_FALL_REG2 0x2A +#define SN5S330_INT_MASK_FALL_REG3 0x2B + +#define PPX_ILIM_DEGLITCH_0_US_20 0x1 +#define PPX_ILIM_DEGLITCH_0_US_50 0x2 +#define PPX_ILIM_DEGLITCH_0_US_100 0x3 +#define PPX_ILIM_DEGLITCH_0_US_200 0x4 +#define PPX_ILIM_DEGLITCH_0_US_1000 0x5 +#define PPX_ILIM_DEGLITCH_0_US_2000 0x6 +#define PPX_ILIM_DEGLITCH_0_US_10000 0x7 + +/* Internal VBUS Switch Current Limit Settings (min) */ +#define SN5S330_ILIM_0_35 0 +#define SN5S330_ILIM_0_63 1 +#define SN5S330_ILIM_0_90 2 +#define SN5S330_ILIM_1_14 3 +#define SN5S330_ILIM_1_38 4 +#define SN5S330_ILIM_1_62 5 +#define SN5S330_ILIM_1_86 6 +#define SN5S330_ILIM_2_10 7 +#define SN5S330_ILIM_2_34 8 +#define SN5S330_ILIM_2_58 9 +#define SN5S330_ILIM_2_82 10 +#define SN5S330_ILIM_3_06 11 +#define SN5S330_ILIM_3_30 12 + +/* FUNC_SET_2 */ +#define SN5S330_SBU_EN (1 << 4) + +/* FUNC_SET_3 */ +#define SN5S330_PP1_EN (1 << 0) +#define SN5S330_PP2_EN (1 << 1) +#define SN5S330_SET_RCP_MODE_PP1 (1 << 5) +#define SN5S330_SET_RCP_MODE_PP2 (1 << 6) + +#define SN5S330_CC_EN (1 << 4) + +/* FUNC_SET_9 */ +#define SN5S330_PP2_CONFIG (1 << 2) +#define SN5S330_OVP_EN_CC (1 << 4) + +/* INT_STATUS_REG4 */ +#define SN5S330_DB_BOOT (1 << 1) + +/** + * Turn on/off the PP1 or PP2 FET. + * + * @param chip_idx: The index into the sn5s330_chips[] table. + * @param pp: The power path index (PP1 or PP2). + * @param enable: 1 to turn on the FET, 0 to turn off. + * @return EC_SUCCESS on success, + * otherwise if failed to enable the FET. + */ +int sn5s330_pp_fet_enable(uint8_t chip_idx, enum sn5s330_pp_idx pp, int enable); + +#endif /* defined(__CROS_EC_SN5S330_H) */ -- cgit v1.2.1