From patchwork Fri May 31 15:08:14 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sebastian Reichel X-Patchwork-Id: 1942201 X-Patchwork-Delegate: marek.vasut@gmail.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=collabora.com header.i=@collabora.com header.a=rsa-sha256 header.s=mail header.b=z8oLs/w9; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) Received: from phobos.denx.de (phobos.denx.de [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VrRRx3zfvz20Pc for ; Sat, 1 Jun 2024 01:12:13 +1000 (AEST) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 4CB9F887A1; Fri, 31 May 2024 17:11:14 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=quarantine dis=none) header.from=collabora.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=pass (2048-bit key; unprotected) header.d=collabora.com header.i=@collabora.com header.b="z8oLs/w9"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id 1268B882BE; Fri, 31 May 2024 17:11:13 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_PASS,T_SCC_BODY_TEXT_LINE, T_SPF_HELO_TEMPERROR autolearn=unavailable autolearn_force=no version=3.4.2 Received: from madrid.collaboradmins.com (madrid.collaboradmins.com [46.235.227.194]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id 2DD33887CB for ; Fri, 31 May 2024 17:10:54 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=quarantine dis=none) header.from=collabora.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=sebastian.reichel@collabora.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com; s=mail; t=1717168253; bh=vUJ1QUKrEPwb+8v7Q9IIvl9K75VKc4Vo+1jKJsTB1yU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=z8oLs/w9WNFBaChw05VzEwG6TFE8ftbFY23emVynVwrHdksXgRGL+g0qcqZSt6ycH AJ0Wpo0lcO+wKxaZ4TiTLREbhQTZJbowhmmN+u4IbNV5q4Bu2RcqWZQsyv0zmKd6Vk ++aY1HQ1X0XNunnABB5NUwoR1PU5ws1CrjtgkTwla+rSiGSTrsuTploO/9VfLcjW2o pVzlMTBmdM0rgTTxwCwIAS4NAxoUGzZb5W7Knv4otBwadPik2IqLVE3WHfTIaRnmdT ZfNmU3jGtNqTc9uPBHxig6HWN7Pogj1Gv9c19fuQk9h0n28p58T2ym5+HLnHrh+BO9 XOKArQx8adrKQ== Received: from jupiter.universe (cola.collaboradmins.com [195.201.22.229]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) (Authenticated sender: sre) by madrid.collaboradmins.com (Postfix) with ESMTPSA id 56A1537821DD; Fri, 31 May 2024 15:10:53 +0000 (UTC) Received: by jupiter.universe (Postfix, from userid 1000) id E2BB94800CD; Fri, 31 May 2024 17:10:52 +0200 (CEST) From: Sebastian Reichel To: Marek Vasut , u-boot@lists.denx.de, Eugen Hristev , Jonas Karlman Cc: Kever Yang , Philipp Tomsich , Simon Glass , Sebastian Reichel , Wang Jie Subject: [PATCH v1 2/5] usb: tcpm: fusb302: add driver Date: Fri, 31 May 2024 17:08:14 +0200 Message-ID: <20240531151026.84846-3-sebastian.reichel@collabora.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240531151026.84846-1-sebastian.reichel@collabora.com> References: <20240531151026.84846-1-sebastian.reichel@collabora.com> MIME-Version: 1.0 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.39 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de X-Virus-Status: Clean Now that the TCPM framework exists we can introduce fusb302 driver using it. This chip is a very common USB-C controller chip with PD support, which can be found in the Radxa Rock 5B among many other boards. Apart from Power Delivery, it also handles detection of the cable orientation. That can be used to control a mux for connecting the right USB3 lane pair to the USB3 controller. The driver is originally from the Linux kernel, but has been adapted to the requirements of U-Boot and its TCPM framework. Co-developed-by: Wang Jie Signed-off-by: Wang Jie Signed-off-by: Sebastian Reichel --- drivers/usb/tcpm/Kconfig | 8 + drivers/usb/tcpm/Makefile | 1 + drivers/usb/tcpm/fusb302.c | 1427 ++++++++++++++++++++++++++++++++ drivers/usb/tcpm/fusb302_reg.h | 177 ++++ 4 files changed, 1613 insertions(+) create mode 100644 drivers/usb/tcpm/fusb302.c create mode 100644 drivers/usb/tcpm/fusb302_reg.h diff --git a/drivers/usb/tcpm/Kconfig b/drivers/usb/tcpm/Kconfig index 55bf8e202b90..9be4b496e82f 100644 --- a/drivers/usb/tcpm/Kconfig +++ b/drivers/usb/tcpm/Kconfig @@ -6,3 +6,11 @@ config TYPEC_TCPM help The Type-C Port Controller Manager provides a USB PD and USB Type-C state machine for use with Type-C Port Controllers. + +config TYPEC_FUSB302 + tristate "Fairchild FUSB302 Type-C chip driver" + depends on DM && DM_I2C && TYPEC_TCPM + help + The Fairchild FUSB302 Type-C chip driver that works with + Type-C Port Controller Manager to provide USB PD and USB + Type-C functionalities. diff --git a/drivers/usb/tcpm/Makefile b/drivers/usb/tcpm/Makefile index f3815b7e466e..bb3bffdb4971 100644 --- a/drivers/usb/tcpm/Makefile +++ b/drivers/usb/tcpm/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC_TCPM) += tcpm.o +obj-$(CONFIG_TYPEC_FUSB302) += fusb302.o diff --git a/drivers/usb/tcpm/fusb302.c b/drivers/usb/tcpm/fusb302.c new file mode 100644 index 000000000000..fe509fcd4f5e --- /dev/null +++ b/drivers/usb/tcpm/fusb302.c @@ -0,0 +1,1427 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2016-2017 Google, Inc + * + * Fairchild FUSB302 Type-C Chip Driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include "fusb302_reg.h" + +#define usleep_range(a, b) udelay((b)) + +enum toggling_mode { + TOGGLING_MODE_OFF, + TOGGLING_MODE_DRP, + TOGGLING_MODE_SNK, + TOGGLING_MODE_SRC, +}; + +enum src_current_status { + SRC_CURRENT_DEFAULT, + SRC_CURRENT_MEDIUM, + SRC_CURRENT_HIGH, +}; + +static const u8 ra_mda_value[] = { + [SRC_CURRENT_DEFAULT] = 4, /* 210mV */ + [SRC_CURRENT_MEDIUM] = 9, /* 420mV */ + [SRC_CURRENT_HIGH] = 18, /* 798mV */ +}; + +static const u8 rd_mda_value[] = { + [SRC_CURRENT_DEFAULT] = 38, /* 1638mV */ + [SRC_CURRENT_MEDIUM] = 38, /* 1638mV */ + [SRC_CURRENT_HIGH] = 61, /* 2604mV */ +}; + +#define LOG_BUFFER_ENTRIES 1024 +#define LOG_BUFFER_ENTRY_SIZE 128 + +struct fusb302_chip { + struct udevice *udev; + struct udevice *vbus_regulator; + struct ofnode *child_node; + struct tcpm_port *tcpm_port; + struct tcpc_dev tcpc_dev; + + int irq; + + enum toggling_mode toggling_mode; + enum src_current_status src_current_status; + bool intr_togdone; + bool intr_bc_lvl; + bool intr_comp_chng; + + /* port status */ + bool vconn_on; + bool vbus_on; + bool charge_on; + bool vbus_present; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc1; + enum typec_cc_status cc2; +}; + +static int fusb302_i2c_write(struct fusb302_chip *chip, + u8 address, u8 data) +{ + int ret = 0; + + ret = dm_i2c_write(chip->udev, address, &data, 1); + if (ret) + dev_err(chip->udev, "cannot write 0x%02x to 0x%02x, ret=%d\n", + data, address, ret); + + return ret; +} + +static int fusb302_i2c_block_write(struct fusb302_chip *chip, u8 address, + u8 length, const u8 *data) +{ + int ret = 0; + + if (length <= 0) + return ret; + + ret = dm_i2c_write(chip->udev, address, data, length); + if (ret) + dev_err(chip->udev, "cannot block write 0x%02x, len=%d, ret=%d\n", + address, length, ret); + + return ret; +} + +static int fusb302_i2c_read(struct fusb302_chip *chip, + u8 address, u8 *data) +{ + int ret = 0, retries; + + for (retries = 0; retries < 3; retries++) { + ret = dm_i2c_read(chip->udev, address, data, 1); + if (ret == 0) + return ret; + dev_err(chip->udev, "cannot read %02x, ret=%d\n", + address, ret); + } + + return ret; +} + +static int fusb302_i2c_block_read(struct fusb302_chip *chip, u8 address, + u8 length, u8 *data) +{ + int ret = 0; + + if (length <= 0) + return ret; + + ret = dm_i2c_read(chip->udev, address, data, length); + if (ret) + dev_err(chip->udev, "cannot block read 0x%02x, len=%d, ret=%d\n", + address, length, ret); + return ret; +} + +static int fusb302_i2c_mask_write(struct fusb302_chip *chip, u8 address, + u8 mask, u8 value) +{ + int ret = 0; + u8 data; + + ret = fusb302_i2c_read(chip, address, &data); + if (ret) + return ret; + data &= ~mask; + data |= value; + ret = fusb302_i2c_write(chip, address, data); + if (ret) + return ret; + + return ret; +} + +static int fusb302_i2c_set_bits(struct fusb302_chip *chip, u8 address, + u8 set_bits) +{ + return fusb302_i2c_mask_write(chip, address, 0x00, set_bits); +} + +static int fusb302_i2c_clear_bits(struct fusb302_chip *chip, u8 address, + u8 clear_bits) +{ + return fusb302_i2c_mask_write(chip, address, clear_bits, 0x00); +} + +static int fusb302_sw_reset(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_RESET, + FUSB_REG_RESET_SW_RESET); + if (ret) + dev_err(chip->udev, "cannot sw reset the fusb302: %d\n", ret); + else + dev_dbg(chip->udev, "fusb302 sw reset finished\n"); + + return ret; +} + +static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip, u8 retry_count) +{ + int ret = 0; + + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, retry_count | + FUSB_REG_CONTROL3_AUTO_RETRY); + + return ret; +} + +/* + * mask all interrupt on the chip + */ +static int fusb302_mask_interrupt(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_MASK, 0xFF); + if (ret) + return ret; + ret = fusb302_i2c_write(chip, FUSB_REG_MASKA, 0xFF); + if (ret) + return ret; + ret = fusb302_i2c_write(chip, FUSB_REG_MASKB, 0xFF); + if (ret) + return ret; + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_INT_MASK); + return ret; +} + +/* + * initialize interrupt on the chip + * - unmasked interrupt: VBUS_OK + */ +static int fusb302_init_interrupt(struct fusb302_chip *chip) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_MASK, + 0xFF & ~FUSB_REG_MASK_VBUSOK); + if (ret) + return ret; + ret = fusb302_i2c_write(chip, FUSB_REG_MASKA, 0xFF); + if (ret) + return ret; + ret = fusb302_i2c_write(chip, FUSB_REG_MASKB, 0xFF); + if (ret) + return ret; + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_INT_MASK); + return ret; +} + +static int fusb302_set_power_mode(struct fusb302_chip *chip, u8 power_mode) +{ + int ret = 0; + + ret = fusb302_i2c_write(chip, FUSB_REG_POWER, power_mode); + + return ret; +} + +static int tcpm_init(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 data; + + ret = fusb302_sw_reset(chip); + if (ret) + return ret; + ret = fusb302_enable_tx_auto_retries(chip, FUSB_REG_CONTROL3_N_RETRIES_3); + if (ret) + return ret; + ret = fusb302_init_interrupt(chip); + if (ret) + return ret; + ret = fusb302_set_power_mode(chip, FUSB_REG_POWER_PWR_ALL); + if (ret) + return ret; + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &data); + if (ret) + return ret; + chip->vbus_present = !!(data & FUSB_REG_STATUS0_VBUSOK); + ret = fusb302_i2c_read(chip, FUSB_REG_DEVICE_ID, &data); + if (ret) + return ret; + dev_info(chip->udev, "fusb302 device ID: 0x%02x\n", data); + + return ret; +} + +static int tcpm_get_vbus(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + return chip->vbus_present ? 1 : 0; +} + +static int fusb302_set_src_current(struct fusb302_chip *chip, + enum src_current_status status) +{ + int ret = 0; + + chip->src_current_status = status; + switch (status) { + case SRC_CURRENT_DEFAULT: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_DEF); + break; + case SRC_CURRENT_MEDIUM: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_MED); + break; + case SRC_CURRENT_HIGH: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_HOST_CUR_MASK, + FUSB_REG_CONTROL0_HOST_CUR_HIGH); + break; + default: + break; + } + + return ret; +} + +static int fusb302_set_toggling(struct fusb302_chip *chip, + enum toggling_mode mode) +{ + int ret = 0; + + /* first disable toggling */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_TOGGLE); + if (ret) + return ret; + /* mask interrupts for SRC or SNK */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG); + if (ret) + return ret; + chip->intr_bc_lvl = false; + chip->intr_comp_chng = false; + /* configure toggling mode: none/snk/src/drp */ + switch (mode) { + case TOGGLING_MODE_OFF: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_NONE); + break; + case TOGGLING_MODE_SNK: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_UFP); + break; + case TOGGLING_MODE_SRC: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_DFP); + break; + case TOGGLING_MODE_DRP: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_MODE_MASK, + FUSB_REG_CONTROL2_MODE_DRP); + break; + default: + break; + } + + if (ret) + return ret; + + if (mode == TOGGLING_MODE_OFF) { + /* mask TOGDONE interrupt */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, + FUSB_REG_MASKA_TOGDONE); + if (ret) + return ret; + chip->intr_togdone = false; + } else { + /* Datasheet says vconn MUST be off when toggling */ + if (chip->vconn_on) + dev_warn(chip->udev, "Vconn is on during toggle start\n"); + /* unmask TOGDONE interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, + FUSB_REG_MASKA_TOGDONE); + if (ret) + return ret; + chip->intr_togdone = true; + /* start toggling */ + ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL2, + FUSB_REG_CONTROL2_TOGGLE); + if (ret) + return ret; + /* during toggling, consider cc as Open */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + } + chip->toggling_mode = mode; + + return ret; +} + +static const char * const typec_cc_status_name[] = { + [TYPEC_CC_OPEN] = "Open", + [TYPEC_CC_RA] = "Ra", + [TYPEC_CC_RD] = "Rd", + [TYPEC_CC_RP_DEF] = "Rp-def", + [TYPEC_CC_RP_1_5] = "Rp-1.5", + [TYPEC_CC_RP_3_0] = "Rp-3.0", +}; + +static const enum src_current_status cc_src_current[] = { + [TYPEC_CC_OPEN] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RA] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RD] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RP_DEF] = SRC_CURRENT_DEFAULT, + [TYPEC_CC_RP_1_5] = SRC_CURRENT_MEDIUM, + [TYPEC_CC_RP_3_0] = SRC_CURRENT_HIGH, +}; + +static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + u8 switches0_mask = FUSB_REG_SWITCHES0_CC1_PU_EN | + FUSB_REG_SWITCHES0_CC2_PU_EN | + FUSB_REG_SWITCHES0_CC1_PD_EN | + FUSB_REG_SWITCHES0_CC2_PD_EN; + u8 rd_mda, switches0_data = 0x00; + int ret = 0; + + switch (cc) { + case TYPEC_CC_OPEN: + break; + case TYPEC_CC_RD: + switches0_data |= FUSB_REG_SWITCHES0_CC1_PD_EN | + FUSB_REG_SWITCHES0_CC2_PD_EN; + break; + case TYPEC_CC_RP_DEF: + case TYPEC_CC_RP_1_5: + case TYPEC_CC_RP_3_0: + switches0_data |= (chip->cc_polarity == TYPEC_POLARITY_CC1) ? + FUSB_REG_SWITCHES0_CC1_PU_EN : + FUSB_REG_SWITCHES0_CC2_PU_EN; + break; + default: + dev_err(chip->udev, "unsupported CC value: %s\n", + typec_cc_status_name[cc]); + ret = -EINVAL; + goto done; + } + + ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF); + if (ret) { + dev_err(chip->udev, "cannot set toggling mode: %d\n", ret); + goto done; + } + + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + switches0_mask, switches0_data); + if (ret) { + dev_err(chip->udev, "cannot set pull-up/-down: %d\n", ret); + goto done; + } + /* reset the cc status */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + + /* adjust current for SRC */ + ret = fusb302_set_src_current(chip, cc_src_current[cc]); + if (ret) { + dev_err(chip->udev, "cannot set src current %s: %d\n", + typec_cc_status_name[cc], ret); + goto done; + } + + /* enable/disable interrupts, BC_LVL for SNK and COMP_CHNG for SRC */ + switch (cc) { + case TYPEC_CC_RP_DEF: + case TYPEC_CC_RP_1_5: + case TYPEC_CC_RP_3_0: + rd_mda = rd_mda_value[cc_src_current[cc]]; + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret) { + dev_err(chip->udev, "cannot set SRC measure value: %d\n", ret); + goto done; + } + ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG, + FUSB_REG_MASK_BC_LVL); + if (ret) { + dev_err(chip->udev, "cannot set SRC irq: %d\n", ret); + goto done; + } + chip->intr_comp_chng = true; + break; + case TYPEC_CC_RD: + ret = fusb302_i2c_mask_write(chip, FUSB_REG_MASK, + FUSB_REG_MASK_BC_LVL | + FUSB_REG_MASK_COMP_CHNG, + FUSB_REG_MASK_COMP_CHNG); + if (ret) { + dev_err(chip->udev, "cannot set SRC irq: %d\n", ret); + goto done; + } + chip->intr_bc_lvl = true; + break; + default: + break; + } +done: + return ret; +} + +static int tcpm_get_cc(struct tcpc_dev *dev, enum typec_cc_status *cc1, + enum typec_cc_status *cc2) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + + *cc1 = chip->cc1; + *cc2 = chip->cc2; + dev_dbg(chip->udev, "get cc1 = %s, cc2 = %s\n", typec_cc_status_name[*cc1], + typec_cc_status_name[*cc2]); + + return 0; +} + +static int tcpm_set_polarity(struct tcpc_dev *dev, + enum typec_cc_polarity polarity) +{ + return 0; +} + +static int tcpm_set_vconn(struct tcpc_dev *dev, bool on) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 switches0_data = 0x00; + u8 switches0_mask = FUSB_REG_SWITCHES0_VCONN_CC1 | + FUSB_REG_SWITCHES0_VCONN_CC2; + + if (chip->vconn_on == on) { + dev_dbg(chip->udev, "vconn is already %s\n", on ? "on" : "off"); + goto done; + } + if (on) { + switches0_data = (chip->cc_polarity == TYPEC_POLARITY_CC1) ? + FUSB_REG_SWITCHES0_VCONN_CC2 : + FUSB_REG_SWITCHES0_VCONN_CC1; + } + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + switches0_mask, switches0_data); + if (ret) + goto done; + dev_dbg(chip->udev, "set vconn = %s\n", on ? "on" : "off"); +done: + return ret; +} + +static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge) +{ + return 0; +} + +static int fusb302_pd_tx_flush(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL0, + FUSB_REG_CONTROL0_TX_FLUSH); +} + +static int fusb302_pd_rx_flush(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL1, + FUSB_REG_CONTROL1_RX_FLUSH); +} + +static int fusb302_pd_set_auto_goodcrc(struct fusb302_chip *chip, bool on) +{ + if (on) + return fusb302_i2c_set_bits(chip, FUSB_REG_SWITCHES1, + FUSB_REG_SWITCHES1_AUTO_GCRC); + return fusb302_i2c_clear_bits(chip, FUSB_REG_SWITCHES1, + FUSB_REG_SWITCHES1_AUTO_GCRC); +} + +static int fusb302_pd_set_interrupts(struct fusb302_chip *chip, bool on) +{ + int ret = 0; + u8 mask_interrupts = FUSB_REG_MASK_COLLISION; + u8 maska_interrupts = FUSB_REG_MASKA_RETRYFAIL | + FUSB_REG_MASKA_HARDSENT | + FUSB_REG_MASKA_TX_SUCCESS | + FUSB_REG_MASKA_HARDRESET; + u8 maskb_interrupts = FUSB_REG_MASKB_GCRCSENT; + + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, mask_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASK, mask_interrupts); + if (ret) + return ret; + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, maska_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASKA, maska_interrupts); + if (ret) + return ret; + ret = on ? + fusb302_i2c_clear_bits(chip, FUSB_REG_MASKB, maskb_interrupts) : + fusb302_i2c_set_bits(chip, FUSB_REG_MASKB, maskb_interrupts); + return ret; +} + +static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + ret = fusb302_pd_rx_flush(chip); + if (ret) { + dev_err(chip->udev, "cannot flush pd rx buffer: %d\n", ret); + goto done; + } + ret = fusb302_pd_tx_flush(chip); + if (ret) { + dev_err(chip->udev, "cannot flush pd tx buffer: %d\n", ret); + goto done; + } + ret = fusb302_pd_set_auto_goodcrc(chip, on); + if (ret) { + dev_err(chip->udev, "cannot turn %s auto GoodCRC: %d\n", + on ? "on" : "off", ret); + goto done; + } + ret = fusb302_pd_set_interrupts(chip, on); + if (ret) { + dev_err(chip->udev, "cannot turn %s pd interrupts: %d\n", + on ? "on" : "off", ret); + goto done; + } + dev_dbg(chip->udev, "set pd RX %s\n", on ? "on" : "off"); +done: + return ret; +} + +static const char * const typec_role_name[] = { + [TYPEC_SINK] = "Sink", + [TYPEC_SOURCE] = "Source", +}; + +static const char * const typec_data_role_name[] = { + [TYPEC_DEVICE] = "Device", + [TYPEC_HOST] = "Host", +}; + +static int tcpm_set_roles(struct tcpc_dev *dev, bool attached, + enum typec_role pwr, enum typec_data_role data) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + u8 switches1_mask = FUSB_REG_SWITCHES1_POWERROLE | + FUSB_REG_SWITCHES1_DATAROLE; + u8 switches1_data = 0x00; + + if (pwr == TYPEC_SOURCE) + switches1_data |= FUSB_REG_SWITCHES1_POWERROLE; + if (data == TYPEC_HOST) + switches1_data |= FUSB_REG_SWITCHES1_DATAROLE; + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1, + switches1_mask, switches1_data); + if (ret) { + dev_err(chip->udev, "unable to set pd header %s, %s, ret=%d\n", + typec_role_name[pwr], typec_data_role_name[data], ret); + goto done; + } + dev_dbg(chip->udev, "pd header : %s, %s\n", typec_role_name[pwr], + typec_data_role_name[data]); +done: + + return ret; +} + +static int tcpm_start_toggling(struct tcpc_dev *dev, + enum typec_port_type port_type, + enum typec_cc_status cc) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + enum toggling_mode mode = TOGGLING_MODE_OFF; + int ret = 0; + + switch (port_type) { + case TYPEC_PORT_SRC: + mode = TOGGLING_MODE_SRC; + break; + case TYPEC_PORT_SNK: + mode = TOGGLING_MODE_SNK; + break; + case TYPEC_PORT_DRP: + mode = TOGGLING_MODE_DRP; + break; + } + + ret = fusb302_set_src_current(chip, cc_src_current[cc]); + if (ret) { + dev_err(chip->udev, "unable to set src current %s, ret=%d", + typec_cc_status_name[cc], ret); + goto done; + } + ret = fusb302_set_toggling(chip, mode); + if (ret) { + dev_err(chip->udev, "unable to start drp toggling: %d\n", ret); + goto done; + } + dev_info(chip->udev, "fusb302 start drp toggling\n"); +done: + + return ret; +} + +static int fusb302_pd_send_message(struct fusb302_chip *chip, + const struct pd_message *msg) +{ + int ret = 0; + u8 buf[40]; + u8 pos = 0; + int len; + + /* SOP tokens */ + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC1; + buf[pos++] = FUSB302_TKN_SYNC2; + + len = pd_header_cnt_le(msg->header) * 4; + /* plug 2 for header */ + len += 2; + if (len > 0x1F) { + dev_err(chip->udev, "PD message too long %d (incl. header)", len); + return -EINVAL; + } + /* packsym tells the FUSB302 chip that the next X bytes are payload */ + buf[pos++] = FUSB302_TKN_PACKSYM | (len & 0x1F); + memcpy(&buf[pos], &msg->header, sizeof(msg->header)); + pos += sizeof(msg->header); + + len -= 2; + memcpy(&buf[pos], msg->payload, len); + pos += len; + + /* CRC */ + buf[pos++] = FUSB302_TKN_JAMCRC; + /* EOP */ + buf[pos++] = FUSB302_TKN_EOP; + /* turn tx off after sending message */ + buf[pos++] = FUSB302_TKN_TXOFF; + /* start transmission */ + buf[pos++] = FUSB302_TKN_TXON; + + ret = fusb302_i2c_block_write(chip, FUSB_REG_FIFOS, pos, buf); + if (ret) + return ret; + dev_dbg(chip->udev, "Send PD message (header=0x%x len=%d)\n", msg->header, len); + + return ret; +} + +static int fusb302_pd_send_hardreset(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, + FUSB_REG_CONTROL3_SEND_HARDRESET); +} + +static const char * const transmit_type_name[] = { + [TCPC_TX_SOP] = "SOP", + [TCPC_TX_SOP_PRIME] = "SOP'", + [TCPC_TX_SOP_PRIME_PRIME] = "SOP''", + [TCPC_TX_SOP_DEBUG_PRIME] = "DEBUG'", + [TCPC_TX_SOP_DEBUG_PRIME_PRIME] = "DEBUG''", + [TCPC_TX_HARD_RESET] = "HARD_RESET", + [TCPC_TX_CABLE_RESET] = "CABLE_RESET", + [TCPC_TX_BIST_MODE_2] = "BIST_MODE_2", +}; + +static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type, + const struct pd_message *msg, unsigned int negotiated_rev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + + switch (type) { + case TCPC_TX_SOP: + /* nRetryCount 3 in P2.0 spec, whereas 2 in PD3.0 spec */ + ret = fusb302_enable_tx_auto_retries(chip, negotiated_rev > PD_REV20 ? + FUSB_REG_CONTROL3_N_RETRIES_2 : + FUSB_REG_CONTROL3_N_RETRIES_3); + if (ret) + dev_err(chip->udev, "cannot update retry count: %d\n", ret); + + ret = fusb302_pd_send_message(chip, msg); + if (ret) + dev_err(chip->udev, "cannot send PD message: %d\n", ret); + break; + case TCPC_TX_HARD_RESET: + ret = fusb302_pd_send_hardreset(chip); + if (ret) + dev_err(chip->udev, "cannot send hardreset: %d\n", ret); + break; + default: + dev_err(chip->udev, "type %s not supported", transmit_type_name[type]); + ret = -EINVAL; + } + + return ret; +} + +static enum typec_cc_status fusb302_bc_lvl_to_cc(u8 bc_lvl) +{ + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_1230_MAX) + return TYPEC_CC_RP_3_0; + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_600_1230) + return TYPEC_CC_RP_1_5; + if (bc_lvl == FUSB_REG_STATUS0_BC_LVL_200_600) + return TYPEC_CC_RP_DEF; + return TYPEC_CC_OPEN; +} + +static void fusb302_bc_lvl_handler(struct fusb302_chip *chip) +{ + int ret = 0; + u8 status0; + u8 bc_lvl; + enum typec_cc_status cc_status; + + if (!chip->intr_bc_lvl) { + dev_err(chip->udev, "BC_LVL interrupt is turned off, abort\n"); + goto done; + } + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret) + goto done; + + dev_dbg(chip->udev, "BC_LVL handler, status0 = 0x%02x\n", status0); + if (status0 & FUSB_REG_STATUS0_ACTIVITY) + dev_info(chip->udev, "CC activities detected, delay handling\n"); + bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK; + cc_status = fusb302_bc_lvl_to_cc(bc_lvl); + if (chip->cc_polarity == TYPEC_POLARITY_CC1) { + if (chip->cc1 != cc_status) { + dev_dbg(chip->udev, "cc1: %s -> %s\n", + typec_cc_status_name[chip->cc1], + typec_cc_status_name[cc_status]); + chip->cc1 = cc_status; + tcpm_cc_change(chip->tcpm_port); + } + } else { + if (chip->cc2 != cc_status) { + dev_dbg(chip->udev, "cc2: %s -> %s\n", + typec_cc_status_name[chip->cc2], + typec_cc_status_name[cc_status]); + chip->cc2 = cc_status; + tcpm_cc_change(chip->tcpm_port); + } + } + +done: + return; +} + +static void fusb302_interrupt_handle(struct fusb302_chip *chip); +static void fusb302_poll_event(struct tcpc_dev *dev) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + + fusb302_interrupt_handle(chip); +} + +static int fusb302_enter_low_power_mode(struct tcpc_dev *dev, + bool attached, bool pd_capable) +{ + struct fusb302_chip *chip = container_of(dev, struct fusb302_chip, + tcpc_dev); + int ret = 0; + unsigned int reg; + + ret = fusb302_mask_interrupt(chip); + if (ret) + return ret; + if (attached && pd_capable) + reg = FUSB_REG_POWER_PWR_MEDIUM; + else if (attached) + reg = FUSB_REG_POWER_PWR_LOW; + else + reg = 0; + + return fusb302_set_power_mode(chip, reg); +} + +static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev) +{ + fusb302_tcpc_dev->init = tcpm_init; + fusb302_tcpc_dev->get_vbus = tcpm_get_vbus; + fusb302_tcpc_dev->set_cc = tcpm_set_cc; + fusb302_tcpc_dev->get_cc = tcpm_get_cc; + fusb302_tcpc_dev->set_polarity = tcpm_set_polarity; + fusb302_tcpc_dev->set_vconn = tcpm_set_vconn; + fusb302_tcpc_dev->set_vbus = tcpm_set_vbus; + fusb302_tcpc_dev->set_pd_rx = tcpm_set_pd_rx; + fusb302_tcpc_dev->set_roles = tcpm_set_roles; + fusb302_tcpc_dev->start_toggling = tcpm_start_toggling; + fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit; + fusb302_tcpc_dev->poll_event = fusb302_poll_event; + fusb302_tcpc_dev->enter_low_power_mode = fusb302_enter_low_power_mode; +} + +static const char * const cc_polarity_name[] = { + [TYPEC_POLARITY_CC1] = "Polarity_CC1", + [TYPEC_POLARITY_CC2] = "Polarity_CC2", +}; + +static int fusb302_set_cc_polarity_and_pull(struct fusb302_chip *chip, + enum typec_cc_polarity cc_polarity, + bool pull_up, bool pull_down) +{ + int ret = 0; + u8 switches0_data = 0x00; + u8 switches1_mask = FUSB_REG_SWITCHES1_TXCC1_EN | + FUSB_REG_SWITCHES1_TXCC2_EN; + u8 switches1_data = 0x00; + + if (pull_down) + switches0_data |= FUSB_REG_SWITCHES0_CC1_PD_EN | + FUSB_REG_SWITCHES0_CC2_PD_EN; + + if (cc_polarity == TYPEC_POLARITY_CC1) { + switches0_data |= FUSB_REG_SWITCHES0_MEAS_CC1; + if (chip->vconn_on) + switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC2; + if (pull_up) + switches0_data |= FUSB_REG_SWITCHES0_CC1_PU_EN; + switches1_data = FUSB_REG_SWITCHES1_TXCC1_EN; + } else { + switches0_data |= FUSB_REG_SWITCHES0_MEAS_CC2; + if (chip->vconn_on) + switches0_data |= FUSB_REG_SWITCHES0_VCONN_CC1; + if (pull_up) + switches0_data |= FUSB_REG_SWITCHES0_CC2_PU_EN; + switches1_data = FUSB_REG_SWITCHES1_TXCC2_EN; + } + ret = fusb302_i2c_write(chip, FUSB_REG_SWITCHES0, switches0_data); + if (ret) + return ret; + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES1, + switches1_mask, switches1_data); + if (ret) + return ret; + chip->cc_polarity = cc_polarity; + + return ret; +} + +static int fusb302_handle_togdone_snk(struct fusb302_chip *chip, + u8 togdone_result) +{ + int ret = 0; + u8 status0; + u8 bc_lvl; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc_status_active, cc1, cc2; + + /* set polarity and pull_up, pull_down */ + cc_polarity = (togdone_result == FUSB_REG_STATUS1A_TOGSS_SNK1) ? + TYPEC_POLARITY_CC1 : TYPEC_POLARITY_CC2; + ret = fusb302_set_cc_polarity_and_pull(chip, cc_polarity, false, true); + if (ret) { + dev_err(chip->udev, "cannot set cc polarity %s, ret = %d\n", + cc_polarity_name[cc_polarity], ret); + return ret; + } + /* fusb302_set_cc_polarity() has set the correct measure block */ + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + return ret; + bc_lvl = status0 & FUSB_REG_STATUS0_BC_LVL_MASK; + cc_status_active = fusb302_bc_lvl_to_cc(bc_lvl); + /* restart toggling if the cc status on the active line is OPEN */ + if (cc_status_active == TYPEC_CC_OPEN) { + dev_info(chip->udev, "restart toggling as CC_OPEN detected\n"); + ret = fusb302_set_toggling(chip, chip->toggling_mode); + return ret; + } + /* update tcpm with the new cc value */ + cc1 = (cc_polarity == TYPEC_POLARITY_CC1) ? + cc_status_active : TYPEC_CC_OPEN; + cc2 = (cc_polarity == TYPEC_POLARITY_CC2) ? + cc_status_active : TYPEC_CC_OPEN; + if (chip->cc1 != cc1 || chip->cc2 != cc2) { + chip->cc1 = cc1; + chip->cc2 = cc2; + tcpm_cc_change(chip->tcpm_port); + } + /* turn off toggling */ + ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF); + if (ret) { + dev_err(chip->udev, "cannot set toggling mode off, ret=%d\n", ret); + return ret; + } + /* unmask bc_lvl interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, FUSB_REG_MASK_BC_LVL); + if (ret) { + dev_err(chip->udev, "cannot unmask bc_lcl irq, ret=%d\n", ret); + return ret; + } + chip->intr_bc_lvl = true; + dev_dbg(chip->udev, "detected cc1=%s, cc2=%s\n", + typec_cc_status_name[cc1], + typec_cc_status_name[cc2]); + + return ret; +} + +/* On error returns < 0, otherwise a typec_cc_status value */ +static int fusb302_get_src_cc_status(struct fusb302_chip *chip, + enum typec_cc_polarity cc_polarity, + enum typec_cc_status *cc) +{ + u8 ra_mda = ra_mda_value[chip->src_current_status]; + u8 rd_mda = rd_mda_value[chip->src_current_status]; + u8 switches0_data, status0; + int ret; + + /* Step 1: Set switches so that we measure the right CC pin */ + switches0_data = (cc_polarity == TYPEC_POLARITY_CC1) ? + FUSB_REG_SWITCHES0_CC1_PU_EN | FUSB_REG_SWITCHES0_MEAS_CC1 : + FUSB_REG_SWITCHES0_CC2_PU_EN | FUSB_REG_SWITCHES0_MEAS_CC2; + ret = fusb302_i2c_write(chip, FUSB_REG_SWITCHES0, switches0_data); + if (ret < 0) + return ret; + + fusb302_i2c_read(chip, FUSB_REG_SWITCHES0, &status0); + dev_dbg(chip->udev, "get_src_cc_status switches: 0x%0x", status0); + + /* Step 2: Set compararator volt to differentiate between Open and Rd */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret) + return ret; + + usleep_range(50, 100); + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret) + return ret; + + dev_dbg(chip->udev, "get_src_cc_status rd_mda status0: 0x%0x", status0); + if (status0 & FUSB_REG_STATUS0_COMP) { + *cc = TYPEC_CC_OPEN; + return 0; + } + + /* Step 3: Set compararator input to differentiate between Rd and Ra. */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, ra_mda); + if (ret) + return ret; + + usleep_range(50, 100); + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret) + return ret; + + dev_dbg(chip->udev, "get_src_cc_status ra_mda status0: 0x%0x", status0); + if (status0 & FUSB_REG_STATUS0_COMP) + *cc = TYPEC_CC_RD; + else + *cc = TYPEC_CC_RA; + + return 0; +} + +static int fusb302_handle_togdone_src(struct fusb302_chip *chip, + u8 togdone_result) +{ + /* + * - set polarity (measure cc, vconn, tx) + * - set pull_up, pull_down + * - set cc1, cc2, and update to tcpm_port + * - set I_COMP interrupt on + */ + int ret = 0; + u8 rd_mda = rd_mda_value[chip->src_current_status]; + enum toggling_mode toggling_mode = chip->toggling_mode; + enum typec_cc_polarity cc_polarity; + enum typec_cc_status cc1, cc2; + + /* + * The toggle-engine will stop in a src state if it sees either Ra or + * Rd. Determine the status for both CC pins, starting with the one + * where toggling stopped, as that is where the switches point now. + */ + if (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1) + ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC1, &cc1); + else + ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC2, &cc2); + if (ret) + return ret; + /* we must turn off toggling before we can measure the other pin */ + ret = fusb302_set_toggling(chip, TOGGLING_MODE_OFF); + if (ret) { + dev_err(chip->udev, "cannot set toggling mode off, ret=%d\n", ret); + return ret; + } + /* get the status of the other pin */ + if (togdone_result == FUSB_REG_STATUS1A_TOGSS_SRC1) + ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC2, &cc2); + else + ret = fusb302_get_src_cc_status(chip, TYPEC_POLARITY_CC1, &cc1); + if (ret) + return ret; + + /* determine polarity based on the status of both pins */ + if (cc1 == TYPEC_CC_RD && (cc2 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_RA)) { + cc_polarity = TYPEC_POLARITY_CC1; + } else if (cc2 == TYPEC_CC_RD && + (cc1 == TYPEC_CC_OPEN || cc1 == TYPEC_CC_RA)) { + cc_polarity = TYPEC_POLARITY_CC2; + } else { + dev_err(chip->udev, "unexpected CC status cc1=%s, cc2=%s, restarting toggling\n", + typec_cc_status_name[cc1], + typec_cc_status_name[cc2]); + return fusb302_set_toggling(chip, toggling_mode); + } + /* set polarity and pull_up, pull_down */ + ret = fusb302_set_cc_polarity_and_pull(chip, cc_polarity, true, false); + if (ret < 0) { + dev_err(chip->udev, "cannot set cc polarity %s, ret=%d\n", + cc_polarity_name[cc_polarity], ret); + return ret; + } + /* update tcpm with the new cc value */ + if (chip->cc1 != cc1 || chip->cc2 != cc2) { + chip->cc1 = cc1; + chip->cc2 = cc2; + tcpm_cc_change(chip->tcpm_port); + } + /* set MDAC to Rd threshold, and unmask I_COMP for unplug detection */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); + if (ret) + return ret; + /* unmask comp_chng interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASK, + FUSB_REG_MASK_COMP_CHNG); + if (ret) { + dev_err(chip->udev, "cannot unmask comp_chng irq, ret=%d\n", ret); + return ret; + } + chip->intr_comp_chng = true; + dev_dbg(chip->udev, "detected cc1=%s, cc2=%s\n", + typec_cc_status_name[cc1], + typec_cc_status_name[cc2]); + + return ret; +} + +static int fusb302_handle_togdone(struct fusb302_chip *chip) +{ + int ret = 0; + u8 status1a; + u8 togdone_result; + + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS1A, &status1a); + if (ret < 0) + return ret; + togdone_result = (status1a >> FUSB_REG_STATUS1A_TOGSS_POS) & + FUSB_REG_STATUS1A_TOGSS_MASK; + switch (togdone_result) { + case FUSB_REG_STATUS1A_TOGSS_SNK1: + case FUSB_REG_STATUS1A_TOGSS_SNK2: + return fusb302_handle_togdone_snk(chip, togdone_result); + case FUSB_REG_STATUS1A_TOGSS_SRC1: + case FUSB_REG_STATUS1A_TOGSS_SRC2: + return fusb302_handle_togdone_src(chip, togdone_result); + case FUSB_REG_STATUS1A_TOGSS_AA: + /* doesn't support */ + dev_err(chip->udev, "AudioAccessory not supported\n"); + fusb302_set_toggling(chip, chip->toggling_mode); + break; + default: + dev_err(chip->udev, "TOGDONE with an invalid state: %d\n", + togdone_result); + fusb302_set_toggling(chip, chip->toggling_mode); + break; + } + return ret; +} + +static int fusb302_pd_reset(struct fusb302_chip *chip) +{ + return fusb302_i2c_set_bits(chip, FUSB_REG_RESET, + FUSB_REG_RESET_PD_RESET); +} + +static int fusb302_pd_read_message(struct fusb302_chip *chip, + struct pd_message *msg) +{ + int ret = 0; + u8 token; + u8 crc[4]; + int len; + + /* first SOP token */ + ret = fusb302_i2c_read(chip, FUSB_REG_FIFOS, &token); + if (ret) + return ret; + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 2, + (u8 *)&msg->header); + if (ret) + return ret; + len = pd_header_cnt_le(msg->header) * 4; + /* add 4 to length to include the CRC */ + if (len > PD_MAX_PAYLOAD * 4) { + dev_err(chip->udev, "PD message too long %d\n", len); + return -EINVAL; + } + if (len > 0) { + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, len, + (u8 *)msg->payload); + if (ret) + return ret; + } + /* another 4 bytes to read CRC out */ + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 4, crc); + if (ret) + return ret; + dev_dbg(chip->udev, "Received PD message (header=0x%x len=%d)\n", msg->header, len); + + /* + * Check if we've read off a GoodCRC message. If so then indicate to + * TCPM that the previous transmission has completed. Otherwise we pass + * the received message over to TCPM for processing. + * + * We make this check here instead of basing the reporting decision on + * the IRQ event type, as it's possible for the chip to report the + * TX_SUCCESS and GCRCSENT events out of order on occasion, so we need + * to check the message type to ensure correct reporting to TCPM. + */ + if (!len && (pd_header_type_le(msg->header) == PD_CTRL_GOOD_CRC)) + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); + else + tcpm_pd_receive(chip->tcpm_port, msg); + + return ret; +} + +static void fusb302_interrupt_handle(struct fusb302_chip *chip) +{ + int ret = 0; + u8 interrupt; + u8 interrupta; + u8 interruptb; + u8 status0; + bool vbus_present; + bool comp_result; + bool intr_togdone; + bool intr_bc_lvl; + bool intr_comp_chng; + struct pd_message pd_msg; + + /* grab a snapshot of intr flags */ + intr_togdone = chip->intr_togdone; + intr_bc_lvl = chip->intr_bc_lvl; + intr_comp_chng = chip->intr_comp_chng; + + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPT, &interrupt); + if (ret) + return; + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTA, &interrupta); + if (ret) + return; + ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPTB, &interruptb); + if (ret) + return; + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret) + return; + + /* + * Since we are polling the IRQs, avoid printing messages when there + * no interrupts at all to avoid spamming the log. + */ + if (interrupt != 0 || interrupta != 0 || interruptb != 0) + dev_dbg(chip->udev, "IRQ: 0x%02x, a: 0x%02x, b: 0x%02x, status0: 0x%02x\n", + interrupt, interrupta, interruptb, status0); + + if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) { + vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK); + dev_dbg(chip->udev, "IRQ: VBUS_OK, vbus=%s\n", + vbus_present ? "On" : "Off"); + if (vbus_present != chip->vbus_present) { + chip->vbus_present = vbus_present; + tcpm_vbus_change(chip->tcpm_port); + } + } + + if ((interrupta & FUSB_REG_INTERRUPTA_TOGDONE) && intr_togdone) { + dev_dbg(chip->udev, "IRQ: TOGDONE\n"); + ret = fusb302_handle_togdone(chip); + if (ret) { + dev_err(chip->udev, "handle togdone error: %d\n", ret); + return; + } + } + + if ((interrupt & FUSB_REG_INTERRUPT_BC_LVL) && intr_bc_lvl) { + dev_dbg(chip->udev, "IRQ: BC_LVL, handler pending\n"); + fusb302_bc_lvl_handler(chip); + } + + if ((interrupt & FUSB_REG_INTERRUPT_COMP_CHNG) && intr_comp_chng) { + comp_result = !!(status0 & FUSB_REG_STATUS0_COMP); + dev_dbg(chip->udev, "IRQ: COMP_CHNG, comp=%s\n", + comp_result ? "true" : "false"); + if (comp_result) { + /* cc level > Rd_threshold, detach */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + tcpm_cc_change(chip->tcpm_port); + } + } + + if (interrupt & FUSB_REG_INTERRUPT_COLLISION) { + dev_dbg(chip->udev, "IRQ: PD collision\n"); + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED); + } + + if (interrupta & FUSB_REG_INTERRUPTA_RETRYFAIL) { + dev_dbg(chip->udev, "IRQ: PD retry failed\n"); + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_FAILED); + } + + if (interrupta & FUSB_REG_INTERRUPTA_HARDSENT) { + dev_dbg(chip->udev, "IRQ: PD hardreset sent\n"); + ret = fusb302_pd_reset(chip); + if (ret) { + dev_err(chip->udev, "cannot PD reset, ret=%d\n", ret); + return; + } + tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS); + } + + if (interrupta & FUSB_REG_INTERRUPTA_TX_SUCCESS) { + dev_dbg(chip->udev, "IRQ: PD tx success\n"); + ret = fusb302_pd_read_message(chip, &pd_msg); + if (ret) { + dev_err(chip->udev, "cannot read in PD message, ret=%d\n", ret); + return; + } + } + + if (interrupta & FUSB_REG_INTERRUPTA_HARDRESET) { + dev_dbg(chip->udev, "IRQ: PD received hardreset\n"); + ret = fusb302_pd_reset(chip); + if (ret) { + dev_err(chip->udev, "cannot PD reset, ret=%d\n", ret); + return; + } + tcpm_pd_hard_reset(chip->tcpm_port); + } + + if (interruptb & FUSB_REG_INTERRUPTB_GCRCSENT) { + dev_dbg(chip->udev, "IRQ: PD sent good CRC\n"); + ret = fusb302_pd_read_message(chip, &pd_msg); + if (ret) { + dev_err(chip->udev, "cannot read in PD message, ret=%d\n", ret); + return; + } + } +} + +static int fusb302_probe(struct udevice *dev) +{ + struct fusb302_chip *chip = dev_get_priv(dev); + + chip->udev = dev; + + chip->tcpc_dev.connector_node = dev_read_subnode(dev, "connector"); + if (!ofnode_valid(chip->tcpc_dev.connector_node)) { + dev_err(chip->udev, "'connector' node is not found\n"); + return -ENODEV; + } + + init_tcpc_dev(&chip->tcpc_dev); + + chip->tcpm_port = tcpm_port_init(dev, &chip->tcpc_dev); + if (IS_ERR(chip->tcpm_port)) { + dev_err(chip->udev, "failed to tcpm port init\n"); + return PTR_ERR(chip->tcpm_port); + } + + tcpm_poll_event(chip->tcpm_port); + + return 0; +} + +static int fusb302_get_voltage(struct udevice *dev) +{ + struct fusb302_chip *chip = dev_get_priv(dev); + + return tcpm_get_voltage(chip->tcpm_port); +} + +static int fusb302_get_current(struct udevice *dev) +{ + struct fusb302_chip *chip = dev_get_priv(dev); + + return tcpm_get_current(chip->tcpm_port); +} + +static const char *fusb302_get_state(struct udevice *dev) +{ + struct fusb302_chip *chip = dev_get_priv(dev); + + return tcpm_get_state(chip->tcpm_port); +} + +static struct dm_tcpm_ops fusb302_ops = { + .get_voltage = fusb302_get_voltage, + .get_current = fusb302_get_current, + .get_state = fusb302_get_state, +}; + +static const struct udevice_id fusb302_ids[] = { + { .compatible = "fcs,fusb302" }, + { } +}; + +U_BOOT_DRIVER(fusb302) = { + .name = "fusb302", + .id = UCLASS_TCPM, + .of_match = fusb302_ids, + .ops = &fusb302_ops, + .probe = fusb302_probe, + .priv_auto = sizeof(struct fusb302_chip), +}; diff --git a/drivers/usb/tcpm/fusb302_reg.h b/drivers/usb/tcpm/fusb302_reg.h new file mode 100644 index 000000000000..edc0e4b0f1e6 --- /dev/null +++ b/drivers/usb/tcpm/fusb302_reg.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2016-2017 Google, Inc + * + * Fairchild FUSB302 Type-C Chip Driver + */ + +#ifndef FUSB302_REG_H +#define FUSB302_REG_H + +#define FUSB_REG_DEVICE_ID 0x01 +#define FUSB_REG_SWITCHES0 0x02 +#define FUSB_REG_SWITCHES0_CC2_PU_EN BIT(7) +#define FUSB_REG_SWITCHES0_CC1_PU_EN BIT(6) +#define FUSB_REG_SWITCHES0_VCONN_CC2 BIT(5) +#define FUSB_REG_SWITCHES0_VCONN_CC1 BIT(4) +#define FUSB_REG_SWITCHES0_MEAS_CC2 BIT(3) +#define FUSB_REG_SWITCHES0_MEAS_CC1 BIT(2) +#define FUSB_REG_SWITCHES0_CC2_PD_EN BIT(1) +#define FUSB_REG_SWITCHES0_CC1_PD_EN BIT(0) +#define FUSB_REG_SWITCHES1 0x03 +#define FUSB_REG_SWITCHES1_POWERROLE BIT(7) +#define FUSB_REG_SWITCHES1_SPECREV1 BIT(6) +#define FUSB_REG_SWITCHES1_SPECREV0 BIT(5) +#define FUSB_REG_SWITCHES1_DATAROLE BIT(4) +#define FUSB_REG_SWITCHES1_AUTO_GCRC BIT(2) +#define FUSB_REG_SWITCHES1_TXCC2_EN BIT(1) +#define FUSB_REG_SWITCHES1_TXCC1_EN BIT(0) +#define FUSB_REG_MEASURE 0x04 +#define FUSB_REG_MEASURE_MDAC5 BIT(7) +#define FUSB_REG_MEASURE_MDAC4 BIT(6) +#define FUSB_REG_MEASURE_MDAC3 BIT(5) +#define FUSB_REG_MEASURE_MDAC2 BIT(4) +#define FUSB_REG_MEASURE_MDAC1 BIT(3) +#define FUSB_REG_MEASURE_MDAC0 BIT(2) +#define FUSB_REG_MEASURE_VBUS BIT(1) +#define FUSB_REG_MEASURE_XXXX5 BIT(0) +#define FUSB_REG_CONTROL0 0x06 +#define FUSB_REG_CONTROL0_TX_FLUSH BIT(6) +#define FUSB_REG_CONTROL0_INT_MASK BIT(5) +#define FUSB_REG_CONTROL0_HOST_CUR_MASK (0xC) +#define FUSB_REG_CONTROL0_HOST_CUR_HIGH (0xC) +#define FUSB_REG_CONTROL0_HOST_CUR_MED (0x8) +#define FUSB_REG_CONTROL0_HOST_CUR_DEF (0x4) +#define FUSB_REG_CONTROL0_TX_START BIT(0) +#define FUSB_REG_CONTROL1 0x07 +#define FUSB_REG_CONTROL1_ENSOP2DB BIT(6) +#define FUSB_REG_CONTROL1_ENSOP1DB BIT(5) +#define FUSB_REG_CONTROL1_BIST_MODE2 BIT(4) +#define FUSB_REG_CONTROL1_RX_FLUSH BIT(2) +#define FUSB_REG_CONTROL1_ENSOP2 BIT(1) +#define FUSB_REG_CONTROL1_ENSOP1 BIT(0) +#define FUSB_REG_CONTROL2 0x08 +#define FUSB_REG_CONTROL2_MODE BIT(1) +#define FUSB_REG_CONTROL2_MODE_MASK (0x6) +#define FUSB_REG_CONTROL2_MODE_DFP (0x6) +#define FUSB_REG_CONTROL2_MODE_UFP (0x4) +#define FUSB_REG_CONTROL2_MODE_DRP (0x2) +#define FUSB_REG_CONTROL2_MODE_NONE (0x0) +#define FUSB_REG_CONTROL2_TOGGLE BIT(0) +#define FUSB_REG_CONTROL3 0x09 +#define FUSB_REG_CONTROL3_SEND_HARDRESET BIT(6) +#define FUSB_REG_CONTROL3_BIST_TMODE BIT(5) /* 302B Only */ +#define FUSB_REG_CONTROL3_AUTO_HARDRESET BIT(4) +#define FUSB_REG_CONTROL3_AUTO_SOFTRESET BIT(3) +#define FUSB_REG_CONTROL3_N_RETRIES BIT(1) +#define FUSB_REG_CONTROL3_N_RETRIES_MASK (0x6) +#define FUSB_REG_CONTROL3_N_RETRIES_3 (0x6) +#define FUSB_REG_CONTROL3_N_RETRIES_2 (0x4) +#define FUSB_REG_CONTROL3_N_RETRIES_1 (0x2) +#define FUSB_REG_CONTROL3_AUTO_RETRY BIT(0) +#define FUSB_REG_MASK 0x0A +#define FUSB_REG_MASK_VBUSOK BIT(7) +#define FUSB_REG_MASK_ACTIVITY BIT(6) +#define FUSB_REG_MASK_COMP_CHNG BIT(5) +#define FUSB_REG_MASK_CRC_CHK BIT(4) +#define FUSB_REG_MASK_ALERT BIT(3) +#define FUSB_REG_MASK_WAKE BIT(2) +#define FUSB_REG_MASK_COLLISION BIT(1) +#define FUSB_REG_MASK_BC_LVL BIT(0) +#define FUSB_REG_POWER 0x0B +#define FUSB_REG_POWER_PWR BIT(0) +#define FUSB_REG_POWER_PWR_LOW 0x1 +#define FUSB_REG_POWER_PWR_MEDIUM 0x3 +#define FUSB_REG_POWER_PWR_HIGH 0x7 +#define FUSB_REG_POWER_PWR_ALL 0xF +#define FUSB_REG_RESET 0x0C +#define FUSB_REG_RESET_PD_RESET BIT(1) +#define FUSB_REG_RESET_SW_RESET BIT(0) +#define FUSB_REG_MASKA 0x0E +#define FUSB_REG_MASKA_OCP_TEMP BIT(7) +#define FUSB_REG_MASKA_TOGDONE BIT(6) +#define FUSB_REG_MASKA_SOFTFAIL BIT(5) +#define FUSB_REG_MASKA_RETRYFAIL BIT(4) +#define FUSB_REG_MASKA_HARDSENT BIT(3) +#define FUSB_REG_MASKA_TX_SUCCESS BIT(2) +#define FUSB_REG_MASKA_SOFTRESET BIT(1) +#define FUSB_REG_MASKA_HARDRESET BIT(0) +#define FUSB_REG_MASKB 0x0F +#define FUSB_REG_MASKB_GCRCSENT BIT(0) +#define FUSB_REG_STATUS0A 0x3C +#define FUSB_REG_STATUS0A_SOFTFAIL BIT(5) +#define FUSB_REG_STATUS0A_RETRYFAIL BIT(4) +#define FUSB_REG_STATUS0A_POWER BIT(2) +#define FUSB_REG_STATUS0A_RX_SOFT_RESET BIT(1) +#define FUSB_REG_STATUS0A_RX_HARD_RESET BIT(0) +#define FUSB_REG_STATUS1A 0x3D +#define FUSB_REG_STATUS1A_TOGSS BIT(3) +#define FUSB_REG_STATUS1A_TOGSS_RUNNING 0x0 +#define FUSB_REG_STATUS1A_TOGSS_SRC1 0x1 +#define FUSB_REG_STATUS1A_TOGSS_SRC2 0x2 +#define FUSB_REG_STATUS1A_TOGSS_SNK1 0x5 +#define FUSB_REG_STATUS1A_TOGSS_SNK2 0x6 +#define FUSB_REG_STATUS1A_TOGSS_AA 0x7 +#define FUSB_REG_STATUS1A_TOGSS_POS (3) +#define FUSB_REG_STATUS1A_TOGSS_MASK (0x7) +#define FUSB_REG_STATUS1A_RXSOP2DB BIT(2) +#define FUSB_REG_STATUS1A_RXSOP1DB BIT(1) +#define FUSB_REG_STATUS1A_RXSOP BIT(0) +#define FUSB_REG_INTERRUPTA 0x3E +#define FUSB_REG_INTERRUPTA_OCP_TEMP BIT(7) +#define FUSB_REG_INTERRUPTA_TOGDONE BIT(6) +#define FUSB_REG_INTERRUPTA_SOFTFAIL BIT(5) +#define FUSB_REG_INTERRUPTA_RETRYFAIL BIT(4) +#define FUSB_REG_INTERRUPTA_HARDSENT BIT(3) +#define FUSB_REG_INTERRUPTA_TX_SUCCESS BIT(2) +#define FUSB_REG_INTERRUPTA_SOFTRESET BIT(1) +#define FUSB_REG_INTERRUPTA_HARDRESET BIT(0) +#define FUSB_REG_INTERRUPTB 0x3F +#define FUSB_REG_INTERRUPTB_GCRCSENT BIT(0) +#define FUSB_REG_STATUS0 0x40 +#define FUSB_REG_STATUS0_VBUSOK BIT(7) +#define FUSB_REG_STATUS0_ACTIVITY BIT(6) +#define FUSB_REG_STATUS0_COMP BIT(5) +#define FUSB_REG_STATUS0_CRC_CHK BIT(4) +#define FUSB_REG_STATUS0_ALERT BIT(3) +#define FUSB_REG_STATUS0_WAKE BIT(2) +#define FUSB_REG_STATUS0_BC_LVL_MASK 0x03 +#define FUSB_REG_STATUS0_BC_LVL_0_200 0x0 +#define FUSB_REG_STATUS0_BC_LVL_200_600 0x1 +#define FUSB_REG_STATUS0_BC_LVL_600_1230 0x2 +#define FUSB_REG_STATUS0_BC_LVL_1230_MAX 0x3 +#define FUSB_REG_STATUS0_BC_LVL1 BIT(1) +#define FUSB_REG_STATUS0_BC_LVL0 BIT(0) +#define FUSB_REG_STATUS1 0x41 +#define FUSB_REG_STATUS1_RXSOP2 BIT(7) +#define FUSB_REG_STATUS1_RXSOP1 BIT(6) +#define FUSB_REG_STATUS1_RX_EMPTY BIT(5) +#define FUSB_REG_STATUS1_RX_FULL BIT(4) +#define FUSB_REG_STATUS1_TX_EMPTY BIT(3) +#define FUSB_REG_STATUS1_TX_FULL BIT(2) +#define FUSB_REG_INTERRUPT 0x42 +#define FUSB_REG_INTERRUPT_VBUSOK BIT(7) +#define FUSB_REG_INTERRUPT_ACTIVITY BIT(6) +#define FUSB_REG_INTERRUPT_COMP_CHNG BIT(5) +#define FUSB_REG_INTERRUPT_CRC_CHK BIT(4) +#define FUSB_REG_INTERRUPT_ALERT BIT(3) +#define FUSB_REG_INTERRUPT_WAKE BIT(2) +#define FUSB_REG_INTERRUPT_COLLISION BIT(1) +#define FUSB_REG_INTERRUPT_BC_LVL BIT(0) +#define FUSB_REG_FIFOS 0x43 + +/* Tokens defined for the FUSB302 TX FIFO */ +enum fusb302_txfifo_tokens { + FUSB302_TKN_TXON = 0xA1, + FUSB302_TKN_SYNC1 = 0x12, + FUSB302_TKN_SYNC2 = 0x13, + FUSB302_TKN_SYNC3 = 0x1B, + FUSB302_TKN_RST1 = 0x15, + FUSB302_TKN_RST2 = 0x16, + FUSB302_TKN_PACKSYM = 0x80, + FUSB302_TKN_JAMCRC = 0xFF, + FUSB302_TKN_EOP = 0x14, + FUSB302_TKN_TXOFF = 0xFE, +}; + +#endif