All of lore.kernel.org
 help / color / mirror / Atom feed
From: Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
To: unlisted-recipients:; (no To-header on input)
Cc: azkali <a.ffcc7@gmail.com>,
	Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>,
	Adam Jiang <chaoj@nvidia.com>, CTCaer <ctcaer@gmail.com>,
	Rob Herring <robh+dt@kernel.org>,
	Krzysztof Kozlowski <krzysztof.kozlowski+dt@linaro.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Derek Kiernan <derek.kiernan@amd.com>,
	Dragan Cvetic <dragan.cvetic@amd.com>,
	Arnd Bergmann <arnd@arndb.de>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	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	[thread overview]
Message-ID: <20230824153059.212244-3-linkmauve@linkmauve.fr> (raw)
In-Reply-To: <20230824153059.212244-1-linkmauve@linkmauve.fr>

From: azkali <a.ffcc7@gmail.com>

This is used as the USB-C Power Delivery controller of the Nintendo
Switch.

Signed-off-by: Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
Signed-off-by: azkali <a.ffcc7@gmail.com>
Signed-off-by: Adam Jiang <chaoj@nvidia.com>
Signed-off-by: CTCaer <ctcaer@gmail.com>
---
 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 <linkmauve@linkmauve.fr>
 S:	Supported
 F:	Documentation/devicetree/bindings/misc/rohm,bm92txx.yaml
+F:	drivers/misc/bm92txx.c
 
 ROSE NETWORK LAYER
 M:	Ralf Baechle <ralf@linux-mips.org>
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.
 
+config BM92TXX
+	tristate "Rohm Semiconductor BM92TXX USB Type-C Support"
+	depends on I2C=y
+	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)      += xilinx_tmr_manager.o
 obj-$(CONFIG_TMR_INJECT)	+= xilinx_tmr_inject.o
 obj-$(CONFIG_TPS6594_ESM)	+= tps6594-esm.o
 obj-$(CONFIG_TPS6594_PFSM)	+= tps6594-pfsm.o
+obj-$(CONFIG_BM92TXX)		+= 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 <ctcaer@gmail.com>
+ * Copyright (c) 2023 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+ *
+ * Authors:
+ *     Adam Jiang <chaoj@nvidia.com>
+ *     CTCaer <ctcaer@gmail.com>
+ *     Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/debugfs.h>
+#include <linux/extcon-provider.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/role.h>
+#include <linux/uaccess.h>
+
+/* 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 Valid, 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 enable */
+#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_SHIFT)
+#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 Capable UFP */
+#define SYS_CONFIG2_NO_COMM_DFP          BIT(1) /* Force no USB comms Capable DFP */
+#define SYS_CONFIG2_NO_COMM_ON_NO_BATT   BIT(2) /* Force no USB comms on dead battery */
+#define SYS_CONFIG2_AUTO_SPDSNK_EN       BIT(6) /* Enable SPDSNK without SYS_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_SHIFT)
+
+/* VENDOR_CONFIG_REG */
+#define VENDOR_CONFIG_OCP_DISABLE  BIT(2) /* Disable Over-current protection */
+
+/* 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 end */
+#define SEND_VDM_2_CMD            0x1313 /* Send VMD SOP'' EM cable far end */
+#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/Contract */
+#define BIST_CARR_M2_CONT_STR_CMD 0x2F2F /* Send BIST carrier mode 2 continuous 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 DP 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 = 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[] = {
+	"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[] = {
+	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[] = {
+	{ 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[] = {
+	100, 150, 500, 900, 1200, 1500, 2000, 3000
+};
+
+/* USB-PD common VDMs */
+unsigned char vdm_discover_id_msg[6] = {OUTGOING_VDM_REG, 4,
+	VDM_CMD_DISC_ID, VDM_STRUCTURED, 0x00, 0xFF};
+
+unsigned char vdm_discover_svid_msg[6] = {OUTGOING_VDM_REG, 4,
+	VDM_CMD_DISC_SVID, VDM_STRUCTURED, 0x00, 0xFF};
+
+unsigned char vdm_discover_mode_msg[6] = {OUTGOING_VDM_REG, 4,
+	VDM_CMD_DISC_MODE, VDM_STRUCTURED, 0x01, 0xFF}; /* DisplayPort Alt Mode */
+
+unsigned char vdm_exit_dp_alt_mode_msg[6] = {OUTGOING_VDM_REG, 4,
+	VDM_CMD_EXIT_MODE, VDM_STRUCTURED | 1, 0x01, 0xFF};
+
+unsigned char vdm_enter_nin_alt_mode_msg[6] = {OUTGOING_VDM_REG, 4,
+	VDM_CMD_ENTER_MODE, VDM_STRUCTURED | 1, 0x7E, 0x05};
+
+/* Nintendo Dock VDMs */
+unsigned char vdm_query_device_msg[10] = {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] = {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] = {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] = {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] = {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] = {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 = info->i2c_client->addr;
+	xfer_msg[0].len = len;
+	xfer_msg[0].flags = I2C_M_NOSTART;
+	xfer_msg[0].buf = buf;
+
+	dev_dbg(&info->i2c_client->dev, "write reg cmd = 0x%02X len = %u\n", buf[0], len);
+	return (i2c_transfer(info->i2c_client->adapter, xfer_msg, 1) != 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 = reg;
+
+	xfer_msg[0].addr = info->i2c_client->addr;
+	xfer_msg[0].len = 1;
+	xfer_msg[0].flags = 0;
+	xfer_msg[0].buf = &reg_addr;
+
+	xfer_msg[1].addr = info->i2c_client->addr;
+	xfer_msg[1].len = num;
+	xfer_msg[1].flags = I2C_M_RD;
+	xfer_msg[1].buf = buf;
+
+	err = 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 != 2);
+}
+
+static int bm92t_send_cmd(struct bm92t_info *info, unsigned short *cmd)
+{
+	int ret;
+	unsigned char reg;
+	unsigned char *_cmd = (unsigned char *) cmd;
+	unsigned char msg[3];
+
+	if (!cmd)
+		return -EINVAL;
+
+	reg = COMMAND_REG;
+
+	msg[0] = reg;
+	msg[1] = _cmd[0];
+	msg[2] = _cmd[1];
+
+	ret = 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) == DATA_ROLE_UFP);
+}
+
+static inline bool bm92t_is_dfp(const short status1_data)
+{
+	return (((status1_data & STATUS1_DR_MASK) >> STATUS1_DR_SHIFT) == DATA_ROLE_DFP);
+}
+
+static inline bool bm92t_is_lastcmd_ok(struct bm92t_info *info,
+	const char *cmd, const short status1_data)
+{
+	unsigned int lastcmd_status =
+		(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 == LASTCMD_COMPLETE);
+}
+
+static int bm92t_handle_dp_config_and_hpd(struct bm92t_info *info)
+{
+	int err;
+	bool pin_valid = false;
+	unsigned char msg[5];
+	unsigned short cmd = DP_CFG_AND_START_HPD_CMD;
+	unsigned char cfg[6] = {OUTGOING_VDM_REG, 0x04,
+		VDO_DP_SUPPORT | VDO_DP_U_UFP_D, 0x00, 0x00, 0x00};
+
+	err = 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] = 0x00;
+			cfg[4] = VDO_DP_PIN_D;
+			pin_valid = true;
+		}
+	} else if (!(msg[1] & VDO_DP_RECEPTACLE)) { /* Plug */
+		if (msg[2] & VDO_DP_PIN_D) { /* 2 DP Lanes */
+			cfg[3] = VDO_DP_PIN_D;
+			cfg[4] = 0x00;
+			pin_valid = true;
+		} else if (msg[2] & VDO_DP_PIN_C) { /* 4 DP Lanes - 2 Unused */
+			cfg[3] = VDO_DP_PIN_C;
+			cfg[4] = 0x00;
+			pin_valid = 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] = VDO_DP_PIN_D;
+			cfg[4] = 0x00;
+			pin_valid = true;
+		} else if (msg[3] & VDO_DP_PIN_C) { /* 4 DP Lanes - 2 Unused */
+			cfg[3] = VDO_DP_PIN_C;
+			cfg[4] = 0x00;
+			pin_valid = true;
+		}
+	}
+
+	/* Check that UFP_U/UFP_D Pin D assignment is supported */
+	if (!err && msg[0] == 4 && pin_valid) {
+		/* Set DP configuration */
+		err = 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 = 0;
+
+	if (info->batt_chg_reg != NULL)
+		ret = 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 = 0;
+	bool is_enabled;
+
+	dev_dbg(&info->i2c_client->dev, "%s VBUS\n", enable ? "Enabling" : "Disabling");
+	if (info->vbus_reg != NULL) {
+		is_enabled = regulator_is_enabled(info->vbus_reg);
+		if (enable && !is_enabled)
+			ret = regulator_enable(info->vbus_reg);
+		else if (is_enabled)
+			ret = regulator_disable(info->vbus_reg);
+	}
+
+	return ret;
+}
+
+static int bm92t_set_source_mode(struct bm92t_info *info, unsigned int role)
+{
+	int err = 0;
+	unsigned short value;
+	unsigned char msg[3] = {CONFIG1_REG, 0, 0};
+
+	err = bm92t_read_reg(info, CONFIG1_REG,
+		(unsigned char *) &value, sizeof(value));
+	if (err < 0)
+		return err;
+
+	if (((value & CONFIG1_SPDSRC_MASK) >> CONFIG1_SPDSRC_SHIFT) != role) {
+		value &= ~CONFIG1_SPDSRC_MASK;
+		value |= role << CONFIG1_SPDSRC_SHIFT;
+		msg[1] = value & 0xFF;
+		msg[2] = (value >> 8) & 0xFF;
+		err = bm92t_write_reg(info, msg, sizeof(msg));
+	}
+
+	return err;
+}
+
+static int bm92t_set_dp_alerts(struct bm92t_info *info, bool enable)
+{
+	int err = 0;
+	unsigned char msg[3] = {DP_ALERT_EN_REG, 0, 0};
+
+	msg[1] = enable ? 0xFF : 0x00;
+	msg[2] = enable ? 0xFF : 0x00;
+	err = bm92t_write_reg(info, msg, sizeof(msg));
+
+	return err;
+}
+
+static int bm92t_enable_ocp(struct bm92t_info *info)
+{
+	int err = 0;
+	unsigned short value;
+	unsigned char msg[3] = {VENDOR_CONFIG_REG, 0, 0};
+
+	bm92t_read_reg(info, VENDOR_CONFIG_REG,
+		(unsigned char *) &value, sizeof(value));
+	if (value & VENDOR_CONFIG_OCP_DISABLE) {
+		value &= ~VENDOR_CONFIG_OCP_DISABLE;
+		msg[1] = value & 0xFF;
+		msg[2] = (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 = 0;
+	unsigned short cmd = 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 = bm92t_read_reg(info, ALERT_STATUS_REG,
+				     (unsigned char *) &alert_data,
+				     sizeof(alert_data));
+		goto ret;
+	}
+
+	err = bm92t_read_reg(info, STATUS1_REG,
+			     (unsigned char *) &status1_data,
+			     sizeof(status1_data));
+	if (err < 0)
+		goto ret;
+	err = 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 = 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 = ARRAY_SIZE(bm92t_extcon_cable_names);
+
+	for (i = 0; i < count; i++)
+		if (bm92t_extcon_cable_names[i].cable == 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 = extcon_get_state(info->edev, cable);
+
+	if (state != 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 = USB_ROLE_DEVICE;
+		break;
+	case EXTCON_USB_HOST:
+		role = USB_ROLE_HOST;
+		break;
+	default:
+		role = USB_ROLE_NONE;
+		break;
+	}
+
+	if (role != 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 = state;
+	dev_dbg(&info->i2c_client->dev, "state = %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 = amperage;
+	struct bm92t_platform_data *pdata = info->pdata;
+
+	/* Subtract a USB2 or USB3 port current */
+	if (voltage > 5000)
+		charging_limit -= (PD_POWER_RESERVE_UA / voltage);
+	else
+		charging_limit -= (NON_PD_POWER_RESERVE_UA / voltage);
+
+	/* Set limits */
+	switch (voltage) {
+	case 5000:
+		charging_limit = min(charging_limit, pdata->pd_5v_current_limit);
+		break;
+	case 9000:
+		charging_limit = min(charging_limit, pdata->pd_9v_current_limit);
+		break;
+	case 12000:
+		charging_limit = min(charging_limit, pdata->pd_12v_current_limit);
+		break;
+	case 15000:
+	default:
+		charging_limit = min(charging_limit, pdata->pd_15v_current_limit);
+		break;
+	}
+
+	/* Set actual amperage */
+	for (i = ARRAY_SIZE(current_input_limits) - 1; i >= 0; i--) {
+		if (charging_limit >= current_input_limits[i]) {
+			charging_limit = current_input_limits[i];
+			break;
+		}
+	}
+
+	info->cable.charging_limit = charging_limit;
+}
+
+static void bm92t_power_work(struct work_struct *work)
+{
+	struct bm92t_info *info = container_of(
+		to_delayed_work(work), struct bm92t_info, power_work);
+
+	bm92t_set_current_limit(info, info->cable.charging_limit * 1000u);
+	info->charging_enabled = 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 = 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 = 0;
+	unsigned int amperage, voltage, wattage, type;
+
+	dev = &info->i2c_client->dev;
+
+	memset(&info->cable, 0, sizeof(struct bm92t_device));
+
+	err = bm92t_read_reg(info, READ_PDOS_SRC_REG, pdos, sizeof(pdos));
+	pdos_no = 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 = 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 = true;
+
+	/* Check for dock mode */
+	if (!info->pdata->dock_power_limit_disable &&
+	    pdos_no == 2 &&
+	    (pdo[0].volt * 50) == DOCK_ID_VOLTAGE_MV  &&
+	    (pdo[0].amp * 10)  == DOCK_ID_CURRENT_MA) {
+		/* Only accept 15V, >= 2.6A for dock mode. */
+		if (pdo[1].type == PDO_TYPE_FIXED &&
+		   (pdo[1].volt * 50) == DOCK_INPUT_VOLTAGE_MV &&
+		   (pdo[1].amp * 10)  >= DOCK_INPUT_CURRENT_LIMIT_MIN_MA &&
+		   (pdo[1].amp * 10)  <= DOCK_INPUT_CURRENT_LIMIT_MAX_MA) {
+			dev_info(dev, "Device in Nintendo mode\n");
+			info->cable.pdo_no = 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 = 0; i < pdos_no; ++i) {
+		type = pdo[i].type;
+		voltage = pdo[i].volt * 50;
+		amperage = pdo[i].amp * 10;
+		wattage = 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 <= 3A and select max wattage with max voltage. */
+		if (type == PDO_TYPE_FIXED &&
+		    amperage >= PD_INPUT_CURRENT_LIMIT_MIN_MA &&
+		    amperage <= PD_INPUT_CURRENT_LIMIT_MAX_MA) {
+			if (wattage > prev_wattage ||
+			   (voltage > (info->cable.pdo.volt * 50) &&
+				wattage && wattage == prev_wattage) ||
+			   (!info->cable.pdo_no && !amperage && voltage == 5000)) {
+				prev_wattage = wattage;
+				info->cable.pdo_no = 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 = { 0 };
+	unsigned char msg[6] = { SET_RDO_REG, 0x04, 0x00, 0x00, 0x00, 0x00};
+	unsigned short cmd = 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 = 1;
+	rdo.obj_no = info->cable.pdo_no;
+	rdo.max_amp = info->cable.pdo.amp;
+	rdo.op_amp = info->cable.charging_limit / 10;
+
+	memcpy(&msg[2], &rdo, sizeof(struct rd_object));
+
+	err = 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 = SEND_VDM_CMD;
+
+	err = 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] = fade;
+	vdm_usbhub_led_msg[11] = time_off;
+	vdm_usbhub_led_msg[12] = time_on;
+	vdm_usbhub_led_msg[13] = 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 = 100;
+
+	if (info->state == 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 != 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 = 100;
+
+	if (info->state == NINTENDO_CONFIG_HANDLED ||
+		info->state == NORMAL_CONFIG_HANDLED) {
+
+		if (info->state == 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] = sleep ? 1 : 0;
+
+		bm92t_send_vdm(info, vdm_usbhub_dp_sleep_msg,
+			sizeof(vdm_usbhub_dp_sleep_msg));
+
+		while (info->state != NINTENDO_CONFIG_HANDLED ||
+			   info->state != 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 = 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 = container_of(work, struct bm92t_info, work);
+	dev = &info->i2c_client->dev;
+
+	/* Read status registers at 02h, 03h and 04h */
+	err = bm92t_read_reg(info, ALERT_STATUS_REG,
+			     (unsigned char *) &alert_data,
+			     sizeof(alert_data));
+	if (err < 0)
+		goto ret;
+	err = bm92t_read_reg(info, STATUS1_REG,
+			     (unsigned char *) &status1_data,
+			     sizeof(status1_data));
+	if (err < 0)
+		goto ret;
+	err = bm92t_read_reg(info, STATUS2_REG,
+			     (unsigned char *) &status2_data,
+			     sizeof(status2_data));
+	if (err < 0)
+		goto ret;
+	err = bm92t_read_reg(info, DP_STATUS_REG,
+			     (unsigned char *) &dp_data,
+			     sizeof(dp_data));
+	if (err < 0)
+		goto ret;
+
+	dev_info_ratelimited(dev,
+		"Alert= 0x%04X Status1= 0x%04X Status2= 0x%04X DP= 0x%04X State= %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 == ALERT_DP_EVENT)
+		goto ret;
+
+	/* Check for errors */
+	err = status1_data & STATUS1_FAULT_MASK;
+	if (err) {
+		dev_err(dev, "Internal error occurred. Ecode = %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 == 0) {
+			bm92t_system_reset_auto(info, true);
+			sys_reset = true;
+		}
+		goto ret;
+	}
+
+	/* Check if sys reset happened */
+	if (sys_reset) {
+		sys_reset = 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 = 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 = false;
+				bm92t_extcon_cable_update(info, EXTCON_CHG_USB_PD, false);
+			}
+
+			/* Reset USB modes and state */
+			retries_usbhub = 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 = 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 = 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 = 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 = bm92t_read_reg(info, CURRENT_PDO_REG,
+				pdo, sizeof(pdo));
+			memcpy(&curr_pdo, &pdo[1], sizeof(struct pd_object));
+			err = 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 = PS_RDY_CMD;
+			err = 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 = DR_SWAP_CMD;
+				err = 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 = ACCEPT_VDM_CMD;
+			err = 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 = 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 == VDO_ID_TYPE_ALTERNATE &&
+				  info->cable.vdo.vid == VID_NINTENDO &&
+				  info->cable.vdo.pid == 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 = 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 = ACCEPT_VDM_CMD;
+			err = 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 = bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm));
+
+			if (vdm[1] == (VDM_ACK | VDM_CMD_DISC_SVID)) {
+				dev_info(dev, "Supported SVIDs:\n");
+				for (i = 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 = ACCEPT_VDM_CMD;
+			err = 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 = 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] == (VDM_ACK | VDM_CMD_DISC_MODE) &&
+					vdm[2] == VDM_STRUCTURED &&
+					vdm[3] == 0x01 && vdm[4] == 0xFF && /* SVID DisplayPort */
+					vdm[5] & VDO_DP_UFP_D &&
+					vdm[5] & VDO_DP_SUPPORT) {
+				dev_info(dev, "DisplayPort Alt Mode supported");
+				for (i = 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 = DP_ENTER_MODE_CMD;
+				err = 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 = ACCEPT_VDM_CMD;
+			err = 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 = bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm));
+
+			/* Check if supported. */
+			if (!(vdm[1] == (VDM_ACK | VDM_CMD_ENTER_MODE) &&
+				vdm[2] == (VDM_STRUCTURED | 1) &&
+				vdm[3] == 0x7e && vdm[4] == 0x05)) {
+				dev_err(dev, "Failed to enter Nintendo Alt Mode!\n");
+				break;
+			}
+
+			/* Enter automatic DisplayPort handling */
+			cmd = DP_ENTER_MODE_CMD;
+			err = 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 = 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 = ACCEPT_VDM_CMD;
+			err = 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 = bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm));
+
+			if (!err && vdm[6] == VDM_ND_DOCK &&
+				 vdm[7] == (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 = ACCEPT_VDM_CMD;
+			err = 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_msg));
+			bm92t_state_machine(info, VDM_ND_ENABLE_USBHUB_SENT);
+		}
+		break;
+
+	case VDM_ND_ENABLE_USBHUB_SENT:
+		if (bm92t_received_vdm(alert_data)) {
+			cmd = ACCEPT_VDM_CMD;
+			err = 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 = bm92t_read_reg(info, INCOMING_VDM_REG, vdm, sizeof(vdm));
+
+			if ((vdm[6] == VDM_ND_DOCK &&
+				 vdm[7] == (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 = ACCEPT_VDM_CMD;
+			err = 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 = 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 = ACCEPT_VDM_CMD;
+			err = 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 = 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 = 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 = 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 = 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 = (struct bm92t_info *) (s->private);
+
+	switch (size) {
+	case 2:
+		err = bm92t_read_reg(info, reg_addr,
+			    (unsigned char *) &reg_val16, sizeof(reg_val16));
+		if (!err)
+			seq_printf(s, "%s 0x%04X\n", reg_name, reg_val16);
+		break;
+	case 4:
+		err = bm92t_read_reg(info, reg_addr,
+			    (unsigned char *) &reg_val32, sizeof(reg_val32));
+		if (!err)
+			seq_printf(s, "%s 0x%08X\n", reg_name, reg_val32);
+		break;
+	case 5:
+		err = 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 = -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 = bm92t_regs_print(s, "ALERT_STATUS:  ", ALERT_STATUS_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "STATUS1:       ", STATUS1_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "STATUS2:       ", STATUS2_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "DP_STATUS:     ", DP_STATUS_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "CONFIG1:       ", CONFIG1_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "CONFIG2:       ", CONFIG2_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "SYS_CONFIG1:   ", SYS_CONFIG1_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "SYS_CONFIG2:   ", SYS_CONFIG2_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "SYS_CONFIG3:   ", SYS_CONFIG3_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "VENDOR_CONFIG: ", VENDOR_CONFIG_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "DEV_CAPS:      ", DEV_CAPS_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "ALERT_ENABLE:  ", ALERT_ENABLE_REG, 4);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "DP_ALERT_EN:   ", DP_ALERT_EN_REG, 2);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "AUTO_NGT_FIXED:", AUTO_NGT_FIXED_REG, 5);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "AUTO_NGT_BATT: ", AUTO_NGT_BATT_REG, 5);
+	if (err)
+		return err;
+	err = bm92t_regs_print(s, "CURRENT_PDO:   ", CURRENT_PDO_REG, 5);
+	if (err)
+		return err;
+	err = 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 = {
+	.open = bm92t_regs_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static int bm92t_state_show(struct seq_file *s, void *data)
+{
+	struct bm92t_info *info = (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 = {
+	.open = bm92t_state_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static int bm92t_fw_info_show(struct seq_file *s, void *data)
+{
+	struct bm92t_info *info = (struct bm92t_info *) (s->private);
+
+	seq_printf(s, "fw_type: 0x%02X, fw_revision: 0x%02X\n", info->fw_type, info->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 = {
+	.open = bm92t_fw_info_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = 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 = (struct bm92t_info *) (file->private_data);
+	unsigned int duty, time_on, time_off, fade;
+	char buf[32];
+	int ret;
+
+	count = min_t(size_t, count, (sizeof(buf)-1));
+	if (copy_from_user(buf, userbuf, count))
+		return -EFAULT;
+
+	buf[count] = 0;
+
+	ret = sscanf(buf, "%i %i %i %i",
+		&duty, &time_on, &time_off, &fade);
+
+	if (ret == 4) {
+		if (info->state == VDM_ACCEPT_ND_ENABLE_USBHUB_REPLY ||
+		    info->state == 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 fade\n");
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static const struct file_operations bm92t_led_fops = {
+	.open = simple_open,
+	.write = 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 = (struct bm92t_info *) (file->private_data);
+	unsigned short cmd;
+	char buf[8];
+	int ret;
+
+	count = min_t(size_t, count, (sizeof(buf)-1));
+	if (copy_from_user(buf, userbuf, count))
+		return -EFAULT;
+
+	buf[count] = 0;
+
+	ret = kstrtou16(buf, 0, &cmd);
+
+	if (ret != 0) {
+		return -EINVAL;
+
+	bm92t_send_cmd(info, &cmd);
+
+	return count;
+}
+
+static const struct file_operations bm92t_cmd_fops = {
+	.open = simple_open,
+	.write = 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 = (struct bm92t_info *) (file->private_data);
+	unsigned int val;
+	char buf[8];
+	int ret;
+
+	count = min_t(size_t, count, (sizeof(buf)-1));
+	if (copy_from_user(buf, userbuf, count))
+		return -EFAULT;
+
+	buf[count] = 0;
+
+	ret = kstrtouint(buf, 0, &val);
+
+	if (ret != 0)
+		return -EINVAL;
+
+	bm92t_usbhub_dp_sleep(info, val ? true : false);
+
+	return count;
+}
+
+static const struct file_operations bm92t_usbhub_dp_sleep_fops = {
+	.open = simple_open,
+	.write = bm92t_usbhub_dp_sleep_write,
+};
+
+static int bm92t_debug_init(struct bm92t_info *info)
+{
+	info->debugfs_root = 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[] = {
+	{ .compatible = "rohm,bm92t10", },
+	{ .compatible = "rohm,bm92t20", },
+	{ .compatible = "rohm,bm92t30", },
+	{ .compatible = "rohm,bm92t36", },
+	{ .compatible = "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 = dev->of_node;
+	struct bm92t_platform_data *pdata;
+	int ret = 0;
+
+	if (!np)
+		return dev->platform_data;
+
+	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return ERR_PTR(-ENOMEM);
+
+	pdata->dp_signal_toggle_on_resume = of_property_read_bool(np,
+					"rohm,dp-signal-toggle-on-resume");
+	pdata->led_static_on_suspend = of_property_read_bool(np,
+					"rohm,led-static-on-suspend");
+	pdata->dock_power_limit_disable = of_property_read_bool(np,
+					"rohm,dock-power-limit-disable");
+	pdata->dp_alerts_enable = of_property_read_bool(np,
+					"rohm,dp-alerts-enable");
+
+	ret = of_property_read_u32(np, "rohm,pd-5v-current-limit-ma",
+			&pdata->pd_5v_current_limit);
+	if (ret)
+		pdata->pd_5v_current_limit = PD_05V_CHARGING_CURRENT_LIMIT_MA;
+
+	ret = of_property_read_u32(np, "rohm,pd-9v-current-limit-ma",
+			&pdata->pd_9v_current_limit);
+	if (ret)
+		pdata->pd_9v_current_limit = PD_09V_CHARGING_CURRENT_LIMIT_MA;
+
+	ret = of_property_read_u32(np, "rohm,pd-12v-current-limit-ma",
+			&pdata->pd_12v_current_limit);
+	if (ret)
+		pdata->pd_12v_current_limit = PD_12V_CHARGING_CURRENT_LIMIT_MA;
+
+	ret = of_property_read_u32(np, "rohm,pd-15v-current-limit-ma",
+			&pdata->pd_15v_current_limit);
+	if (ret)
+		pdata->pd_15v_current_limit = 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 = devm_regulator_get(&client->dev, "pd_bat_chg");
+	if (IS_ERR(batt_chg_reg)) {
+		err = PTR_ERR(batt_chg_reg);
+		if (err == -EPROBE_DEFER)
+			return err;
+
+		dev_err(&client->dev, "pd_bat_chg reg not registered: %d\n", err);
+		batt_chg_reg = NULL;
+	}
+
+	vbus_reg = devm_regulator_get(&client->dev, "vbus");
+	if (IS_ERR(vbus_reg)) {
+		err = PTR_ERR(vbus_reg);
+		if (err == -EPROBE_DEFER)
+			return err;
+
+		dev_err(&client->dev, "vbus reg not registered: %d\n", err);
+		vbus_reg = NULL;
+	}
+
+	info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	if (client->dev.of_node) {
+		info->pdata = bm92t_parse_dt(&client->dev);
+		if (IS_ERR(info->pdata))
+			return PTR_ERR(info->pdata);
+	} else {
+		info->pdata = client->dev.platform_data;
+		if (!info->pdata) {
+			dev_err(&client->dev, "no platform data provided\n");
+			return -EINVAL;
+		}
+	}
+
+	ep = 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 = fwnode_graph_get_remote_endpoint(ep);
+	if (!remote) {
+		dev_err(&client->dev, "Remote not found\n");
+		return -ENODEV;
+	}
+
+	info->role_sw = fwnode_usb_role_switch_get(remote);
+	if (IS_ERR_OR_NULL(info->role_sw)) {
+		err = 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 = client;
+
+	info->batt_chg_reg = batt_chg_reg;
+	info->vbus_reg = vbus_reg;
+
+	/* Initialized state */
+	info->state = INIT_STATE;
+	info->first_init = true;
+
+	/* Allocate extcon */
+	info->edev = devm_extcon_dev_allocate(&client->dev, bm92t_extcon_cable);
+	if (IS_ERR(info->edev))
+		return -ENOMEM;
+
+	/* Register extcon */
+	err = 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 = create_singlethread_workqueue("bm92t-event-queue");
+	if (!info->event_wq) {
+		dev_err(&client->dev, "Cannot create work queue\n");
+		return -ENOMEM;
+	}
+
+	err = bm92t_read_reg(info, FW_TYPE_REG, (unsigned char *) &reg_value, sizeof(reg_value));
+	info->fw_type = reg_value;
+
+	err = bm92t_read_reg(info, FW_REVISION_REG, (unsigned char *) &reg_value,
+			     sizeof(reg_value));
+	info->fw_revision = 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 <= 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 = dev_get_drvdata(dev);
+	struct i2c_client *client = 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 = dev_get_drvdata(dev);
+	struct i2c_client *client = info->i2c_client;
+	bool enable_led = info->state == 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 = {
+	.suspend = bm92t_pm_suspend,
+	.resume = bm92t_pm_resume,
+};
+#endif
+
+static const struct i2c_device_id bm92t_id[] = {
+	{ "bm92t", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, bm92t_id);
+
+static struct i2c_driver bm92t_i2c_driver = {
+	.driver = {
+		.name = "bm92t",
+		.owner = THIS_MODULE,
+		.of_match_table = bm92t_of_match,
+#ifdef CONFIG_PM
+		.pm = &bm92t_pm_ops,
+#endif
+	},
+	.id_table = bm92t_id,
+	.probe = bm92t_probe,
+	.remove = bm92t_remove,
+	.shutdown = 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");
-- 
2.42.0


  parent reply	other threads:[~2023-08-24 15:32 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-08-24 15:30 [PATCH 0/2] Add a driver for the ROHM BM92Txx USB-C Power Delivery controller Emmanuel Gil Peyrot
2023-08-24 15:30 ` [PATCH 1/2] dt-bindings: misc: rohm,bm92txx: Add BM92Txx support Emmanuel Gil Peyrot
2023-08-24 16:16   ` Krzysztof Kozlowski
2023-08-24 15:30 ` Emmanuel Gil Peyrot [this message]
2023-08-24 15:40   ` [PATCH 2/2] misc: bm92txx: Add driver for the ROHM BM92Txx Greg Kroah-Hartman
2023-08-24 16:26   ` Krzysztof Kozlowski
2023-08-24 16:28   ` Krzysztof Kozlowski
2023-08-28 15:06     ` Emmanuel Gil Peyrot
2023-08-28 17:05       ` Krzysztof Kozlowski
2023-08-28 17:08         ` Krzysztof Kozlowski
2023-08-25 18:24   ` kernel test robot
2023-08-25 18:45   ` kernel test robot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230824153059.212244-3-linkmauve@linkmauve.fr \
    --to=linkmauve@linkmauve.fr \
    --cc=a.ffcc7@gmail.com \
    --cc=arnd@arndb.de \
    --cc=chaoj@nvidia.com \
    --cc=conor+dt@kernel.org \
    --cc=ctcaer@gmail.com \
    --cc=derek.kiernan@amd.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dragan.cvetic@amd.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=krzysztof.kozlowski+dt@linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=robh+dt@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.