From nobody Sat Jul 25 03:05:25 2026 Received: from m16.mail.163.com (m16.mail.163.com [117.135.210.3]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2D13A34AB03; Mon, 20 Jul 2026 08:18:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=117.135.210.3 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784535505; cv=none; b=VDeKXrw2wyuICzWu7M+j23fNdqAXJrRLm6iJTvxK7Rp26RXTMARIfbPl/B29tvE3Frjik0ZBOrtMJVav0tWpfxwjdA6G4pW0gPG7l+vBZMb8L+91KiGMVfQqz4OMOI7qisqxJ8v7+nGL8RVTbrxOBmiEMDKrcgV3Tpi97fjPiv0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784535505; c=relaxed/simple; bh=kh+4forG9eyv2B/t196KzV1WdpdWQaSAkOWLP27ZN80=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=hz+X+qiz3G5FuVF+KrN4mx1NDu3IbVK3+sEwQdnldOUPhpU8bHp/7WnfwPSKJ93XnJR+okkB1w0BSi2M4G5CtaqbSv8nIuCEqB8n7LqzTjisDv8x+Qzd720pLDmW0PVeXu/1TEjUUBxjmhgIDQBVREup2PJmxwZzEYqFdtjgvcU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=LzdAj6g1; arc=none smtp.client-ip=117.135.210.3 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="LzdAj6g1" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:Date:Subject:MIME-Version:Content-Type: Message-Id:To; bh=IhXATI2C4JzWL8tShtQ2nZtOGBBOqoNx3sHCUlOGW3U=; b=LzdAj6g1I3JA12YDFFhkfiOjN0e+RNkmUh4J6Y0C3njfK+a8w+t+CVeuVN1mKm jaVfDBn8xLxdFbW5qrSEfIueLhvkPF/w8uczr8W8PCL78RSf52bD9uXc6NmbV9PF szyjVYNcJuhzlkhkCA5PEkC/o5cU+10+DoGUzyTCx4VJw= Received: from [127.0.1.1] (unknown []) by gzga-smtp-mtada-g0-3 (Coremail) with SMTP id _____wA3PReg2V1qcZ5iKg--.42916S3; Mon, 20 Jul 2026 16:17:40 +0800 (CST) From: Hongyang Zhao Date: Mon, 20 Jul 2026 16:17:11 +0800 Subject: [PATCH 1/3] dt-bindings: usb: Add Hynetek HUSB320 Type-C controller Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-husb320-v1-1-4a1e259d7dc8@163.com> References: <20260720-husb320-v1-0-4a1e259d7dc8@163.com> In-Reply-To: <20260720-husb320-v1-0-4a1e259d7dc8@163.com> To: Greg Kroah-Hartman , Heikki Krogerus , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Liam Girdwood , Mark Brown Cc: Roger Shimizu , linux-usb@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Hongyang Zhao X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784535459; l=5179; i=hongyang.zhao@163.com; s=20260127; h=from:subject:message-id; bh=kh+4forG9eyv2B/t196KzV1WdpdWQaSAkOWLP27ZN80=; b=mJUQwdiGMIm9ZMcBbsBXmZilbETueok5dNFqiRRo2KMud4VV+d9pfsdAUvZ+9NCEu7Y00JGVy Ht/scPTSuLqD859cgPtRRnJnkh2xHnXhH9PqmSbQ72rXHqZKfkzO2Bp X-Developer-Key: i=hongyang.zhao@163.com; a=ed25519; pk=D9yL5W9Zj0lPBDAq9gzY++1849VlXuTWAkROzZ88J/4= X-CM-TRANSID: _____wA3PReg2V1qcZ5iKg--.42916S3 X-Coremail-Antispam: 1Uf129KBjvJXoWxCF1xtrWruFW7Zr48uFyrXrb_yoWrXF1fpF ZxCrs3J3y0gryfuaykJF18u3WYyFWkC3Z5Cr17X34UKFs8Kas3t3yakry5Xa47GrykXa43 XFWDZF1xtwnrCaUanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDUYxBIdaVFxhVjvjDU0xZFpf9x07UU5rxUUUUU= X-CM-SenderInfo: 5krqw5hdqjh6xkdrqiywtou0bp/xtbCzwWRbmpd2aVfEQAA3w The HUSB320 is an autonomous USB Type-C Configuration Channel controller supporting source, sink, and dual-role port operation. Document its two I2C addresses, active-low interrupt, optional supplies and enable GPIO, and managed USB-C connector. Signed-off-by: Hongyang Zhao --- .../devicetree/bindings/usb/hynetek,husb320.yaml | 171 +++++++++++++++++= ++++ 1 file changed, 171 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/hynetek,husb320.yaml b/D= ocumentation/devicetree/bindings/usb/hynetek,husb320.yaml new file mode 100644 index 000000000000..b84b6b725e84 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/hynetek,husb320.yaml @@ -0,0 +1,171 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/hynetek,husb320.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Hynetek HUSB320 USB Type-C controller + +maintainers: + - Hongyang Zhao + +description: + The HUSB320 is an autonomous USB Type-C Configuration Channel controller. + It supports source, sink and dual-role port configurations, including + optional Try.SRC and Try.SNK role preferences. The controller reports ca= ble + attachment, orientation, advertised current and accessory detection over + I2C and an active-low interrupt. + +properties: + compatible: + const: hynetek,husb320 + + reg: + description: + 7-bit I2C address selected by the ADDR/ORIENT pin strap. Ground sele= cts + 0x21 and VDD selects 0x31. These correspond to the 8-bit write addre= sses + 0x42 and 0x62 shown in the datasheet. + enum: [0x21, 0x31] + + interrupts: + maxItems: 1 + + wakeup-source: true + + vdd-supply: + description: Power supply for the controller + + enable-gpios: + description: Active-low controller enable + maxItems: 1 + + connector: + type: object + $ref: /schemas/connector/usb-connector.yaml# + unevaluatedProperties: false + description: + USB Type-C connector managed by the controller. A dual-role data port + requires a High-Speed graph connection to a USB role-switch provider. + + properties: + compatible: + const: usb-c-connector + + try-power-role: + enum: [source, sink] + + required: + - compatible + - pd-disable + - power-role + - typec-power-opmode + + allOf: + - if: + properties: + power-role: + enum: [source, dual] + then: + required: + - vbus-supply + + - if: + properties: + power-role: + const: source + then: + properties: + data-role: + const: host + try-power-role: false + + - if: + properties: + power-role: + const: sink + then: + properties: + data-role: + const: device + try-power-role: false + + - if: + properties: + power-role: + const: dual + then: + properties: + data-role: + const: dual + + - if: + required: + - data-role + properties: + data-role: + const: dual + then: + anyOf: + - required: + - port + - required: + - ports + +required: + - compatible + - reg + - interrupts + - connector + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + typec@31 { + compatible =3D "hynetek,husb320"; + reg =3D <0x31>; + vdd-supply =3D <&vreg_3v3>; + enable-gpios =3D <&gpio 54 GPIO_ACTIVE_LOW>; + interrupts-extended =3D <&gpio 55 IRQ_TYPE_LEVEL_LOW>; + wakeup-source; + + connector { + compatible =3D "usb-c-connector"; + label =3D "USB-C"; + vbus-supply =3D <&usb_vbus>; + data-role =3D "dual"; + power-role =3D "dual"; + try-power-role =3D "sink"; + typec-power-opmode =3D "default"; + pd-disable; + + ports { + #address-cells =3D <1>; + #size-cells =3D <0>; + + port@0 { + reg =3D <0>; + + endpoint { + remote-endpoint =3D <&usb_role>; + }; + }; + + port@1 { + reg =3D <1>; + + endpoint { + remote-endpoint =3D <&usb_ss>; + }; + }; + }; + }; + }; + }; --=20 2.43.0 From nobody Sat Jul 25 03:05:25 2026 Received: from m16.mail.163.com (m16.mail.163.com [117.135.210.4]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E7F513CC7FA; Mon, 20 Jul 2026 08:18:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=117.135.210.4 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784535506; cv=none; b=hZn/6sj5eoF2DnZi5XUUu8gTbbVeCM8k9tse8lFClVWaxbYZmFlNGyk7hVGXGZZeW7yU3VVsm4IjuEwueKFiqHrTYET+M4HRteAJQG0penwigA8i0DkQP7/k91yGM7uKYnv7W6Zo8a3Iewi9pAag75t99RCSSRluJGfXIWCYVNU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784535506; c=relaxed/simple; bh=iLKf/Dd8LtIhYROa19iUYUDDiHHiH97m3EcHR2blTpQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=pdFUmkxOR3YIfzU0MO6KJvruXWc9II3QOKqSwTVKqtXU8c1JWDROGIGzdMy6/3qvwopvLvCEBMTlby7sJ8x9f4b/lv4T3jauu8W67RLExFhv9+xzgKKoec+hcUxOEfZ456ulhHPcawuWiCan3ySE35y0m7Y7J0bJky8MGs7ykrk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=QXhppxnO; arc=none smtp.client-ip=117.135.210.4 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="QXhppxnO" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:Date:Subject:MIME-Version:Content-Type: Message-Id:To; bh=R+WK8mHf/jHRkLgHyh94U1tjIzUzGwfXZQzzLWlUDis=; b=QXhppxnOk1FYbvCBagcwWr4DP7gWdTmT3lC6A0NQ7HJd/biLdpx7ijb+nFdcCl JlQ+0M6H5fN2rl9cplnuK1FvMMaxAyVmJwpSKCz3VT3L3FWkvhVE7PwvanCcXFmH 6WNbvR+l7hDdc8k0i8TPqX30yq3H4DD5qkVKZ/1otHU+s= Received: from [127.0.1.1] (unknown []) by gzga-smtp-mtada-g0-3 (Coremail) with SMTP id _____wA3PReg2V1qcZ5iKg--.42916S4; Mon, 20 Jul 2026 16:17:41 +0800 (CST) From: Hongyang Zhao Date: Mon, 20 Jul 2026 16:17:12 +0800 Subject: [PATCH 2/3] usb: typec: Add Hynetek HUSB320 Type-C controller driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-husb320-v1-2-4a1e259d7dc8@163.com> References: <20260720-husb320-v1-0-4a1e259d7dc8@163.com> In-Reply-To: <20260720-husb320-v1-0-4a1e259d7dc8@163.com> To: Greg Kroah-Hartman , Heikki Krogerus , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Liam Girdwood , Mark Brown Cc: Roger Shimizu , linux-usb@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Hongyang Zhao X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784535459; l=32998; i=hongyang.zhao@163.com; s=20260127; h=from:subject:message-id; bh=iLKf/Dd8LtIhYROa19iUYUDDiHHiH97m3EcHR2blTpQ=; b=NtbnKWsWLpF/Q3QkwRS8EajEID/VYwS06D3x0zvkrPfQZqamR2j14LiWml3Fca/Ob80WpZgrS 3JcTE7//zOMDiAtgT4Vz9i+g4/9PK+WIhqU9Ib1Fxg4WcinmFt7AZYQ X-Developer-Key: i=hongyang.zhao@163.com; a=ed25519; pk=D9yL5W9Zj0lPBDAq9gzY++1849VlXuTWAkROzZ88J/4= X-CM-TRANSID: _____wA3PReg2V1qcZ5iKg--.42916S4 X-Coremail-Antispam: 1Uf129KBjvAXoWfZw43XrykWr4xWw43WF47CFg_yoW5ur15Wo WSvr1fCr1rtr43Crs5Gr4Iqry0va15GrW3Kr4rurW7ua1UCr1qqF1rGw43J34xtrW5uFn3 Jrs3ArZ3WFsYkFyxn29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UbIYCTnIWIevJa73UjIFyTuYvjxUUuWLUUUUU X-CM-SenderInfo: 5krqw5hdqjh6xkdrqiywtou0bp/xtbC8gaRbmpd2aazRwAA33 The HUSB320 is an autonomous Configuration Channel controller which supports fixed source or sink operation and dual-role ports without USB Power Delivery. Add a Type-C class driver for attach and detach detection, orientation and Rp current reporting, platform VBUS control, USB role switching, Try.SRC and Try.SNK, and audio and debug accessories. Signed-off-by: Hongyang Zhao Acked-by: Heikki Krogerus --- drivers/usb/typec/Kconfig | 12 + drivers/usb/typec/Makefile | 1 + drivers/usb/typec/husb320.c | 1177 +++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 1190 insertions(+) diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index 2f80c2792dbd..22ef8bacacf7 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -88,6 +88,18 @@ config TYPEC_HD3SS3220 If you choose to build this driver as a dynamically linked module, the module will be called hd3ss3220.ko. =20 +config TYPEC_HUSB320 + tristate "Hynetek HUSB320 Type-C controller driver" + depends on I2C + depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH + select REGMAP_I2C + help + Say Y or M here if your system has a Hynetek HUSB320 autonomous + USB Type-C controller. + + If you choose to build this driver as a dynamically linked module, the + module will be called husb320.ko. + config TYPEC_STUSB160X tristate "STMicroelectronics STUSB160x Type-C controller driver" depends on USB_ROLE_SWITCH || !USB_ROLE_SWITCH diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index 8a6a1c663eb6..1b61c34d6e66 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_TYPEC_UCSI) +=3D ucsi/ obj-$(CONFIG_TYPEC_TPS6598X) +=3D tipd/ obj-$(CONFIG_TYPEC_ANX7411) +=3D anx7411.o obj-$(CONFIG_TYPEC_HD3SS3220) +=3D hd3ss3220.o +obj-$(CONFIG_TYPEC_HUSB320) +=3D husb320.o obj-$(CONFIG_TYPEC_STUSB160X) +=3D stusb160x.o obj-$(CONFIG_TYPEC_RT1719) +=3D rt1719.o obj-$(CONFIG_TYPEC_WUSB3801) +=3D wusb3801.o diff --git a/drivers/usb/typec/husb320.c b/drivers/usb/typec/husb320.c new file mode 100644 index 000000000000..fd9f949d9b52 --- /dev/null +++ b/drivers/usb/typec/husb320.c @@ -0,0 +1,1177 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hynetek HUSB320 USB Type-C controller driver + * + * Copyright (C) 2026 Hongyang Zhao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HUSB320_REG_DEVICE_ID 0x01 +#define HUSB320_REG_DEVICE_TYPE 0x02 +#define HUSB320_REG_PORT_ROLE 0x03 +#define HUSB320_REG_CONTROL 0x04 +#define HUSB320_REG_CONTROL1 0x05 +#define HUSB320_REG_MANUAL 0x09 +#define HUSB320_REG_RESET 0x0a +#define HUSB320_REG_MASK 0x0e +#define HUSB320_REG_MASK1 0x0f +#define HUSB320_REG_STATUS 0x11 +#define HUSB320_REG_TYPE 0x13 +#define HUSB320_REG_INTERRUPT 0x14 +#define HUSB320_REG_INTERRUPT1 0x15 + +#define HUSB320_DEVICE_ID_VERSION GENMASK(7, 4) +#define HUSB320_DEVICE_ID_REVISION GENMASK(3, 0) +#define HUSB320_DEVICE_ID_VERSION_1 1 +#define HUSB320_DEVICE_TYPE 0x03 + +#define HUSB320_PORT_ROLE_TRY GENMASK(5, 4) +#define HUSB320_PORT_ROLE_MODE GENMASK(2, 0) +#define HUSB320_PORT_ROLE_SOURCE BIT(0) +#define HUSB320_PORT_ROLE_SINK BIT(1) +#define HUSB320_PORT_ROLE_DRP BIT(2) +#define HUSB320_TRY_NONE 0 +#define HUSB320_TRY_SINK 1 +#define HUSB320_TRY_SOURCE 2 + +#define HUSB320_CONTROL_HOST_CURRENT GENMASK(2, 1) +#define HUSB320_CONTROL_INT_MASK BIT(0) +#define HUSB320_HOST_CURRENT_DEFAULT 1 +#define HUSB320_HOST_CURRENT_1_5A 2 +#define HUSB320_HOST_CURRENT_3_0A 3 + +#define HUSB320_CONTROL1_ENABLE BIT(3) + +#define HUSB320_MANUAL_ERROR_RECOVERY BIT(0) + +#define HUSB320_RESET_SW BIT(0) + +/* The datasheet defines the fault mask, but reserves the matching IRQ bit= . */ +#define HUSB320_MASK_FAULT BIT(5) +#define HUSB320_MASK_AUTOSINK BIT(3) +#define HUSB320_MASK1_FORCE GENMASK(2, 1) + +#define HUSB320_STATUS_ORIENTATION GENMASK(5, 4) +#define HUSB320_STATUS_ORIENTATION_CC1 1 +#define HUSB320_STATUS_ORIENTATION_CC2 2 +#define HUSB320_STATUS_CURRENT GENMASK(2, 1) +#define HUSB320_STATUS_ATTACHED BIT(0) + +#define HUSB320_TYPE_DEBUG_SOURCE BIT(6) +#define HUSB320_TYPE_DEBUG_SINK BIT(5) +#define HUSB320_TYPE_SINK BIT(4) +#define HUSB320_TYPE_SOURCE BIT(3) +#define HUSB320_TYPE_AUDIO_VBUS BIT(1) +#define HUSB320_TYPE_AUDIO BIT(0) + +#define HUSB320_INT_ORIENTATION BIT(6) +#define HUSB320_INT_VBUS_CHANGE BIT(4) +#define HUSB320_INT_AUTOSINK BIT(3) +#define HUSB320_INT_CURRENT_CHANGE BIT(2) +#define HUSB320_INT_DETACH BIT(1) +#define HUSB320_INT_ATTACH BIT(0) +#define HUSB320_INT_VALID (HUSB320_INT_ORIENTATION | \ + HUSB320_INT_VBUS_CHANGE | \ + HUSB320_INT_AUTOSINK | \ + HUSB320_INT_CURRENT_CHANGE | \ + HUSB320_INT_DETACH | \ + HUSB320_INT_ATTACH) +#define HUSB320_INT1_VALID GENMASK(2, 1) + +enum husb320_state { + HUSB320_STATE_UNATTACHED, + HUSB320_STATE_SOURCE, + HUSB320_STATE_SINK, + HUSB320_STATE_DEBUG_SOURCE, + HUSB320_STATE_DEBUG_SINK, + HUSB320_STATE_AUDIO, +}; + +struct husb320 { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *enable_gpio; + struct regulator *vbus; + struct usb_role_switch *role_sw; + struct typec_capability cap; + struct typec_port *port; + struct typec_partner *partner; + struct fwnode_handle *connector; + /* Serializes controller access and Type-C state updates. */ + struct mutex lock; + enum typec_port_type port_type; + enum typec_role preferred_role; + enum typec_pwr_opmode source_opmode; + enum husb320_state state; + enum usb_role usb_role; + int connector_mode; + bool has_data; + bool mode_valid; + bool shutting_down; + bool state_valid; + bool vbus_on; +}; + +static const struct regmap_config husb320_regmap_config =3D { + .reg_bits =3D 8, + .val_bits =3D 8, + .max_register =3D HUSB320_REG_INTERRUPT1, +}; + +static enum typec_role husb320_default_power_role(struct husb320 *husb) +{ + switch (husb->port_type) { + case TYPEC_PORT_SRC: + return TYPEC_SOURCE; + case TYPEC_PORT_SNK: + return TYPEC_SINK; + case TYPEC_PORT_DRP: + default: + if (husb->preferred_role =3D=3D TYPEC_SOURCE) + return TYPEC_SOURCE; + + return TYPEC_SINK; + } +} + +static enum typec_data_role husb320_default_data_role(struct husb320 *husb) +{ + switch (husb->cap.data) { + case TYPEC_PORT_DFP: + return TYPEC_HOST; + case TYPEC_PORT_UFP: + return TYPEC_DEVICE; + case TYPEC_PORT_DRD: + default: + return husb320_default_power_role(husb) =3D=3D TYPEC_SOURCE ? + TYPEC_HOST : TYPEC_DEVICE; + } +} + +static void husb320_set_unattached_roles(struct husb320 *husb) +{ + enum typec_role power_role =3D husb320_default_power_role(husb); + + typec_set_pwr_role(husb->port, power_role); + if (husb->has_data) + typec_set_data_role(husb->port, + husb320_default_data_role(husb)); + typec_set_pwr_opmode(husb->port, + power_role =3D=3D TYPEC_SOURCE ? + husb->source_opmode : TYPEC_PWR_MODE_USB); +} + +static int husb320_set_vbus(struct husb320 *husb, bool enable) +{ + int ret; + + if (!husb->vbus || husb->vbus_on =3D=3D enable) + return 0; + + if (enable) + ret =3D regulator_enable(husb->vbus); + else + ret =3D regulator_disable(husb->vbus); + if (ret) + return ret; + + husb->vbus_on =3D enable; + + return 0; +} + +static int husb320_set_mode(struct husb320 *husb, int mode) +{ + int ret; + + if (husb->mode_valid && husb->connector_mode =3D=3D mode) + return 0; + + ret =3D typec_set_mode(husb->port, mode); + if (ret) + return ret; + + husb->connector_mode =3D mode; + husb->mode_valid =3D true; + + return 0; +} + +static void husb320_unregister_partner(struct husb320 *husb) +{ + if (!husb->partner) + return; + + typec_unregister_partner(husb->partner); + husb->partner =3D NULL; +} + +static int husb320_register_partner(struct husb320 *husb, + enum typec_accessory accessory) +{ + struct typec_partner_desc desc =3D { + .accessory =3D accessory, + .usb_pd =3D false, + }; + + if (husb->partner) + return 0; + + husb->partner =3D typec_register_partner(husb->port, &desc); + if (IS_ERR(husb->partner)) { + int ret =3D PTR_ERR(husb->partner); + + husb->partner =3D NULL; + return ret; + } + + return 0; +} + +static int husb320_disconnect(struct husb320 *husb) +{ + int err =3D 0; + int ret; + + ret =3D husb320_set_vbus(husb, false); + if (ret) + err =3D ret; + + ret =3D usb_role_switch_set_role(husb->role_sw, USB_ROLE_NONE); + if (ret) { + if (!err) + err =3D ret; + } else { + husb->usb_role =3D USB_ROLE_NONE; + } + + ret =3D husb320_set_mode(husb, TYPEC_STATE_SAFE); + if (ret && !err) + err =3D ret; + + husb320_unregister_partner(husb); + + if (typec_get_orientation(husb->port) !=3D TYPEC_ORIENTATION_NONE) { + ret =3D typec_set_orientation(husb->port, TYPEC_ORIENTATION_NONE); + if (ret && !err) + err =3D ret; + } + + husb320_set_unattached_roles(husb); + + husb->state =3D HUSB320_STATE_UNATTACHED; + husb->state_valid =3D !err; + + return err; +} + +static int husb320_hw_disable(struct husb320 *husb); + +static void husb320_error_recovery(struct husb320 *husb) +{ + int cleanup_ret; + int disable_ret; + int recovery_ret; + + cleanup_ret =3D husb320_disconnect(husb); + recovery_ret =3D regmap_write(husb->regmap, HUSB320_REG_MANUAL, + HUSB320_MANUAL_ERROR_RECOVERY); + if (cleanup_ret) + dev_err_ratelimited(husb->dev, + "failed to clean up port: %d\n", cleanup_ret); + if (recovery_ret) + dev_err_ratelimited(husb->dev, + "failed to start error recovery: %d\n", + recovery_ret); + + if (!cleanup_ret && !recovery_ret) + return; + + husb->shutting_down =3D true; + disable_ret =3D husb320_hw_disable(husb); + if (disable_ret) + dev_err_ratelimited(husb->dev, + "failed to disable controller: %d\n", + disable_ret); +} + +static enum typec_pwr_opmode husb320_sink_opmode(unsigned int status) +{ + switch (FIELD_GET(HUSB320_STATUS_CURRENT, status)) { + case 2: + return TYPEC_PWR_MODE_1_5A; + case 3: + return TYPEC_PWR_MODE_3_0A; + case 0: + case 1: + default: + return TYPEC_PWR_MODE_USB; + } +} + +static enum typec_role husb320_state_power_role(enum husb320_state state) +{ + switch (state) { + case HUSB320_STATE_SOURCE: + case HUSB320_STATE_DEBUG_SOURCE: + case HUSB320_STATE_AUDIO: + return TYPEC_SOURCE; + case HUSB320_STATE_SINK: + case HUSB320_STATE_DEBUG_SINK: + case HUSB320_STATE_UNATTACHED: + default: + return TYPEC_SINK; + } +} + +static enum typec_accessory husb320_state_accessory(enum husb320_state sta= te) +{ + switch (state) { + case HUSB320_STATE_DEBUG_SOURCE: + case HUSB320_STATE_DEBUG_SINK: + return TYPEC_ACCESSORY_DEBUG; + case HUSB320_STATE_AUDIO: + return TYPEC_ACCESSORY_AUDIO; + default: + return TYPEC_ACCESSORY_NONE; + } +} + +static enum usb_role husb320_state_usb_role(struct husb320 *husb, + enum husb320_state state) +{ + if (!husb->has_data) + return USB_ROLE_NONE; + + if ((state =3D=3D HUSB320_STATE_SOURCE || + state =3D=3D HUSB320_STATE_DEBUG_SOURCE) && + husb->cap.data !=3D TYPEC_PORT_UFP) + return USB_ROLE_HOST; + + if ((state =3D=3D HUSB320_STATE_SINK || + state =3D=3D HUSB320_STATE_DEBUG_SINK) && + husb->cap.data !=3D TYPEC_PORT_DFP) + return USB_ROLE_DEVICE; + + return USB_ROLE_NONE; +} + +static int husb320_state_mode(enum husb320_state state) +{ + switch (state) { + case HUSB320_STATE_DEBUG_SOURCE: + case HUSB320_STATE_DEBUG_SINK: + return TYPEC_MODE_DEBUG; + case HUSB320_STATE_AUDIO: + return TYPEC_MODE_AUDIO; + case HUSB320_STATE_SOURCE: + case HUSB320_STATE_SINK: + return TYPEC_STATE_USB; + case HUSB320_STATE_UNATTACHED: + default: + return TYPEC_STATE_SAFE; + } +} + +static int husb320_apply_state(struct husb320 *husb, + enum husb320_state state, + enum typec_orientation orientation, + unsigned int status) +{ + enum typec_accessory accessory =3D husb320_state_accessory(state); + enum usb_role usb_role =3D husb320_state_usb_role(husb, state); + enum typec_pwr_opmode opmode; + enum typec_role power_role; + int ret; + + if (state =3D=3D HUSB320_STATE_UNATTACHED && husb->state_valid && + husb->state =3D=3D HUSB320_STATE_UNATTACHED && !husb->partner && + !husb->vbus_on && + husb->usb_role =3D=3D USB_ROLE_NONE) + return 0; + + if (state =3D=3D HUSB320_STATE_UNATTACHED) + return husb320_disconnect(husb); + + if (state !=3D husb->state && + (!husb->state_valid || + husb->state !=3D HUSB320_STATE_UNATTACHED)) { + ret =3D husb320_disconnect(husb); + if (ret) + return ret; + } + + if (typec_get_orientation(husb->port) !=3D orientation) { + ret =3D typec_set_orientation(husb->port, orientation); + if (ret) + return ret; + } + + /* Accessory states do not request the platform VBUS supply. */ + ret =3D husb320_set_vbus(husb, state =3D=3D HUSB320_STATE_SOURCE); + if (ret) + return ret; + + if (usb_role !=3D husb->usb_role) { + ret =3D usb_role_switch_set_role(husb->role_sw, usb_role); + if (ret) + return ret; + husb->usb_role =3D usb_role; + } + + ret =3D husb320_set_mode(husb, husb320_state_mode(state)); + if (ret) + return ret; + + power_role =3D husb320_state_power_role(state); + opmode =3D state =3D=3D HUSB320_STATE_SINK ? + husb320_sink_opmode(status) : + (power_role =3D=3D TYPEC_SOURCE ? husb->source_opmode : + TYPEC_PWR_MODE_USB); + + if (usb_role =3D=3D USB_ROLE_HOST) + typec_set_data_role(husb->port, TYPEC_HOST); + else if (usb_role =3D=3D USB_ROLE_DEVICE) + typec_set_data_role(husb->port, TYPEC_DEVICE); + typec_set_pwr_role(husb->port, power_role); + typec_set_pwr_opmode(husb->port, opmode); + + ret =3D husb320_register_partner(husb, accessory); + if (ret) { + husb->state_valid =3D false; + dev_err_ratelimited(husb->dev, + "failed to register partner: %d\n", ret); + return ret; + } + + husb->state =3D state; + husb->state_valid =3D true; + + return 0; +} + +static enum typec_orientation husb320_get_orientation(unsigned int status) +{ + switch (FIELD_GET(HUSB320_STATUS_ORIENTATION, status)) { + case HUSB320_STATUS_ORIENTATION_CC1: + return TYPEC_ORIENTATION_NORMAL; + case HUSB320_STATUS_ORIENTATION_CC2: + return TYPEC_ORIENTATION_REVERSE; + default: + return TYPEC_ORIENTATION_NONE; + } +} + +static enum husb320_state husb320_get_state(unsigned int status, + unsigned int type) +{ + if (!(status & HUSB320_STATUS_ATTACHED)) + return HUSB320_STATE_UNATTACHED; + + if (type & HUSB320_TYPE_DEBUG_SOURCE) + return HUSB320_STATE_DEBUG_SOURCE; + if (type & HUSB320_TYPE_DEBUG_SINK) + return HUSB320_STATE_DEBUG_SINK; + if (type & HUSB320_TYPE_SINK) + return HUSB320_STATE_SINK; + if (type & HUSB320_TYPE_SOURCE) + return HUSB320_STATE_SOURCE; + if (type & (HUSB320_TYPE_AUDIO_VBUS | HUSB320_TYPE_AUDIO)) + return HUSB320_STATE_AUDIO; + + return HUSB320_STATE_UNATTACHED; +} + +static int husb320_update_state(struct husb320 *husb) +{ + enum typec_orientation orientation; + enum husb320_state state; + unsigned int status; + unsigned int type; + int ret; + + ret =3D regmap_read(husb->regmap, HUSB320_REG_STATUS, &status); + if (ret) + return ret; + + ret =3D regmap_read(husb->regmap, HUSB320_REG_TYPE, &type); + if (ret) + return ret; + + state =3D husb320_get_state(status, type); + orientation =3D state =3D=3D HUSB320_STATE_UNATTACHED ? + TYPEC_ORIENTATION_NONE : husb320_get_orientation(status); + + ret =3D husb320_apply_state(husb, state, orientation, status); + if (!ret) + dev_dbg(husb->dev, "status=3D%#x type=3D%#x state=3D%u\n", + status, type, state); + + return ret; +} + +static irqreturn_t husb320_irq_thread(int irq, void *data) +{ + struct husb320 *husb =3D data; + unsigned int interrupt; + unsigned int interrupt1; + int disable_ret; + int ret; + + mutex_lock(&husb->lock); + if (husb->shutting_down) { + disable_irq_nosync(irq); + mutex_unlock(&husb->lock); + return IRQ_HANDLED; + } + + ret =3D regmap_read(husb->regmap, HUSB320_REG_INTERRUPT, &interrupt); + if (ret) + goto err; + + ret =3D regmap_read(husb->regmap, HUSB320_REG_INTERRUPT1, &interrupt1); + if (ret) + goto err; + + interrupt &=3D HUSB320_INT_VALID; + interrupt1 &=3D HUSB320_INT1_VALID; + if (!interrupt && !interrupt1) { + mutex_unlock(&husb->lock); + return IRQ_NONE; + } + + if (interrupt) { + ret =3D regmap_write(husb->regmap, HUSB320_REG_INTERRUPT, + interrupt); + if (ret) + goto err; + } + + if (interrupt1) { + ret =3D regmap_write(husb->regmap, HUSB320_REG_INTERRUPT1, + interrupt1); + if (ret) + goto err; + } + + if (interrupt & HUSB320_INT_DETACH) { + ret =3D husb320_disconnect(husb); + if (ret) + dev_err_ratelimited(husb->dev, + "failed to disconnect port: %d\n", + ret); + } + + ret =3D husb320_update_state(husb); + if (ret) { + dev_err_ratelimited(husb->dev, + "failed to update port state: %d\n", ret); + husb320_error_recovery(husb); + } + + mutex_unlock(&husb->lock); + + return IRQ_HANDLED; + +err: + dev_err_ratelimited(husb->dev, "failed to service interrupt: %d\n", + ret); + husb->shutting_down =3D true; + husb320_error_recovery(husb); + disable_ret =3D husb320_hw_disable(husb); + if (disable_ret) + dev_err_ratelimited(husb->dev, + "failed to disable controller: %d\n", + disable_ret); + disable_irq_nosync(irq); + mutex_unlock(&husb->lock); + + return IRQ_HANDLED; +} + +static unsigned int husb320_port_role(enum typec_port_type type) +{ + switch (type) { + case TYPEC_PORT_SRC: + return HUSB320_PORT_ROLE_SOURCE; + case TYPEC_PORT_SNK: + return HUSB320_PORT_ROLE_SINK; + case TYPEC_PORT_DRP: + default: + return HUSB320_PORT_ROLE_DRP; + } +} + +static unsigned int husb320_try_role(int role) +{ + switch (role) { + case TYPEC_SINK: + return HUSB320_TRY_SINK; + case TYPEC_SOURCE: + return HUSB320_TRY_SOURCE; + case TYPEC_NO_PREFERRED_ROLE: + default: + return HUSB320_TRY_NONE; + } +} + +static int husb320_port_type_set(struct typec_port *port, + enum typec_port_type type) +{ + struct husb320 *husb =3D typec_get_drvdata(port); + unsigned int port_role; + int ret; + + mutex_lock(&husb->lock); + if (husb->shutting_down) { + ret =3D -ESHUTDOWN; + goto out_unlock; + } + + port_role =3D husb320_port_role(type); + if (type =3D=3D TYPEC_PORT_DRP) + port_role |=3D FIELD_PREP(HUSB320_PORT_ROLE_TRY, + husb320_try_role(husb->preferred_role)); + + ret =3D husb320_disconnect(husb); + if (ret) { + husb320_error_recovery(husb); + goto out_unlock; + } + + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_PORT_ROLE, + HUSB320_PORT_ROLE_TRY | + HUSB320_PORT_ROLE_MODE, + port_role); + if (ret) { + husb320_error_recovery(husb); + goto out_unlock; + } + + husb->port_type =3D type; + husb320_set_unattached_roles(husb); +out_unlock: + mutex_unlock(&husb->lock); + + return ret; +} + +static int husb320_try_role_set(struct typec_port *port, int role) +{ + struct husb320 *husb =3D typec_get_drvdata(port); + int ret; + + mutex_lock(&husb->lock); + if (husb->shutting_down) { + ret =3D -ESHUTDOWN; + goto out_unlock; + } + + if (husb->port_type =3D=3D TYPEC_PORT_DRP) + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_PORT_ROLE, + HUSB320_PORT_ROLE_TRY, + FIELD_PREP(HUSB320_PORT_ROLE_TRY, + husb320_try_role(role))); + else + ret =3D 0; + if (!ret) + husb->preferred_role =3D role; +out_unlock: + mutex_unlock(&husb->lock); + + return ret; +} + +static const struct typec_operations husb320_typec_ops =3D { + .port_type_set =3D husb320_port_type_set, + .try_role =3D husb320_try_role_set, +}; + +static unsigned int husb320_host_current(enum typec_pwr_opmode opmode) +{ + switch (opmode) { + case TYPEC_PWR_MODE_1_5A: + return HUSB320_HOST_CURRENT_1_5A; + case TYPEC_PWR_MODE_3_0A: + return HUSB320_HOST_CURRENT_3_0A; + case TYPEC_PWR_MODE_USB: + default: + return HUSB320_HOST_CURRENT_DEFAULT; + } +} + +static int husb320_hw_init(struct husb320 *husb) +{ + unsigned int port_role; + unsigned int control; + int ret; + + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_RESET, + HUSB320_RESET_SW, HUSB320_RESET_SW); + if (ret) + return ret; + + /* Wait for stable status and I2C access after the soft reset. */ + msleep(100); + + control =3D HUSB320_CONTROL_INT_MASK | + FIELD_PREP(HUSB320_CONTROL_HOST_CURRENT, + husb320_host_current(husb->source_opmode)); + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_CONTROL, + HUSB320_CONTROL_INT_MASK | + HUSB320_CONTROL_HOST_CURRENT, + control); + if (ret) + return ret; + + port_role =3D husb320_port_role(husb->port_type); + if (husb->port_type =3D=3D TYPEC_PORT_DRP) + port_role |=3D FIELD_PREP(HUSB320_PORT_ROLE_TRY, + husb320_try_role(husb->preferred_role)); + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_PORT_ROLE, + HUSB320_PORT_ROLE_TRY | + HUSB320_PORT_ROLE_MODE, + port_role); + if (ret) + return ret; + + ret =3D regmap_write(husb->regmap, HUSB320_REG_MASK, + HUSB320_MASK_FAULT | HUSB320_MASK_AUTOSINK); + if (ret) + return ret; + + ret =3D regmap_write(husb->regmap, HUSB320_REG_MASK1, + HUSB320_MASK1_FORCE); + if (ret) + return ret; + + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_CONTROL1, + HUSB320_CONTROL1_ENABLE, + HUSB320_CONTROL1_ENABLE); + if (ret) + return ret; + + ret =3D regmap_write(husb->regmap, HUSB320_REG_INTERRUPT, + HUSB320_INT_VALID); + if (ret) + return ret; + + return regmap_write(husb->regmap, HUSB320_REG_INTERRUPT1, + HUSB320_INT1_VALID); +} + +static int husb320_hw_disable(struct husb320 *husb) +{ + int err =3D 0; + int ret; + + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_CONTROL, + HUSB320_CONTROL_INT_MASK, + HUSB320_CONTROL_INT_MASK); + if (ret) + err =3D ret; + + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_CONTROL1, + HUSB320_CONTROL1_ENABLE, 0); + if (ret && !err) + err =3D ret; + + return err; +} + +static int husb320_set_sink_mode(struct husb320 *husb) +{ + int ret; + + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_CONTROL, + HUSB320_CONTROL_INT_MASK, + HUSB320_CONTROL_INT_MASK); + if (ret) + return ret; + + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_PORT_ROLE, + HUSB320_PORT_ROLE_TRY | + HUSB320_PORT_ROLE_MODE, + HUSB320_PORT_ROLE_SINK); + if (ret) + return ret; + + return regmap_update_bits(husb->regmap, HUSB320_REG_CONTROL1, + HUSB320_CONTROL1_ENABLE, + HUSB320_CONTROL1_ENABLE); +} + +static int husb320_parse_connector(struct husb320 *husb) +{ + const char *opmode; + int ret; + + husb->connector =3D device_get_named_child_node(husb->dev, "connector"); + if (!husb->connector) + return -ENODEV; + + /* The connector is parsed as a fwnode and is not populated as a device. = */ + fw_devlink_purge_absent_suppliers(husb->connector); + + ret =3D typec_get_fw_cap(&husb->cap, husb->connector); + if (ret) + return ret; + + ret =3D fwnode_property_read_string(husb->connector, + "typec-power-opmode", &opmode); + if (ret) + return ret; + + ret =3D typec_find_pwr_opmode(opmode); + if (ret < 0) + return ret; + if (ret =3D=3D TYPEC_PWR_MODE_PD) + return -EINVAL; + + husb->source_opmode =3D ret; + husb->has_data =3D fwnode_property_present(husb->connector, "data-role"); + if (husb->has_data && + ((husb->cap.type =3D=3D TYPEC_PORT_SRC && + husb->cap.data !=3D TYPEC_PORT_DFP) || + (husb->cap.type =3D=3D TYPEC_PORT_SNK && + husb->cap.data !=3D TYPEC_PORT_UFP) || + (husb->cap.type =3D=3D TYPEC_PORT_DRP && + husb->cap.data !=3D TYPEC_PORT_DRD))) { + return dev_err_probe(husb->dev, -EINVAL, + "power and data roles do not match\n"); + } else if (!husb->has_data) { + if (husb->cap.type =3D=3D TYPEC_PORT_SNK) + husb->cap.data =3D TYPEC_PORT_UFP; + else if (husb->cap.type =3D=3D TYPEC_PORT_DRP) + husb->cap.data =3D TYPEC_PORT_DRD; + } + + husb->cap.revision =3D USB_TYPEC_REV_2_0; + husb->cap.accessory[0] =3D TYPEC_ACCESSORY_AUDIO; + husb->cap.accessory[1] =3D TYPEC_ACCESSORY_DEBUG; + husb->cap.orientation_aware =3D true; + husb->cap.driver_data =3D husb; + husb->cap.ops =3D &husb320_typec_ops; + husb->port_type =3D husb->cap.type; + husb->preferred_role =3D husb->cap.prefer_role; + + return 0; +} + +static int husb320_get_role_switch(struct husb320 *husb) +{ + int ret; + + if (!husb->has_data) + return 0; + + husb->role_sw =3D fwnode_usb_role_switch_get(husb->connector); + if (IS_ERR(husb->role_sw)) { + ret =3D PTR_ERR(husb->role_sw); + husb->role_sw =3D NULL; + if (ret =3D=3D -ENODEV && husb->cap.data !=3D TYPEC_PORT_DRD) + return 0; + + return dev_err_probe(husb->dev, ret, + "failed to get USB role switch\n"); + } + + if (!husb->role_sw && husb->cap.data =3D=3D TYPEC_PORT_DRD) + return dev_err_probe(husb->dev, -ENODEV, + "USB data role has no role switch\n"); + + return 0; +} + +static int husb320_get_vbus(struct husb320 *husb) +{ + struct device_node *node =3D to_of_node(husb->connector); + + if (node) + husb->vbus =3D devm_of_regulator_get_optional(husb->dev, node, "vbus"); + else + husb->vbus =3D devm_regulator_get_optional(husb->dev, "vbus"); + + if (IS_ERR(husb->vbus)) { + int ret =3D PTR_ERR(husb->vbus); + + husb->vbus =3D NULL; + if (ret !=3D -ENODEV) + return dev_err_probe(husb->dev, ret, + "failed to get VBUS supply\n"); + } + + if (husb->cap.type !=3D TYPEC_PORT_SNK && !husb->vbus) + return dev_err_probe(husb->dev, -ENODEV, + "source-capable port requires VBUS supply\n"); + + return 0; +} + +static void husb320_disable(void *data) +{ + struct husb320 *husb =3D data; + + gpiod_set_value_cansleep(husb->enable_gpio, 0); +} + +static int husb320_power_on(struct husb320 *husb) +{ + bool needs_delay =3D false; + int ret; + + ret =3D devm_regulator_get_enable_optional(husb->dev, "vdd"); + if (ret =3D=3D -ENODEV) { + ret =3D 0; + } else if (ret) { + return dev_err_probe(husb->dev, ret, + "failed to enable VDD supply\n"); + } else { + needs_delay =3D true; + } + + husb->enable_gpio =3D devm_gpiod_get_optional(husb->dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(husb->enable_gpio)) + return dev_err_probe(husb->dev, PTR_ERR(husb->enable_gpio), + "failed to enable controller\n"); + + if (husb->enable_gpio) { + ret =3D devm_add_action_or_reset(husb->dev, husb320_disable, husb); + if (ret) + return ret; + needs_delay =3D true; + } + + /* Wait for the controller to become I2C-accessible after power-on. */ + if (needs_delay) + msleep(100); + + return 0; +} + +static int husb320_check_device(struct husb320 *husb) +{ + unsigned int device_id; + unsigned int device_type; + int ret; + + ret =3D regmap_read(husb->regmap, HUSB320_REG_DEVICE_ID, &device_id); + if (ret) + return dev_err_probe(husb->dev, ret, + "failed to read device ID\n"); + + ret =3D regmap_read(husb->regmap, HUSB320_REG_DEVICE_TYPE, &device_type); + if (ret) + return dev_err_probe(husb->dev, ret, + "failed to read device type\n"); + + if (FIELD_GET(HUSB320_DEVICE_ID_VERSION, device_id) !=3D + HUSB320_DEVICE_ID_VERSION_1 || + device_type !=3D HUSB320_DEVICE_TYPE) + return dev_err_probe(husb->dev, -ENODEV, + "unsupported device ID %#x type %#x\n", + device_id, device_type); + + dev_dbg(husb->dev, "version %lu revision %lu\n", + FIELD_GET(HUSB320_DEVICE_ID_VERSION, device_id), + FIELD_GET(HUSB320_DEVICE_ID_REVISION, device_id)); + + return 0; +} + +static int husb320_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct husb320 *husb; + int ret; + + husb =3D devm_kzalloc(dev, sizeof(*husb), GFP_KERNEL); + if (!husb) + return -ENOMEM; + + husb->dev =3D dev; + husb->state =3D HUSB320_STATE_UNATTACHED; + husb->usb_role =3D USB_ROLE_NONE; + mutex_init(&husb->lock); + i2c_set_clientdata(client, husb); + + ret =3D husb320_power_on(husb); + if (ret) + return ret; + + husb->regmap =3D devm_regmap_init_i2c(client, &husb320_regmap_config); + if (IS_ERR(husb->regmap)) + return dev_err_probe(dev, PTR_ERR(husb->regmap), + "failed to initialize regmap\n"); + + ret =3D husb320_check_device(husb); + if (ret) + return ret; + + if (client->irq <=3D 0) + return dev_err_probe(dev, client->irq ?: -EINVAL, + "missing interrupt\n"); + + ret =3D husb320_parse_connector(husb); + if (ret) + goto err_put_connector; + + ret =3D husb320_get_role_switch(husb); + if (ret) + goto err_put_connector; + + ret =3D husb320_get_vbus(husb); + if (ret) + goto err_put_role; + + ret =3D husb320_hw_init(husb); + if (ret) + goto err_hw_disable; + + husb->port =3D typec_register_port(dev, &husb->cap); + if (IS_ERR(husb->port)) { + ret =3D PTR_ERR(husb->port); + goto err_hw_disable; + } + + ret =3D devm_request_threaded_irq(dev, client->irq, NULL, + husb320_irq_thread, IRQF_ONESHOT, + dev_name(dev), husb); + if (ret) + goto err_unregister_port; + + mutex_lock(&husb->lock); + ret =3D husb320_update_state(husb); + mutex_unlock(&husb->lock); + if (ret) + goto err_free_irq; + + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_CONTROL, + HUSB320_CONTROL_INT_MASK, 0); + if (ret) + goto err_free_irq; + + return 0; + +err_free_irq: + mutex_lock(&husb->lock); + husb->shutting_down =3D true; + mutex_unlock(&husb->lock); + devm_free_irq(dev, client->irq, husb); +err_unregister_port: + mutex_lock(&husb->lock); + husb->shutting_down =3D true; + husb320_disconnect(husb); + mutex_unlock(&husb->lock); + typec_unregister_port(husb->port); +err_hw_disable: + husb320_hw_disable(husb); +err_put_role: + usb_role_switch_put(husb->role_sw); +err_put_connector: + fwnode_handle_put(husb->connector); + + return ret; +} + +static void husb320_remove(struct i2c_client *client) +{ + struct husb320 *husb =3D i2c_get_clientdata(client); + int ret; + + mutex_lock(&husb->lock); + husb->shutting_down =3D true; + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_CONTROL, + HUSB320_CONTROL_INT_MASK, + HUSB320_CONTROL_INT_MASK); + mutex_unlock(&husb->lock); + if (ret) + dev_err(husb->dev, "failed to mask interrupts: %d\n", ret); + + devm_free_irq(husb->dev, client->irq, husb); + + mutex_lock(&husb->lock); + ret =3D husb320_disconnect(husb); + if (ret) + dev_err(husb->dev, "failed to disconnect port: %d\n", ret); + ret =3D husb320_hw_disable(husb); + if (ret) + dev_err(husb->dev, "failed to disable controller: %d\n", ret); + mutex_unlock(&husb->lock); + + typec_unregister_port(husb->port); + usb_role_switch_put(husb->role_sw); + fwnode_handle_put(husb->connector); +} + +static void husb320_shutdown(struct i2c_client *client) +{ + struct husb320 *husb =3D i2c_get_clientdata(client); + int ret; + + mutex_lock(&husb->lock); + husb->shutting_down =3D true; + ret =3D regmap_update_bits(husb->regmap, HUSB320_REG_CONTROL, + HUSB320_CONTROL_INT_MASK, + HUSB320_CONTROL_INT_MASK); + mutex_unlock(&husb->lock); + if (ret) + dev_err(husb->dev, "failed to mask interrupts: %d\n", ret); + + disable_irq(client->irq); + + mutex_lock(&husb->lock); + ret =3D husb320_disconnect(husb); + if (ret) { + dev_err(husb->dev, "failed to disconnect port: %d\n", ret); + } else if (husb->cap.type !=3D TYPEC_PORT_SRC) { + ret =3D husb320_set_sink_mode(husb); + if (!ret) + goto out_unlock; + + dev_err(husb->dev, "failed to configure shutdown mode: %d\n", ret); + } + + ret =3D husb320_hw_disable(husb); + if (ret) + dev_err(husb->dev, "failed to disable controller: %d\n", ret); + +out_unlock: + mutex_unlock(&husb->lock); +} + +static const struct of_device_id husb320_of_match[] =3D { + { .compatible =3D "hynetek,husb320" }, + { } +}; +MODULE_DEVICE_TABLE(of, husb320_of_match); + +static struct i2c_driver husb320_driver =3D { + .driver =3D { + .name =3D "husb320", + .of_match_table =3D husb320_of_match, + }, + .probe =3D husb320_probe, + .remove =3D husb320_remove, + .shutdown =3D husb320_shutdown, +}; +module_i2c_driver(husb320_driver); + +MODULE_AUTHOR("Hongyang Zhao "); +MODULE_DESCRIPTION("Hynetek HUSB320 USB Type-C controller driver"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Sat Jul 25 03:05:25 2026 Received: from m16.mail.163.com (m16.mail.163.com [117.135.210.3]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 45636396561; Mon, 20 Jul 2026 08:18:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=117.135.210.3 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784535507; cv=none; b=FOmkKFbDxPzpTais5vHIkEPoZQ+paPCLfWSrLpBKhVfrrIyrjR+lj8RQB3JNcaOWdNkSTE3hLq9JHHyCaoCVb/c9ipZ8kfJ1ThparmZrxyBl2E/fyVnUr4ZZNJv91UxH4BS6kVuxB9N8mpnCUQ4bQBu4I1MKjz/tO7xfpa76mAs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784535507; c=relaxed/simple; bh=p1wwbP5bwV3lDoRslyupb8RtdrRw71t4vI/wJBVdGb4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=LJLTdmSxFByKGdXplx7UQ69QKac/EhYp9Wfce1UDudmDny35c86oc3WpdInImBOzVKk3WNa6LF+WDo4OWmJ3QX0U06pCh1vT6Ds+aPfFsHveL7SCajXsk5UzV56hIcn670GAJziqn5VpMqDHaj2c8/GOw+ui4hvsfQOe16ZvBs0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com; spf=pass smtp.mailfrom=163.com; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b=oQ566gWt; arc=none smtp.client-ip=117.135.210.3 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=163.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=163.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=163.com header.i=@163.com header.b="oQ566gWt" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=163.com; s=s110527; h=From:Date:Subject:MIME-Version:Content-Type: Message-Id:To; bh=JHgAjjXPDLM5eHmdZJZ6d3x8pQYxUthF9UqqtcaQEOs=; b=oQ566gWtvgE6IOZjQqpXQnyOLSwXFrs5+gMMUrGFcF5t5EI8fy6r/UDxFnZkEz 8D8qDaW1iSQuYP2CX+o1zqtAnSDNg2CPofxy3S54gfGVIgcZEItQTsHbgEdJ8XNS cujysBGikEgsJWlFYfJl/B4yDsGijaWgx5yGF4dJw52C4= Received: from [127.0.1.1] (unknown []) by gzga-smtp-mtada-g0-3 (Coremail) with SMTP id _____wA3PReg2V1qcZ5iKg--.42916S5; Mon, 20 Jul 2026 16:17:42 +0800 (CST) From: Hongyang Zhao Date: Mon, 20 Jul 2026 16:17:13 +0800 Subject: [PATCH 3/3] MAINTAINERS: Add HUSB320 Type-C controller entry Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260720-husb320-v1-3-4a1e259d7dc8@163.com> References: <20260720-husb320-v1-0-4a1e259d7dc8@163.com> In-Reply-To: <20260720-husb320-v1-0-4a1e259d7dc8@163.com> To: Greg Kroah-Hartman , Heikki Krogerus , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Liam Girdwood , Mark Brown Cc: Roger Shimizu , linux-usb@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Hongyang Zhao X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1784535459; l=769; i=hongyang.zhao@163.com; s=20260127; h=from:subject:message-id; bh=p1wwbP5bwV3lDoRslyupb8RtdrRw71t4vI/wJBVdGb4=; b=3eRz+Z4YSRryMtE9LSX1d+8ZJSIpqYRPGGweGllVOMxc29thpR0XGJvE0qaGOvYsOQC/X1STD 7JTIFLpJ4T4AtWSoW9bi1v3L/wk4h2wHZA1ABUYR4ercABeD2Bta7/T X-Developer-Key: i=hongyang.zhao@163.com; a=ed25519; pk=D9yL5W9Zj0lPBDAq9gzY++1849VlXuTWAkROzZ88J/4= X-CM-TRANSID: _____wA3PReg2V1qcZ5iKg--.42916S5 X-Coremail-Antispam: 1Uf129KBjvdXoW7GFyDWw4Utr48AFy7trykAFb_yoW3CrX_u3 W8GFy8XF4rCF4fJ3yrAr9Iy34Yyw4ftF4xX3ZFqwsrArWDt3sYyr1ktasakwn5Wr4fuFWD XasxJFWvgr17ujkaLaAFLSUrUUUUjb8apTn2vfkv8UJUUUU8Yxn0WfASr-VFAUDa7-sFnT 9fnUUvcSsGvfC2KfnxnUUI43ZEXa7IUbD73JUUUUU== X-CM-SenderInfo: 5krqw5hdqjh6xkdrqiywtou0bp/xtbC8QeRbmpd2afBSAAA3J Add myself as maintainer for the Hynetek HUSB320 binding and driver. Signed-off-by: Hongyang Zhao --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 806bd2d80d15..d3390b1daa1a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12056,6 +12056,13 @@ L: linux-kernel@vger.kernel.org S: Maintained F: arch/x86/kernel/cpu/hygon.c =20 +HYNETEK HUSB320 TYPE-C DRIVER +M: Hongyang Zhao +L: linux-usb@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/usb/hynetek,husb320.yaml +F: drivers/usb/typec/husb320.c + HYNIX HI556 SENSOR DRIVER M: Sakari Ailus L: linux-media@vger.kernel.org --=20 2.43.0