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 = ®_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 *) ®_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 *) ®_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 *) ®_value, sizeof(reg_value)); + info->fw_type = reg_value; + + err = bm92t_read_reg(info, FW_REVISION_REG, (unsigned char *) ®_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