[PATCH 2/2] misc: bm92txx: Add driver for the ROHM BM92Txx

From: Emmanuel Gil Peyrot
Date: Thu Aug 24 2023 - 11:32:02 EST


From: azkali <a.ffcc7@xxxxxxxxx>

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

Signed-off-by: Emmanuel Gil Peyrot <linkmauve@xxxxxxxxxxxx>
Signed-off-by: azkali <a.ffcc7@xxxxxxxxx>
Signed-off-by: Adam Jiang <chaoj@xxxxxxxxxx>
Signed-off-by: CTCaer <ctcaer@xxxxxxxxx>
---
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@xxxxxxxxxxxx>
S: Supported
F: Documentation/devicetree/bindings/misc/rohm,bm92txx.yaml
+F: drivers/misc/bm92txx.c

ROSE NETWORK LAYER
M: Ralf Baechle <ralf@xxxxxxxxxxxxxx>
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@xxxxxxxxx>
+ * Copyright (c) 2023 Emmanuel Gil Peyrot <linkmauve@xxxxxxxxxxxx>
+ *
+ * Authors:
+ * Adam Jiang <chaoj@xxxxxxxxxx>
+ * CTCaer <ctcaer@xxxxxxxxx>
+ * Emmanuel Gil Peyrot <linkmauve@xxxxxxxxxxxx>
+ */
+
+#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