From nobody Tue Dec 16 23:59:11 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 56927C7EE43 for ; Thu, 24 Aug 2023 15:32:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S242193AbjHXPba (ORCPT ); Thu, 24 Aug 2023 11:31:30 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58798 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S242204AbjHXPbM (ORCPT ); Thu, 24 Aug 2023 11:31:12 -0400 Received: from luna.linkmauve.fr (82-65-109-163.subs.proxad.net [82.65.109.163]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 925D619BA; Thu, 24 Aug 2023 08:31:10 -0700 (PDT) Received: by luna.linkmauve.fr (Postfix, from userid 1000) id 63F8C8CC7F8; Thu, 24 Aug 2023 17:31:09 +0200 (CEST) From: Emmanuel Gil Peyrot Cc: Emmanuel Gil Peyrot , azkali , Adam Jiang , CTCaer , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Derek Kiernan , Dragan Cvetic , Arnd Bergmann , Greg Kroah-Hartman , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 1/2] dt-bindings: misc: rohm,bm92txx: Add BM92Txx support Date: Thu, 24 Aug 2023 17:30:53 +0200 Message-ID: <20230824153059.212244-2-linkmauve@linkmauve.fr> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20230824153059.212244-1-linkmauve@linkmauve.fr> References: <20230824153059.212244-1-linkmauve@linkmauve.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable To: unlisted-recipients:; (no To-header on input) Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" The BM92T36 is used in the Nintendo Switch as its USB-C Power Delivery controller. Signed-off-by: Emmanuel Gil Peyrot --- .../bindings/misc/rohm,bm92txx.yaml | 67 +++++++++++++++++++ MAINTAINERS | 5 ++ 2 files changed, 72 insertions(+) create mode 100644 Documentation/devicetree/bindings/misc/rohm,bm92txx.yaml diff --git a/Documentation/devicetree/bindings/misc/rohm,bm92txx.yaml b/Doc= umentation/devicetree/bindings/misc/rohm,bm92txx.yaml new file mode 100644 index 000000000000..0322a7f096f0 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/rohm,bm92txx.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright (C) 2023 Emmanuel Gil Peyrot +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/misc/rohm,bm92txx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: BM92Txx USB-C Power Delivery Controller + +maintainers: + - Emmanuel Gil Peyrot + +properties: + compatible: + enum: + - rohm,bm92t10 + - rohm,bm92t20 + - rohm,bm92t30 + - rohm,bm92t36 + - rohm,bm92t50 + + reg: + maxItems: 1 + + rohm,dp-signal-toggle-on-resume: + type: boolean + description: | + Do a toggle on resume instead of disable in suspend and enable in re= sume, + because this also disables the LED effects. + + rohm,led-static-on-suspend: + type: boolean + description: Dim or breathing dock LED. + + rohm,dock-power-limit-disable: + type: boolean + description: Disable the power limit in dock mode. + + rohm,dp-alerts-enable: + type: boolean + description: Enable DisplayPort alerts. + + rohm,pd-5v-current-limit-ma: + $ref: /schemas/types.yaml#/definitions/uint32 + default: 2000 + description: Current limit in mA when voltage is 5V. + + rohm,pd-9v-current-limit-ma: + $ref: /schemas/types.yaml#/definitions/uint32 + default: 2000 + description: Current limit in mA when voltage is 9V. + + rohm,pd-12v-current-limit-ma: + $ref: /schemas/types.yaml#/definitions/uint32 + default: 1500 + description: Current limit in mA when voltage is 12V. + + rohm,pd-15v-current-limit-ma: + $ref: /schemas/types.yaml#/definitions/uint32 + default: 1200 + description: Current limit in mA when voltage is 15V. + +required: + - compatible + - reg + +additionalProperties: false diff --git a/MAINTAINERS b/MAINTAINERS index d590ce31aa72..cc100a02fa7b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18453,6 +18453,11 @@ F: include/linux/mfd/rohm-bd957x.h F: include/linux/mfd/rohm-generic.h F: include/linux/mfd/rohm-shared.h =20 +ROHM USB-C POWER DELIVERY CONTROLLERS +M: Emmanuel Gil Peyrot +S: Supported +F: Documentation/devicetree/bindings/misc/rohm,bm92txx.yaml + ROSE NETWORK LAYER M: Ralf Baechle L: linux-hams@vger.kernel.org --=20 2.42.0 From nobody Tue Dec 16 23:59:11 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3F348C83003 for ; Thu, 24 Aug 2023 15:32:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S242199AbjHXPbb (ORCPT ); Thu, 24 Aug 2023 11:31:31 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:58814 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S242206AbjHXPbR (ORCPT ); Thu, 24 Aug 2023 11:31:17 -0400 Received: from luna.linkmauve.fr (82-65-109-163.subs.proxad.net [82.65.109.163]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id F223ACEA; Thu, 24 Aug 2023 08:31:11 -0700 (PDT) Received: by luna.linkmauve.fr (Postfix, from userid 1000) id B93D98CC7FB; Thu, 24 Aug 2023 17:31:10 +0200 (CEST) From: Emmanuel Gil Peyrot Cc: azkali , Emmanuel Gil Peyrot , Adam Jiang , CTCaer , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Derek Kiernan , Dragan Cvetic , Arnd Bergmann , Greg Kroah-Hartman , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 2/2] misc: bm92txx: Add driver for the ROHM BM92Txx Date: Thu, 24 Aug 2023 17:30:54 +0200 Message-ID: <20230824153059.212244-3-linkmauve@linkmauve.fr> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20230824153059.212244-1-linkmauve@linkmauve.fr> References: <20230824153059.212244-1-linkmauve@linkmauve.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable To: unlisted-recipients:; (no To-header on input) Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" From: azkali This is used as the USB-C Power Delivery controller of the Nintendo Switch. Signed-off-by: Emmanuel Gil Peyrot Signed-off-by: azkali Signed-off-by: Adam Jiang Signed-off-by: CTCaer --- MAINTAINERS | 1 + drivers/misc/Kconfig | 11 + drivers/misc/Makefile | 1 + drivers/misc/bm92txx.c | 2403 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2416 insertions(+) create mode 100644 drivers/misc/bm92txx.c diff --git a/MAINTAINERS b/MAINTAINERS index cc100a02fa7b..fe80d7693944 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18457,6 +18457,7 @@ ROHM USB-C POWER DELIVERY CONTROLLERS M: Emmanuel Gil Peyrot S: Supported F: Documentation/devicetree/bindings/misc/rohm,bm92txx.yaml +F: drivers/misc/bm92txx.c =20 ROSE NETWORK LAYER M: Ralf Baechle diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 75e427f124b2..a2483819766a 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -561,6 +561,17 @@ config TPS6594_PFSM This driver can also be built as a module. If so, the module will be called tps6594-pfsm. =20 +config BM92TXX + tristate "Rohm Semiconductor BM92TXX USB Type-C Support" + depends on I2C=3Dy + help + Say yes here to support for Rohm Semiconductor BM92TXX. This is a USB + Type-C connection IC. This driver provies common support for power + negotiation, USB ID detection and Hot-plug-detection on Display Port. + + This driver can also be built as a module. If so, the module + will be called bm92txx. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index f2a4d1ff65d4..b334d9366eff 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -67,3 +67,4 @@ obj-$(CONFIG_TMR_MANAGER) +=3D xilinx_tmr_manager.o obj-$(CONFIG_TMR_INJECT) +=3D xilinx_tmr_inject.o obj-$(CONFIG_TPS6594_ESM) +=3D tps6594-esm.o obj-$(CONFIG_TPS6594_PFSM) +=3D tps6594-pfsm.o +obj-$(CONFIG_BM92TXX) +=3D bm92txx.o diff --git a/drivers/misc/bm92txx.c b/drivers/misc/bm92txx.c new file mode 100644 index 000000000000..b8f227787faa --- /dev/null +++ b/drivers/misc/bm92txx.c @@ -0,0 +1,2403 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * bm92txx.c + * + * Copyright (c) 2015-2017, NVIDIA CORPORATION, All Rights Reserved. + * Copyright (c) 2020-2021 CTCaer + * Copyright (c) 2023 Emmanuel Gil Peyrot + * + * Authors: + * Adam Jiang + * CTCaer + * Emmanuel Gil Peyrot + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Registers */ +#define ALERT_STATUS_REG 0x02 +#define STATUS1_REG 0x03 +#define STATUS2_REG 0x04 +#define COMMAND_REG 0x05 /* Send special command */ +#define CONFIG1_REG 0x06 /* Controller Configuration 1 */ +#define DEV_CAPS_REG 0x07 +#define READ_PDOS_SRC_REG 0x08 /* Data size: 28 */ +#define CONFIG2_REG 0x17 /* Controller Configuration 2 */ +#define DP_STATUS_REG 0x18 +#define DP_ALERT_EN_REG 0x19 +#define VENDOR_CONFIG_REG 0x1A /* Vendor Configuration 1 */ +#define AUTO_NGT_FIXED_REG 0x20 /* Data size: 4 */ +#define AUTO_NGT_BATT_REG 0x23 /* Data size: 4 */ +#define SYS_CONFIG1_REG 0x26 /* System Configuration 1 */ +#define SYS_CONFIG2_REG 0x27 /* System Configuration 2 */ +#define CURRENT_PDO_REG 0x28 /* Data size: 4 */ +#define CURRENT_RDO_REG 0x2B /* Data size: 4 */ +#define ALERT_ENABLE_REG 0x2E +#define SYS_CONFIG3_REG 0x2F /* System Configuration 3 */ +#define SET_RDO_REG 0x30 /* Data size: 4 */ +#define PDOS_SNK_CONS_REG 0x33 /* PDO Sink Consumer. Data size: 16 */ +#define PDOS_SRC_PROV_REG 0x3C /* PDO Source Provider. Data size: 28 */ +#define FW_TYPE_REG 0x4B +#define FW_REVISION_REG 0x4C +#define MAN_ID_REG 0x4D +#define DEV_ID_REG 0x4E +#define REV_ID_REG 0x4F +#define INCOMING_VDM_REG 0x50 /* Max data size: 28 */ +#define OUTGOING_VDM_REG 0x60 /* Max data size: 28 */ + +/* ALERT_STATUS_REG */ +#define ALERT_SNK_FAULT BIT(0) +#define ALERT_SRC_FAULT BIT(1) +#define ALERT_CMD_DONE BIT(2) +#define ALERT_PLUGPULL BIT(3) +#define ALERT_DP_EVENT BIT(6) +#define ALERT_DR_SWAP BIT(10) +#define ALERT_VDM_RECEIVED BIT(11) +#define ALERT_CONTRACT BIT(12) +#define ALERT_SRC_PLUGIN BIT(13) +#define ALERT_PDO BIT(14) + +/* STATUS1_REG */ +#define STATUS1_FAULT_MASK (3 << 0) +#define STATUS1_SPDSRC2 BIT(3) /* VBUS2 enabled */ +#define STATUS1_LASTCMD_SHIFT 4 +#define STATUS1_LASTCMD_MASK (7 << STATUS1_LASTCMD_SHIFT) +#define STATUS1_INSERT BIT(7) /* Cable inserted */ +#define STATUS1_DR_SHIFT 8 +#define STATUS1_DR_MASK (3 << STATUS1_DR_SHIFT) +#define STATUS1_VSAFE BIT(10) /* 0: No power, 1: VSAFE 5V or PDO */ +#define STATUS1_CSIDE BIT(11) /* Type-C Plug Side. 0: CC1 Side Val= id, 1: CC2 Side Valid */ +#define STATUS1_SRC_MODE BIT(12) /* 0: Sink Mode, 1: Source mode (OTG= ) */ +#define STATUS1_CMD_BUSY BIT(13) /* Command in progress */ +#define STATUS1_SPDSNK BIT(14) /* Sink mode */ +#define STATUS1_SPDSRC1 BIT(15) /* VBUS enabled */ + +#define LASTCMD_COMPLETE 0 +#define LASTCMD_ABORTED 2 +#define LASTCMD_INVALID 4 +#define LASTCMD_REJECTED 6 +#define LASTCMD_TERMINATED 7 + +#define DATA_ROLE_NONE 0 +#define DATA_ROLE_UFP 1 +#define DATA_ROLE_DFP 2 +#define DATA_ROLE_ACC 3 + +/* STATUS2_REG */ +#define STATUS2_PDOI_MASK BIT(3) +#define STATUS2_VCONN_ON BIT(9) +#define STATUS2_ACC_SHIFT 10 +#define STATUS2_ACC_MASK (3 << STATUS2_ACC_SHIFT) /* Accessory mode */ +#define STATUS2_EM_CABLE BIT(12) /* Electronically marked cable. Safe = for 1.3A */ +#define STATUS2_OTG_INSERT BIT(13) + +#define PDOI_SRC_OR_NO 0 +#define PDOI_SNK 1 + +#define ACC_DISABLED 0 +#define ACC_AUDIO 1 +#define ACC_DEBUG 2 +#define ACC_VCONN 3 + +/* DP_STATUS_REG */ +#define DP_STATUS_SNK_CONN BIT(1) +#define DP_STATUS_SIGNAL_ON BIT(7) +#define DP_STATUS_INSERT BIT(14) +#define DP_STATUS_HPD BIT(15) + +/* CONFIG1_REG */ +#define CONFIG1_AUTO_DR_SWAP BIT(1) +#define CONFIG1_SLEEP_REQUEST BIT(4) +#define CONFIG1_AUTONGTSNK_VAR_EN BIT(5) +#define CONFIG1_AUTONGTSNK_FIXED_EN BIT(6) +#define CONFIG1_AUTONGTSNK_EN BIT(7) +#define CONFIG1_AUTONGTSNK_BATT_EN BIT(8) +#define CONFIG1_VINOUT_DELAY_EN BIT(9) /* VIN/VOUT turn on delay ena= ble */ +#define CONFIG1_VINOUT_TIME_ON_SHIFT 10 /* VIN/VOUT turn on delay */ +#define CONFIG1_VINOUT_TIME_ON_MASK (3 << CONFIG1_VINOUT_TIME_ON_SHIFT) +#define CONFIG1_SPDSRC_SHIFT 14 +#define CONFIG1_SPDSRC_MASK (3 << CONFIG1_SPDSRC_SHIFT) + +#define VINOUT_TIME_ON_1MS 0 +#define VINOUT_TIME_ON_5MS 1 +#define VINOUT_TIME_ON_10MS 2 +#define VINOUT_TIME_ON_20MS 3 + +#define SPDSRC12_ON 0 /* SPDSRC 1/2 on */ +#define SPDSRC2_ON 1 +#define SPDSRC1_ON 2 +#define SPDSRC12_OFF 3 /* SPDSRC 1/2 off */ + +/* CONFIG2_REG */ +#define CONFIG2_PR_SWAP_MASK (3 << 0) +#define CONFIG2_DR_SWAP_SHIFT 2 +#define CONFIG2_DR_SWAP_MASK (3 << CONFIG2_DR_SWAP_SHIFT) +#define CONFIG2_VSRC_SWAP BIT(4) /* VCONN source swap. 0: Reject, = 1: Accept */ +#define CONFIG2_NO_USB_SUSPEND BIT(5) +#define CONFIG2_EXT_POWERED BIT(7) +#define CONFIG2_TYPEC_AMP_SHIFT 8 +#define CONFIG2_TYPEC_AMP_MASK (3 << CONFIG2_TYPEC_AMP_SHIFT) + +#define PR_SWAP_ALWAYS_REJECT 0 +#define PR_SWAP_ACCEPT_SNK_REJECT_SRC 1 /* Accept when power sink */ +#define PR_SWAP_ACCEPT_SRC_REJECT_SNK 2 /* Accept when power source */ +#define PR_SWAP_ALWAYS_ACCEPT 3 + +#define DR_SWAP_ALWAYS_REJECT 0 +#define DR_SWAP_ACCEPT_UFP_REJECT_DFP 1 /* Accept when device */ +#define DR_SWAP_ACCEPT_DFP_REJECT_UFP 2 /* Accept when host */ +#define DR_SWAP_ALWAYS_ACCEPT 3 + +#define TYPEC_AMP_0_5A_5V 0 +#define TYPEC_AMP_1_5A_5V 1 +#define TYPEC_AMP_3_0A_5V 2 + +/* SYS_CONFIG1_REG */ +#define SYS_CONFIG1_PLUG_MASK (0xF << 0) +#define SYS_CONFIG1_USE_AUTONGT BIT(6) +#define SYS_CONFIG1_PDO_SNK_CONS BIT(8) +#define SYS_CONFIG1_PDO_SNK_CONS_SHIFT 9 /* Number of Sink PDOs */ +#define SYS_CONFIG1_PDO_SNK_CONS_MASK (7 << SYS_CONFIG1_PDO_SNK_CONS_SHI= FT) +#define SYS_CONFIG1_PDO_SRC_PROV BIT(12) +#define SYS_CONFIG1_DOUT4_SHIFT 13 +#define SYS_CONFIG1_DOUT4_MASK (3 << SYS_CONFIG1_DOUT4_SHIFT) +#define SYS_CONFIG1_WAKE_ON_INSERT BIT(15) + +#define PLUG_TYPE_C 9 +#define PLUG_TYPE_C_3A 10 +#define PLUG_TYPE_C_5A 11 + +#define DOUT4_PDO4 0 +#define DOUT4_PDO5 1 +#define DOUT4_PDO6 2 +#define DOUT4_PDO7 3 + +/* SYS_CONFIG2_REG */ +#define SYS_CONFIG2_NO_COMM_UFP BIT(0) /* Force no USB comms Capa= ble UFP */ +#define SYS_CONFIG2_NO_COMM_DFP BIT(1) /* Force no USB comms Capa= ble DFP */ +#define SYS_CONFIG2_NO_COMM_ON_NO_BATT BIT(2) /* Force no USB comms on d= ead battery */ +#define SYS_CONFIG2_AUTO_SPDSNK_EN BIT(6) /* Enable SPDSNK without S= YS_RDY */ +#define SYS_CONFIG2_BST_EN BIT(8) +#define SYS_CONFIG2_PDO_SRC_PROV_SHIFT 9 /* Number of Source provisioned= PDOs */ +#define SYS_CONFIG2_PDO_SRC_PROV_MASK (7 << SYS_CONFIG2_PDO_SRC_PROV_SH= IFT) + +/* VENDOR_CONFIG_REG */ +#define VENDOR_CONFIG_OCP_DISABLE BIT(2) /* Disable Over-current protecti= on */ + +/* DEV_CAPS_REG */ +#define DEV_CAPS_ALERT_STS BIT(0) +#define DEV_CAPS_ALERT_EN BIT(1) +#define DEV_CAPS_VIN_EN BIT(2) +#define DEV_CAPS_VOUT_EN0 BIT(3) +#define DEV_CAPS_SPDSRC2 BIT(4) +#define DEV_CAPS_SPDSRC1 BIT(5) +#define DEV_CAPS_SPRL BIT(6) +#define DEV_CAPS_SPDSNK BIT(7) +#define DEV_CAPS_OCP BIT(8) /* Over current protection */ +#define DEV_CAPS_DP_SRC BIT(9) /* DisplayPort capable Source */ +#define DEV_CAPS_DP_SNK BIT(10) /* DisplayPort capable Sink */ +#define DEV_CAPS_VOUT_EN1 BIT(11) + +/* COMMAND_REG command list */ +#define ABORT_LASTCMD_SENT_CMD 0x0101 +#define PR_SWAP_CMD 0x0303 /* Power Role swap request */ +#define PS_RDY_CMD 0x0505 /* Power supply ready */ +#define GET_SRC_CAP_CMD 0x0606 /* Get Source capabilities */ +#define SEND_RDO_CMD 0x0707 +#define PD_HARD_RST_CMD 0x0808 /* Hard reset link */ +#define STORE_SYSCFG_CMD 0x0909 /* Store system configuration */ +#define UPDATE_PDO_SRC_PROV_CMD 0x0A0A /* Update PDO Source Provider */ +#define GET_SNK_CAP_CMD 0x0B0B /* Get Sink capabilities */ +#define STORE_CFG2_CMD 0x0C0C /* Store controller configuration= 2 */ +#define SYS_RESET_CMD 0x0D0D /* Full USB-PD IC reset */ +#define RESET_PS_RDY_CMD 0x1010 /* Reset power supply ready */ +#define SEND_VDM_CMD 0x1111 /* Send VMD SOP */ +#define SEND_VDM_1_CMD 0x1212 /* Send VMD SOP' EM cable near e= nd */ +#define SEND_VDM_2_CMD 0x1313 /* Send VMD SOP'' EM cable far en= d */ +#define SEND_VDM_1_DBG_CMD 0x1414 /* Send VMD SOP' debug */ +#define SEND_VDM_2_DBG_CMD 0x1515 /* Send VMD SOP'' debug */ +#define ACCEPT_VDM_CMD 0x1616 /* Receive VDM */ +#define MODE_ENTERED_CMD 0x1717 /* Alt mode entered */ +#define DR_SWAP_CMD 0x1818 /* Data Role swap request */ +#define VC_SWAP_CMD 0x1919 /* VCONN swap request */ +#define BIST_REQ_CARR_M2_CMD 0x2424 /* Request BIST carrier mode 2 */ +#define BIST_TEST_DATA_CMD 0x2B2B /* Send BIST test data */ +#define PD_SOFT_RST_CMD 0x2C2C /* Reset power and get new PDO/Co= ntract */ +#define BIST_CARR_M2_CONT_STR_CMD 0x2F2F /* Send BIST carrier mode 2 conti= nuous string */ +#define DP_ENTER_MODE_CMD 0x3131 /* Discover DP Alt mode */ +#define DP_STOP_CMD 0x3232 /* Cancel DP Alt mode discovery */ +#define START_HPD_CMD 0x3434 /* Start handling HPD */ +#define DP_CFG_AND_START_HPD_CMD 0x3636 /* Configure and enter selected D= P Alt mode and start + * handling HPD + */ +#define STOP_HPD_CMD 0x3939 /* Stop handling HPD */ +#define STOP_HPD_EXIT_DP_CMD 0x3B3B /* Stop handling HPD and exit DP = Alt mode */ + +/* General defines */ +#define PDO_TYPE_FIXED 0 +#define PDO_TYPE_BATT 1 +#define PDO_TYPE_VAR 2 + +#define PDO_INFO_DR_DATA (1 << 5) +#define PDO_INFO_USB_COMM (1 << 6) +#define PDO_INFO_EXT_POWER (1 << 7) +#define PDO_INFO_HP_CAP (1 << 8) +#define PDO_INFO_DR_POWER (1 << 9) + +/* VDM/VDO */ +#define VDM_CMD_RESERVED 0x00 +#define VDM_CMD_DISC_ID 0x01 +#define VDM_CMD_DISC_SVID 0x02 +#define VDM_CMD_DISC_MODE 0x03 +#define VDM_CMD_ENTER_MODE 0x04 +#define VDM_CMD_EXIT_MODE 0x05 +#define VDM_CMD_ATTENTION 0x06 +#define VDM_CMD_DP_STATUS 0x10 +#define VDM_CMD_DP_CONFIG 0x11 + +#define VDM_ACK 0x40 +#define VDM_NAK 0x80 +#define VDM_BUSY 0xC0 +#define VDM_UNSTRUCTURED 0x00 +#define VDM_STRUCTURED 0x80 + +/* VDM Discover ID */ +#define VDO_ID_TYPE_NONE 0 +#define VDO_ID_TYPE_PD_HUB 1 +#define VDO_ID_TYPE_PD_PERIPH 2 +#define VDO_ID_TYPE_PASS_CBL 3 +#define VDO_ID_TYPE_ACTI_CBL 4 +#define VDO_ID_TYPE_ALTERNATE 5 + +/* VDM Discover Mode Caps [From device UFP_U to host DFP_U)] */ +#define VDO_DP_UFP_D BIT(0) /* DisplayPort Sink */ +#define VDO_DP_DFP_D BIT(1) /* DisplayPort Source */ +#define VDO_DP_SUPPORT BIT(2) +#define VDO_DP_RECEPTACLE BIT(6) + +/* VDM DP Configuration [From host (DFP_U) to device (UFP_U)] */ +#define VDO_DP_U_DFP_D BIT(0) /* UFP_U as DisplayPort Source */ +#define VDO_DP_U_UFP_D BIT(1) /* UFP_U as DisplayPort Sink */ +#define VDO_DP_SUPPORT BIT(2) +#define VDO_DP_RECEPTACLE BIT(6) + +/* VDM Mode Caps and DP Configuration pins */ +#define VDO_DP_PIN_A BIT(0) +#define VDO_DP_PIN_B BIT(1) +#define VDO_DP_PIN_C BIT(2) +#define VDO_DP_PIN_D BIT(3) +#define VDO_DP_PIN_E BIT(4) +#define VDO_DP_PIN_F BIT(5) + +/* Known VID/SVID */ +#define VID_NINTENDO 0x057E +#define PID_NIN_DOCK 0x2003 +#define PID_NIN_CHARGER 0x2004 + +#define SVID_NINTENDO VID_NINTENDO +#define SVID_DP 0xFF01 + +/* Nintendo dock VDM Commands */ +#define VDM_NCMD_LED_CONTROL 0x01 /* Reply size 12 */ +#define VDM_NCMD_DEVICE_STATE 0x16 /* Reply size 12 */ +#define VDM_NCMD_DP_SIGNAL_DISABLE 0x1C /* Reply size 8 */ +#define VDM_NCMD_HUB_RESET 0x1E /* Reply size 8 */ +#define VDM_NCMD_HUB_CONTROL 0x20 /* Reply size 8 */ + +/* Nintendo dock VDM Request Type */ +#define VDM_ND_READ 0 +#define VDM_ND_WRITE 1 + +/* Nintendo dock VDM Reply Status */ +#define VDM_ND_BUSY 1 + +/* Nintendo dock VDM Request/Reply Source */ +#define VDM_ND_HOST 1 +#define VDM_ND_DOCK 2 + +/* Nintendo dock VDM Message Type */ +#define VDM_ND_REQST 0x00 +#define VDM_ND_REPLY 0x40 + +/* Nintendo dock identifiers and limits */ +#define DOCK_ID_VOLTAGE_MV 5000u +#define DOCK_ID_CURRENT_MA 500u +#define DOCK_INPUT_VOLTAGE_MV 15000u +#define DOCK_INPUT_CURRENT_LIMIT_MIN_MA 2600u +#define DOCK_INPUT_CURRENT_LIMIT_MAX_MA 3000u + +/* Power limits */ +#define PD_05V_CHARGING_CURRENT_LIMIT_MA 2000u +#define PD_09V_CHARGING_CURRENT_LIMIT_MA 2000u +#define PD_12V_CHARGING_CURRENT_LIMIT_MA 1500u +#define PD_15V_CHARGING_CURRENT_LIMIT_MA 1200u + +#define NON_PD_POWER_RESERVE_UA 2500000u +#define PD_POWER_RESERVE_UA 4500000u + +#define PD_INPUT_CURRENT_LIMIT_MIN_MA 0u +#define PD_INPUT_CURRENT_LIMIT_MAX_MA 3000u +#define PD_INPUT_VOLTAGE_LIMIT_MAX_MV 17000u + +/* All states with ND are for Nintendo Dock */ +enum bm92t_state_type { + INIT_STATE =3D 0, + NEW_PDO, + PS_RDY_SENT, + DR_SWAP_SENT, + VDM_DISC_ID_SENT, + VDM_ACCEPT_DISC_ID_REPLY, + VDM_DISC_SVID_SENT, + VDM_ACCEPT_DISC_SVID_REPLY, + VDM_DISC_MODE_SENT, + VDM_ACCEPT_DISC_MODE_REPLY, + VDM_ENTER_ND_ALT_MODE_SENT, + VDM_ACCEPT_ENTER_NIN_ALT_MODE_REPLY, + DP_DISCOVER_MODE, + DP_CFG_START_HPD_SENT, + VDM_ND_QUERY_DEVICE_SENT, + VDM_ACCEPT_ND_QUERY_DEVICE_REPLY, + VDM_ND_ENABLE_USBHUB_SENT, + VDM_ACCEPT_ND_ENABLE_USBHUB_REPLY, + VDM_ND_LED_ON_SENT, + VDM_ACCEPT_ND_LED_ON_REPLY, + VDM_ND_CUSTOM_CMD_SENT, + VDM_ACCEPT_ND_CUSTOM_CMD_REPLY, + VDM_CUSTOM_CMD_SENT, + VDM_ACCEPT_CUSTOM_CMD_REPLY, + NINTENDO_CONFIG_HANDLED, + NORMAL_CONFIG_HANDLED +}; + +struct __packed pd_object { + unsigned int amp:10; + unsigned int volt:10; + unsigned int info:10; + unsigned int type:2; +}; + +struct __packed rd_object { + unsigned int max_amp:10; + unsigned int op_amp:10; + unsigned int info:6; + unsigned int usb_comms:1; + unsigned int mismatch:1; + unsigned int obj_no:4; +}; + +struct __packed vd_object { + unsigned int vid:16; + unsigned int rsvd:10; + unsigned int modal:1; + unsigned int type:3; + unsigned int ufp:1; + unsigned int dfp:1; + + unsigned int xid; + + unsigned int bcd:16; + unsigned int pid:16; + + unsigned int prod_type; +}; + +struct bm92t_device { + int pdo_no; + unsigned int charging_limit; + bool drd_support; + bool is_nintendo_dock; + struct pd_object pdo; + struct vd_object vdo; +}; + +struct bm92t_platform_data { + bool dp_signal_toggle_on_resume; + bool led_static_on_suspend; + bool dock_power_limit_disable; + bool dp_alerts_enable; + + unsigned int pd_5v_current_limit; + unsigned int pd_9v_current_limit; + unsigned int pd_12v_current_limit; + unsigned int pd_15v_current_limit; +}; + +struct bm92t_info { + struct i2c_client *i2c_client; + struct bm92t_platform_data *pdata; + struct work_struct work; + struct workqueue_struct *event_wq; + struct completion cmd_done; + + int state; + bool first_init; + + struct extcon_dev *edev; + struct delayed_work oneshot_work; + struct delayed_work power_work; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs_root; +#endif + struct regulator *batt_chg_reg; + struct regulator *vbus_reg; + bool charging_enabled; + unsigned int fw_type; + unsigned int fw_revision; + + struct bm92t_device cable; + + struct usb_role_switch *role_sw; +}; + +static const char * const states[] =3D { + "INIT_STATE", + "NEW_PDO", + "PS_RDY_SENT", + "DR_SWAP_SENT", + "VDM_DISC_ID_SENT", + "VDM_ACCEPT_DISC_ID_REPLY", + "VDM_DISC_SVID_SENT", + "VDM_ACCEPT_DISC_SVID_REPLY", + "VDM_DISC_MODE_SENT", + "VDM_ACCEPT_DISC_MODE_REPLY", + "VDM_ENTER_ND_ALT_MODE_SENT", + "VDM_ACCEPT_ENTER_NIN_ALT_MODE_REPLY", + "DP_DISCOVER_MODE", + "DP_CFG_START_HPD_SENT", + "VDM_ND_QUERY_DEVICE_SENT", + "VDM_ACCEPT_ND_QUERY_DEVICE_REPLY", + "VDM_ND_ENABLE_USBHUB_SENT", + "VDM_ACCEPT_ND_ENABLE_USBHUB_REPLY", + "VDM_ND_LED_ON_SENT", + "VDM_ACCEPT_ND_LED_ON_REPLY", + "VDM_ND_CUSTOM_CMD_SENT", + "VDM_ACCEPT_ND_CUSTOM_CMD_REPLY", + "VDM_CUSTOM_CMD_SENT", + "VDM_ACCEPT_CUSTOM_CMD_REPLY", + "NINTENDO_CONFIG_HANDLED", + "NORMAL_CONFIG_HANDLED" +}; + +static const unsigned int bm92t_extcon_cable[] =3D { + EXTCON_USB_HOST, /* Id */ + EXTCON_USB, /* Vbus */ + EXTCON_CHG_USB_PD, /* USB-PD */ + EXTCON_DISP_DP, /* DisplayPort. Handled by HPD so not used. */ + EXTCON_NONE +}; + +struct bm92t_extcon_cables { + unsigned int cable; + char *name; +}; + +static const struct bm92t_extcon_cables bm92t_extcon_cable_names[] =3D { + { EXTCON_USB_HOST, "USB HOST"}, + { EXTCON_USB, "USB"}, + { EXTCON_CHG_USB_PD, "USB-PD"}, + { EXTCON_DISP_DP, "DisplayPort"}, + { EXTCON_NONE, "None"}, + { -1, "Unknown"} +}; + +/* bq2419x current input limits */ +static const unsigned int current_input_limits[] =3D { + 100, 150, 500, 900, 1200, 1500, 2000, 3000 +}; + +/* USB-PD common VDMs */ +unsigned char vdm_discover_id_msg[6] =3D {OUTGOING_VDM_REG, 4, + VDM_CMD_DISC_ID, VDM_STRUCTURED, 0x00, 0xFF}; + +unsigned char vdm_discover_svid_msg[6] =3D {OUTGOING_VDM_REG, 4, + VDM_CMD_DISC_SVID, VDM_STRUCTURED, 0x00, 0xFF}; + +unsigned char vdm_discover_mode_msg[6] =3D {OUTGOING_VDM_REG, 4, + VDM_CMD_DISC_MODE, VDM_STRUCTURED, 0x01, 0xFF}; /* DisplayPort Alt Mode */ + +unsigned char vdm_exit_dp_alt_mode_msg[6] =3D {OUTGOING_VDM_REG, 4, + VDM_CMD_EXIT_MODE, VDM_STRUCTURED | 1, 0x01, 0xFF}; + +unsigned char vdm_enter_nin_alt_mode_msg[6] =3D {OUTGOING_VDM_REG, 4, + VDM_CMD_ENTER_MODE, VDM_STRUCTURED | 1, 0x7E, 0x05}; + +/* Nintendo Dock VDMs */ +unsigned char vdm_query_device_msg[10] =3D {OUTGOING_VDM_REG, 8, + VDM_ND_REQST, VDM_UNSTRUCTURED, 0x7E, 0x05, + VDM_ND_READ, VDM_ND_HOST, VDM_NCMD_DEVICE_STATE, 0x00}; + +unsigned char vdm_usbhub_enable_msg[10] =3D {OUTGOING_VDM_REG, 8, + VDM_ND_REQST, VDM_UNSTRUCTURED, 0x7E, 0x05, + VDM_ND_WRITE, VDM_ND_HOST, VDM_NCMD_HUB_CONTROL, 0x00}; + +unsigned char vdm_usbhub_disable_msg[10] =3D {OUTGOING_VDM_REG, 8, + VDM_ND_REQST, VDM_UNSTRUCTURED, 0x7E, 0x05, + VDM_ND_READ, VDM_ND_HOST, VDM_NCMD_HUB_CONTROL, 0x00}; + +unsigned char vdm_usbhub_reset_msg[10] =3D {OUTGOING_VDM_REG, 8, + VDM_ND_REQST, VDM_UNSTRUCTURED, 0x7E, 0x05, + VDM_ND_READ, VDM_ND_HOST, VDM_NCMD_HUB_RESET, 0x00}; + +unsigned char vdm_usbhub_dp_sleep_msg[10] =3D {OUTGOING_VDM_REG, 8, + VDM_ND_REQST, VDM_UNSTRUCTURED, 0x7E, 0x05, + 0x00, VDM_ND_HOST, VDM_NCMD_DP_SIGNAL_DISABLE, 0x00}; + +unsigned char vdm_usbhub_led_msg[14] =3D {OUTGOING_VDM_REG, 12, + VDM_ND_REQST, VDM_UNSTRUCTURED, 0x7E, 0x05, + VDM_ND_WRITE, VDM_ND_HOST, VDM_NCMD_LED_CONTROL, 0x00, + 0x00, 0x00, 0x00, 0x00}; /* Fade, Time off, Time on, Duty */ + +static int bm92t_write_reg(struct bm92t_info *info, + unsigned char *buf, unsigned int len) +{ + struct i2c_msg xfer_msg[1]; + + xfer_msg[0].addr =3D info->i2c_client->addr; + xfer_msg[0].len =3D len; + xfer_msg[0].flags =3D I2C_M_NOSTART; + xfer_msg[0].buf =3D buf; + + dev_dbg(&info->i2c_client->dev, "write reg cmd =3D 0x%02X len =3D %u\n", = buf[0], len); + return (i2c_transfer(info->i2c_client->adapter, xfer_msg, 1) !=3D 1); +} + +static int bm92t_read_reg(struct bm92t_info *info, + unsigned char reg, unsigned char *buf, int num) +{ + struct i2c_msg xfer_msg[2]; + int err; + unsigned char reg_addr; + + reg_addr =3D reg; + + xfer_msg[0].addr =3D info->i2c_client->addr; + xfer_msg[0].len =3D 1; + xfer_msg[0].flags =3D 0; + xfer_msg[0].buf =3D ®_addr; + + xfer_msg[1].addr =3D info->i2c_client->addr; + xfer_msg[1].len =3D num; + xfer_msg[1].flags =3D I2C_M_RD; + xfer_msg[1].buf =3D buf; + + err =3D i2c_transfer(info->i2c_client->adapter, xfer_msg, 2); + if (err < 0) + dev_err(&info->i2c_client->dev, "%s: transfer error %d\n", __func__, err= ); + return (err !=3D 2); +} + +static int bm92t_send_cmd(struct bm92t_info *info, unsigned short *cmd) +{ + int ret; + unsigned char reg; + unsigned char *_cmd =3D (unsigned char *) cmd; + unsigned char msg[3]; + + if (!cmd) + return -EINVAL; + + reg =3D COMMAND_REG; + + msg[0] =3D reg; + msg[1] =3D _cmd[0]; + msg[2] =3D _cmd[1]; + + ret =3D bm92t_write_reg(info, msg, 3); + dev_dbg(&info->i2c_client->dev, "Sent cmd 0x%02X 0x%02X return value %d\n= ", + _cmd[0], _cmd[1], ret); + return ret; +} + +static inline bool bm92t_is_success(const short alert_data) +{ + return (alert_data & ALERT_CMD_DONE); +} + +static inline bool bm92t_received_vdm(const short alert_data) +{ + return (alert_data & ALERT_VDM_RECEIVED); +} + +static inline bool bm92t_is_plugged(const short status1_data) +{ + return (status1_data & STATUS1_INSERT); +} + +static inline bool bm92t_is_ufp(const short status1_data) +{ + return (((status1_data & STATUS1_DR_MASK) >> STATUS1_DR_SHIFT) =3D=3D DAT= A_ROLE_UFP); +} + +static inline bool bm92t_is_dfp(const short status1_data) +{ + return (((status1_data & STATUS1_DR_MASK) >> STATUS1_DR_SHIFT) =3D=3D DAT= A_ROLE_DFP); +} + +static inline bool bm92t_is_lastcmd_ok(struct bm92t_info *info, + const char *cmd, const short status1_data) +{ + unsigned int lastcmd_status =3D + (status1_data & STATUS1_LASTCMD_MASK) >> STATUS1_LASTCMD_SHIFT; + + switch (lastcmd_status) { + case LASTCMD_COMPLETE: + break; + case LASTCMD_ABORTED: + dev_err(&info->i2c_client->dev, "%s aborted!", cmd); + break; + case LASTCMD_INVALID: + dev_err(&info->i2c_client->dev, "%s invalid!", cmd); + break; + case LASTCMD_REJECTED: + dev_err(&info->i2c_client->dev, "%s rejected!", cmd); + break; + case LASTCMD_TERMINATED: + dev_err(&info->i2c_client->dev, "%s terminated!", cmd); + break; + default: + dev_err(&info->i2c_client->dev, "%s failed! (%d)", cmd, lastcmd_status); + } + + return (lastcmd_status =3D=3D LASTCMD_COMPLETE); +} + +static int bm92t_handle_dp_config_and_hpd(struct bm92t_info *info) +{ + int err; + bool pin_valid =3D false; + unsigned char msg[5]; + unsigned short cmd =3D DP_CFG_AND_START_HPD_CMD; + unsigned char cfg[6] =3D {OUTGOING_VDM_REG, 0x04, + VDO_DP_SUPPORT | VDO_DP_U_UFP_D, 0x00, 0x00, 0x00}; + + err =3D bm92t_read_reg(info, INCOMING_VDM_REG, msg, sizeof(msg)); + + /* Prepare UFP_U as UFP_D configuration */ + if (info->cable.is_nintendo_dock) { + /* Dock reports Plug but uses Receptactle */ + /* Both plug & receptacle pin assignment work, */ + /* because dock ignores them. Use the latter though. */ + if (msg[3] & VDO_DP_PIN_D) { + cfg[3] =3D 0x00; + cfg[4] =3D VDO_DP_PIN_D; + pin_valid =3D true; + } + } else if (!(msg[1] & VDO_DP_RECEPTACLE)) { /* Plug */ + if (msg[2] & VDO_DP_PIN_D) { /* 2 DP Lanes */ + cfg[3] =3D VDO_DP_PIN_D; + cfg[4] =3D 0x00; + pin_valid =3D true; + } else if (msg[2] & VDO_DP_PIN_C) { /* 4 DP Lanes - 2 Unused */ + cfg[3] =3D VDO_DP_PIN_C; + cfg[4] =3D 0x00; + pin_valid =3D true; + } + } else if (msg[1] & VDO_DP_RECEPTACLE) { /* Receptacle */ + /* Set Receptacle pin assignment */ + if (msg[3] & VDO_DP_PIN_D) { /* 2 DP Lanes */ + cfg[3] =3D VDO_DP_PIN_D; + cfg[4] =3D 0x00; + pin_valid =3D true; + } else if (msg[3] & VDO_DP_PIN_C) { /* 4 DP Lanes - 2 Unused */ + cfg[3] =3D VDO_DP_PIN_C; + cfg[4] =3D 0x00; + pin_valid =3D true; + } + } + + /* Check that UFP_U/UFP_D Pin D assignment is supported */ + if (!err && msg[0] =3D=3D 4 && pin_valid) { + /* Set DP configuration */ + err =3D bm92t_write_reg(info, (unsigned char *) cfg, sizeof(cfg)); + if (err) { + dev_err(&info->i2c_client->dev, "Writing DP cfg failed!\n"); + return -ENODEV; + } + /* Configure DP Alt mode and start handling HPD */ + bm92t_send_cmd(info, &cmd); + } else { + dev_err(&info->i2c_client->dev, + "No compatible DP Pin assignment (%d: %02X %02X %02X)!\n", + msg[0], msg[1], msg[2], msg[3]); + return -ENODEV; + } + + return 0; +} + +static int bm92t_set_current_limit(struct bm92t_info *info, int max_ua) +{ + int ret =3D 0; + + if (info->batt_chg_reg !=3D NULL) + ret =3D regulator_set_current_limit(info->batt_chg_reg, 0, max_ua); + + return ret; +} + +static int bm92t_set_vbus_enable(struct bm92t_info *info, bool enable) +{ + int ret =3D 0; + bool is_enabled; + + dev_dbg(&info->i2c_client->dev, "%s VBUS\n", enable ? "Enabling" : "Disab= ling"); + if (info->vbus_reg !=3D NULL) { + is_enabled =3D regulator_is_enabled(info->vbus_reg); + if (enable && !is_enabled) + ret =3D regulator_enable(info->vbus_reg); + else if (is_enabled) + ret =3D regulator_disable(info->vbus_reg); + } + + return ret; +} + +static int bm92t_set_source_mode(struct bm92t_info *info, unsigned int rol= e) +{ + int err =3D 0; + unsigned short value; + unsigned char msg[3] =3D {CONFIG1_REG, 0, 0}; + + err =3D bm92t_read_reg(info, CONFIG1_REG, + (unsigned char *) &value, sizeof(value)); + if (err < 0) + return err; + + if (((value & CONFIG1_SPDSRC_MASK) >> CONFIG1_SPDSRC_SHIFT) !=3D role) { + value &=3D ~CONFIG1_SPDSRC_MASK; + value |=3D role << CONFIG1_SPDSRC_SHIFT; + msg[1] =3D value & 0xFF; + msg[2] =3D (value >> 8) & 0xFF; + err =3D bm92t_write_reg(info, msg, sizeof(msg)); + } + + return err; +} + +static int bm92t_set_dp_alerts(struct bm92t_info *info, bool enable) +{ + int err =3D 0; + unsigned char msg[3] =3D {DP_ALERT_EN_REG, 0, 0}; + + msg[1] =3D enable ? 0xFF : 0x00; + msg[2] =3D enable ? 0xFF : 0x00; + err =3D bm92t_write_reg(info, msg, sizeof(msg)); + + return err; +} + +static int bm92t_enable_ocp(struct bm92t_info *info) +{ + int err =3D 0; + unsigned short value; + unsigned char msg[3] =3D {VENDOR_CONFIG_REG, 0, 0}; + + bm92t_read_reg(info, VENDOR_CONFIG_REG, + (unsigned char *) &value, sizeof(value)); + if (value & VENDOR_CONFIG_OCP_DISABLE) { + value &=3D ~VENDOR_CONFIG_OCP_DISABLE; + msg[1] =3D value & 0xFF; + msg[2] =3D (value >> 8) & 0xFF; + bm92t_write_reg(info, msg, sizeof(msg)); + } + + return err; +} + +static int bm92t_system_reset_auto(struct bm92t_info *info, bool force) +{ + int err =3D 0; + unsigned short cmd =3D SYS_RESET_CMD; + unsigned short alert_data, status1_data, dp_data; + + if (force) { + dev_info(&info->i2c_client->dev, "SYS Reset requested!\n"); + bm92t_send_cmd(info, &cmd); + msleep(33); + + /* Clear alerts */ + err =3D bm92t_read_reg(info, ALERT_STATUS_REG, + (unsigned char *) &alert_data, + sizeof(alert_data)); + goto ret; + } + + err =3D bm92t_read_reg(info, STATUS1_REG, + (unsigned char *) &status1_data, + sizeof(status1_data)); + if (err < 0) + goto ret; + err =3D bm92t_read_reg(info, DP_STATUS_REG, + (unsigned char *) &dp_data, + sizeof(dp_data)); + if (err < 0) + goto ret; + + /* Check if UFP is in invalid state */ + if (bm92t_is_plugged(status1_data)) { + if (bm92t_is_dfp(status1_data) || + dp_data & DP_STATUS_HPD || + !bm92t_is_lastcmd_ok(info, "Unknown cmd", status1_data)) { + dev_err(&info->i2c_client->dev, "Invalid state, initiating SYS Reset!\n= "); + bm92t_send_cmd(info, &cmd); + msleep(100); + + /* Clear alerts */ + err =3D bm92t_read_reg(info, ALERT_STATUS_REG, + (unsigned char *) &alert_data, + sizeof(alert_data)); + } + } + +ret: + return err; +} + +static char *bm92t_extcon_cable_get_name(const unsigned int cable) +{ + int i, count; + + count =3D ARRAY_SIZE(bm92t_extcon_cable_names); + + for (i =3D 0; i < count; i++) + if (bm92t_extcon_cable_names[i].cable =3D=3D cable) + return bm92t_extcon_cable_names[i].name; + + return bm92t_extcon_cable_names[count - 1].name; +} + +static void bm92t_extcon_cable_update(struct bm92t_info *info, + const unsigned int cable, bool is_attached) +{ + enum usb_role role; + int state =3D extcon_get_state(info->edev, cable); + + if (state !=3D is_attached) { + dev_info(&info->i2c_client->dev, "extcon cable (%02d: %s) %s\n", + cable, bm92t_extcon_cable_get_name(cable), + is_attached ? "attached" : "detached"); + extcon_set_state(info->edev, cable, is_attached); + } + + switch (cable) { + case EXTCON_USB: + role =3D USB_ROLE_DEVICE; + break; + case EXTCON_USB_HOST: + role =3D USB_ROLE_HOST; + break; + default: + role =3D USB_ROLE_NONE; + break; + } + + if (role !=3D USB_ROLE_NONE && is_attached) { + dev_info(&info->i2c_client->dev, + "%s: Changing to role(%d)\n", __func__, role); + usb_role_switch_set_role(info->role_sw, role); + } +} + +static inline void bm92t_state_machine(struct bm92t_info *info, int state) +{ + info->state =3D state; + dev_dbg(&info->i2c_client->dev, "state =3D %s\n", states[state]); +} + +static void bm92t_calculate_current_limit(struct bm92t_info *info, + unsigned int voltage, unsigned int amperage) +{ + int i; + unsigned int charging_limit =3D amperage; + struct bm92t_platform_data *pdata =3D info->pdata; + + /* Subtract a USB2 or USB3 port current */ + if (voltage > 5000) + charging_limit -=3D (PD_POWER_RESERVE_UA / voltage); + else + charging_limit -=3D (NON_PD_POWER_RESERVE_UA / voltage); + + /* Set limits */ + switch (voltage) { + case 5000: + charging_limit =3D min(charging_limit, pdata->pd_5v_current_limit); + break; + case 9000: + charging_limit =3D min(charging_limit, pdata->pd_9v_current_limit); + break; + case 12000: + charging_limit =3D min(charging_limit, pdata->pd_12v_current_limit); + break; + case 15000: + default: + charging_limit =3D min(charging_limit, pdata->pd_15v_current_limit); + break; + } + + /* Set actual amperage */ + for (i =3D ARRAY_SIZE(current_input_limits) - 1; i >=3D 0; i--) { + if (charging_limit >=3D current_input_limits[i]) { + charging_limit =3D current_input_limits[i]; + break; + } + } + + info->cable.charging_limit =3D charging_limit; +} + +static void bm92t_power_work(struct work_struct *work) +{ + struct bm92t_info *info =3D container_of( + to_delayed_work(work), struct bm92t_info, power_work); + + bm92t_set_current_limit(info, info->cable.charging_limit * 1000u); + info->charging_enabled =3D true; + + extcon_set_state(info->edev, EXTCON_CHG_USB_PD, true); +} + +static void + bm92t_extcon_cable_set_init_state(struct work_struct *work) +{ + struct bm92t_info *info =3D container_of( + to_delayed_work(work), struct bm92t_info, oneshot_work); + + dev_info(&info->i2c_client->dev, "extcon cable is set to init state\n"); + + disable_irq(info->i2c_client->irq); + + bm92t_set_vbus_enable(info, false); + + /* In case UFP is in an invalid state, request a SYS reset */ + bm92t_system_reset_auto(info, false); + + /* Enable over current protection */ + bm92t_enable_ocp(info); + + /* Enable power to SPDSRC for supporting both OTG and charger */ + bm92t_set_source_mode(info, SPDSRC12_ON); + + /* Enable DisplayPort alerts */ + bm92t_set_dp_alerts(info, info->pdata->dp_alerts_enable); + + bm92t_extcon_cable_update(info, EXTCON_USB_HOST, false); + bm92t_extcon_cable_update(info, EXTCON_USB, true); + + msleep(1000); /* WAR: Allow USB device enumeration at boot. */ + + queue_work(info->event_wq, &info->work); +} + +static bool bm92t_check_pdo(struct bm92t_info *info) +{ + int i, err, pdos_no; + struct device *dev; + unsigned char pdos[29]; + struct pd_object pdo[7]; + unsigned int prev_wattage =3D 0; + unsigned int amperage, voltage, wattage, type; + + dev =3D &info->i2c_client->dev; + + memset(&info->cable, 0, sizeof(struct bm92t_device)); + + err =3D bm92t_read_reg(info, READ_PDOS_SRC_REG, pdos, sizeof(pdos)); + pdos_no =3D pdos[0] / sizeof(struct pd_object); + + /* Check if errors or no pdo received */ + if (err || !pdos_no) + return 0; + + dev_info(dev, "Supported PDOs:\n"); + memcpy(pdo, pdos + 1, pdos[0]); + for (i =3D 0; i < pdos_no; ++i) { + dev_info(dev, "PDO %d: %4dmA %5dmV %s\n", + i + 1, pdo[i].amp * 10, pdo[i].volt * 50, + (pdo[i].info & PDO_INFO_DR_DATA) ? "DRD" : "No DRD"); + } + + if (pdo[0].info & PDO_INFO_DR_DATA) + info->cable.drd_support =3D true; + + /* Check for dock mode */ + if (!info->pdata->dock_power_limit_disable && + pdos_no =3D=3D 2 && + (pdo[0].volt * 50) =3D=3D DOCK_ID_VOLTAGE_MV && + (pdo[0].amp * 10) =3D=3D DOCK_ID_CURRENT_MA) { + /* Only accept 15V, >=3D 2.6A for dock mode. */ + if (pdo[1].type =3D=3D PDO_TYPE_FIXED && + (pdo[1].volt * 50) =3D=3D DOCK_INPUT_VOLTAGE_MV && + (pdo[1].amp * 10) >=3D DOCK_INPUT_CURRENT_LIMIT_MIN_MA && + (pdo[1].amp * 10) <=3D DOCK_INPUT_CURRENT_LIMIT_MAX_MA) { + dev_info(dev, "Device in Nintendo mode\n"); + info->cable.pdo_no =3D 2; + memcpy(&info->cable.pdo, &pdo[1], sizeof(struct pd_object)); + return 1; + } + + dev_info(dev, "Adapter in dock mode with improper current\n"); + return 0; + } + + /* Not in dock mode. Check for max possible wattage */ + for (i =3D 0; i < pdos_no; ++i) { + type =3D pdo[i].type; + voltage =3D pdo[i].volt * 50; + amperage =3D pdo[i].amp * 10; + wattage =3D voltage * amperage; + + /* Only USB-PD defined voltages with max 15V. */ + switch (voltage) { + case 5000: + case 9000: + case 12000: + case 15000: + break; + default: + continue; + } + + /* Only accept <=3D 3A and select max wattage with max voltage. */ + if (type =3D=3D PDO_TYPE_FIXED && + amperage >=3D PD_INPUT_CURRENT_LIMIT_MIN_MA && + amperage <=3D PD_INPUT_CURRENT_LIMIT_MAX_MA) { + if (wattage > prev_wattage || + (voltage > (info->cable.pdo.volt * 50) && + wattage && wattage =3D=3D prev_wattage) || + (!info->cable.pdo_no && !amperage && voltage =3D=3D 5000)) { + prev_wattage =3D wattage; + info->cable.pdo_no =3D i + 1; + memcpy(&info->cable.pdo, &pdo[i], sizeof(struct pd_object)); + } + } + } + + if (info->cable.pdo_no) { + dev_info(&info->i2c_client->dev, "Device in powered mode\n"); + return 1; + } + + return 0; +} + +static int bm92t_send_rdo(struct bm92t_info *info) +{ + int err; + + struct rd_object rdo =3D { 0 }; + unsigned char msg[6] =3D { SET_RDO_REG, 0x04, 0x00, 0x00, 0x00, 0x00}; + unsigned short cmd =3D SEND_RDO_CMD; + + /* Calculate operating current */ + bm92t_calculate_current_limit(info, info->cable.pdo.volt * 50, + info->cable.pdo.amp * 10); + + dev_info(&info->i2c_client->dev, + "Requesting %d: min %dmA, max %4dmA, %5dmV\n", + info->cable.pdo_no, info->cable.charging_limit, + info->cable.pdo.amp * 10, + info->cable.pdo.volt * 50); + + rdo.usb_comms =3D 1; + rdo.obj_no =3D info->cable.pdo_no; + rdo.max_amp =3D info->cable.pdo.amp; + rdo.op_amp =3D info->cable.charging_limit / 10; + + memcpy(&msg[2], &rdo, sizeof(struct rd_object)); + + err =3D bm92t_write_reg(info, msg, sizeof(msg)); + if (!err) + bm92t_send_cmd(info, &cmd); + + if (err) { + dev_err(&info->i2c_client->dev, "Send RDO failure!\n"); + return -ENODEV; + } + return 0; +} + +static int bm92t_send_vdm(struct bm92t_info *info, unsigned char *msg, + unsigned int len) +{ + int err; + unsigned short cmd =3D SEND_VDM_CMD; + + err =3D bm92t_write_reg(info, msg, len); + if (!err) + bm92t_send_cmd(info, &cmd); + + if (err) { + dev_err(&info->i2c_client->dev, "Send VDM failure!\n"); + return -ENODEV; + } + return 0; +} + +static void bm92t_usbhub_led_cfg(struct bm92t_info *info, + unsigned char duty, unsigned char time_on, + unsigned char time_off, unsigned char fade) +{ + vdm_usbhub_led_msg[10] =3D fade; + vdm_usbhub_led_msg[11] =3D time_off; + vdm_usbhub_led_msg[12] =3D time_on; + vdm_usbhub_led_msg[13] =3D duty; + + bm92t_send_vdm(info, vdm_usbhub_led_msg, sizeof(vdm_usbhub_led_msg)); +} + +static void bm92t_usbhub_led_cfg_wait(struct bm92t_info *info, + unsigned char duty, unsigned char time_on, + unsigned char time_off, unsigned char fade) +{ + int retries =3D 100; + + if (info->state =3D=3D NINTENDO_CONFIG_HANDLED) { + bm92t_state_machine(info, VDM_ND_CUSTOM_CMD_SENT); + bm92t_usbhub_led_cfg(info, duty, time_on, time_off, fade); + while (info->state !=3D NINTENDO_CONFIG_HANDLED) { + retries--; + if (retries < 0) + break; + usleep_range(1000, 2000); + } + } +} + +static void bm92t_usbhub_dp_sleep(struct bm92t_info *info, bool sleep) +{ + int retries =3D 100; + + if (info->state =3D=3D NINTENDO_CONFIG_HANDLED || + info->state =3D=3D NORMAL_CONFIG_HANDLED) { + + if (info->state =3D=3D NINTENDO_CONFIG_HANDLED) + bm92t_state_machine(info, VDM_ND_CUSTOM_CMD_SENT); + else + bm92t_state_machine(info, VDM_CUSTOM_CMD_SENT); + + vdm_usbhub_dp_sleep_msg[6] =3D sleep ? 1 : 0; + + bm92t_send_vdm(info, vdm_usbhub_dp_sleep_msg, + sizeof(vdm_usbhub_dp_sleep_msg)); + + while (info->state !=3D NINTENDO_CONFIG_HANDLED || + info->state !=3D NORMAL_CONFIG_HANDLED) { + retries--; + if (retries < 0) + break; + usleep_range(1000, 2000); + } + } +} + +static void bm92t_print_dp_dev_info(struct device *dev, + struct vd_object *vdo) +{ + dev_info(dev, "Connected PD device:\n"); + dev_info(dev, "VID: %04X, PID: %04X\n", vdo->vid, vdo->pid); + + switch (vdo->type) { + case VDO_ID_TYPE_NONE: + dev_info(dev, "Type: Undefined\n"); + break; + case VDO_ID_TYPE_PD_HUB: + dev_info(dev, "Type: PD HUB\n"); + break; + case VDO_ID_TYPE_PD_PERIPH: + dev_info(dev, "Type: PD Peripheral\n"); + break; + case VDO_ID_TYPE_PASS_CBL: + dev_info(dev, "Type: Passive Cable\n"); + break; + case VDO_ID_TYPE_ACTI_CBL: + dev_info(dev, "Type: Active Cable\n"); + break; + case VDO_ID_TYPE_ALTERNATE: + dev_info(dev, "Type: Alternate Mode Adapter\n"); + break; + default: + dev_info(dev, "Type: Unknown (%d)\n", vdo->type); + break; + } +} + +static void bm92t_event_handler(struct work_struct *work) +{ + static bool sys_reset; + static int retries_usbhub =3D 10; + int i, err; + struct bm92t_info *info; + struct device *dev; + struct pd_object curr_pdo; + struct rd_object curr_rdo; + unsigned short cmd; + unsigned short alert_data; + unsigned short status1_data; + unsigned short status2_data; + unsigned short dp_data; + unsigned char vdm[29], pdo[5], rdo[5]; + + info =3D container_of(work, struct bm92t_info, work); + dev =3D &info->i2c_client->dev; + + /* Read status registers at 02h, 03h and 04h */ + err =3D bm92t_read_reg(info, ALERT_STATUS_REG, + (unsigned char *) &alert_data, + sizeof(alert_data)); + if (err < 0) + goto ret; + err =3D bm92t_read_reg(info, STATUS1_REG, + (unsigned char *) &status1_data, + sizeof(status1_data)); + if (err < 0) + goto ret; + err =3D bm92t_read_reg(info, STATUS2_REG, + (unsigned char *) &status2_data, + sizeof(status2_data)); + if (err < 0) + goto ret; + err =3D bm92t_read_reg(info, DP_STATUS_REG, + (unsigned char *) &dp_data, + sizeof(dp_data)); + if (err < 0) + goto ret; + + dev_info_ratelimited(dev, + "Alert=3D 0x%04X Status1=3D 0x%04X Status2=3D 0x%04X DP=3D 0x%04X State= =3D %s\n", + alert_data, status1_data, status2_data, dp_data, states[info->state]); + + /* Report sink error */ + if (alert_data & ALERT_SNK_FAULT) + dev_err(dev, "Sink fault occurred!\n"); + + /* Report source error */ + if (alert_data & ALERT_SRC_FAULT) + dev_err(dev, "Source fault occurred!\n"); + + /* TODO: DP event handling */ + if (alert_data =3D=3D ALERT_DP_EVENT) + goto ret; + + /* Check for errors */ + err =3D status1_data & STATUS1_FAULT_MASK; + if (err) { + dev_err(dev, "Internal error occurred. Ecode =3D %d\n", err); + bm92t_state_machine(info, INIT_STATE); + bm92t_extcon_cable_update(info, EXTCON_USB_HOST, false); + bm92t_extcon_cable_update(info, EXTCON_USB, false); + bm92t_set_vbus_enable(info, false); + if (bm92t_is_plugged(status1_data) || alert_data & ALERT_SNK_FAULT || + alert_data =3D=3D 0) { + bm92t_system_reset_auto(info, true); + sys_reset =3D true; + } + goto ret; + } + + /* Check if sys reset happened */ + if (sys_reset) { + sys_reset =3D false; + msleep(100); + + /* Enable over current protection */ + bm92t_enable_ocp(info); + + /* Enable power to SPDSRC for supporting both OTG and charger */ + bm92t_set_source_mode(info, SPDSRC12_ON); + } + + /* Do a PD hard reset in case of a source fault */ + if (alert_data & ALERT_SRC_FAULT) { + cmd =3D PD_HARD_RST_CMD; + bm92t_send_cmd(info, &cmd); + goto src_fault; + } + + /* Check if cable removed */ + if (alert_data & ALERT_PLUGPULL) { + if (!bm92t_is_plugged(status1_data)) { /* Pull out event */ +src_fault: + /* Cancel any pending charging enable work */ + cancel_delayed_work(&info->power_work); + + /* Disable VBUS in case it's enabled */ + bm92t_set_vbus_enable(info, false); + + /* Disable charging */ + if (info->charging_enabled) { + bm92t_set_current_limit(info, 0); + info->charging_enabled =3D false; + bm92t_extcon_cable_update(info, EXTCON_CHG_USB_PD, false); + } + + /* Reset USB modes and state */ + retries_usbhub =3D 10; + bm92t_extcon_cable_update(info, EXTCON_USB_HOST, false); + bm92t_extcon_cable_update(info, EXTCON_USB, false); + bm92t_state_machine(info, INIT_STATE); + goto ret; + } else if (status1_data & STATUS1_SRC_MODE && /* OTG plug-in event */ + status2_data & STATUS2_OTG_INSERT) { + /* Enable VBUS for sourcing power to OTG device */ + bm92t_set_vbus_enable(info, true); + + /* Set USB to host mode */ + bm92t_extcon_cable_update(info, EXTCON_USB, false); + bm92t_extcon_cable_update(info, EXTCON_USB_HOST, true); + goto ret; + } else if (alert_data & ALERT_CONTRACT && !info->first_init) { + /* When there's a plug-in wake-up, check if a new contract */ + /* was received. If yes continue with init. */ + + /* In case of no new PDO, wait for it. Otherwise PD will fail. */ + /* In case of non-PD charger, this doesn't affect the result. */ + if (!(alert_data & ALERT_PDO)) + msleep(500); + } else /* Simple plug-in event */ + goto ret; + } + + switch (info->state) { + case INIT_STATE: + if (alert_data & ALERT_SRC_PLUGIN) { + dev_info(dev, "Device in OTG mode\n"); + info->first_init =3D false; + if (bm92t_is_dfp(status1_data)) { + /* Reset cable info */ + memset(&info->cable, 0, sizeof(struct bm92t_device)); + + bm92t_send_vdm(info, vdm_discover_id_msg, + sizeof(vdm_discover_id_msg)); + bm92t_state_machine(info, VDM_DISC_ID_SENT); + } + break; + } + + if (status1_data & STATUS1_SRC_MODE && + status2_data & STATUS2_OTG_INSERT) { + info->first_init =3D false; + dev_info(dev, "Device in OTG mode (no alert)\n"); + break; + } + + if ((alert_data & ALERT_CONTRACT) || info->first_init) { + /* Disable USB if first init and unplugged */ + if (!bm92t_is_plugged(status1_data)) { + bm92t_extcon_cable_update(info, EXTCON_USB, false); + goto init_contract_out; + } + + /* Check if sink mode is enabled for first init */ + /* If not, exit and wait for next alert */ + if (info->first_init && + !(alert_data & ALERT_CONTRACT) && + !(status1_data & STATUS1_SPDSNK)) { + goto init_contract_out; + } + + /* Negotiate new power profile */ + if (!bm92t_check_pdo(info)) { + dev_err(dev, "Power Negotiation failed\n"); + bm92t_state_machine(info, INIT_STATE); + msleep(550); /* WAR: BQ2419x good power test */ + bm92t_extcon_cable_update(info, EXTCON_USB, true); + goto init_contract_out; + } + + /* Power negotiation succeeded */ + bm92t_send_rdo(info); + bm92t_state_machine(info, NEW_PDO); + msleep(20); + +init_contract_out: + info->first_init =3D false; + break; + } + + /* Check if forced workqueue and unplugged */ + if (!alert_data && !bm92t_is_plugged(status1_data)) + bm92t_extcon_cable_update(info, EXTCON_USB, false); + break; + + case NEW_PDO: + if (bm92t_is_success(alert_data)) + dev_dbg(dev, "cmd done in NEW_PDO state\n"); + + if (alert_data & ALERT_CONTRACT) { + /* Check PDO/RDO */ + err =3D bm92t_read_reg(info, CURRENT_PDO_REG, + pdo, sizeof(pdo)); + memcpy(&curr_pdo, &pdo[1], sizeof(struct pd_object)); + err =3D bm92t_read_reg(info, CURRENT_RDO_REG, + rdo, sizeof(rdo)); + memcpy(&curr_rdo, &rdo[1], sizeof(struct rd_object)); + + dev_info(dev, "New PD Contract:\n"); + dev_info(dev, "PDO: %d: %dmA, %dmV\n", + info->cable.pdo_no, curr_pdo.amp * 10, curr_pdo.volt * 50); + dev_info(dev, "RDO: op %dmA, %dmA max\n", + curr_rdo.op_amp * 10, curr_rdo.max_amp * 10); + + if (curr_rdo.mismatch) + dev_err(dev, "PD mismatch!\n"); + + cmd =3D PS_RDY_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, PS_RDY_SENT); + } + break; + + case PS_RDY_SENT: + if (bm92t_is_success(alert_data)) { + bm92t_extcon_cable_update(info, EXTCON_USB_HOST, true); + schedule_delayed_work(&info->power_work, + msecs_to_jiffies(2000)); + + if (bm92t_is_ufp(status1_data)) { + /* Check if Dual-Role Data is supported */ + if (!info->cable.drd_support) { + dev_err(dev, "Device in UFP and DRD not supported!\n"); + break; + } + + cmd =3D DR_SWAP_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, DR_SWAP_SENT); + } else if (bm92t_is_dfp(status1_data)) { + dev_dbg(dev, "Already in DFP mode\n"); + bm92t_send_vdm(info, vdm_discover_id_msg, + sizeof(vdm_discover_id_msg)); + bm92t_state_machine(info, VDM_DISC_ID_SENT); + } + } + break; + + case DR_SWAP_SENT: + if ((bm92t_is_success(alert_data) && + bm92t_is_plugged(status1_data) && + bm92t_is_lastcmd_ok(info, "DR_SWAP_CMD", status1_data) && + bm92t_is_dfp(status1_data))) { + bm92t_send_vdm(info, vdm_discover_id_msg, + sizeof(vdm_discover_id_msg)); + bm92t_state_machine(info, VDM_DISC_ID_SENT); + } + break; + + case VDM_DISC_ID_SENT: + if (bm92t_received_vdm(alert_data)) { + cmd =3D ACCEPT_VDM_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, VDM_ACCEPT_DISC_ID_REPLY); + } else if (bm92t_is_success(alert_data)) + dev_dbg(dev, "cmd done in VDM_DISC_ID_SENT\n"); + break; + + case VDM_ACCEPT_DISC_ID_REPLY: + if (bm92t_is_success(alert_data)) { + /* Check incoming VDM */ + err =3D bm92t_read_reg(info, INCOMING_VDM_REG, + vdm, sizeof(vdm)); + + memcpy(&info->cable.vdo, &vdm[5], sizeof(struct vd_object)); + + bm92t_print_dp_dev_info(dev, &info->cable.vdo); + + /* Check if Nintendo dock. */ + if (!(info->cable.vdo.type =3D=3D VDO_ID_TYPE_ALTERNATE && + info->cable.vdo.vid =3D=3D VID_NINTENDO && + info->cable.vdo.pid =3D=3D PID_NIN_DOCK)) { + dev_err(dev, "VID/PID not Nintendo Dock\n"); + bm92t_send_vdm(info, vdm_discover_svid_msg, + sizeof(vdm_discover_svid_msg)); + bm92t_state_machine(info, VDM_DISC_SVID_SENT); + } else { + info->cable.is_nintendo_dock =3D true; + bm92t_send_vdm(info, vdm_enter_nin_alt_mode_msg, + sizeof(vdm_enter_nin_alt_mode_msg)); + bm92t_state_machine(info, VDM_ENTER_ND_ALT_MODE_SENT); + } + } + break; + + case VDM_DISC_SVID_SENT: + if (bm92t_received_vdm(alert_data)) { + cmd =3D ACCEPT_VDM_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, VDM_ACCEPT_DISC_SVID_REPLY); + } else if (bm92t_is_success(alert_data)) + dev_dbg(dev, "cmd done in VDM_DISC_SVID_SENT\n"); + break; + + case VDM_ACCEPT_DISC_SVID_REPLY: + if (bm92t_is_success(alert_data)) { + /* Check discovered SVIDs */ + err =3D bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm)); + + if (vdm[1] =3D=3D (VDM_ACK | VDM_CMD_DISC_SVID)) { + dev_info(dev, "Supported SVIDs:\n"); + for (i =3D 0; i < ((vdm[0] - 4) / 2); i++) + dev_info(dev, "SVID%d %04X\n", + i, vdm[5 + i * 2] | (vdm[6 + i * 2] << 8)); + + /* Request DisplayPort Alt mode support SVID (0xFF01) */ + bm92t_send_vdm(info, vdm_discover_mode_msg, + sizeof(vdm_discover_mode_msg)); + bm92t_state_machine(info, VDM_DISC_MODE_SENT); + } + } + break; + + case VDM_DISC_MODE_SENT: + if (bm92t_received_vdm(alert_data)) { + cmd =3D ACCEPT_VDM_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, VDM_ACCEPT_DISC_MODE_REPLY); + } else if (bm92t_is_success(alert_data)) + dev_dbg(dev, "cmd done in VDM_DISC_MODE_SENT\n"); + break; + + case VDM_ACCEPT_DISC_MODE_REPLY: + if (bm92t_is_success(alert_data)) { + /* Check incoming VDM */ + err =3D bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm)); + + /* Check if DisplayPort Alt mode is supported */ + if (vdm[0] > 4 && /* Has VDO objects */ + vdm[1] =3D=3D (VDM_ACK | VDM_CMD_DISC_MODE) && + vdm[2] =3D=3D VDM_STRUCTURED && + vdm[3] =3D=3D 0x01 && vdm[4] =3D=3D 0xFF && /* SVID DisplayPort */ + vdm[5] & VDO_DP_UFP_D && + vdm[5] & VDO_DP_SUPPORT) { + dev_info(dev, "DisplayPort Alt Mode supported"); + for (i =3D 0; i < ((vdm[0] - 4) / 4); i++) + dev_info(dev, "DPCap%d %08X\n", + i, vdm[5 + i * 4] | (vdm[6 + i * 4] << 8) | + (vdm[7 + i * 4] << 16) | (vdm[8 + i * 4] << 24)); + + /* Enter automatic DisplayPort handling */ + cmd =3D DP_ENTER_MODE_CMD; + err =3D bm92t_send_cmd(info, &cmd); + msleep(100); /* WAR: may not need to wait */ + bm92t_state_machine(info, DP_DISCOVER_MODE); + } + } + break; + + case VDM_ENTER_ND_ALT_MODE_SENT: + if (bm92t_received_vdm(alert_data)) { + cmd =3D ACCEPT_VDM_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, VDM_ACCEPT_ENTER_NIN_ALT_MODE_REPLY); + } else if (bm92t_is_success(alert_data)) + dev_dbg(dev, "cmd done in VDM_ENTER_ND_ALT_MODE_SENT\n"); + break; + + case VDM_ACCEPT_ENTER_NIN_ALT_MODE_REPLY: + if (bm92t_is_success(alert_data)) { + /* Check incoming VDM */ + err =3D bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm)); + + /* Check if supported. */ + if (!(vdm[1] =3D=3D (VDM_ACK | VDM_CMD_ENTER_MODE) && + vdm[2] =3D=3D (VDM_STRUCTURED | 1) && + vdm[3] =3D=3D 0x7e && vdm[4] =3D=3D 0x05)) { + dev_err(dev, "Failed to enter Nintendo Alt Mode!\n"); + break; + } + + /* Enter automatic DisplayPort handling */ + cmd =3D DP_ENTER_MODE_CMD; + err =3D bm92t_send_cmd(info, &cmd); + msleep(100); /* WAR: may not need to wait */ + bm92t_state_machine(info, DP_DISCOVER_MODE); + } + break; + + case DP_DISCOVER_MODE: + if (bm92t_is_success(alert_data)) { + err =3D bm92t_handle_dp_config_and_hpd(info); + if (!err) + bm92t_state_machine(info, DP_CFG_START_HPD_SENT); + else + bm92t_state_machine(info, INIT_STATE); + } + break; + + case DP_CFG_START_HPD_SENT: + if (bm92t_is_success(alert_data)) { + if (bm92t_is_plugged(status1_data) && + bm92t_is_lastcmd_ok(info, "DP_CFG_AND_START_HPD_CMD", + status1_data)) { + if (info->cable.is_nintendo_dock) { + bm92t_send_vdm(info, vdm_query_device_msg, + sizeof(vdm_query_device_msg)); + bm92t_state_machine(info, VDM_ND_QUERY_DEVICE_SENT); + } else + bm92t_state_machine(info, NORMAL_CONFIG_HANDLED); + } + } + break; + + /* Nintendo Dock VDMs */ + case VDM_ND_QUERY_DEVICE_SENT: + if (bm92t_received_vdm(alert_data)) { + cmd =3D ACCEPT_VDM_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, VDM_ACCEPT_ND_QUERY_DEVICE_REPLY); + } else if (bm92t_is_success(alert_data)) + dev_dbg(dev, "cmd done in VDM_ND_QUERY_DEVICE_SENT\n"); + break; + + case VDM_ACCEPT_ND_QUERY_DEVICE_REPLY: + if (bm92t_is_success(alert_data)) { + /* Check incoming VDM */ + err =3D bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm)); + + if (!err && vdm[6] =3D=3D VDM_ND_DOCK && + vdm[7] =3D=3D (VDM_NCMD_DEVICE_STATE + 1)) { + /* Check if USB HUB is supported */ + if (vdm[11] & 0x02) { + bm92t_extcon_cable_update(info, EXTCON_USB_HOST, false); + msleep(500); + bm92t_extcon_cable_update(info, EXTCON_USB, true); + dev_err(dev, "Dock has old FW!\n"); + } + dev_info(dev, "device state: %02X %02X %02X %02X\n", + vdm[9], vdm[10], vdm[11], vdm[12]); + } else + dev_err(dev, "Failed to get dock state reply!"); + + /* Set dock LED */ + bm92t_usbhub_led_cfg(info, 128, 0, 0, 64); + bm92t_state_machine(info, VDM_ND_LED_ON_SENT); + } + break; + + case VDM_ND_LED_ON_SENT: + if (bm92t_received_vdm(alert_data)) { + cmd =3D ACCEPT_VDM_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, VDM_ACCEPT_ND_LED_ON_REPLY); + } else if (bm92t_is_success(alert_data)) + dev_dbg(dev, "cmd done in VDM_ND_LED_ON_SENT\n"); + break; + + case VDM_ACCEPT_ND_LED_ON_REPLY: + if (bm92t_is_success(alert_data)) { + msleep(500); /* Wait for hub to power up */ + bm92t_send_vdm(info, vdm_usbhub_enable_msg, sizeof(vdm_usbhub_enable_ms= g)); + bm92t_state_machine(info, VDM_ND_ENABLE_USBHUB_SENT); + } + break; + + case VDM_ND_ENABLE_USBHUB_SENT: + if (bm92t_received_vdm(alert_data)) { + cmd =3D ACCEPT_VDM_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, VDM_ACCEPT_ND_ENABLE_USBHUB_REPLY); + } else if (bm92t_is_success(alert_data)) + dev_dbg(dev, "cmd done in VDM_ND_ENABLE_USBHUB_SENT\n"); + break; + + case VDM_ACCEPT_ND_ENABLE_USBHUB_REPLY: + if (bm92t_is_success(alert_data)) { + /* Check incoming VDM */ + err =3D bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm)); + + if ((vdm[6] =3D=3D VDM_ND_DOCK && + vdm[7] =3D=3D (VDM_NCMD_HUB_CONTROL + 1) && + retries_usbhub)) { + if (vdm[5] & VDM_ND_BUSY) { + msleep(250); + dev_info(dev, "Retrying USB HUB enable...\n"); + bm92t_send_vdm(info, vdm_usbhub_enable_msg, + sizeof(vdm_usbhub_enable_msg)); + bm92t_state_machine(info, VDM_ND_ENABLE_USBHUB_SENT); + retries_usbhub--; + break; + } + } else if (!retries_usbhub) + dev_err(dev, "USB HUB enable timed out!\n"); + else + dev_err(dev, "USB HUB enable failed!\n"); + + bm92t_state_machine(info, NINTENDO_CONFIG_HANDLED); + } + break; + + case VDM_ND_CUSTOM_CMD_SENT: + if (bm92t_received_vdm(alert_data)) { + cmd =3D ACCEPT_VDM_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, VDM_ACCEPT_ND_CUSTOM_CMD_REPLY); + } else if (bm92t_is_success(alert_data)) + dev_dbg(dev, "cmd done in VDM_ND_CUSTOM_CMD_SENT\n"); + break; + + case VDM_ACCEPT_ND_CUSTOM_CMD_REPLY: + if (bm92t_is_success(alert_data)) { + /* Read incoming VDM */ + err =3D bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm)); + bm92t_state_machine(info, NINTENDO_CONFIG_HANDLED); + } + break; + /* End of Nintendo Dock VDMs */ + + case VDM_CUSTOM_CMD_SENT: + if (bm92t_received_vdm(alert_data)) { + cmd =3D ACCEPT_VDM_CMD; + err =3D bm92t_send_cmd(info, &cmd); + bm92t_state_machine(info, VDM_ACCEPT_CUSTOM_CMD_REPLY); + } else if (bm92t_is_success(alert_data)) + dev_dbg(dev, "cmd done in VDM_CUSTOM_CMD_SENT\n"); + break; + + case VDM_ACCEPT_CUSTOM_CMD_REPLY: + if (bm92t_is_success(alert_data)) { + /* Read incoming VDM */ + err =3D bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm)); + bm92t_state_machine(info, NORMAL_CONFIG_HANDLED); + } + break; + + case NORMAL_CONFIG_HANDLED: + case NINTENDO_CONFIG_HANDLED: + break; + + default: + dev_err(dev, "Invalid state!\n"); + break; + } + +ret: + enable_irq(info->i2c_client->irq); +} + +static irqreturn_t bm92t_interrupt_handler(int irq, void *handle) +{ + struct bm92t_info *info =3D handle; + + disable_irq_nosync(info->i2c_client->irq); + queue_work(info->event_wq, &info->work); + return IRQ_HANDLED; +} + +static void bm92t_remove(struct i2c_client *client) +{ + struct bm92t_info *info =3D i2c_get_clientdata(client); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove_recursive(info->debugfs_root); +#endif +} + +static void bm92t_shutdown(struct i2c_client *client) +{ + struct bm92t_info *info =3D i2c_get_clientdata(client); + + /* Disable Dock LED if enabled */ + bm92t_usbhub_led_cfg_wait(info, 0, 0, 0, 128); + + /* Disable SPDSRC */ + bm92t_set_source_mode(info, SPDSRC12_OFF); + + /* Disable DisplayPort Alerts */ + if (info->pdata->dp_alerts_enable) + bm92t_set_dp_alerts(info, false); +} + +#ifdef CONFIG_DEBUG_FS +static int bm92t_regs_print(struct seq_file *s, const char *reg_name, + unsigned char reg_addr, int size) +{ + int err; + unsigned char msg[5]; + unsigned short reg_val16; + unsigned short reg_val32; + struct bm92t_info *info =3D (struct bm92t_info *) (s->private); + + switch (size) { + case 2: + err =3D bm92t_read_reg(info, reg_addr, + (unsigned char *) ®_val16, sizeof(reg_val16)); + if (!err) + seq_printf(s, "%s 0x%04X\n", reg_name, reg_val16); + break; + case 4: + err =3D bm92t_read_reg(info, reg_addr, + (unsigned char *) ®_val32, sizeof(reg_val32)); + if (!err) + seq_printf(s, "%s 0x%08X\n", reg_name, reg_val32); + break; + case 5: + err =3D bm92t_read_reg(info, reg_addr, msg, sizeof(msg)); + if (!err) + seq_printf(s, "%s 0x%02X%02X%02X%02X\n", + reg_name, msg[4], msg[3], msg[2], msg[1]); + break; + default: + err =3D -EINVAL; + break; + } + + if (err) + dev_err(&info->i2c_client->dev, "Cannot read 0x%02X\n", reg_addr); + + return err; +} + +static int bm92t_regs_show(struct seq_file *s, void *data) +{ + int err; + + err =3D bm92t_regs_print(s, "ALERT_STATUS: ", ALERT_STATUS_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "STATUS1: ", STATUS1_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "STATUS2: ", STATUS2_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "DP_STATUS: ", DP_STATUS_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "CONFIG1: ", CONFIG1_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "CONFIG2: ", CONFIG2_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "SYS_CONFIG1: ", SYS_CONFIG1_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "SYS_CONFIG2: ", SYS_CONFIG2_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "SYS_CONFIG3: ", SYS_CONFIG3_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "VENDOR_CONFIG: ", VENDOR_CONFIG_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "DEV_CAPS: ", DEV_CAPS_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "ALERT_ENABLE: ", ALERT_ENABLE_REG, 4); + if (err) + return err; + err =3D bm92t_regs_print(s, "DP_ALERT_EN: ", DP_ALERT_EN_REG, 2); + if (err) + return err; + err =3D bm92t_regs_print(s, "AUTO_NGT_FIXED:", AUTO_NGT_FIXED_REG, 5); + if (err) + return err; + err =3D bm92t_regs_print(s, "AUTO_NGT_BATT: ", AUTO_NGT_BATT_REG, 5); + if (err) + return err; + err =3D bm92t_regs_print(s, "CURRENT_PDO: ", CURRENT_PDO_REG, 5); + if (err) + return err; + err =3D bm92t_regs_print(s, "CURRENT_RDO: ", CURRENT_RDO_REG, 5); + if (err) + return err; + + return 0; +} + +static int bm92t_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, bm92t_regs_show, inode->i_private); +} + +static const struct file_operations bm92t_regs_fops =3D { + .open =3D bm92t_regs_open, + .read =3D seq_read, + .llseek =3D seq_lseek, + .release =3D single_release, +}; + +static int bm92t_state_show(struct seq_file *s, void *data) +{ + struct bm92t_info *info =3D (struct bm92t_info *) (s->private); + + seq_printf(s, "%s\n", states[info->state]); + return 0; +} +static int bm92t_state_open(struct inode *inode, struct file *file) +{ + return single_open(file, bm92t_state_show, inode->i_private); +} + +static const struct file_operations bm92t_state_fops =3D { + .open =3D bm92t_state_open, + .read =3D seq_read, + .llseek =3D seq_lseek, + .release =3D single_release, +}; + +static int bm92t_fw_info_show(struct seq_file *s, void *data) +{ + struct bm92t_info *info =3D (struct bm92t_info *) (s->private); + + seq_printf(s, "fw_type: 0x%02X, fw_revision: 0x%02X\n", info->fw_type, in= fo->fw_revision); + return 0; +} +static int bm92t_fw_info_open(struct inode *inode, struct file *file) +{ + return single_open(file, bm92t_fw_info_show, inode->i_private); +} + +static const struct file_operations bm92t_fwinfo_fops =3D { + .open =3D bm92t_fw_info_open, + .read =3D seq_read, + .llseek =3D seq_lseek, + .release =3D single_release, +}; + +static ssize_t bm92t_led_write(struct file *file, + const char __user *userbuf, size_t count, loff_t *ppos) +{ + struct bm92t_info *info =3D (struct bm92t_info *) (file->private_data); + unsigned int duty, time_on, time_off, fade; + char buf[32]; + int ret; + + count =3D min_t(size_t, count, (sizeof(buf)-1)); + if (copy_from_user(buf, userbuf, count)) + return -EFAULT; + + buf[count] =3D 0; + + ret =3D sscanf(buf, "%i %i %i %i", + &duty, &time_on, &time_off, &fade); + + if (ret =3D=3D 4) { + if (info->state =3D=3D VDM_ACCEPT_ND_ENABLE_USBHUB_REPLY || + info->state =3D=3D NINTENDO_CONFIG_HANDLED) { + bm92t_state_machine(info, VDM_ND_CUSTOM_CMD_SENT); + bm92t_usbhub_led_cfg(info, duty, time_on, time_off, fade); + } else + dev_err(&info->i2c_client->dev, "LED is not supported\n"); + } else { + dev_err(&info->i2c_client->dev, "LED syntax is: duty time_on time_off fa= de\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations bm92t_led_fops =3D { + .open =3D simple_open, + .write =3D bm92t_led_write, +}; + +static ssize_t bm92t_cmd_write(struct file *file, + const char __user *userbuf, size_t count, loff_t *ppos) +{ + struct bm92t_info *info =3D (struct bm92t_info *) (file->private_data); + unsigned short cmd; + char buf[8]; + int ret; + + count =3D min_t(size_t, count, (sizeof(buf)-1)); + if (copy_from_user(buf, userbuf, count)) + return -EFAULT; + + buf[count] =3D 0; + + ret =3D kstrtou16(buf, 0, &cmd); + + if (ret !=3D 0) { + return -EINVAL; + + bm92t_send_cmd(info, &cmd); + + return count; +} + +static const struct file_operations bm92t_cmd_fops =3D { + .open =3D simple_open, + .write =3D bm92t_cmd_write, +}; + +static ssize_t bm92t_usbhub_dp_sleep_write(struct file *file, + const char __user *userbuf, size_t count, loff_t *ppos) +{ + struct bm92t_info *info =3D (struct bm92t_info *) (file->private_data); + unsigned int val; + char buf[8]; + int ret; + + count =3D min_t(size_t, count, (sizeof(buf)-1)); + if (copy_from_user(buf, userbuf, count)) + return -EFAULT; + + buf[count] =3D 0; + + ret =3D kstrtouint(buf, 0, &val); + + if (ret !=3D 0) + return -EINVAL; + + bm92t_usbhub_dp_sleep(info, val ? true : false); + + return count; +} + +static const struct file_operations bm92t_usbhub_dp_sleep_fops =3D { + .open =3D simple_open, + .write =3D bm92t_usbhub_dp_sleep_write, +}; + +static int bm92t_debug_init(struct bm92t_info *info) +{ + info->debugfs_root =3D debugfs_create_dir("bm92txx", NULL); + if (!info->debugfs_root) + goto failed; + + if (!debugfs_create_file("regs", 0400, + info->debugfs_root, + info, + &bm92t_regs_fops)) + goto failed; + + if (!debugfs_create_file("state", 0400, + info->debugfs_root, + info, + &bm92t_state_fops)) + goto failed; + + if (!debugfs_create_file("fw_info", 0400, + info->debugfs_root, + info, + &bm92t_fwinfo_fops)) + goto failed; + + if (!debugfs_create_file("led", 0200, + info->debugfs_root, + info, + &bm92t_led_fops)) + goto failed; + + if (!debugfs_create_file("cmd", 0200, + info->debugfs_root, + info, + &bm92t_cmd_fops)) + goto failed; + + if (!debugfs_create_file("sleep", 0200, + info->debugfs_root, + info, + &bm92t_usbhub_dp_sleep_fops)) + goto failed; + + return 0; + +failed: + dev_err(&info->i2c_client->dev, "%s: failed\n", __func__); + debugfs_remove_recursive(info->debugfs_root); + return -ENOMEM; +} +#endif + +static const struct of_device_id bm92t_of_match[] =3D { + { .compatible =3D "rohm,bm92t10", }, + { .compatible =3D "rohm,bm92t20", }, + { .compatible =3D "rohm,bm92t30", }, + { .compatible =3D "rohm,bm92t36", }, + { .compatible =3D "rohm,bm92t50", }, + { }, +}; + +MODULE_DEVICE_TABLE(of, bm92t_of_match); + +#ifdef CONFIG_OF +static struct bm92t_platform_data *bm92t_parse_dt(struct device *dev) +{ + struct device_node *np =3D dev->of_node; + struct bm92t_platform_data *pdata; + int ret =3D 0; + + if (!np) + return dev->platform_data; + + pdata =3D devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->dp_signal_toggle_on_resume =3D of_property_read_bool(np, + "rohm,dp-signal-toggle-on-resume"); + pdata->led_static_on_suspend =3D of_property_read_bool(np, + "rohm,led-static-on-suspend"); + pdata->dock_power_limit_disable =3D of_property_read_bool(np, + "rohm,dock-power-limit-disable"); + pdata->dp_alerts_enable =3D of_property_read_bool(np, + "rohm,dp-alerts-enable"); + + ret =3D of_property_read_u32(np, "rohm,pd-5v-current-limit-ma", + &pdata->pd_5v_current_limit); + if (ret) + pdata->pd_5v_current_limit =3D PD_05V_CHARGING_CURRENT_LIMIT_MA; + + ret =3D of_property_read_u32(np, "rohm,pd-9v-current-limit-ma", + &pdata->pd_9v_current_limit); + if (ret) + pdata->pd_9v_current_limit =3D PD_09V_CHARGING_CURRENT_LIMIT_MA; + + ret =3D of_property_read_u32(np, "rohm,pd-12v-current-limit-ma", + &pdata->pd_12v_current_limit); + if (ret) + pdata->pd_12v_current_limit =3D PD_12V_CHARGING_CURRENT_LIMIT_MA; + + ret =3D of_property_read_u32(np, "rohm,pd-15v-current-limit-ma", + &pdata->pd_15v_current_limit); + if (ret) + pdata->pd_15v_current_limit =3D PD_15V_CHARGING_CURRENT_LIMIT_MA; + + return pdata; +} +#else +static struct bm92t_platform_data *bm92t_parse_dt(struct device *dev) +{ + return NULL; +} +#endif + +static int bm92t_probe(struct i2c_client *client) +{ + struct bm92t_info *info; + struct regulator *batt_chg_reg; + struct regulator *vbus_reg; + struct fwnode_handle *ep, *remote; + int err; + unsigned short reg_value; + + /* Get battery charger and VBUS regulators */ + batt_chg_reg =3D devm_regulator_get(&client->dev, "pd_bat_chg"); + if (IS_ERR(batt_chg_reg)) { + err =3D PTR_ERR(batt_chg_reg); + if (err =3D=3D -EPROBE_DEFER) + return err; + + dev_err(&client->dev, "pd_bat_chg reg not registered: %d\n", err); + batt_chg_reg =3D NULL; + } + + vbus_reg =3D devm_regulator_get(&client->dev, "vbus"); + if (IS_ERR(vbus_reg)) { + err =3D PTR_ERR(vbus_reg); + if (err =3D=3D -EPROBE_DEFER) + return err; + + dev_err(&client->dev, "vbus reg not registered: %d\n", err); + vbus_reg =3D NULL; + } + + info =3D devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + if (client->dev.of_node) { + info->pdata =3D bm92t_parse_dt(&client->dev); + if (IS_ERR(info->pdata)) + return PTR_ERR(info->pdata); + } else { + info->pdata =3D client->dev.platform_data; + if (!info->pdata) { + dev_err(&client->dev, "no platform data provided\n"); + return -EINVAL; + } + } + + ep =3D fwnode_graph_get_endpoint_by_id(dev_fwnode(&client->dev), 0, 0, 0); + if (!ep) { + dev_err(&client->dev, "Endpoint not found\n"); + return -ENODEV; + } + + remote =3D fwnode_graph_get_remote_endpoint(ep); + if (!remote) { + dev_err(&client->dev, "Remote not found\n"); + return -ENODEV; + } + + info->role_sw =3D fwnode_usb_role_switch_get(remote); + if (IS_ERR_OR_NULL(info->role_sw)) { + err =3D PTR_ERR(info->role_sw); + dev_err(&client->dev, "Failed to retrieve fwnode: %d\n", err); + return err; + } + + i2c_set_clientdata(client, info); + + info->i2c_client =3D client; + + info->batt_chg_reg =3D batt_chg_reg; + info->vbus_reg =3D vbus_reg; + + /* Initialized state */ + info->state =3D INIT_STATE; + info->first_init =3D true; + + /* Allocate extcon */ + info->edev =3D devm_extcon_dev_allocate(&client->dev, bm92t_extcon_cable); + if (IS_ERR(info->edev)) + return -ENOMEM; + + /* Register extcon */ + err =3D devm_extcon_dev_register(&client->dev, info->edev); + if (err < 0) { + dev_err(&client->dev, "Cannot register extcon device\n"); + return err; + } + + /* Create workqueue */ + info->event_wq =3D create_singlethread_workqueue("bm92t-event-queue"); + if (!info->event_wq) { + dev_err(&client->dev, "Cannot create work queue\n"); + return -ENOMEM; + } + + err =3D bm92t_read_reg(info, FW_TYPE_REG, (unsigned char *) ®_value, s= izeof(reg_value)); + info->fw_type =3D reg_value; + + err =3D bm92t_read_reg(info, FW_REVISION_REG, (unsigned char *) ®_valu= e, + sizeof(reg_value)); + info->fw_revision =3D reg_value; + + dev_info(&info->i2c_client->dev, "fw_type: 0x%02X, fw_revision: 0x%02X\n"= , info->fw_type, + info->fw_revision); + + if (info->fw_revision <=3D 0x644) + return -EINVAL; + + /* Disable Source mode at boot */ + bm92t_set_source_mode(info, SPDSRC12_OFF); + + INIT_WORK(&info->work, bm92t_event_handler); + INIT_DELAYED_WORK(&info->oneshot_work, bm92t_extcon_cable_set_init_state); + + INIT_DELAYED_WORK(&info->power_work, bm92t_power_work); + + if (client->irq > 0) { + if (request_irq(client->irq, bm92t_interrupt_handler, IRQF_TRIGGER_LOW, = "bm92t", + info)) { + dev_err(&client->dev, "Request irq failed\n"); + destroy_workqueue(info->event_wq); + return -EBUSY; + } + } + + schedule_delayed_work(&info->oneshot_work, msecs_to_jiffies(5000)); + +#ifdef CONFIG_DEBUG_FS + bm92t_debug_init(info); +#endif + + dev_info(&client->dev, "bm92txx driver loading done\n"); + return 0; +} + +#ifdef CONFIG_PM +static int bm92t_pm_suspend(struct device *dev) +{ + struct bm92t_info *info =3D dev_get_drvdata(dev); + struct i2c_client *client =3D info->i2c_client; + + /* Dim or breathing Dock LED */ + if (info->pdata->led_static_on_suspend) + bm92t_usbhub_led_cfg_wait(info, 16, 0, 0, 128); + else + bm92t_usbhub_led_cfg_wait(info, 32, 1, 255, 255); + + if (client->irq > 0) { + disable_irq(client->irq); + enable_irq_wake(client->irq); + } + + return 0; +} + +static int bm92t_pm_resume(struct device *dev) +{ + struct bm92t_info *info =3D dev_get_drvdata(dev); + struct i2c_client *client =3D info->i2c_client; + bool enable_led =3D info->state =3D=3D NINTENDO_CONFIG_HANDLED; + + if (client->irq > 0) { + enable_irq(client->irq); + disable_irq_wake(client->irq); + } + + /* + * Toggle DP signal + * Do a toggle on resume instead of disable in suspend + * and enable in resume, because this also disables the + * led effects. + */ + if (info->pdata->dp_signal_toggle_on_resume) { + bm92t_usbhub_dp_sleep(info, true); + bm92t_usbhub_dp_sleep(info, false); + } + + /* Set Dock LED to ON state */ + if (enable_led) + bm92t_usbhub_led_cfg_wait(info, 128, 0, 0, 64); + + return 0; +} + +static const struct dev_pm_ops bm92t_pm_ops =3D { + .suspend =3D bm92t_pm_suspend, + .resume =3D bm92t_pm_resume, +}; +#endif + +static const struct i2c_device_id bm92t_id[] =3D { + { "bm92t", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, bm92t_id); + +static struct i2c_driver bm92t_i2c_driver =3D { + .driver =3D { + .name =3D "bm92t", + .owner =3D THIS_MODULE, + .of_match_table =3D bm92t_of_match, +#ifdef CONFIG_PM + .pm =3D &bm92t_pm_ops, +#endif + }, + .id_table =3D bm92t_id, + .probe =3D bm92t_probe, + .remove =3D bm92t_remove, + .shutdown =3D bm92t_shutdown, +}; + +static int __init bm92t_init(void) +{ + return i2c_add_driver(&bm92t_i2c_driver); +} +subsys_initcall_sync(bm92t_init); + +static void __exit bm92t_exit(void) +{ + i2c_del_driver(&bm92t_i2c_driver); +} +module_exit(bm92t_exit); + +MODULE_LICENSE("GPL"); --=20 2.42.0